org.alfresco.rest.workflow.api.tests.TaskWorkflowApiTest.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.rest.workflow.api.tests.TaskWorkflowApiTest.java

Source

/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.rest.workflow.api.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.runtime.Clock;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.DelegationState;
import org.activiti.engine.task.IdentityLinkType;
import org.activiti.engine.task.Task;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.workflow.WorkflowConstants;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.activiti.ActivitiScriptNode;
import org.alfresco.rest.api.tests.PersonInfo;
import org.alfresco.rest.api.tests.RepoService.TestNetwork;
import org.alfresco.rest.api.tests.RepoService.TestPerson;
import org.alfresco.rest.api.tests.RepoService.TestSite;
import org.alfresco.rest.api.tests.client.HttpResponse;
import org.alfresco.rest.api.tests.client.PublicApiException;
import org.alfresco.rest.api.tests.client.RequestContext;
import org.alfresco.rest.api.tests.client.data.MemberOfSite;
import org.alfresco.rest.api.tests.client.data.SiteRole;
import org.alfresco.rest.workflow.api.model.ProcessInfo;
import org.alfresco.rest.workflow.api.tests.WorkflowApiClient.TasksClient;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ISO8601DateFormat;
import org.apache.commons.lang.StringUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.http.HttpStatus;

/**
 * Task related Rest api tests using http client to communicate with the rest apis in the repository.
 * 
 * @author Tijs Rademakers
 * @author Frederik Heremans
 *
 */
@Category(OwnJVMTestsCategory.class)
public class TaskWorkflowApiTest extends EnterpriseWorkflowTestApi {
    @Test
    public void testGetTaskById() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        // Alter current engine date
        Calendar createdCal = Calendar.getInstance();
        createdCal.set(Calendar.MILLISECOND, 0);
        Clock actiClock = activitiProcessEngine.getProcessEngineConfiguration().getClock();
        actiClock.setCurrentTime(createdCal.getTime());

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);

        try {
            final Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            // Set some task-properties not set by process-definition
            Calendar dueDateCal = Calendar.getInstance();
            dueDateCal.set(Calendar.MILLISECOND, 0);
            dueDateCal.add(Calendar.DAY_OF_YEAR, 1);
            Date dueDate = dueDateCal.getTime();

            task.setDescription("This is a test description");
            task.setAssignee(requestContext.getRunAsUser());
            task.setOwner("john");
            task.setDueDate(dueDate);
            activitiProcessEngine.getTaskService().saveTask(task);

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Check resulting task
            JSONObject taskJSONObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskJSONObject);
            assertEquals(task.getId(), taskJSONObject.get("id"));
            assertEquals(processInstance.getId(), taskJSONObject.get("processId"));
            assertEquals(processInstance.getProcessDefinitionId(), taskJSONObject.get("processDefinitionId"));
            assertEquals("adhocTask", taskJSONObject.get("activityDefinitionId"));
            assertEquals("Adhoc Task", taskJSONObject.get("name"));
            assertEquals("This is a test description", taskJSONObject.get("description"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("assignee"));
            assertEquals("john", taskJSONObject.get("owner"));
            assertEquals(dueDate, parseDate(taskJSONObject, "dueAt"));
            assertEquals(createdCal.getTime(), parseDate(taskJSONObject, "startedAt"));
            assertEquals(2l, taskJSONObject.get("priority"));
            assertEquals("wf:adhocTask", taskJSONObject.get("formResourceKey"));
            assertNull(taskJSONObject.get("endedAt"));
            assertNull(taskJSONObject.get("durationInMs"));

            // get unclaimed task
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            taskJSONObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskJSONObject);
            assertEquals(task.getId(), taskJSONObject.get("id"));

            // get delegated task
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            activitiProcessEngine.getTaskService().setOwner(task.getId(), requestContext.getRunAsUser());
            activitiProcessEngine.getTaskService().delegateTask(task.getId(), otherContext.getRunAsUser());
            taskJSONObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskJSONObject);
            assertEquals(task.getId(), taskJSONObject.get("id"));
            assertEquals(otherContext.getRunAsUser(), taskJSONObject.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("owner"));

            // get resolved task
            activitiProcessEngine.getTaskService().resolveTask(task.getId());
            taskJSONObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskJSONObject);
            assertEquals(task.getId(), taskJSONObject.get("id"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("owner"));

            // get completed task
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    activitiProcessEngine.getTaskService().complete(task.getId());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());
            taskJSONObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskJSONObject);
            assertEquals(task.getId(), taskJSONObject.get("id"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("owner"));
            assertNotNull(taskJSONObject.get("endedAt"));

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskByIdAuthorization() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        // Start process by one user and try to access the task as the task assignee instead of the process
        // initiator to see if the assignee is authorized to get the task
        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Try accessing task when NOT involved in the task
            try {
                tasksClient.findTaskById(task.getId());
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Set assignee, task should be accessible now
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            JSONObject jsonObject = tasksClient.findTaskById(task.getId());
            assertNotNull(jsonObject);

            // Fetching task as admin should be possible
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            jsonObject = tasksClient.findTaskById(task.getId());
            assertNotNull(jsonObject);

            // Fetching the task as a admin from another tenant shouldn't be possible
            TestNetwork anotherNetwork = getOtherNetwork(requestContext.getNetworkId());
            tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + anotherNetwork.getId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            try {
                tasksClient.findTaskById(task.getId());
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetUnexistingTaskById() throws Exception {
        initApiClientWithTestUser();
        TasksClient tasksClient = publicApiClient.tasksClient();
        try {
            tasksClient.findTaskById("unexisting");
            fail("Exception expected");
        } catch (PublicApiException expected) {
            assertEquals(HttpStatus.NOT_FOUND.value(), expected.getHttpResponse().getStatusCode());
            assertErrorSummary("The entity with id: unexisting was not found", expected.getHttpResponse());
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTask() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
        RequestContext adminContext = new RequestContext(requestContext.getNetworkId(), tenantAdmin);

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);

        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();

            TasksClient tasksClient = publicApiClient.tasksClient();

            Calendar dueDateCal = Calendar.getInstance();
            dueDateCal.set(Calendar.MILLISECOND, 0);
            dueDateCal.add(Calendar.DAY_OF_YEAR, 1);
            Date dueDate = dueDateCal.getTime();

            JSONObject taskBody = new JSONObject();
            taskBody.put("name", "Updated name");
            taskBody.put("description", "Updated description");
            taskBody.put("dueAt", formatDate(dueDate));
            taskBody.put("priority", 1234);
            taskBody.put("assignee", "john");
            taskBody.put("owner", "james");

            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays
                    .asList(new String[] { "name", "description", "dueAt", "priority", "assignee", "owner" }));

            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("Updated name", result.get("name"));
            assertEquals("Updated description", result.get("description"));
            assertEquals(1234, Integer.valueOf(result.get("priority").toString()).intValue());
            assertEquals("john", result.get("assignee"));
            assertEquals("james", result.get("owner"));
            assertEquals(dueDate, parseDate(result, "dueAt"));

            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            assertEquals("Updated name", task.getName());
            assertEquals("Updated description", task.getDescription());
            assertEquals(1234, task.getPriority());
            assertEquals("john", task.getAssignee());
            assertEquals("james", task.getOwner());
            assertEquals(dueDate, task.getDueDate());

            // update owner with admin user id
            taskBody = new JSONObject();
            taskBody.put("owner", adminContext.getRunAsUser());
            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "owner" }));

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals(adminContext.getRunAsUser(), result.get("owner"));

            // update owner with initiator user id
            taskBody = new JSONObject();
            taskBody.put("owner", requestContext.getRunAsUser());
            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "owner" }));

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals(requestContext.getRunAsUser(), result.get("owner"));

            // update owner with other user id
            taskBody = new JSONObject();
            taskBody.put("owner", otherContext.getRunAsUser());
            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "owner" }));

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals(otherContext.getRunAsUser(), result.get("owner"));

            // update due date to date more in the future
            dueDateCal.add(Calendar.DAY_OF_YEAR, 1);
            dueDate = dueDateCal.getTime();

            taskBody = new JSONObject();
            taskBody.put("dueAt", formatDate(dueDate));

            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "dueAt" }));

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals(dueDate, parseDate(result, "dueAt"));

            // update due date to date a day less in the future
            dueDateCal.add(Calendar.DAY_OF_YEAR, -1);
            dueDate = dueDateCal.getTime();

            taskBody = new JSONObject();
            taskBody.put("dueAt", formatDate(dueDate));

            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "dueAt" }));

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals(dueDate, parseDate(result, "dueAt"));

            // update due date to current time
            dueDateCal = Calendar.getInstance();
            dueDateCal.set(Calendar.MILLISECOND, 0);
            dueDate = dueDateCal.getTime();

            taskBody = new JSONObject();
            taskBody.put("dueAt", formatDate(dueDate));

            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "dueAt" }));

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals(dueDate, parseDate(result, "dueAt"));

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskMnt13276() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String initiatorId = requestContext.getRunAsUser();
        ProcessInfo processInfo = startReviewPooledProcess(requestContext);

        // create test users
        final List<TestPerson> persons = transactionHelper
                .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<List<TestPerson>>() {
                    @SuppressWarnings("synthetic-access")
                    public List<TestPerson> execute() throws Throwable {
                        ArrayList<TestPerson> persons = new ArrayList<TestPerson>();
                        String temp = "_" + System.currentTimeMillis();
                        persons.add(currentNetwork.createUser(new PersonInfo("user0", "user0", "user0" + temp,
                                "password", null, "skype", "location", "telephone", "mob", "instant", "google")));
                        persons.add(currentNetwork.createUser(new PersonInfo("user1", "user1", "user1" + temp,
                                "password", null, "skype", "location", "telephone", "mob", "instant", "google")));
                        persons.add(currentNetwork.createUser(new PersonInfo("user2", "user2", "user2" + temp,
                                "password", null, "skype", "location", "telephone", "mob", "instant", "google")));
                        return persons;
                    }
                }, false, true);

        final MemberOfSite memberOfSite = currentNetwork.getSiteMemberships(initiatorId).get(0);

        // startReviewPooledProcess() uses initiator's site id and role name for construct bpm_groupAssignee, thus we need appropriate things for created users
        transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>() {
            public Void execute() throws Throwable {
                TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                    @Override
                    public Void doWork() throws Exception {
                        TestSite initiatorSite = (TestSite) memberOfSite.getSite();
                        initiatorSite.inviteToSite(persons.get(0).getId(), memberOfSite.getRole());
                        initiatorSite.inviteToSite(persons.get(1).getId(), memberOfSite.getRole());
                        // this user wouldn't be in group
                        initiatorSite.inviteToSite(persons.get(2).getId(),
                                SiteRole.SiteConsumer == memberOfSite.getRole() ? SiteRole.SiteCollaborator
                                        : SiteRole.SiteConsumer);
                        return null;
                    }
                }, AuthenticationUtil.getAdminUserName(), currentNetwork.getId());
                return null;
            }
        }, false, true);

        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInfo.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Updating the task by user in group
            JSONObject taskBody = new JSONObject();
            taskBody.put("name", "Updated name by user in group");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "name" }));
            requestContext.setRunAsUser(persons.get(0).getId());
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("Updated name by user in group", result.get("name"));
            task = activitiProcessEngine.getTaskService().createTaskQuery().processInstanceId(processInfo.getId())
                    .singleResult();
            assertNotNull(task);
            assertEquals("Updated name by user in group", task.getName());

            // Updating the task by user not in group
            try {
                taskBody.put("name", "Updated name by user not in group");
                requestContext.setRunAsUser(persons.get(2).getId());
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("User not from group should not see items.");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // claim task
            TaskService taskService = activitiProcessEngine.getTaskService();
            task = taskService.createTaskQuery().processInstanceId(processInfo.getId()).singleResult();
            taskService.setAssignee(task.getId(), persons.get(1).getId());
            // Updating by user in group for claimed task by another user
            try {
                taskBody = new JSONObject();
                taskBody.put("name", "Updated name by user in group for claimed task");
                selectedFields.addAll(Arrays.asList(new String[] { "name" }));
                requestContext.setRunAsUser(persons.get(0).getId());
                result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("User from group should not see items for claimed task by another user.");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInfo.getId());
        }
    }

    /* 
     * Test association definition extraction from the dictionary as per MNT-11472.
     *  
     * We are going to test association definition extraction through dictionary, when one is not retrieved in context.
     * Context doesn't contains all type definitions, because it requires entire history extraction of a process that causes performance implications.   
     * Type definition extraction from the dictionary is quite fast and it doesn't affect performance.
     * 
     */
    @Test
    @SuppressWarnings("unchecked")
    public void testAssociationDefinitionExtraction() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        // The "wbpm:delegatee" custom association is defined in bpmDelegateeModel.xml. This association has "cm:person" target type.
        final String delegatee = "wbpm_delegatee";
        final String user = requestContext.getRunAsUser();
        final String networkId = requestContext.getNetworkId();

        // Get person
        final ActivitiScriptNode person = TenantUtil.runAsUserTenant(new TenantRunAsWork<ActivitiScriptNode>() {
            @Override
            public ActivitiScriptNode doWork() throws Exception {
                return getPersonNodeRef(user);
            }
        }, user, networkId);

        // Start custom process instance
        ProcessInstance processInstance = TenantUtil.runAsUserTenant(new TenantRunAsWork<ProcessInstance>() {
            @Override
            public ProcessInstance doWork() throws Exception {
                deployProcessDefinition("workflow/testCustomDelegatee.bpmn20.xml");
                String processDefinitionKey = "@" + networkId + "@myProcess";
                Map<String, Object> variables = new HashMap<String, Object>();
                variables.put("bpm_assignee", person);
                variables.put("wf_notifyMe", Boolean.FALSE);
                variables.put(WorkflowConstants.PROP_INITIATOR, person);
                return activitiProcessEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey,
                        "myProcessKey", variables);
            }
        }, user, networkId);

        // Get task 
        Task activeTask = activitiProcessEngine.getTaskService().createTaskQuery()
                .processInstanceId(processInstance.getId()).singleResult();
        assertNotNull(activeTask);
        try {
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Define the "wbpm_delegatee" variable for the taskDefine delegatee user name. POST request will be executed.
            JSONArray variablesArray = new JSONArray();
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", delegatee);
            variableBody.put("value", user);
            variableBody.put("scope", "local");
            variablesArray.add(variableBody);

            try {
                // Set variable for the task.
                JSONObject response = tasksClient.createTaskVariables(activeTask.getId(), variablesArray);
                assertNotNull(response);
                JSONObject variable = (JSONObject) response.get("entry");
                assertEquals(delegatee, variable.get("name"));

                // Check that d:noderef type was returned with appropriate nodeRef Id value. 
                assertEquals("d:noderef", variable.get("type"));
                assertEquals(person.getNodeRef().getId(), variable.get("value"));

                // Get process variables. GET request will be executed.
                response = publicApiClient.processesClient().getProcessvariables(processInstance.getId());
                assertNotNull(response);
                assertTrue(delegatee + " variable was not set", response.toJSONString().contains(delegatee));
                JSONArray entries = (JSONArray) response.get("entries");

                // Find "wbpm_delegatee" variable.
                for (Object entry : entries) {
                    variable = (JSONObject) ((JSONObject) entry).get("entry");
                    if (variable.get("name").equals(delegatee)) {
                        // Check that type corresponds to the target class. 
                        // It means that assoc type def was retrieved successfully from the dictionary.
                        assertEquals("cm:person", variable.get("type"));
                        // Value should be an actual user name.
                        assertEquals(user, variable.get("value"));
                    }
                }
            } catch (PublicApiException ex) {
                fail("Unexpected result " + ex);
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskAuthorization() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Updating the task when NOT assignee/owner or initiator results in an error
            JSONObject taskBody = new JSONObject();
            taskBody.put("name", "Updated name");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "name" }));
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Set assignee to current user, update should succeed
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            taskBody.put("name", "Updated name by assignee");

            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("Updated name by assignee", result.get("name"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            assertEquals("Updated name by assignee", task.getName());

            // Set owner to current user, update should succeed
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            activitiProcessEngine.getTaskService().setOwner(task.getId(), requestContext.getRunAsUser());
            taskBody.put("name", "Updated name by owner");

            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("Updated name by owner", result.get("name"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            assertEquals("Updated name by owner", task.getName());

            // Update as process initiator
            taskBody.put("name", "Updated name by initiator");
            requestContext.setRunAsUser(initiator);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("Updated name by initiator", result.get("name"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            assertEquals("Updated name by initiator", task.getName());

            // Update as administrator
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));

            taskBody.put("name", "Updated name by admin");
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("Updated name by admin", result.get("name"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            assertEquals("Updated name by admin", task.getName());
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateUnexistingTask() throws Exception {
        initApiClientWithTestUser();
        TasksClient tasksClient = publicApiClient.tasksClient();
        try {
            JSONObject taskBody = new JSONObject();
            taskBody.put("name", "Updated name");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "name" }));

            tasksClient.updateTask("unexisting", taskBody, selectedFields);
            fail("Exception expected");
        } catch (PublicApiException expected) {
            assertEquals(HttpStatus.NOT_FOUND.value(), expected.getHttpResponse().getStatusCode());
            assertErrorSummary("The entity with id: unexisting was not found", expected.getHttpResponse());
        }
    }

    @Test
    public void testUpdateTaskInvalidProperty() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Try updating an unexisting property
            try {
                JSONObject taskBody = new JSONObject();
                List<String> selectedFields = new ArrayList<String>();
                selectedFields.add("unexisting");
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("The property selected for update does not exist for this resource: unexisting",
                        expected.getHttpResponse());
            }

            // Try updating a readonly-property
            try {
                JSONObject taskBody = new JSONObject();
                List<String> selectedFields = new ArrayList<String>();
                selectedFields.add("id");
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("The property selected for update is read-only: id", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testClaimTask() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Claiming the task when NOT part of candidate-group results in an error
            JSONObject taskBody = new JSONObject();
            taskBody.put("state", "claimed");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state", "assignee" }));
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Set candidate for task, but keep assignee
            List<MemberOfSite> memberships = getTestFixture().getNetwork(requestContext.getNetworkId())
                    .getSiteMemberships(requestContext.getRunAsUser());
            assertTrue(memberships.size() > 0);
            MemberOfSite memberOfSite = memberships.get(0);
            String group = "GROUP_site_" + memberOfSite.getSiteId() + "_" + memberOfSite.getRole().name();
            activitiProcessEngine.getTaskService().addCandidateGroup(task.getId(), group);

            // Claiming the task when part of candidate-group but another person has this task assigned results in conflict
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.CONFLICT.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("The task is already claimed by another user.", expected.getHttpResponse());
            }

            // Claiming the task when part of candidate-group and NO assignee is currenlty set should work
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            taskBody = new JSONObject();
            taskBody.put("state", "claimed");
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result);
            assertEquals(requestContext.getRunAsUser(), result.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), activitiProcessEngine.getTaskService().createTaskQuery()
                    .taskId(task.getId()).singleResult().getAssignee());

            // Re-claiming the same task with the current assignee shouldn't be a problem
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result);
            assertEquals(requestContext.getRunAsUser(), result.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), activitiProcessEngine.getTaskService().createTaskQuery()
                    .taskId(task.getId()).singleResult().getAssignee());

            // Claiming as a candidateUser should also work
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            activitiProcessEngine.getTaskService().deleteGroupIdentityLink(task.getId(), group,
                    IdentityLinkType.CANDIDATE);
            activitiProcessEngine.getTaskService().addCandidateUser(task.getId(), requestContext.getRunAsUser());
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result);
            assertEquals(requestContext.getRunAsUser(), result.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), activitiProcessEngine.getTaskService().createTaskQuery()
                    .taskId(task.getId()).singleResult().getAssignee());

            // Claiming as a task owner should also work
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            activitiProcessEngine.getTaskService().setOwner(task.getId(), requestContext.getRunAsUser());
            activitiProcessEngine.getTaskService().deleteUserIdentityLink(task.getId(),
                    requestContext.getRunAsUser(), IdentityLinkType.CANDIDATE);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result);
            assertEquals(requestContext.getRunAsUser(), result.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), activitiProcessEngine.getTaskService().createTaskQuery()
                    .taskId(task.getId()).singleResult().getAssignee());

            // Claiming as admin should work
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));

            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            activitiProcessEngine.getTaskService().deleteUserIdentityLink(task.getId(),
                    requestContext.getRunAsUser(), IdentityLinkType.CANDIDATE);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result);
            assertEquals(tenantAdmin, result.get("assignee"));
            assertEquals(tenantAdmin, activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId())
                    .singleResult().getAssignee());

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUnClaimTask() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String user = requestContext.getRunAsUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Unclaiming the task when NOT assignee, owner, initiator or admin results in error
            JSONObject taskBody = new JSONObject();
            taskBody.put("state", "unclaimed");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state" }));
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Unclaiming as process initiator
            requestContext.setRunAsUser(initiator);
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNull(result.get("assignee"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId()).singleResult()
                    .getAssignee());

            // Unclaiming as assignee
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), user);
            requestContext.setRunAsUser(user);
            assertNotNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId())
                    .singleResult().getAssignee());
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNull(result.get("assignee"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId()).singleResult()
                    .getAssignee());

            // Unclaim as owner
            activitiProcessEngine.getTaskService().setOwner(task.getId(), user);
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), initiator);
            assertNotNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId())
                    .singleResult().getAssignee());
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNull(result.get("assignee"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId()).singleResult()
                    .getAssignee());

            // Unclaim as admin
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));

            activitiProcessEngine.getTaskService().setAssignee(task.getId(), initiator);
            activitiProcessEngine.getTaskService().deleteUserIdentityLink(task.getId(),
                    requestContext.getRunAsUser(), IdentityLinkType.CANDIDATE);
            assertNotNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId())
                    .singleResult().getAssignee());
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNull(result.get("assignee"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(task.getId()).singleResult()
                    .getAssignee());
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testCompleteTask() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String user = requestContext.getRunAsUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        ProcessInstance processCompleteAsAssignee = startAdhocProcess(initiator, requestContext.getNetworkId(),
                null);
        ProcessInstance processCompleteAsOwner = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        ProcessInstance processCompleteAsInitiator = startAdhocProcess(initiator, requestContext.getNetworkId(),
                null);
        ProcessInstance processCompleteAsAdmin = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        ProcessInstance processCompleteWithVariables = startAdhocProcess(initiator, requestContext.getNetworkId(),
                null);
        try {
            Task asAssigneeTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processCompleteAsAssignee.getId()).singleResult();
            Task asOwnerTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processCompleteAsOwner.getId()).singleResult();
            Task asInitiatorTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processCompleteAsInitiator.getId()).singleResult();
            Task asAdminTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processCompleteAsAdmin.getId()).singleResult();
            Task withVariablesTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processCompleteWithVariables.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Unclaiming the task when NOT assignee, owner, initiator or admin results in error
            JSONObject taskBody = new JSONObject();
            taskBody.put("state", "completed");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state" }));
            try {
                tasksClient.updateTask(asAssigneeTask.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Completing as assignee initiator
            activitiProcessEngine.getTaskService().setAssignee(asAssigneeTask.getId(), user);
            JSONObject result = tasksClient.updateTask(asAssigneeTask.getId(), taskBody, selectedFields);
            assertEquals("completed", result.get("state"));
            assertNotNull(result.get("endedAt"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(asAssigneeTask.getId())
                    .singleResult());

            // Completing as process initiator
            requestContext.setRunAsUser(initiator);
            activitiProcessEngine.getTaskService().setAssignee(asInitiatorTask.getId(), null);
            result = tasksClient.updateTask(asInitiatorTask.getId(), taskBody, selectedFields);
            assertEquals("completed", result.get("state"));
            assertNotNull(result.get("endedAt"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(asInitiatorTask.getId())
                    .singleResult());

            // Completing as owner
            requestContext.setRunAsUser(user);
            asOwnerTask.setOwner(user);
            activitiProcessEngine.getTaskService().saveTask(asOwnerTask);
            result = tasksClient.updateTask(asOwnerTask.getId(), taskBody, selectedFields);
            assertEquals("completed", result.get("state"));
            assertNotNull(result.get("endedAt"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(asOwnerTask.getId())
                    .singleResult());

            // Complete as admin
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            asAdminTask.setOwner(null);
            activitiProcessEngine.getTaskService().saveTask(asAdminTask);
            result = tasksClient.updateTask(asAdminTask.getId(), taskBody, selectedFields);
            assertEquals("completed", result.get("state"));
            assertNotNull(result.get("endedAt"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(asAdminTask.getId())
                    .singleResult());

            // Complete with variables
            requestContext.setRunAsUser(initiator);
            activitiProcessEngine.getTaskService().setAssignee(withVariablesTask.getId(), null);

            JSONArray variablesArray = new JSONArray();
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "newGlobalVariable");
            variableBody.put("value", 1234);
            variableBody.put("scope", "global");
            variablesArray.add(variableBody);
            variableBody = new JSONObject();
            variableBody.put("name", "newLocalVariable");
            variableBody.put("value", 5678);
            variableBody.put("scope", "local");
            variablesArray.add(variableBody);

            taskBody.put("variables", variablesArray);
            selectedFields.add("variables");
            result = tasksClient.updateTask(withVariablesTask.getId(), taskBody, selectedFields);
            assertEquals("completed", result.get("state"));
            assertNotNull(result.get("endedAt"));
            assertNull(activitiProcessEngine.getTaskService().createTaskQuery().taskId(withVariablesTask.getId())
                    .singleResult());
            HistoricTaskInstance historyTask = activitiProcessEngine.getHistoryService()
                    .createHistoricTaskInstanceQuery().taskId(withVariablesTask.getId()).includeProcessVariables()
                    .includeTaskLocalVariables().singleResult();

            assertEquals(1234, historyTask.getProcessVariables().get("newGlobalVariable"));
            assertEquals(5678, historyTask.getTaskLocalVariables().get("newLocalVariable"));
            assertNotNull("The outcome should not be null for completed task.",
                    historyTask.getTaskLocalVariables().get("bpm_outcome"));
            JSONObject variables = tasksClient.findTaskVariables(withVariablesTask.getId());
            assertNotNull(variables);
            JSONObject list = (JSONObject) variables.get("list");
            assertNotNull(list);
            JSONArray entries = (JSONArray) list.get("entries");
            assertNotNull(entries);

            boolean foundGlobal = false;
            boolean foundLocal = false;
            for (Object entry : entries) {
                JSONObject variableObject = (JSONObject) ((JSONObject) entry).get("entry");
                if ("newGlobalVariable".equals(variableObject.get("name"))) {
                    assertEquals(1234L, variableObject.get("value"));
                    foundGlobal = true;
                } else if ("newLocalVariable".equals(variableObject.get("name"))) {
                    assertEquals(5678L, variableObject.get("value"));
                    foundLocal = true;
                }
            }

            assertTrue(foundGlobal);
            assertTrue(foundLocal);
        } finally {
            cleanupProcessInstance(processCompleteAsAssignee, processCompleteAsAdmin, processCompleteAsInitiator,
                    processCompleteAsOwner, processCompleteWithVariables);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testDelegateTask() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String user = requestContext.getRunAsUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Delegating as non-assignee/owner/initiator/admin should result in an error
            JSONObject taskBody = new JSONObject();
            taskBody.put("state", "delegated");
            taskBody.put("assignee", initiator);
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state", "assignee" }));
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Delegating (as assignee) and not passing in an asisgnee should result in an error
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), user);
            taskBody = new JSONObject();
            taskBody.put("state", "delegated");
            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state" }));
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary(
                        "When delegating a task, assignee should be selected and provided in the request.",
                        expected.getHttpResponse());
            }

            // Delegating as assignee
            taskBody.put("state", "delegated");
            taskBody.put("assignee", initiator);
            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state", "assignee" }));
            assertNull(task.getDelegationState());
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("delegated", result.get("state"));
            assertEquals(initiator, result.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), result.get("owner"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.PENDING, task.getDelegationState());
            assertEquals(initiator, task.getAssignee());
            assertEquals(requestContext.getRunAsUser(), task.getOwner());

            // Delegating as owner
            task.setDelegationState(null);
            task.setOwner(requestContext.getRunAsUser());
            task.setAssignee(null);
            activitiProcessEngine.getTaskService().saveTask(task);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("delegated", result.get("state"));
            assertEquals(initiator, result.get("assignee"));
            assertEquals(requestContext.getRunAsUser(), result.get("owner"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.PENDING, task.getDelegationState());
            assertEquals(initiator, task.getAssignee());
            assertEquals(requestContext.getRunAsUser(), task.getOwner());

            // Delegating as process initiator
            task.setDelegationState(null);
            task.setOwner(null);
            task.setAssignee(null);
            activitiProcessEngine.getTaskService().saveTask(task);
            requestContext.setRunAsUser(initiator);
            taskBody.put("assignee", user);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("delegated", result.get("state"));
            assertEquals(user, result.get("assignee"));
            assertEquals(initiator, result.get("owner"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.PENDING, task.getDelegationState());
            assertEquals(user, task.getAssignee());
            assertEquals(initiator, task.getOwner());

            // Delegating as administrator
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            task.setDelegationState(null);
            task.setOwner(null);
            task.setAssignee(null);
            activitiProcessEngine.getTaskService().saveTask(task);
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            requestContext.setRunAsUser(tenantAdmin);
            taskBody.put("assignee", user);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("delegated", result.get("state"));
            assertEquals(user, result.get("assignee"));
            assertEquals(tenantAdmin, result.get("owner"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.PENDING, task.getDelegationState());
            assertEquals(user, task.getAssignee());
            assertEquals(tenantAdmin, task.getOwner());
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testSetOutcome() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        ProcessInfo processInf = startReviewPooledProcess(requestContext);
        Task task = activitiProcessEngine.getTaskService().createTaskQuery().processInstanceId(processInf.getId())
                .singleResult();
        TasksClient tasksClient = publicApiClient.tasksClient();
        activitiProcessEngine.getTaskService().saveTask(task);
        Map<String, String> params = new HashMap<String, String>();
        params.put("select", "state,variables");
        HttpResponse response = tasksClient.update("tasks", task.getId(), null, null,
                "{\"state\":\"completed\",\"variables\":[{\"name\":\"wf_reviewOutcome\",\"value\":\"Approve\",\"scope\":\"local\"},{\"name\":\"bpm_comment\",\"value\":\"approved by me\",\"scope\":\"local\"}]}",
                "Failed to update task", params);
        assertEquals(200, response.getStatusCode());
        HistoricTaskInstance historyTask = activitiProcessEngine.getHistoryService()
                .createHistoricTaskInstanceQuery().taskId(task.getId()).includeProcessVariables()
                .includeTaskLocalVariables().singleResult();
        String outcome = (String) historyTask.getTaskLocalVariables().get("bpm_outcome");
        assertEquals("Approve", outcome);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testResolveTask() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String user = requestContext.getRunAsUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Resolving as non-assignee/owner/initiator/admin should result in an error
            JSONObject taskBody = new JSONObject();
            taskBody.put("state", "resolved");
            taskBody.put("assignee", initiator);
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state", "assignee" }));
            try {
                tasksClient.updateTask(task.getId(), taskBody, selectedFields);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Resolving as assignee
            task.delegate(user);
            activitiProcessEngine.getTaskService().saveTask(task);
            taskBody.put("state", "resolved");
            taskBody.put("assignee", initiator);
            selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state", "assignee" }));
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("resolved", result.get("state"));
            assertEquals(initiator, result.get("assignee"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.RESOLVED, task.getDelegationState());
            assertEquals(initiator, task.getAssignee());
            HistoricTaskInstance historyTask = activitiProcessEngine.getHistoryService()
                    .createHistoricTaskInstanceQuery().taskId(task.getId()).includeProcessVariables()
                    .includeTaskLocalVariables().singleResult();
            assertNotNull("The outcome should not be null for resolved task.",
                    historyTask.getTaskLocalVariables().get("bpm_outcome"));

            // Resolving as owner
            task.setDelegationState(null);
            task.setOwner(requestContext.getRunAsUser());
            task.setAssignee(null);
            activitiProcessEngine.getTaskService().saveTask(task);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("resolved", result.get("state"));
            assertEquals(user, result.get("assignee"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.RESOLVED, task.getDelegationState());
            assertEquals(user, task.getAssignee());

            // Resolving as process initiator
            task.setDelegationState(null);
            task.setOwner(null);
            task.setAssignee(null);
            activitiProcessEngine.getTaskService().saveTask(task);
            requestContext.setRunAsUser(initiator);
            taskBody.put("assignee", user);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("resolved", result.get("state"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.RESOLVED, task.getDelegationState());

            // Resolving as administrator
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            task.setDelegationState(null);
            task.setOwner(initiator);
            task.setAssignee(null);
            activitiProcessEngine.getTaskService().saveTask(task);
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            requestContext.setRunAsUser(tenantAdmin);
            taskBody.put("assignee", user);
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("resolved", result.get("state"));
            assertEquals(initiator, result.get("assignee"));
            task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertEquals(DelegationState.RESOLVED, task.getDelegationState());
            assertEquals(initiator, task.getAssignee());
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testTaskStateTransitions() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInfo processInstance = startAdhocProcess(requestContext, null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Unclaimed to claimed
            JSONObject taskBody = new JSONObject();
            taskBody.put("state", "claimed");
            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "state", "assignee" }));
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("claimed", result.get("state"));

            // claimed to unclaimed
            taskBody = new JSONObject();
            taskBody.put("state", "unclaimed");
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertEquals("unclaimed", result.get("state"));
        } finally {
            cleanupProcessInstance(processInstance.getId());
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testChangeDueDate() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();

            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject taskObject = tasksClient.findTaskById(task.getId());
            assertNull(taskObject.get("dueAt"));

            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays
                    .asList(new String[] { "name", "description", "dueAt", "priority", "assignee", "owner" }));

            // set due date
            JSONObject taskBody = new JSONObject();
            String dueAt = formatDate(new Date());
            taskBody.put("dueAt", dueAt);
            JSONObject result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result.get("dueAt"));

            taskObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskObject.get("dueAt"));

            taskBody = new JSONObject();
            taskBody.put("dueAt", taskObject.get("dueAt"));
            result = tasksClient.updateTask(task.getId(), taskBody, selectedFields);
            assertNotNull(result.get("dueAt"));

            taskObject = tasksClient.findTaskById(task.getId());
            assertNotNull(taskObject.get("dueAt"));

            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "bpm_workflowDueDate");
            variableBody.put("value", formatDate(new Date()));
            variableBody.put("type", "d:date");
            variableBody.put("scope", "global");

            tasksClient.updateTaskVariable(task.getId(), "bpm_workflowDueDate", variableBody);
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testGetTasks() throws Exception {
        // Alter current engine date
        Calendar createdCal = Calendar.getInstance();
        createdCal.set(Calendar.MILLISECOND, 0);
        Clock actiClock = activitiProcessEngine.getProcessEngineConfiguration().getClock();
        actiClock.setCurrentTime(createdCal.getTime());

        RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            // Set some task-properties not set by process-definition
            Calendar dueDateCal = Calendar.getInstance();
            dueDateCal.set(Calendar.MILLISECOND, 0);
            dueDateCal.add(Calendar.DAY_OF_YEAR, 1);
            Date dueDate = dueDateCal.getTime();

            task.setDescription("This is a test description");
            task.setAssignee(requestContext.getRunAsUser());
            task.setOwner("john");
            task.setDueDate(dueDate);
            activitiProcessEngine.getTaskService().saveTask(task);

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Check resulting task
            JSONObject taskListJSONObject = tasksClient.findTasks(null);
            assertNotNull(taskListJSONObject);
            JSONArray jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(1, jsonEntries.size());

            JSONObject taskJSONObject = (JSONObject) ((JSONObject) jsonEntries.get(0)).get("entry");
            assertNotNull(taskJSONObject);
            assertEquals(task.getId(), taskJSONObject.get("id"));
            assertEquals(processInstance.getId(), taskJSONObject.get("processId"));
            assertEquals(processInstance.getProcessDefinitionId(), taskJSONObject.get("processDefinitionId"));
            assertEquals("adhocTask", taskJSONObject.get("activityDefinitionId"));
            assertEquals("Adhoc Task", taskJSONObject.get("name"));
            assertEquals("This is a test description", taskJSONObject.get("description"));
            assertEquals(requestContext.getRunAsUser(), taskJSONObject.get("assignee"));
            assertEquals("john", taskJSONObject.get("owner"));
            assertEquals(dueDate, parseDate(taskJSONObject, "dueAt"));
            // experiment
            assertEquals(createdCal.getTime().toString(), parseDate(taskJSONObject, "startedAt").toString());
            assertEquals(createdCal.getTime(), parseDate(taskJSONObject, "startedAt"));
            assertEquals(2l, taskJSONObject.get("priority"));
            assertEquals("wf:adhocTask", taskJSONObject.get("formResourceKey"));
            assertNull(taskJSONObject.get("endedAt"));
            assertNull(taskJSONObject.get("durationInMs"));

            // get tasks with user that has no assigned tasks
            publicApiClient.setRequestContext(otherContext);
            taskListJSONObject = tasksClient.findTasks(null);
            assertNotNull(taskListJSONObject);
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(0, jsonEntries.size());

            // get tasks for user which has one task assigned by somebody else
            publicApiClient.setRequestContext(requestContext);
            JSONObject taskBody = new JSONObject();
            taskBody.put("assignee", otherContext.getRunAsUser());

            List<String> selectedFields = new ArrayList<String>();
            selectedFields.addAll(Arrays.asList(new String[] { "assignee" }));

            tasksClient.updateTask(task.getId(), taskBody, selectedFields);

            publicApiClient.setRequestContext(otherContext);
            taskListJSONObject = tasksClient.findTasks(null);
            assertNotNull(taskListJSONObject);
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(1, jsonEntries.size());
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTasksWithParams() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        Calendar taskCreated = Calendar.getInstance();
        taskCreated.set(Calendar.MILLISECOND, 0);
        String businessKey = UUID.randomUUID().toString();
        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), businessKey);

        ProcessInfo otherInstance = startReviewPooledProcess(otherContext);

        try {
            // Complete the adhoc task
            final Task completedTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();

            assertNotNull(completedTask);

            // Find the review pooled task
            final Task reviewPooledTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(otherInstance.getId()).singleResult();

            assertNotNull(reviewPooledTask);
            // MNT-12221 case, due date should be set as task property, not as variable!!!
            assertNotNull("Due date was not set for review pooled task.", reviewPooledTask.getDueDate());

            String anotherUserId = UUID.randomUUID().toString();

            Calendar completedTaskDue = Calendar.getInstance();
            completedTaskDue.add(Calendar.HOUR, 1);
            completedTaskDue.set(Calendar.MILLISECOND, 0);
            completedTask.setOwner(requestContext.getRunAsUser());
            completedTask.setPriority(3);
            completedTask.setDueDate(completedTaskDue.getTime());
            completedTask.setAssignee(anotherUserId);
            completedTask.setName("Another task name");
            completedTask.setDescription("This is another test description");
            activitiProcessEngine.getTaskService().saveTask(completedTask);

            // Complete task in correct tenant
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    activitiProcessEngine.getTaskService().complete(completedTask.getId());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            // Active task is the second task in the adhoc-process (Verify task completed)
            Task activeTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(activeTask);

            Calendar activeTaskDue = Calendar.getInstance();

            activeTaskDue.add(Calendar.HOUR, 2);
            activeTaskDue.set(Calendar.MILLISECOND, 0);
            activeTask.setDueDate(activeTaskDue.getTime());
            activeTask.setName("Task name");
            activeTask.setDescription("This is a test description");
            activeTask.setOwner(requestContext.getRunAsUser());
            activeTask.setPriority(2);
            activeTask.setAssignee(requestContext.getRunAsUser());
            activitiProcessEngine.getTaskService().saveTask(activeTask);
            activitiProcessEngine.getTaskService().addCandidateUser(activeTask.getId(), anotherUserId);
            activitiProcessEngine.getTaskService().addCandidateGroup(activeTask.getId(), "sales");
            activitiProcessEngine.getTaskService().setVariableLocal(activeTask.getId(), "numberVar", 10);

            final Task otherTask = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(otherInstance.getId()).singleResult();

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Test status filtering - active
            Map<String, String> params = new HashMap<String, String>();
            params.put("where", "(status = 'active' AND processId = '" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, false, activeTask.getId());

            // Test status filtering - completed
            params.clear();
            params.put("where", "(status = 'completed' AND processId = '" + processInstance.getId() + "')");
            params.put("orderBy", "dueAt DESC");
            assertTasksPresentInTaskQuery(params, tasksClient, false, completedTask.getId());

            // Test status filtering - any
            params.clear();
            params.put("where", "(status = 'any' AND processId = '" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, false, activeTask.getId(), completedTask.getId());

            // Test status filtering - no value should default to 'active'
            params.clear();
            params.put("where", "(processId = '" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, false, activeTask.getId());

            // Test status filtering - illegal status
            params.clear();
            params.put("where", "(status = 'alfrescorocks')");
            try {
                tasksClient.findTasks(params);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Invalid status parameter: alfrescorocks", expected.getHttpResponse());
            }

            // Next, we test all filtering for active, complete and any tasks

            // Assignee filtering
            params.clear();
            params.put("where", "(status = 'active' AND assignee = '" + requestContext.getRunAsUser() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.put("where", "(status = 'completed' AND assignee = '" + anotherUserId + "')");
            params.put("orderBy", "endedAt");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.put("where", "(status = 'any' AND assignee = '" + anotherUserId + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            // Owner filtering
            params.clear();
            params.put("where", "(status = 'active' AND owner = '" + requestContext.getRunAsUser() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.put("where", "(status = 'completed' AND owner = '" + requestContext.getRunAsUser() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.put("where", "(status = 'any' AND owner = '" + requestContext.getRunAsUser() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId(), completedTask.getId());

            // Candidate user filtering, only available for active tasks. When used with completed/any 400 is returned
            params.clear();
            params.put("where", "(status = 'active' AND candidateUser = '" + anotherUserId + "')");
            // No tasks expected since assignee is set
            assertEquals(0L, getResultSizeForTaskQuery(params, tasksClient));

            // Clear assignee
            activitiProcessEngine.getTaskService().setAssignee(activeTask.getId(), null);
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            // Candidate user with candidate group
            params.clear();
            params.put("where", "(status = 'active' AND candidateUser = '" + otherContext.getRunAsUser() + "')");
            // No tasks expected since assignee is set
            assertEquals(1L, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(status = 'completed' AND candidateUser = '" + anotherUserId + "')");
            try {
                tasksClient.findTasks(params);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary(
                        "Filtering on candidateUser is only allowed in combination with status-parameter 'active'",
                        expected.getHttpResponse());
            }

            params.clear();
            params.put("where", "(status = 'any' AND candidateUser = '" + anotherUserId + "')");
            try {
                tasksClient.findTasks(params);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary(
                        "Filtering on candidateUser is only allowed in combination with status-parameter 'active'",
                        expected.getHttpResponse());
            }

            // Candidate group filtering, only available for active tasks. When used with completed/any 400 is returned
            params.clear();
            params.put("where", "(status = 'active' AND candidateGroup = 'sales' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(status = 'completed' AND candidateGroup = 'sales' AND processId='"
                    + processInstance.getId() + "')");
            try {
                tasksClient.findTasks(params);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary(
                        "Filtering on candidateGroup is only allowed in combination with status-parameter 'active'",
                        expected.getHttpResponse());
            }

            params.clear();
            params.put("where", "(status = 'any' AND candidateGroup = 'sales' AND processId='"
                    + processInstance.getId() + "')");
            try {
                tasksClient.findTasks(params);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary(
                        "Filtering on candidateGroup is only allowed in combination with status-parameter 'active'",
                        expected.getHttpResponse());
            }

            // Name filtering
            params.clear();
            params.put("where",
                    "(status = 'active' AND name = 'Task name' AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(status = 'completed' AND name = 'Another task name' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where", "(status = 'any' AND name = 'Another task name' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            // Description filtering
            params.clear();
            params.put("where", "(status = 'active' AND description = 'This is a test description' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'completed' AND description = 'This is another test description' AND processId='"
                            + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'any' AND description = 'This is another test description' AND processId='"
                            + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            // Priority filtering
            params.clear();
            params.put("where",
                    "(status = 'active' AND priority = 2 AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.put("where",
                    "(status = 'completed' AND priority = 3 AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.put("where",
                    "(status = 'any' AND priority = 3 AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            // Process instance business-key filtering
            params.clear();
            params.put("where", "(status = 'active' AND processBusinessKey = '" + businessKey + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(status = 'completed' AND processBusinessKey = '" + businessKey + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where", "(status = 'any' AND processBusinessKey = '" + businessKey + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId(), activeTask.getId());

            params.clear();
            params.put("where", "(status = 'any' AND processBusinessKey MATCHES('" + businessKey + "'))");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId(), activeTask.getId());

            params.clear();
            params.put("where", "(status = 'any' AND processBusinessKey MATCHES('"
                    + businessKey.substring(0, businessKey.length() - 2) + "%'))");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId(), activeTask.getId());

            // Activity definition id filtering
            params.clear();
            params.put("where", "(status = 'active' AND activityDefinitionId = 'verifyTaskDone' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(status = 'completed' AND activityDefinitionId = 'adhocTask' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where", "(status = 'any' AND activityDefinitionId = 'adhocTask' AND processId='"
                    + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            // Process definition id filtering
            params.clear();
            params.put("where",
                    "(status = 'active' AND processDefinitionId = '" + processInstance.getProcessDefinitionId()
                            + "' AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'completed' AND processDefinitionId = '" + processInstance.getProcessDefinitionId()
                            + "' AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'any' AND processDefinitionId = '" + processInstance.getProcessDefinitionId()
                            + "' AND processId='" + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId(), completedTask.getId());

            // Process definition name filtering 
            params.clear();
            params.put("where",
                    "(status = 'active' AND processDefinitionName = 'Adhoc Activiti Process' AND processId='"
                            + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'completed' AND processDefinitionName = 'Adhoc Activiti Process' AND processId='"
                            + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'any' AND processDefinitionName = 'Adhoc Activiti Process' AND processId='"
                            + processInstance.getId() + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId(), completedTask.getId());

            // Due date filtering
            params.clear();
            params.put("where",
                    "(status = 'active' AND dueAt = '" + ISO8601DateFormat.format(activeTaskDue.getTime()) + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(status = 'completed' AND dueAt = '"
                    + ISO8601DateFormat.format(completedTaskDue.getTime()) + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'any' AND dueAt = '" + ISO8601DateFormat.format(completedTaskDue.getTime()) + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            // Started filtering
            Calendar compareCal = Calendar.getInstance();
            compareCal.set(Calendar.MILLISECOND, 0);
            compareCal.add(Calendar.DAY_OF_YEAR, -1);
            params.clear();
            params.put("where",
                    "(status = 'active' AND startedAt > '" + ISO8601DateFormat.format(compareCal.getTime()) + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId(), otherTask.getId());

            params.clear();
            params.put("where", "(status = 'completed' AND startedAt > '"
                    + ISO8601DateFormat.format(compareCal.getTime()) + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'any' AND startedAt > '" + ISO8601DateFormat.format(compareCal.getTime()) + "')");
            assertTasksPresentInTaskQuery(params, tasksClient, completedTask.getId(), activeTask.getId(),
                    otherTask.getId());

            params.clear();
            params.put("where",
                    "(status = 'any' AND startedAt < '" + ISO8601DateFormat.format(compareCal.getTime()) + "')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/local/numberVar > 'd:int 5')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(variables/local/numberVar > 'd:int 10')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/local/numberVar >= 'd_int 10')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(variables/local/numberVar >= 'd:int 11')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/local/numberVar <= 'd:int 10')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(variables/local/numberVar <= 'd:int 9')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/local/numberVar < 'd_int 15')");
            assertTasksPresentInTaskQuery(params, tasksClient, activeTask.getId());

            params.clear();
            params.put("where", "(variables/local/numberVar < 'd:int 10')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/global/numberVar > 'd:int 5')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/numberVar > 'd:int 5')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where",
                    "(variables/bpm_dueDate = 'd:datetime " + ISO8601DateFormat.format(new Date()) + "')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/bpm_dueDate = 'd:datetime 2013-09-15T12:22:31.866+0000')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/bpm_dueDate > 'd:datetime 2013-09-15T12:22:31.866+0000')");
            // MNT-12221 fix, due date is not saved as variable for review pooled workflow, so nothing should be found
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(dueAt = '" + ISO8601DateFormat.format(reviewPooledTask.getDueDate()) + "')");
            assertEquals(1, getResultSizeForTaskQuery(params, tasksClient));

            params.clear();
            params.put("where", "(variables/bpm_comment MATCHES ('test%'))");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

            // test with OR operator
            params.clear();
            params.put("where", "(status = 'any' OR candidateGroup = 'sales')");
            try {
                tasksClient.findTasks(params);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
            }

            params.clear();
            params.put("where", "(status = 'completed' AND processDefinitionName = 'Adhoc Activiti Process')");
            JSONObject response = publicApiClient.processesClient().getTasks(processInstance.getId(), params);
            assertNotNull(response);
            JSONObject paginationJSON = (JSONObject) response.get("pagination");
            assertEquals(1l, paginationJSON.get("count"));

            params.clear();
            params.put("where", "(status = 'any' AND variables/numberVar < 'd:int 5')");
            assertEquals(0, getResultSizeForTaskQuery(params, tasksClient));

        } finally {
            cleanupProcessInstance(processInstance.getId(), otherInstance.getId());
        }
    }

    @Test
    public void testGetTasksWithPaging() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        // Start 6 processes
        List<ProcessInstance> startedProcesses = new ArrayList<ProcessInstance>();
        try {
            int numberOfTasks = 6;
            for (int i = 0; i < numberOfTasks; i++) {
                startedProcesses
                        .add(startAdhocProcess(requestContext.getRunAsUser(), requestContext.getNetworkId(), null));
            }

            String processDefinitionId = startedProcesses.get(0).getProcessDefinitionId();

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Test with existing processDefinitionId
            Map<String, String> params = new HashMap<String, String>();
            params.put("where", "(processDefinitionId = '" + processDefinitionId
                    + "' AND includeProcessVariables = true AND includeTaskVariables = true)");
            JSONObject taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            JSONObject paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(6l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            assertEquals(0l, paginationJSON.get("skipCount"));
            assertEquals(false, paginationJSON.get("hasMoreItems"));
            JSONArray jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(6, jsonEntries.size());
            validateVariables((JSONObject) jsonEntries.get(0), requestContext);

            // Test with existing processDefinitionId and max items
            params.clear();
            params.put("maxItems", "3");
            params.put("where", "(processDefinitionId = '" + processDefinitionId
                    + "' AND includeProcessVariables = true AND includeTaskVariables = true)");
            taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(3l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            assertEquals(0l, paginationJSON.get("skipCount"));
            assertEquals(true, paginationJSON.get("hasMoreItems"));
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(3, jsonEntries.size());
            validateVariables((JSONObject) jsonEntries.get(0), requestContext);

            // Test with existing processDefinitionId and skip count
            params.clear();
            params.put("skipCount", "2");
            params.put("where", "(processDefinitionId = '" + processDefinitionId
                    + "' AND includeProcessVariables = true AND includeTaskVariables = true)");
            taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(4l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            assertEquals(2l, paginationJSON.get("skipCount"));
            assertEquals(false, paginationJSON.get("hasMoreItems"));
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(4, jsonEntries.size());

            // Test with existing processDefinitionId and max items and skip count
            params.clear();
            params.put("maxItems", "3");
            params.put("skipCount", "2");
            params.put("where", "(processDefinitionId = '" + processDefinitionId
                    + "' AND includeProcessVariables = true AND includeTaskVariables = true)");
            taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(3l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            assertEquals(2l, paginationJSON.get("skipCount"));
            assertEquals(true, paginationJSON.get("hasMoreItems"));
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(3, jsonEntries.size());

            // Test with existing processDefinitionId and max items and skip count
            params.clear();
            params.put("maxItems", "3");
            params.put("skipCount", "4");
            params.put("where", "(processDefinitionId = '" + processDefinitionId
                    + "' AND includeProcessVariables = true AND includeTaskVariables = true)");
            taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(2l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            assertEquals(4l, paginationJSON.get("skipCount"));
            assertEquals(false, paginationJSON.get("hasMoreItems"));
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(2, jsonEntries.size());
        } finally {
            cleanupProcessInstance(startedProcesses.toArray(new ProcessInstance[] {}));
        }
    }

    @Test
    public void testGetTasksWithPagingAndVariablesLimit() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        List<ProcessInstance> startedProcesses = new ArrayList<ProcessInstance>();

        // system.workflow.engine.activiti.taskvariableslimit is set to 200 in test-resources/alfresco-global.properties
        try {
            // MNT-16040: Create tasks with a number of variables just below the taskvariableslimit and test that skipCount is working as expected.
            int numberOfTasks = 15;
            for (int i = 0; i < numberOfTasks; i++) {
                startedProcesses
                        .add(startAdhocProcess(requestContext.getRunAsUser(), requestContext.getNetworkId(), null));
            }
            TaskService taskService = activitiProcessEngine.getTaskService();
            List<Task> taskList = new ArrayList<Task>();
            for (int i = 0; i < numberOfTasks; i++) {
                Task task = taskService.createTaskQuery()
                        .processInstanceId(startedProcesses.get(i).getProcessInstanceId()).singleResult();
                taskService.setPriority(task.getId(), (i + 1) * 10);

                // Add an extra variable to the task, there are other 12 added, so a total of 13 variables for each task.
                taskService.setVariableLocal(task.getId(), "test1", "test1");
                taskList.add(task);
            }

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Test without skipCount
            Map<String, String> params = new HashMap<String, String>();
            params.put("where", "(includeProcessVariables = true AND includeTaskVariables = true)");
            JSONObject taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            JSONObject paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(15l, paginationJSON.get("count"));
            assertEquals(15l, paginationJSON.get("totalItems"));
            assertEquals(0l, paginationJSON.get("skipCount"));
            assertEquals(false, paginationJSON.get("hasMoreItems"));
            JSONArray jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(15, jsonEntries.size());

            // Test with skipCount
            params.clear();
            params.put("skipCount", "5");
            params.put("where", "(includeProcessVariables = true AND includeTaskVariables = true)");
            taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(10l, paginationJSON.get("count"));
            assertEquals(15l, paginationJSON.get("totalItems"));
            assertEquals(5l, paginationJSON.get("skipCount"));
            assertEquals(false, paginationJSON.get("hasMoreItems"));
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(10, jsonEntries.size());

            params.clear();
            params.put("maxItems", "10");
            params.put("where", "(includeProcessVariables = true AND includeTaskVariables = true)");
            taskListJSONObject = tasksClient.findTasks(params);
            assertNotNull(taskListJSONObject);
            paginationJSON = (JSONObject) taskListJSONObject.get("pagination");
            assertEquals(10l, paginationJSON.get("count"));
            assertEquals(15l, paginationJSON.get("totalItems"));
            assertEquals(0l, paginationJSON.get("skipCount"));
            assertEquals(true, paginationJSON.get("hasMoreItems"));
            jsonEntries = (JSONArray) taskListJSONObject.get("entries");
            assertEquals(10, jsonEntries.size());
        } finally {
            cleanupProcessInstance(startedProcesses.toArray(new ProcessInstance[] {}));
        }
    }

    protected void validateVariables(JSONObject entry, RequestContext requestContext) {
        JSONObject taskObject = (JSONObject) entry.get("entry");
        JSONArray variables = (JSONArray) taskObject.get("variables");
        boolean foundInitiator = false;
        boolean foundAssignee = false;
        boolean foundPercentageComplete = false;
        boolean foundReassignable = false;
        for (int i = 0; i < variables.size(); i++) {
            JSONObject variableJSON = (JSONObject) variables.get(i);
            if ("initiator".equals(variableJSON.get("name"))) {
                assertEquals("d:noderef", variableJSON.get("type"));
                assertEquals(requestContext.getRunAsUser(), variableJSON.get("value"));
                foundInitiator = true;
            } else if ("bpm_assignee".equals(variableJSON.get("name"))) {
                assertEquals("cm:person", variableJSON.get("type"));
                assertEquals(requestContext.getRunAsUser(), variableJSON.get("value"));
                foundAssignee = true;
            } else if ("bpm_percentComplete".equals(variableJSON.get("name"))) {
                assertEquals("d:int", variableJSON.get("type"));
                assertEquals(0L, variableJSON.get("value"));
                foundPercentageComplete = true;
            } else if ("bpm_reassignable".equals(variableJSON.get("name"))) {
                assertEquals("d:boolean", variableJSON.get("type"));
                assertEquals(Boolean.TRUE, variableJSON.get("value"));
                foundReassignable = true;
            }
        }

        assertTrue(foundInitiator);
        assertTrue(foundAssignee);
        assertTrue(foundPercentageComplete);
        assertTrue(foundReassignable);
    }

    @Test
    public void testGetTasksWithSorting() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        // Start 6 processes
        List<ProcessInstance> startedProcesses = new ArrayList<ProcessInstance>();
        try {
            int numberOfTasks = 6;
            for (int i = 0; i < numberOfTasks; i++) {
                startedProcesses
                        .add(startAdhocProcess(requestContext.getRunAsUser(), requestContext.getNetworkId(), null));
            }

            List<Task> taskList = new ArrayList<Task>();
            for (int i = 0; i < numberOfTasks; i++) {
                Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                        .processInstanceId(startedProcesses.get(i).getProcessInstanceId()).singleResult();
                activitiProcessEngine.getTaskService().setPriority(task.getId(), (i + 1) * 10);
                taskList.add(task);
            }

            // set last task priority to 1
            activitiProcessEngine.getTaskService().setPriority(taskList.get(numberOfTasks - 1).getId(), 1);

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Test with existing processDefinitionId
            Map<String, String> params = new HashMap<String, String>();
            params.put("where",
                    "(processDefinitionId = '" + startedProcesses.get(0).getProcessDefinitionId() + "')");
            params.put("orderBy", "priority ASC");
            JSONObject tasksResponseJSON = tasksClient.findTasks(params);

            JSONObject paginationJSON = (JSONObject) tasksResponseJSON.get("pagination");
            assertEquals(6l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            JSONArray tasksListJSON = (JSONArray) tasksResponseJSON.get("entries");
            assertEquals(6, tasksListJSON.size());
            JSONObject taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(0)).get("entry");
            assertEquals(taskList.get(numberOfTasks - 1).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(1)).get("entry");
            assertEquals(taskList.get(0).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(2)).get("entry");
            assertEquals(taskList.get(1).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(3)).get("entry");
            assertEquals(taskList.get(2).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(4)).get("entry");
            assertEquals(taskList.get(3).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(5)).get("entry");
            assertEquals(taskList.get(4).getId(), taskJSON.get("id"));

            params.put("orderBy", "priority DESC");
            tasksResponseJSON = tasksClient.findTasks(params);

            paginationJSON = (JSONObject) tasksResponseJSON.get("pagination");
            assertEquals(6l, paginationJSON.get("count"));
            assertEquals(6l, paginationJSON.get("totalItems"));
            tasksListJSON = (JSONArray) tasksResponseJSON.get("entries");
            assertEquals(6, tasksListJSON.size());
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(0)).get("entry");
            assertEquals(taskList.get(4).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(1)).get("entry");
            assertEquals(taskList.get(3).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(2)).get("entry");
            assertEquals(taskList.get(2).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(3)).get("entry");
            assertEquals(taskList.get(1).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(4)).get("entry");
            assertEquals(taskList.get(0).getId(), taskJSON.get("id"));
            taskJSON = (JSONObject) ((JSONObject) tasksListJSON.get(5)).get("entry");
            assertEquals(taskList.get(numberOfTasks - 1).getId(), taskJSON.get("id"));

            params.put("orderBy", "dueAt DESC");
            tasksResponseJSON = tasksClient.findTasks(params);
            tasksListJSON = (JSONArray) tasksResponseJSON.get("entries");
            assertEquals(6, tasksListJSON.size());

        } finally {
            cleanupProcessInstance(startedProcesses.toArray(new ProcessInstance[] {}));
        }
    }

    @Test
    public void testGetTasksAuthorization() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        // Start process by one user and try to access the task as the task assignee instead of the process
        // initiator to see if the assignee is authorized to get the task
        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            TasksClient tasksClient = publicApiClient.tasksClient();

            Map<String, String> params = new HashMap<String, String>();
            params.put("processId", processInstance.getId());

            JSONObject resultingTasks = tasksClient.findTasks(params);
            assertNotNull(resultingTasks);
            JSONArray jsonEntries = (JSONArray) resultingTasks.get("entries");
            assertNotNull(jsonEntries);
            assertEquals(0, jsonEntries.size());

            // Set assignee, task should be in the result now
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());

            resultingTasks = tasksClient.findTasks(params);
            assertNotNull(resultingTasks);
            jsonEntries = (JSONArray) resultingTasks.get("entries");
            assertNotNull(jsonEntries);
            assertEquals(1, jsonEntries.size());

            // Fetching task as admin should be possible
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            resultingTasks = tasksClient.findTasks(params);
            assertNotNull(resultingTasks);
            jsonEntries = (JSONArray) resultingTasks.get("entries");
            assertNotNull(jsonEntries);
            assertEquals(1, jsonEntries.size());

            // Fetching the task as a admin from another tenant shouldn't be possible
            TestNetwork anotherNetwork = getOtherNetwork(requestContext.getNetworkId());
            tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + anotherNetwork.getId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            resultingTasks = tasksClient.findTasks(params);
            assertNotNull(resultingTasks);
            jsonEntries = (JSONArray) resultingTasks.get("entries");
            assertNotNull(jsonEntries);
            assertEquals(0, jsonEntries.size());
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskCandidates() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();
        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            activitiProcessEngine.getTaskService().addCandidateUser(task.getId(), "testuser");
            activitiProcessEngine.getTaskService().addCandidateUser(task.getId(), "testuser2");
            activitiProcessEngine.getTaskService().addCandidateGroup(task.getId(), "testgroup");

            TasksClient tasksClient = publicApiClient.tasksClient();

            JSONObject taskCandidatesJSONObject = tasksClient.findTaskCandidates(task.getId());
            assertNotNull(taskCandidatesJSONObject);
            JSONArray candidateArrayJSON = (JSONArray) ((JSONObject) taskCandidatesJSONObject.get("list"))
                    .get("entries");
            assertEquals(3, candidateArrayJSON.size());

            boolean testUser1Found = false;
            boolean testUser2Found = false;
            boolean testGroupFound = false;

            for (int i = 0; i < candidateArrayJSON.size(); i++) {
                JSONObject entry = (JSONObject) ((JSONObject) candidateArrayJSON.get(i)).get("entry");
                if ("group".equals(entry.get("candidateType"))) {
                    testGroupFound = true;
                    assertEquals("testgroup", entry.get("candidateId"));
                } else if ("user".equals(entry.get("candidateType"))) {
                    if ("testuser".equals(entry.get("candidateId"))) {
                        testUser1Found = true;
                    } else if ("testuser2".equals(entry.get("candidateId"))) {
                        testUser2Found = true;
                    }
                }
            }

            assertTrue(testUser1Found);
            assertTrue(testUser2Found);
            assertTrue(testGroupFound);
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskVariablesAuthentication() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        // Start process by one user and try to access the task variables as the task assignee instead of the process
        // initiator to see if the assignee is authorized to get the task
        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Try accessing task variables when NOT involved in the task
            try {
                tasksClient.findTaskVariables(task.getId());
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Set assignee, task variables should be accessible now
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            JSONObject jsonObject = tasksClient.findTaskVariables(task.getId());
            assertNotNull(jsonObject);

            // Fetching task variables as admin should be possible
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            jsonObject = tasksClient.findTaskVariables(task.getId());
            assertNotNull(jsonObject);

            // Fetching the task variables as a admin from another tenant shouldn't be possible
            TestNetwork anotherNetwork = getOtherNetwork(requestContext.getNetworkId());
            tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + anotherNetwork.getId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            try {
                tasksClient.findTaskVariables(task.getId());
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskVariables() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());
            Map<String, Object> actualGlobalVariables = activitiProcessEngine.getRuntimeService()
                    .getVariables(processInstance.getId());
            assertEquals(5, actualGlobalVariables.size());
            assertEquals(8, actualLocalVariables.size());

            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject variables = tasksClient.findTaskVariables(task.getId());
            assertNotNull(variables);
            JSONObject list = (JSONObject) variables.get("list");
            assertNotNull(list);
            JSONArray entries = (JSONArray) list.get("entries");
            assertNotNull(entries);

            // Check pagination object for size
            JSONObject pagination = (JSONObject) list.get("pagination");
            assertNotNull(pagination);
            assertEquals(12L, pagination.get("count"));
            assertEquals(12L, pagination.get("totalItems"));
            assertEquals(0L, pagination.get("skipCount"));
            assertFalse((Boolean) pagination.get("hasMoreItems"));

            // Should contain one variable less than the actual variables in the engine, tenant-domain var is filtered out
            assertEquals(actualLocalVariables.size() + actualGlobalVariables.size() - 1, entries.size());

            List<JSONObject> localResults = new ArrayList<JSONObject>();
            List<JSONObject> globalResults = new ArrayList<JSONObject>();

            Set<String> expectedLocalVars = new HashSet<String>();
            expectedLocalVars.addAll(actualLocalVariables.keySet());

            Set<String> expectedGlobalVars = new HashSet<String>();
            expectedGlobalVars.addAll(actualGlobalVariables.keySet());
            expectedGlobalVars.remove(ActivitiConstants.VAR_TENANT_DOMAIN);

            // Add JSON entries to map for easy access when asserting values
            Map<String, JSONObject> entriesByName = new HashMap<String, JSONObject>();
            for (int i = 0; i < entries.size(); i++) {
                JSONObject entry = (JSONObject) ((JSONObject) entries.get(i)).get("entry");
                assertNotNull(entry);

                // Check if full entry is present
                assertNotNull(entry.get("scope"));
                assertNotNull(entry.get("name"));
                assertNotNull(entry.get("type"));
                if (!entry.get("name").equals("bpm_hiddenTransitions")) {
                    assertNotNull(entry.get("value"));
                }

                if ("local".equals(entry.get("scope"))) {
                    localResults.add(entry);
                    expectedLocalVars.remove(entry.get("name"));
                } else if ("global".equals(entry.get("scope"))) {
                    globalResults.add(entry);
                    expectedGlobalVars.remove(entry.get("name"));
                }

                entriesByName.put((String) entry.get("name"), entry);
            }

            // Check correct count of globas vs. local
            assertEquals(4, globalResults.size());
            assertEquals(8, localResults.size());

            // Check if all variables are present
            assertEquals(0, expectedGlobalVars.size());
            assertEquals(0, expectedLocalVars.size());

            // Check if types are returned, based on content-model
            JSONObject var = entriesByName.get("bpm_percentComplete");
            assertNotNull(var);
            assertEquals("d:int", var.get("type"));
            assertEquals(0L, var.get("value"));

            var = entriesByName.get("bpm_reassignable");
            assertNotNull(var);
            assertEquals("d:boolean", var.get("type"));
            assertEquals(Boolean.TRUE, var.get("value"));

            var = entriesByName.get("bpm_status");
            assertNotNull(var);
            assertEquals("d:text", var.get("type"));
            assertEquals("Not Yet Started", var.get("value"));

            var = entriesByName.get("bpm_assignee");
            assertNotNull(var);
            assertEquals("cm:person", var.get("type"));

            final String userName = requestContext.getRunAsUser();
            assertEquals(userName, var.get("value"));

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskVariablesPresentInModel() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());
            Map<String, Object> actualGlobalVariables = activitiProcessEngine.getRuntimeService()
                    .getVariables(processInstance.getId());
            assertEquals(5, actualGlobalVariables.size());
            assertEquals(8, actualLocalVariables.size());

            // Update a global value that is present in the model with type given
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "bpm_percentComplete");
            variableBody.put("value", 20);
            variableBody.put("type", "d:int");
            variableBody.put("scope", "global");

            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject resultEntry = tasksClient.updateTaskVariable(task.getId(), "bpm_percentComplete",
                    variableBody);
            assertNotNull(resultEntry);
            JSONObject result = (JSONObject) resultEntry.get("entry");
            assertEquals("bpm_percentComplete", result.get("name"));
            assertEquals(20L, result.get("value"));
            assertEquals("d:int", result.get("type"));
            assertEquals("global", result.get("scope"));
            assertEquals(20, activitiProcessEngine.getRuntimeService().getVariable(processInstance.getId(),
                    "bpm_percentComplete"));

            // Update a local value that is present in the model with name and scope, omitting type to see if type is deducted from model
            variableBody = new JSONObject();
            variableBody.put("name", "bpm_percentComplete");
            variableBody.put("value", 30);
            variableBody.put("scope", "local");

            result = resultEntry = tasksClient.updateTaskVariable(task.getId(), "bpm_percentComplete",
                    variableBody);
            assertNotNull(resultEntry);
            result = (JSONObject) resultEntry.get("entry");
            assertEquals("bpm_percentComplete", result.get("name"));
            assertEquals(30L, result.get("value"));
            assertEquals("d:int", result.get("type"));
            assertEquals("local", result.get("scope"));
            assertEquals(30,
                    activitiProcessEngine.getTaskService().getVariable(task.getId(), "bpm_percentComplete"));
            // Global variable should remain unaffected
            assertEquals(20, activitiProcessEngine.getRuntimeService().getVariable(processInstance.getId(),
                    "bpm_percentComplete"));
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskVariablesExplicitTyped() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());
            Map<String, Object> actualGlobalVariables = activitiProcessEngine.getRuntimeService()
                    .getVariables(processInstance.getId());
            assertEquals(5, actualGlobalVariables.size());
            assertEquals(8, actualLocalVariables.size());

            // Set a new global value that is NOT present in the model with type given
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "newVariable");
            variableBody.put("value", 1234L);
            variableBody.put("type", "d:long");
            variableBody.put("scope", "global");

            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject resultEntry = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
            assertNotNull(resultEntry);
            JSONObject result = (JSONObject) resultEntry.get("entry");
            assertEquals("newVariable", result.get("name"));
            assertEquals(1234L, result.get("value"));
            assertEquals("d:long", result.get("type"));
            assertEquals("global", result.get("scope"));
            assertEquals(1234L,
                    activitiProcessEngine.getRuntimeService().getVariable(processInstance.getId(), "newVariable"));
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskVariablesNoExplicitType() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());
            Map<String, Object> actualGlobalVariables = activitiProcessEngine.getRuntimeService()
                    .getVariables(processInstance.getId());
            assertEquals(5, actualGlobalVariables.size());
            assertEquals(8, actualLocalVariables.size());

            // Raw number value
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "newVariable");
            variableBody.put("value", 1234);
            variableBody.put("scope", "global");

            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject resultEntry = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
            assertNotNull(resultEntry);
            JSONObject result = (JSONObject) resultEntry.get("entry");
            assertEquals("newVariable", result.get("name"));
            assertEquals(1234L, result.get("value"));
            assertEquals("d:int", result.get("type"));
            assertEquals("global", result.get("scope"));
            assertEquals(1234,
                    activitiProcessEngine.getRuntimeService().getVariable(processInstance.getId(), "newVariable"));

            // Raw boolean value
            variableBody = new JSONObject();
            variableBody.put("name", "newVariable");
            variableBody.put("value", Boolean.TRUE);
            variableBody.put("scope", "local");

            resultEntry = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
            assertNotNull(resultEntry);
            result = (JSONObject) resultEntry.get("entry");
            assertEquals("newVariable", result.get("name"));
            assertEquals(Boolean.TRUE, result.get("value"));
            assertEquals("d:boolean", result.get("type"));
            assertEquals("local", result.get("scope"));
            assertEquals(Boolean.TRUE,
                    activitiProcessEngine.getTaskService().getVariable(task.getId(), "newVariable"));

            // Raw string value
            variableBody = new JSONObject();
            variableBody.put("name", "newVariable");
            variableBody.put("value", "test value");
            variableBody.put("scope", "global");

            resultEntry = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
            assertNotNull(resultEntry);
            result = (JSONObject) resultEntry.get("entry");
            assertEquals("newVariable", result.get("name"));
            assertEquals("test value", result.get("value"));
            assertEquals("d:text", result.get("type"));
            assertEquals("global", result.get("scope"));
            assertEquals("test value",
                    activitiProcessEngine.getRuntimeService().getVariable(processInstance.getId(), "newVariable"));
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskVariablesExceptions() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            // Update without name
            JSONObject variableBody = new JSONObject();
            variableBody.put("value", 1234);
            variableBody.put("scope", "global");

            TasksClient tasksClient = publicApiClient.tasksClient();
            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Variable name is required.", expected.getHttpResponse());
            }

            // Update without scope
            variableBody = new JSONObject();
            variableBody.put("value", 1234);
            variableBody.put("name", "newVariable");

            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Variable scope is required and can only be 'local' or 'global'.",
                        expected.getHttpResponse());
            }

            // Update in 'any' scope
            variableBody = new JSONObject();
            variableBody.put("value", 1234);
            variableBody.put("name", "newVariable");
            variableBody.put("scope", "any");

            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Variable scope is required and can only be 'local' or 'global'.",
                        expected.getHttpResponse());
            }

            // Update in illegal scope
            variableBody = new JSONObject();
            variableBody.put("value", 1234);
            variableBody.put("name", "newVariable");
            variableBody.put("scope", "illegal");

            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Illegal value for variable scope: 'illegal'.", expected.getHttpResponse());
            }

            // Update using unsupported type
            variableBody = new JSONObject();
            variableBody.put("value", 1234);
            variableBody.put("name", "newVariable");
            variableBody.put("scope", "local");
            variableBody.put("type", "d:unexisting");

            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Unsupported type of variable: 'd:unexisting'.", expected.getHttpResponse());
            }

            // Update using unsupported type (invalid QName)
            variableBody = new JSONObject();
            variableBody.put("value", 1234);
            variableBody.put("name", "newVariable");
            variableBody.put("scope", "local");
            variableBody.put("type", " 12unexisting");

            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Unsupported type of variable: ' 12unexisting'.", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testUpdateTaskVariableWithWrongType() throws Exception {
        final RequestContext requestContext = initApiClientWithTestUser();

        ProcessInfo processRest = startParallelReviewProcess(requestContext);

        try {
            List<Task> tasks = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processRest.getId()).list();
            assertNotNull(tasks);

            String taskId = tasks.get(0).getId();

            // Update an existing variable with wrong type
            JSONObject variableJson = new JSONObject();
            variableJson.put("name", "wf_requiredApprovePercent");
            variableJson.put("value", 55.99);
            variableJson.put("type", "d:double");
            variableJson.put("scope", "global");

            try {
                publicApiClient.tasksClient().updateTaskVariable(taskId, "wf_requiredApprovePercent", variableJson);
                fail("Exception expected");
            } catch (PublicApiException e) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), e.getHttpResponse().getStatusCode());
            }

            variableJson = new JSONObject();
            variableJson.put("name", "wf_requiredApprovePercent");
            variableJson.put("value", 55.99);
            variableJson.put("type", "d:int");
            variableJson.put("scope", "global");

            JSONObject resultEntry = publicApiClient.tasksClient().updateTaskVariable(taskId,
                    "wf_requiredApprovePercent", variableJson);
            assertNotNull(resultEntry);
            JSONObject result = (JSONObject) resultEntry.get("entry");

            assertEquals("wf_requiredApprovePercent", result.get("name"));
            assertEquals(55l, result.get("value"));
            assertEquals("d:int", result.get("type"));
            assertEquals(55, activitiProcessEngine.getRuntimeService().getVariable(processRest.getId(),
                    "wf_requiredApprovePercent"));

            JSONObject taskVariables = publicApiClient.tasksClient().findTaskVariables(taskId);
            assertNotNull(taskVariables);
            JSONObject list = (JSONObject) taskVariables.get("list");
            assertNotNull(list);

            // Add process variables to map for easy lookup
            Map<String, JSONObject> variablesByName = new HashMap<String, JSONObject>();
            JSONObject entry = null;
            JSONArray entries = (JSONArray) list.get("entries");
            assertNotNull(entries);
            for (int i = 0; i < entries.size(); i++) {
                entry = (JSONObject) entries.get(i);
                assertNotNull(entry);
                entry = (JSONObject) entry.get("entry");
                assertNotNull(entry);
                variablesByName.put((String) entry.get("name"), entry);
            }

            JSONObject approvePercentObject = variablesByName.get("wf_requiredApprovePercent");
            assertNotNull(approvePercentObject);
            assertEquals(55l, approvePercentObject.get("value"));
            assertEquals("d:int", approvePercentObject.get("type"));

            // set a new variable
            variableJson = new JSONObject();
            variableJson.put("name", "testVariable");
            variableJson.put("value", "text");
            variableJson.put("type", "d:text");
            variableJson.put("scope", "local");

            resultEntry = publicApiClient.tasksClient().updateTaskVariable(taskId, "testVariable", variableJson);
            assertNotNull(resultEntry);
            result = (JSONObject) resultEntry.get("entry");

            assertEquals("testVariable", result.get("name"));
            assertEquals("text", result.get("value"));
            assertEquals("d:text", result.get("type"));
            assertEquals("text", activitiProcessEngine.getTaskService().getVariable(taskId, "testVariable"));

            // change the variable value and type (should be working because no content model type)
            variableJson = new JSONObject();
            variableJson.put("name", "testVariable");
            variableJson.put("value", 123);
            variableJson.put("type", "d:int");
            variableJson.put("scope", "local");

            resultEntry = publicApiClient.tasksClient().updateTaskVariable(taskId, "testVariable", variableJson);
            assertNotNull(resultEntry);
            result = (JSONObject) resultEntry.get("entry");

            assertEquals("testVariable", result.get("name"));
            assertEquals(123l, result.get("value"));
            assertEquals("d:int", result.get("type"));
            assertEquals(123, activitiProcessEngine.getTaskService().getVariable(taskId, "testVariable"));

            // change the variable value for a list of noderefs (bpm_assignees)
            final JSONObject updateAssigneesJson = new JSONObject();
            updateAssigneesJson.put("name", "bpm_assignees");
            updateAssigneesJson.put("type", "d:noderef");
            updateAssigneesJson.put("scope", "global");

            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    JSONArray assigneeArray = new JSONArray();
                    assigneeArray.add(requestContext.getRunAsUser());
                    updateAssigneesJson.put("value", assigneeArray);
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            resultEntry = publicApiClient.tasksClient().updateTaskVariable(taskId, "bpm_assignees",
                    updateAssigneesJson);
            assertNotNull(resultEntry);
            final JSONObject updateAssigneeResult = (JSONObject) resultEntry.get("entry");

            assertEquals("bpm_assignees", updateAssigneeResult.get("name"));
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    JSONArray assigneeArray = (JSONArray) updateAssigneeResult.get("value");
                    assertNotNull(assigneeArray);
                    assertEquals(1, assigneeArray.size());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            assertEquals("d:noderef", updateAssigneeResult.get("type"));

            // update the bpm_assignees with a single entry, should result in an error
            final JSONObject updateAssigneeJson = new JSONObject();
            updateAssigneeJson.put("name", "bpm_assignees");
            updateAssigneeJson.put("type", "d:noderef");
            updateAssigneeJson.put("scope", "global");

            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    updateAssigneeJson.put("value", requestContext.getRunAsUser());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            try {
                publicApiClient.tasksClient().updateTaskVariable(taskId, "bpm_assignees", updateAssigneeJson);
                fail("Exception expected");
            } catch (PublicApiException e) {
                assertEquals(HttpStatus.BAD_REQUEST.value(), e.getHttpResponse().getStatusCode());
            }

        } finally {
            cleanupProcessInstance(processRest.getId());
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testUpdateTaskVariablesAuthentication() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        // Start process by one user and try to access the task variables as the task assignee instead of the process
        // initiator to see if the assignee is authorized to get the task
        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "newVariable");
            variableBody.put("value", 1234);
            variableBody.put("scope", "global");

            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            TasksClient tasksClient = publicApiClient.tasksClient();

            // Try updating task variables when NOT involved in the task
            try {
                tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }

            // Set assignee, task variables should be updatable now
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            JSONObject jsonObject = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
            assertNotNull(jsonObject);

            // Updating task variables as admin should be possible
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            jsonObject = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
            assertNotNull(jsonObject);

            // Updating the task variables as a admin from another tenant shouldn't be possible
            TestNetwork anotherNetwork = getOtherNetwork(requestContext.getNetworkId());
            tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + anotherNetwork.getId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            try {
                jsonObject = tasksClient.updateTaskVariable(task.getId(), "newVariable", variableBody);
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testCreateTaskVariablesPresentInModel() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());
            Map<String, Object> actualGlobalVariables = activitiProcessEngine.getRuntimeService()
                    .getVariables(processInstance.getId());
            assertEquals(5, actualGlobalVariables.size());
            assertEquals(8, actualLocalVariables.size());

            // Update a global value that is present in the model with type given
            JSONArray variablesArray = new JSONArray();
            JSONObject variableBody = new JSONObject();
            variableBody.put("name", "bpm_percentComplete");
            variableBody.put("value", 20);
            variableBody.put("type", "d:int");
            variableBody.put("scope", "global");
            variablesArray.add(variableBody);
            variableBody = new JSONObject();
            variableBody.put("name", "bpm_workflowPriority");
            variableBody.put("value", 50);
            variableBody.put("type", "d:int");
            variableBody.put("scope", "local");
            variablesArray.add(variableBody);

            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject result = tasksClient.createTaskVariables(task.getId(), variablesArray);
            assertNotNull(result);
            JSONObject resultObject = (JSONObject) result.get("list");
            JSONArray resultList = (JSONArray) resultObject.get("entries");
            assertEquals(2, resultList.size());
            JSONObject firstResultObject = (JSONObject) ((JSONObject) resultList.get(0)).get("entry");
            assertEquals("bpm_percentComplete", firstResultObject.get("name"));
            assertEquals(20L, firstResultObject.get("value"));
            assertEquals("d:int", firstResultObject.get("type"));
            assertEquals("global", firstResultObject.get("scope"));
            assertEquals(20, activitiProcessEngine.getRuntimeService().getVariable(processInstance.getId(),
                    "bpm_percentComplete"));

            JSONObject secondResultObject = (JSONObject) ((JSONObject) resultList.get(1)).get("entry");
            assertEquals("bpm_workflowPriority", secondResultObject.get("name"));
            assertEquals(50L, secondResultObject.get("value"));
            assertEquals("d:int", secondResultObject.get("type"));
            assertEquals("local", secondResultObject.get("scope"));
            assertEquals(50,
                    activitiProcessEngine.getTaskService().getVariable(task.getId(), "bpm_workflowPriority"));
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskVariablesRawVariableTypes() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Calendar dateCal = Calendar.getInstance();

            // Set all supported variables on the task to check the resulting types
            Map<String, Object> variablesToSet = new HashMap<String, Object>();
            variablesToSet.put("testVarString", "string");
            variablesToSet.put("testVarInteger", 1234);
            variablesToSet.put("testVarLong", 123456789L);
            variablesToSet.put("testVarDouble", 1234.5678D);
            variablesToSet.put("testVarFloat", 1234.0F);
            variablesToSet.put("testVarBoolean", Boolean.TRUE);
            variablesToSet.put("testVarDate", dateCal.getTime());
            variablesToSet.put("testVarQName", ContentModel.TYPE_AUTHORITY);
            variablesToSet.put("testVarNodeRef",
                    new ActivitiScriptNode(new NodeRef("workspace:///testNode"), serviceRegistry));
            variablesToSet.put("testVarRawNodeRef", new NodeRef("workspace:///testNode"));

            activitiProcessEngine.getTaskService().setVariablesLocal(task.getId(), variablesToSet);
            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Get all local variables
            JSONObject variables = tasksClient.findTaskVariables(task.getId(),
                    Collections.singletonMap("where", "(scope = local)"));
            assertNotNull(variables);
            JSONObject list = (JSONObject) variables.get("list");
            assertNotNull(list);
            JSONArray entries = (JSONArray) list.get("entries");
            assertNotNull(entries);

            // Check pagination object for size
            JSONObject pagination = (JSONObject) list.get("pagination");
            assertNotNull(pagination);
            assertEquals(actualLocalVariables.size(), ((Long) pagination.get("count")).intValue());
            assertEquals(actualLocalVariables.size(), ((Long) pagination.get("totalItems")).intValue());
            assertEquals(0L, pagination.get("skipCount"));
            assertFalse((Boolean) pagination.get("hasMoreItems"));

            assertEquals(actualLocalVariables.size(), entries.size());

            // Add JSON entries to map for easy access when asserting values
            Map<String, JSONObject> entriesByName = new HashMap<String, JSONObject>();
            for (int i = 0; i < entries.size(); i++) {
                JSONObject var = (JSONObject) entries.get(i);
                entriesByName.put((String) ((JSONObject) var.get("entry")).get("name"),
                        (JSONObject) var.get("entry"));
            }

            // Check all values and types
            JSONObject var = entriesByName.get("testVarString");
            assertNotNull(var);
            assertEquals("d:text", var.get("type"));
            assertEquals("string", var.get("value"));

            var = entriesByName.get("testVarInteger");
            assertNotNull(var);
            assertEquals("d:int", var.get("type"));
            assertEquals(1234L, var.get("value"));

            var = entriesByName.get("testVarLong");
            assertNotNull(var);
            assertEquals("d:long", var.get("type"));
            assertEquals(123456789L, var.get("value"));

            var = entriesByName.get("testVarDouble");
            assertNotNull(var);
            assertEquals("d:double", var.get("type"));
            assertEquals(1234.5678D, var.get("value"));

            var = entriesByName.get("testVarFloat");
            assertNotNull(var);
            assertEquals("d:float", var.get("type"));
            assertEquals(1234.0D, var.get("value"));

            var = entriesByName.get("testVarBoolean");
            assertNotNull(var);
            assertEquals("d:boolean", var.get("type"));
            assertEquals(Boolean.TRUE, var.get("value"));

            var = entriesByName.get("testVarDate");
            assertNotNull(var);
            assertEquals("d:datetime", var.get("type"));
            assertEquals(dateCal.getTime(), parseDate(var, "value"));

            var = entriesByName.get("testVarQName");
            assertNotNull(var);
            assertEquals("d:qname", var.get("type"));
            assertEquals("cm:authority", var.get("value"));

            var = entriesByName.get("testVarRawNodeRef");
            assertNotNull(var);
            assertEquals("d:noderef", var.get("type"));
            assertEquals("workspace:///testNode", var.get("value"));

            var = entriesByName.get("testVarNodeRef");
            assertNotNull(var);
            assertEquals("d:noderef", var.get("type"));
            assertEquals("workspace:///testNode", var.get("value"));

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskVariablesScoped() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            Map<String, Object> actualLocalVariables = activitiProcessEngine.getTaskService()
                    .getVariablesLocal(task.getId());
            Map<String, Object> actualGlobalVariables = activitiProcessEngine.getRuntimeService()
                    .getVariables(processInstance.getId());
            assertEquals(5, actualGlobalVariables.size());
            assertEquals(8, actualLocalVariables.size());

            TasksClient tasksClient = publicApiClient.tasksClient();

            JSONObject variables = tasksClient.findTaskVariables(task.getId(),
                    Collections.singletonMap("where", "(scope = local)"));
            assertNotNull(variables);
            JSONObject list = (JSONObject) variables.get("list");
            assertNotNull(list);
            JSONArray entries = (JSONArray) list.get("entries");
            assertNotNull(entries);

            // Check pagination object for size
            JSONObject pagination = (JSONObject) list.get("pagination");
            assertNotNull(pagination);
            assertEquals(8L, pagination.get("count"));
            assertEquals(8L, pagination.get("totalItems"));
            assertEquals(0L, pagination.get("skipCount"));
            assertFalse((Boolean) pagination.get("hasMoreItems"));

            assertEquals(actualLocalVariables.size(), entries.size());

            Set<String> expectedLocalVars = new HashSet<String>();
            expectedLocalVars.addAll(actualLocalVariables.keySet());

            for (int i = 0; i < entries.size(); i++) {
                JSONObject entry = (JSONObject) ((JSONObject) entries.get(i)).get("entry");
                assertNotNull(entry);

                // Check if full entry is present with correct scope
                assertEquals("local", entry.get("scope"));
                assertNotNull(entry.get("name"));
                assertNotNull(entry.get("type"));
                if (!entry.get("name").equals("bpm_hiddenTransitions")) {
                    assertNotNull(entry.get("value"));
                }
                expectedLocalVars.remove(entry.get("name"));
            }

            assertEquals(0, expectedLocalVars.size());

            // Now check the global scope
            variables = tasksClient.findTaskVariables(task.getId(),
                    Collections.singletonMap("where", "(scope = global)"));
            assertNotNull(variables);
            list = (JSONObject) variables.get("list");
            assertNotNull(list);
            entries = (JSONArray) list.get("entries");
            assertNotNull(entries);

            // Check pagination object for size
            pagination = (JSONObject) list.get("pagination");
            assertNotNull(pagination);
            assertEquals(4L, pagination.get("count"));
            assertEquals(4L, pagination.get("totalItems"));
            assertEquals(0L, pagination.get("skipCount"));
            assertFalse((Boolean) pagination.get("hasMoreItems"));

            // Should contain one variable less than the actual variables in the engine, tenant-domain var is filtered out
            assertEquals(actualGlobalVariables.size() - 1, entries.size());

            Set<String> expectedGlobalVars = new HashSet<String>();
            expectedGlobalVars.addAll(actualGlobalVariables.keySet());
            expectedGlobalVars.remove(ActivitiConstants.VAR_TENANT_DOMAIN);

            for (int i = 0; i < entries.size(); i++) {
                JSONObject entry = (JSONObject) ((JSONObject) entries.get(i)).get("entry");
                assertNotNull(entry);

                // Check if full entry is present with correct scope
                assertEquals("global", entry.get("scope"));
                assertNotNull(entry.get("name"));
                assertNotNull(entry.get("type"));
                assertNotNull(entry.get("value"));
                expectedGlobalVars.remove(entry.get("name"));
            }

            // Check if all variables are present
            assertEquals(0, expectedGlobalVars.size());

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskVariablesReview() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInfo processInstance = startParallelReviewProcess(requestContext);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).taskAssignee(requestContext.getRunAsUser())
                    .singleResult();

            assertNotNull(task);

            TasksClient tasksClient = publicApiClient.tasksClient();

            JSONObject variables = tasksClient.findTaskVariables(task.getId());
            assertNotNull(variables);
            JSONObject list = (JSONObject) variables.get("list");
            assertNotNull(list);
            JSONArray entries = (JSONArray) list.get("entries");
            assertNotNull(entries);

            // Check pagination object for size
            JSONObject pagination = (JSONObject) list.get("pagination");
            assertNotNull(pagination);
            assertEquals(42L, pagination.get("count"));
            assertEquals(42L, pagination.get("totalItems"));
            assertEquals(0L, pagination.get("skipCount"));
            assertFalse((Boolean) pagination.get("hasMoreItems"));
        } finally {
            cleanupProcessInstance(processInstance.getId());
        }
    }

    @Test
    public void testDeleteTaskVariable() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        activitiProcessEngine.getRuntimeService().setVariable(processInstance.getId(), "overlappingVariable",
                "Value set in process");
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            activitiProcessEngine.getTaskService().setVariableLocal(task.getId(), "myVariable",
                    "This is a variable");
            activitiProcessEngine.getTaskService().setVariableLocal(task.getId(), "overlappingVariable",
                    "Value set in task");

            // Delete a task-variable
            TasksClient tasksClient = publicApiClient.tasksClient();
            tasksClient.deleteTaskVariable(task.getId(), "myVariable");
            assertFalse(activitiProcessEngine.getTaskService().hasVariableLocal(task.getId(), "myVariable"));

            // Delete a task-variable that has the same name as a global process-variable - which should remain untouched after delete
            tasksClient.deleteTaskVariable(task.getId(), "overlappingVariable");
            assertFalse(
                    activitiProcessEngine.getTaskService().hasVariableLocal(task.getId(), "overlappingVariable"));
            assertTrue(activitiProcessEngine.getRuntimeService().hasVariable(processInstance.getId(),
                    "overlappingVariable"));
            assertEquals("Value set in process", activitiProcessEngine.getRuntimeService()
                    .getVariable(processInstance.getId(), "overlappingVariable"));
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testDeleteTaskVariableExceptions() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        activitiProcessEngine.getRuntimeService().setVariable(processInstance.getId(), "overlappingVariable",
                "Value set in process");
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            TasksClient tasksClient = publicApiClient.tasksClient();

            // Delete a variable on an unexisting task
            try {
                tasksClient.deleteTaskVariable("unexisting", "myVar");
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.NOT_FOUND.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("The entity with id: unexisting was not found", expected.getHttpResponse());
            }

            // Delete an unexisting variable on an existing task
            try {
                tasksClient.deleteTaskVariable(task.getId(), "unexistingVarName");
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.NOT_FOUND.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("The entity with id: unexistingVarName was not found",
                        expected.getHttpResponse());
            }

        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testDeleteTaskVariableAuthentication() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String initiator = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();

        // Start process by one user and try to access the task variables as the task assignee instead of the process
        // initiator to see if the assignee is authorized to get the task
        ProcessInstance processInstance = startAdhocProcess(initiator, requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);
            TasksClient tasksClient = publicApiClient.tasksClient();
            activitiProcessEngine.getTaskService().setVariableLocal(task.getId(), "existingVariable", "Value");

            // Try updating task variables when NOT involved in the task
            try {
                tasksClient.deleteTaskVariable(task.getId(), "existingVariable");
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }
            assertTrue(activitiProcessEngine.getTaskService().hasVariableLocal(task.getId(), "existingVariable"));

            // Set assignee, task variables should be updatable now
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            tasksClient.deleteTaskVariable(task.getId(), "existingVariable");
            assertFalse(activitiProcessEngine.getTaskService().hasVariableLocal(task.getId(), "existingVariable"));
            activitiProcessEngine.getTaskService().setVariableLocal(task.getId(), "existingVariable", "Value");

            // Updating task variables as admin should be possible
            String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            tasksClient.deleteTaskVariable(task.getId(), "existingVariable");
            assertFalse(activitiProcessEngine.getTaskService().hasVariableLocal(task.getId(), "existingVariable"));
            activitiProcessEngine.getTaskService().setVariableLocal(task.getId(), "existingVariable", "Value");

            // Updating the task variables as a admin from another tenant shouldn't be possible
            TestNetwork anotherNetwork = getOtherNetwork(requestContext.getNetworkId());
            tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + anotherNetwork.getId();
            publicApiClient.setRequestContext(new RequestContext(TenantUtil.DEFAULT_TENANT, tenantAdmin));
            try {
                tasksClient.deleteTaskVariable(task.getId(), "existingVariable");
                fail("Exception expected");
            } catch (PublicApiException expected) {
                assertEquals(HttpStatus.FORBIDDEN.value(), expected.getHttpResponse().getStatusCode());
                assertErrorSummary("Permission was denied", expected.getHttpResponse());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    @Test
    public void testGetTaskModel() throws Exception {
        RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
        RequestContext adminContext = new RequestContext(requestContext.getNetworkId(), tenantAdmin);

        ProcessInstance processInstance = startAdhocProcess(requestContext.getRunAsUser(),
                requestContext.getNetworkId(), null);
        try {
            Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                    .processInstanceId(processInstance.getId()).singleResult();
            assertNotNull(task);

            JSONObject model = publicApiClient.tasksClient().findTaskFormModel(task.getId());
            assertNotNull(model);

            JSONArray entries = (JSONArray) model.get("entries");
            assertNotNull(entries);

            // Add all entries to a map, to make lookup easier
            Map<String, JSONObject> modelFieldsByName = createEntryMap(entries);
            testModelEntries(modelFieldsByName);

            // get task form model with admin
            publicApiClient.setRequestContext(adminContext);
            model = publicApiClient.tasksClient().findTaskFormModel(task.getId());
            assertNotNull(model);

            entries = (JSONArray) model.get("entries");
            assertNotNull(entries);

            modelFieldsByName = createEntryMap(entries);
            testModelEntries(modelFieldsByName);

            // get task form model with non involved user
            publicApiClient.setRequestContext(otherContext);
            try {
                publicApiClient.tasksClient().findTaskFormModel(task.getId());
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            // get task form model with invalid task id
            publicApiClient.setRequestContext(requestContext);
            try {
                publicApiClient.tasksClient().findTaskFormModel("fakeid");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }
        } finally {
            cleanupProcessInstance(processInstance);
        }
    }

    protected Map<String, JSONObject> createEntryMap(JSONArray entries) {
        Map<String, JSONObject> modelFieldsByName = new HashMap<String, JSONObject>();
        JSONObject entry = null;
        for (int i = 0; i < entries.size(); i++) {
            entry = (JSONObject) entries.get(i);
            assertNotNull(entry);
            entry = (JSONObject) entry.get("entry");
            assertNotNull(entry);
            modelFieldsByName.put((String) entry.get("name"), entry);
        }
        return modelFieldsByName;
    }

    protected void testModelEntries(Map<String, JSONObject> modelFieldsByName) {
        // Check well-known properties and their types

        // Validate bpm:completionDate
        JSONObject modelEntry = modelFieldsByName.get("bpm_completionDate");
        assertNotNull(modelEntry);
        assertEquals("Completion Date", modelEntry.get("title"));
        assertEquals("{http://www.alfresco.org/model/bpm/1.0}completionDate", modelEntry.get("qualifiedName"));
        assertEquals("d:date", modelEntry.get("dataType"));
        assertFalse((Boolean) modelEntry.get("required"));

        // Validate cm:owner
        modelEntry = modelFieldsByName.get("cm_owner");
        assertNotNull(modelEntry);
        assertEquals("Owner", modelEntry.get("title"));
        assertEquals("{http://www.alfresco.org/model/content/1.0}owner", modelEntry.get("qualifiedName"));
        assertEquals("d:text", modelEntry.get("dataType"));
        assertFalse((Boolean) modelEntry.get("required"));

        // Validate bpm:package
        modelEntry = modelFieldsByName.get("bpm_package");
        assertNotNull(modelEntry);
        assertEquals("Content Package", modelEntry.get("title"));
        assertEquals("{http://www.alfresco.org/model/bpm/1.0}package", modelEntry.get("qualifiedName"));
        assertEquals("bpm:workflowPackage", modelEntry.get("dataType"));
        assertFalse((Boolean) modelEntry.get("required"));
    }

    @Test
    public void testGetTaskItems() throws Exception {
        final RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
        RequestContext adminContext = new RequestContext(requestContext.getNetworkId(), tenantAdmin);

        // Create test-document and add to package
        NodeRef[] docNodeRefs = createTestDocuments(requestContext);
        ProcessInfo processInfo = startAdhocProcess(requestContext, docNodeRefs);

        final Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                .processInstanceId(processInfo.getId()).singleResult();

        assertNotNull(task);
        activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);

        try {
            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject itemsJSON = tasksClient.findTaskItems(task.getId());
            assertNotNull(itemsJSON);
            JSONArray entriesJSON = (JSONArray) itemsJSON.get("entries");
            assertNotNull(entriesJSON);
            assertTrue(entriesJSON.size() == 2);
            boolean doc1Found = false;
            boolean doc2Found = false;
            for (Object entryObject : entriesJSON) {
                JSONObject entryObjectJSON = (JSONObject) entryObject;
                JSONObject entryJSON = (JSONObject) entryObjectJSON.get("entry");
                if (entryJSON.get("name").equals("Test Doc1")) {
                    doc1Found = true;
                    assertEquals(docNodeRefs[0].getId(), entryJSON.get("id"));
                    assertEquals("Test Doc1", entryJSON.get("name"));
                    assertEquals("Test Doc1 Title", entryJSON.get("title"));
                    assertEquals("Test Doc1 Description", entryJSON.get("description"));
                    assertNotNull(entryJSON.get("createdAt"));
                    assertEquals(requestContext.getRunAsUser(), entryJSON.get("createdBy"));
                    assertNotNull(entryJSON.get("modifiedAt"));
                    assertEquals(requestContext.getRunAsUser(), entryJSON.get("modifiedBy"));
                    assertNotNull(entryJSON.get("size"));
                    assertNotNull(entryJSON.get("mimeType"));
                } else {
                    doc2Found = true;
                    assertEquals(docNodeRefs[1].getId(), entryJSON.get("id"));
                    assertEquals("Test Doc2", entryJSON.get("name"));
                    assertEquals("Test Doc2 Title", entryJSON.get("title"));
                    assertEquals("Test Doc2 Description", entryJSON.get("description"));
                    assertNotNull(entryJSON.get("createdAt"));
                    assertEquals(requestContext.getRunAsUser(), entryJSON.get("createdBy"));
                    assertNotNull(entryJSON.get("modifiedAt"));
                    assertEquals(requestContext.getRunAsUser(), entryJSON.get("modifiedBy"));
                    assertNotNull(entryJSON.get("size"));
                    assertNotNull(entryJSON.get("mimeType"));
                }
            }
            assertTrue(doc1Found);
            assertTrue(doc2Found);

            // get with admin
            publicApiClient.setRequestContext(adminContext);
            itemsJSON = tasksClient.findTaskItems(task.getId());
            assertNotNull(itemsJSON);
            entriesJSON = (JSONArray) itemsJSON.get("entries");
            assertNotNull(entriesJSON);
            assertTrue(entriesJSON.size() == 2);

            // get with non involved user
            publicApiClient.setRequestContext(otherContext);
            try {
                tasksClient.findTaskItems(task.getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            // get with candidate user
            activitiProcessEngine.getTaskService().addCandidateUser(task.getId(), otherContext.getRunAsUser());
            publicApiClient.setRequestContext(otherContext);
            itemsJSON = tasksClient.findTaskItems(task.getId());
            assertNotNull(itemsJSON);
            entriesJSON = (JSONArray) itemsJSON.get("entries");
            assertNotNull(entriesJSON);
            assertTrue(entriesJSON.size() == 2);

            // get with user from candidate group with no assignee
            List<MemberOfSite> memberships = getTestFixture().getNetwork(otherContext.getNetworkId())
                    .getSiteMemberships(otherContext.getRunAsUser());
            assertTrue(memberships.size() > 0);
            MemberOfSite memberOfSite = memberships.get(0);
            String group = "GROUP_site_" + memberOfSite.getSiteId() + "_" + memberOfSite.getRole().name();

            activitiProcessEngine.getTaskService().deleteCandidateUser(task.getId(), otherContext.getRunAsUser());
            activitiProcessEngine.getTaskService().addCandidateGroup(task.getId(), group);
            publicApiClient.setRequestContext(otherContext);
            itemsJSON = tasksClient.findTaskItems(task.getId());
            assertNotNull(itemsJSON);
            entriesJSON = (JSONArray) itemsJSON.get("entries");
            assertNotNull(entriesJSON);
            assertTrue(entriesJSON.size() == 2);

            // get with user from candidate group with assignee
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            try {
                tasksClient.findTaskItems(task.getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            // invalid task id
            publicApiClient.setRequestContext(requestContext);
            try {
                tasksClient.findTaskItems("fakeid");
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // get items from completed task with initiator
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    activitiProcessEngine.getTaskService().complete(task.getId());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            publicApiClient.setRequestContext(requestContext);
            itemsJSON = tasksClient.findTaskItems(task.getId());
            assertNotNull(itemsJSON);
            entriesJSON = (JSONArray) itemsJSON.get("entries");
            assertNotNull(entriesJSON);
            assertTrue(entriesJSON.size() == 2);

            // get items from completed task with user from candidate group
            publicApiClient.setRequestContext(otherContext);
            try {
                tasksClient.findTaskItems(task.getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }
        } finally {
            cleanupProcessInstance(processInfo.getId());
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testAddTaskItem() throws Exception {
        final RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
        RequestContext adminContext = new RequestContext(requestContext.getNetworkId(), tenantAdmin);

        // Create test-document and add to package
        NodeRef[] docNodeRefs = createTestDocuments(requestContext);
        ProcessInfo processInfo = startAdhocProcess(requestContext, null);

        final Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                .processInstanceId(processInfo.getId()).singleResult();

        assertNotNull(task);
        activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);

        try {
            TasksClient tasksClient = publicApiClient.tasksClient();
            JSONObject createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            JSONObject result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));
            assertEquals("Test Doc1", result.get("name"));
            assertEquals("Test Doc1 Title", result.get("title"));
            assertEquals("Test Doc1 Description", result.get("description"));
            assertNotNull(result.get("createdAt"));
            assertEquals(requestContext.getRunAsUser(), result.get("createdBy"));
            assertNotNull(result.get("modifiedAt"));
            assertEquals(requestContext.getRunAsUser(), result.get("modifiedBy"));
            assertNotNull(result.get("size"));
            assertNotNull(result.get("mimeType"));

            JSONObject itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // add item as admin
            publicApiClient.setRequestContext(adminContext);
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));
            assertEquals("Test Doc1", result.get("name"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // add item with candidate user
            activitiProcessEngine.getTaskService().addCandidateUser(task.getId(), otherPerson);
            publicApiClient.setRequestContext(otherContext);
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));
            assertEquals("Test Doc1", result.get("name"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // add item with not involved user
            activitiProcessEngine.getTaskService().deleteCandidateUser(task.getId(), otherPerson);
            publicApiClient.setRequestContext(otherContext);
            try {
                tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            // add item with user from candidate group with no assignee
            List<MemberOfSite> memberships = getTestFixture().getNetwork(otherContext.getNetworkId())
                    .getSiteMemberships(otherContext.getRunAsUser());
            assertTrue(memberships.size() > 0);
            MemberOfSite memberOfSite = memberships.get(0);
            String group = "GROUP_site_" + memberOfSite.getSiteId() + "_" + memberOfSite.getRole().name();

            activitiProcessEngine.getTaskService().deleteCandidateUser(task.getId(), otherContext.getRunAsUser());
            activitiProcessEngine.getTaskService().addCandidateGroup(task.getId(), group);
            publicApiClient.setRequestContext(otherContext);
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));
            assertEquals("Test Doc1", result.get("name"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // add item with user from candidate group with assignee
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            publicApiClient.setRequestContext(otherContext);
            try {
                tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            // invalid task id
            publicApiClient.setRequestContext(requestContext);
            try {
                tasksClient.addTaskItem("fakeid", createItemObject.toJSONString());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // invalid item id
            createItemObject = new JSONObject();
            createItemObject.put("id", "fakeid");
            try {
                tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // add item to completed task
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    activitiProcessEngine.getTaskService().complete(task.getId());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            try {
                tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }
        } finally {
            cleanupProcessInstance(processInfo.getId());
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testDeleteTaskItem() throws Exception {
        final RequestContext requestContext = initApiClientWithTestUser();

        String otherPerson = getOtherPersonInNetwork(requestContext.getRunAsUser(), requestContext.getNetworkId())
                .getId();
        RequestContext otherContext = new RequestContext(requestContext.getNetworkId(), otherPerson);

        String tenantAdmin = AuthenticationUtil.getAdminUserName() + "@" + requestContext.getNetworkId();
        RequestContext adminContext = new RequestContext(requestContext.getNetworkId(), tenantAdmin);

        // Create test-document and add to package
        NodeRef[] docNodeRefs = createTestDocuments(requestContext);
        ProcessInfo processInfo = startAdhocProcess(requestContext, docNodeRefs);

        final Task task = activitiProcessEngine.getTaskService().createTaskQuery()
                .processInstanceId(processInfo.getId()).singleResult();

        assertNotNull(task);
        activitiProcessEngine.getTaskService().setAssignee(task.getId(), null);

        try {
            TasksClient tasksClient = publicApiClient.tasksClient();
            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());

            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // delete item as admin
            JSONObject createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            JSONObject result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));

            JSONObject itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            publicApiClient.setRequestContext(adminContext);
            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // delete item with candidate user
            createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            activitiProcessEngine.getTaskService().addCandidateUser(task.getId(), otherPerson);
            publicApiClient.setRequestContext(otherContext);
            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // delete item with not involved user
            createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            activitiProcessEngine.getTaskService().deleteCandidateUser(task.getId(), otherPerson);
            publicApiClient.setRequestContext(otherContext);
            try {
                tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            // delete item with user from candidate group with no assignee
            List<MemberOfSite> memberships = getTestFixture().getNetwork(otherContext.getNetworkId())
                    .getSiteMemberships(otherContext.getRunAsUser());
            assertTrue(memberships.size() > 0);
            MemberOfSite memberOfSite = memberships.get(0);
            String group = "GROUP_site_" + memberOfSite.getSiteId() + "_" + memberOfSite.getRole().name();

            activitiProcessEngine.getTaskService().deleteCandidateUser(task.getId(), otherContext.getRunAsUser());
            activitiProcessEngine.getTaskService().addCandidateGroup(task.getId(), group);
            publicApiClient.setRequestContext(otherContext);
            createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // delete item with user from candidate group with assignee
            activitiProcessEngine.getTaskService().setAssignee(task.getId(), requestContext.getRunAsUser());
            publicApiClient.setRequestContext(requestContext);
            createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            publicApiClient.setRequestContext(otherContext);
            try {
                tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(403, e.getHttpResponse().getStatusCode());
            }

            publicApiClient.setRequestContext(requestContext);
            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));
            tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
            try {
                tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // invalid task id
            publicApiClient.setRequestContext(requestContext);
            try {
                tasksClient.deleteTaskItem("fakeid", docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // invalid item id
            try {
                tasksClient.deleteTaskItem(task.getId(), "fakeid");
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }

            // delete item from completed task
            createItemObject = new JSONObject();
            createItemObject.put("id", docNodeRefs[0].getId());
            result = tasksClient.addTaskItem(task.getId(), createItemObject.toJSONString());

            assertNotNull(result);
            assertEquals(docNodeRefs[0].getId(), result.get("id"));

            itemJSON = tasksClient.findTaskItem(task.getId(), docNodeRefs[0].getId());
            assertEquals(docNodeRefs[0].getId(), itemJSON.get("id"));

            TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>() {
                @Override
                public Void doWork() throws Exception {
                    activitiProcessEngine.getTaskService().complete(task.getId());
                    return null;
                }
            }, requestContext.getRunAsUser(), requestContext.getNetworkId());

            try {
                tasksClient.deleteTaskItem(task.getId(), docNodeRefs[0].getId());
                fail("Expected exception");
            } catch (PublicApiException e) {
                assertEquals(404, e.getHttpResponse().getStatusCode());
            }
        } finally {
            cleanupProcessInstance(processInfo.getId());
        }
    }

    protected ProcessInstance startAdhocProcess(final String user, final String networkId,
            final String businessKey) {
        return TenantUtil.runAsUserTenant(new TenantRunAsWork<ProcessInstance>() {
            @Override
            public ProcessInstance doWork() throws Exception {
                String processDefinitionKey = "@" + networkId + "@activitiAdhoc";
                // Set required variables for adhoc process and start
                Map<String, Object> variables = new HashMap<String, Object>();
                ActivitiScriptNode person = getPersonNodeRef(user);
                variables.put("bpm_assignee", person);
                variables.put("wf_notifyMe", Boolean.FALSE);
                variables.put(WorkflowConstants.PROP_INITIATOR, person);
                return activitiProcessEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey,
                        businessKey, variables);
            }
        }, user, networkId);
    }

    protected int getResultSizeForTaskQuery(Map<String, String> params, TasksClient tasksClient) throws Exception {
        JSONObject taskListJSONObject = tasksClient.findTasks(params);
        assertNotNull(taskListJSONObject);
        JSONArray jsonEntries = (JSONArray) taskListJSONObject.get("entries");
        return jsonEntries.size();
    }

    protected void assertTasksPresentInTaskQuery(Map<String, String> params, TasksClient tasksClient,
            String... taskIds) throws Exception {
        assertTasksPresentInTaskQuery(params, tasksClient, true, taskIds);
    }

    protected void assertTasksPresentInTaskQuery(Map<String, String> params, TasksClient tasksClient,
            boolean countShouldMatch, String... taskIds) throws Exception {
        JSONObject taskListJSONObject = tasksClient.findTasks(params);
        assertNotNull(taskListJSONObject);
        JSONArray jsonEntries = (JSONArray) taskListJSONObject.get("entries");
        if (countShouldMatch) {
            assertEquals("Wrong amount of tasks returned by query", taskIds.length, jsonEntries.size());
        }

        List<String> tasksToFind = new ArrayList<String>();
        tasksToFind.addAll(Arrays.asList(taskIds));

        for (int i = 0; i < jsonEntries.size(); i++) {
            JSONObject entry = (JSONObject) ((JSONObject) jsonEntries.get(i)).get("entry");
            assertNotNull(entry);
            String taskId = (String) entry.get("id");
            tasksToFind.remove(taskId);
        }
        assertEquals(
                "Not all tasks have been found in query response, missing: " + StringUtils.join(tasksToFind, ","),
                0, tasksToFind.size());
    }

    protected void cleanupProcessInstance(ProcessInstance... processInstances) {
        // Clean up process-instance regardless of test success/failure
        try {
            for (ProcessInstance processInstance : processInstances) {
                if (processInstance != null) {
                    activitiProcessEngine.getRuntimeService().deleteProcessInstance(processInstance.getId(), null);
                    activitiProcessEngine.getHistoryService()
                            .deleteHistoricProcessInstance(processInstance.getId());
                }
            }
        } catch (Throwable t) {
            // Ignore error during cleanup to prevent swallowing potential assetion-exception
            log("Error while cleaning up process instance", t);
        }
    }

    protected void cleanupProcessInstance(String... processInstances) {
        // Clean up process-instance regardless of test success/failure
        try {
            for (String instanceId : processInstances) {
                activitiProcessEngine.getRuntimeService().deleteProcessInstance(instanceId, null);
                activitiProcessEngine.getHistoryService().deleteHistoricProcessInstance(instanceId);
            }
        } catch (Throwable t) {
            // Ignore error during cleanup to prevent swallowing potential assetion-exception
            log("Error while cleaning up process instance", t);
        }
    }
}