org.camunda.bpm.engine.test.api.externaltask.ExternalTaskServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for org.camunda.bpm.engine.test.api.externaltask.ExternalTaskServiceTest.java

Source

/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.camunda.bpm.engine.test.api.externaltask;

import org.apache.commons.lang.exception.ExceptionUtils;
import static org.hamcrest.CoreMatchers.containsString;

import org.apache.ibatis.jdbc.RuntimeSqlException;
import org.camunda.bpm.engine.BadUserRequestException;
import org.camunda.bpm.engine.ProcessEngineConfiguration;
import org.camunda.bpm.engine.ProcessEngineException;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.ExecutionListener;
import org.camunda.bpm.engine.exception.NotFoundException;
import org.camunda.bpm.engine.exception.NullValueException;
import org.camunda.bpm.engine.externaltask.ExternalTask;
import org.camunda.bpm.engine.externaltask.ExternalTaskQuery;
import org.camunda.bpm.engine.externaltask.ExternalTaskQueryBuilder;
import org.camunda.bpm.engine.externaltask.LockedExternalTask;
import org.camunda.bpm.engine.history.HistoricIncident;
import org.camunda.bpm.engine.history.HistoricProcessInstanceQuery;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.impl.history.HistoryLevel;
import org.camunda.bpm.engine.impl.test.PluggableProcessEngineTestCase;
import org.camunda.bpm.engine.impl.util.ClockUtil;
import org.camunda.bpm.engine.runtime.ActivityInstance;
import org.camunda.bpm.engine.runtime.Incident;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceQuery;
import org.camunda.bpm.engine.runtime.VariableInstance;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.test.Deployment;
import org.camunda.bpm.engine.test.RequiredHistoryLevel;
import org.camunda.bpm.engine.test.util.AssertUtil;
import org.camunda.bpm.engine.variable.VariableMap;
import org.camunda.bpm.engine.variable.Variables;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.joda.time.DateTime;
import org.junit.Assert;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.camunda.bpm.engine.test.util.ActivityInstanceAssert.assertThat;
import static org.camunda.bpm.engine.test.util.ActivityInstanceAssert.describeActivityInstanceTree;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;

/**
 * @author Thorben Lindhauer
 *
 */
public class ExternalTaskServiceTest extends PluggableProcessEngineTestCase {

    protected static final String WORKER_ID = "aWorkerId";
    protected static final long LOCK_TIME = 10000L;
    protected static final String TOPIC_NAME = "externalTaskTopic";

    protected void setUp() throws Exception {
        ClockUtil.setCurrentTime(new Date());
    }

    protected void tearDown() throws Exception {
        ClockUtil.reset();
    }

    public void testFailOnMalformedpriorityInput() {
        try {
            repositoryService.createDeployment()
                    .addClasspathResource(
                            "org/camunda/bpm/engine/test/api/externaltask/externalTaskInvalidPriority.bpmn20.xml")
                    .deploy();
            fail("deploying a process with malformed priority should not succeed");
        } catch (ProcessEngineException e) {
            assertTextPresentIgnoreCase(
                    "value 'NOTaNumber' for attribute 'taskPriority' " + "is not a valid number", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetch() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        assertEquals(1, externalTasks.size());

        LockedExternalTask task = externalTasks.get(0);
        assertNotNull(task.getId());
        assertEquals(processInstance.getId(), task.getProcessInstanceId());
        assertEquals(processInstance.getProcessDefinitionId(), task.getProcessDefinitionId());
        assertEquals("externalTask", task.getActivityId());
        assertEquals("oneExternalTaskProcess", task.getProcessDefinitionKey());
        assertEquals(TOPIC_NAME, task.getTopicName());

        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId())
                .getActivityInstances("externalTask")[0];

        assertEquals(activityInstance.getId(), task.getActivityInstanceId());
        assertEquals(activityInstance.getExecutionIds()[0], task.getExecutionId());

        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME), task.getLockExpirationTime());

        assertEquals(WORKER_ID, task.getWorkerId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskWithPriorityProcess.bpmn20.xml")
    public void testFetchWithPriority() {
        // given
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("twoExternalTaskWithPriorityProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        assertEquals(1, externalTasks.size());

        LockedExternalTask task = externalTasks.get(0);
        assertNotNull(task.getId());
        assertEquals(processInstance.getId(), task.getProcessInstanceId());
        assertEquals(processInstance.getProcessDefinitionId(), task.getProcessDefinitionId());
        assertEquals("externalTaskWithPrio", task.getActivityId());
        assertEquals("twoExternalTaskWithPriorityProcess", task.getProcessDefinitionKey());
        assertEquals(TOPIC_NAME, task.getTopicName());
        assertEquals(7, task.getPriority());

        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId())
                .getActivityInstances("externalTaskWithPrio")[0];

        assertEquals(activityInstance.getId(), task.getActivityInstanceId());
        assertEquals(activityInstance.getExecutionIds()[0], task.getExecutionId());

        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME), task.getLockExpirationTime());

        assertEquals(WORKER_ID, task.getWorkerId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/externalTaskPriorityProcess.bpmn20.xml")
    public void testFetchProcessWithPriority() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskWithPriorityProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(2, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(2, externalTasks.size());

        // then
        //task with no prio gets prio defined by process
        assertEquals(9, externalTasks.get(0).getPriority());
        //task with own prio overrides prio defined by process
        assertEquals(7, externalTasks.get(1).getPriority());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/externalTaskPriorityExpressionProcess.bpmn20.xml")
    public void testFetchProcessWithPriorityExpression() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskWithPriorityProcess",
                Variables.createVariables().putValue("priority", 18));

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(2, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(2, externalTasks.size());

        // then
        //task with no prio gets prio defined by process
        assertEquals(18, externalTasks.get(0).getPriority());
        //task with own prio overrides prio defined by process
        assertEquals(7, externalTasks.get(1).getPriority());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/externalTaskPriorityExpression.bpmn20.xml")
    public void testFetchWithPriorityExpression() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
                "twoExternalTaskWithPriorityProcess", Variables.createVariables().putValue("priority", 18));
        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        assertEquals(1, externalTasks.size());

        LockedExternalTask task = externalTasks.get(0);
        assertNotNull(task.getId());
        assertEquals(processInstance.getId(), task.getProcessInstanceId());
        assertEquals(processInstance.getProcessDefinitionId(), task.getProcessDefinitionId());
        assertEquals("externalTaskWithPrio", task.getActivityId());
        assertEquals("twoExternalTaskWithPriorityProcess", task.getProcessDefinitionKey());
        assertEquals(TOPIC_NAME, task.getTopicName());
        assertEquals(18, task.getPriority());

        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId())
                .getActivityInstances("externalTaskWithPrio")[0];

        assertEquals(activityInstance.getId(), task.getActivityInstanceId());
        assertEquals(activityInstance.getExecutionIds()[0], task.getExecutionId());

        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME), task.getLockExpirationTime());

        assertEquals(WORKER_ID, task.getWorkerId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskWithPriorityProcess.bpmn20.xml")
    public void testFetchWithPriorityOrdering() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskWithPriorityProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(2, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        assertEquals(2, externalTasks.size());
        assertTrue(externalTasks.get(0).getPriority() > externalTasks.get(1).getPriority());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskWithPriorityProcess.bpmn20.xml")
    public void testFetchNextWithPriority() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskWithPriorityProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then the task is locked
        assertEquals(1, externalTasks.size());

        LockedExternalTask task = externalTasks.get(0);
        long firstPrio = task.getPriority();
        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME), task.getLockExpirationTime());

        // another task with next higher priority can be claimed
        externalTasks = externalTaskService.fetchAndLock(1, "anotherWorkerId", true).topic(TOPIC_NAME, LOCK_TIME)
                .execute();
        assertEquals(1, externalTasks.size());
        assertTrue(firstPrio >= externalTasks.get(0).getPriority());

        // the expiration time expires
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        //first can be claimed
        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID, true).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(1, externalTasks.size());
        assertEquals(firstPrio, externalTasks.get(0).getPriority());
    }

    @Deployment
    public void testFetchTopicSelection() {
        // given
        runtimeService.startProcessInstanceByKey("twoTopicsProcess");

        // when
        List<LockedExternalTask> topic1Tasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic("topic1", LOCK_TIME).execute();

        List<LockedExternalTask> topic2Tasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic("topic2", LOCK_TIME).execute();

        // then
        assertEquals(1, topic1Tasks.size());
        assertEquals("topic1", topic1Tasks.get(0).getTopicName());

        assertEquals(1, topic2Tasks.size());
        assertEquals("topic2", topic2Tasks.get(0).getTopicName());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchWithoutTopicName() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        try {
            externalTaskService.fetchAndLock(1, WORKER_ID).topic(null, LOCK_TIME).execute();
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("topicName is null", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchNullWorkerId() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        try {
            externalTaskService.fetchAndLock(1, null).topic(TOPIC_NAME, LOCK_TIME).execute();
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("workerId is null", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchNegativeNumberOfTasks() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        try {
            externalTaskService.fetchAndLock(-1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("maxResults is not greater than or equal to 0", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchLessTasksThanExist() {
        // given
        for (int i = 0; i < 10; i++) {
            runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        }

        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        assertEquals(5, externalTasks.size());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchNegativeLockTime() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        try {
            externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, -1L).execute();
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("lockTime is not greater than 0", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchZeroLockTime() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        try {
            externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 0L).execute();
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("lockTime is not greater than 0", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchNoTopics() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).execute();

        // then
        assertEquals(0, tasks.size());
    }

    @Deployment
    public void testFetchVariables() {
        // given
        runtimeService.startProcessInstanceByKey("subProcessExternalTask",
                Variables.createVariables().putValue("processVar1", 42).putValue("processVar2", 43));

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).variables("processVar1", "subProcessVar", "taskVar").execute();

        // then
        LockedExternalTask task = externalTasks.get(0);
        VariableMap variables = task.getVariables();
        assertEquals(3, variables.size());

        assertEquals(42, variables.get("processVar1"));
        assertEquals(44L, variables.get("subProcessVar"));
        assertEquals(45L, variables.get("taskVar"));

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testFetchVariables.bpmn20.xml")
    public void testShouldNotFetchSerializedVariables() {
        // given
        ExternalTaskCustomValue customValue = new ExternalTaskCustomValue();
        customValue.setTestValue("value1");
        runtimeService.startProcessInstanceByKey("subProcessExternalTask",
                Variables.createVariables().putValue("processVar1", customValue));

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).variables("processVar1").execute();

        // then
        LockedExternalTask task = externalTasks.get(0);
        VariableMap variables = task.getVariables();
        assertEquals(1, variables.size());

        try {
            variables.get("processVar1");
            fail("did not receive an exception although variable was serialized");
        } catch (IllegalStateException e) {
            assertEquals("Object is not deserialized.", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testFetchVariables.bpmn20.xml")
    public void testFetchSerializedVariables() {
        // given
        ExternalTaskCustomValue customValue = new ExternalTaskCustomValue();
        customValue.setTestValue("value1");
        runtimeService.startProcessInstanceByKey("subProcessExternalTask",
                Variables.createVariables().putValue("processVar1", customValue));

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).variables("processVar1").enableCustomObjectDeserialization()
                .execute();

        // then
        LockedExternalTask task = externalTasks.get(0);
        VariableMap variables = task.getVariables();
        assertEquals(1, variables.size());

        final ExternalTaskCustomValue receivedCustomValue = (ExternalTaskCustomValue) variables.get("processVar1");
        assertNotNull(receivedCustomValue);
        assertNotNull(receivedCustomValue.getTestValue());
        assertEquals("value1", receivedCustomValue.getTestValue());
    }

    @Deployment(resources = {
            "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskVariablesTest.testExternalTaskVariablesLocal.bpmn20.xml" })
    public void testFetchOnlyLocalVariables() {

        VariableMap globalVars = Variables.putValue("globalVar", "globalVal");

        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess", globalVars);

        final String workerId = "workerId";
        final String topicName = "testTopic";

        List<LockedExternalTask> lockedExternalTasks = externalTaskService.fetchAndLock(10, workerId)
                .topic(topicName, 60000).execute();

        assertEquals(1, lockedExternalTasks.size());

        LockedExternalTask lockedExternalTask = lockedExternalTasks.get(0);
        VariableMap variables = lockedExternalTask.getVariables();
        assertEquals(2, variables.size());
        assertEquals("globalVal", variables.getValue("globalVar", String.class));
        assertEquals("localVal", variables.getValue("localVar", String.class));

        externalTaskService.unlock(lockedExternalTask.getId());

        lockedExternalTasks = externalTaskService.fetchAndLock(10, workerId).topic(topicName, 60000)
                .variables("globalVar", "localVar").localVariables().execute();

        assertEquals(1, lockedExternalTasks.size());

        lockedExternalTask = lockedExternalTasks.get(0);
        variables = lockedExternalTask.getVariables();
        assertEquals(1, variables.size());
        assertEquals("localVal", variables.getValue("localVar", String.class));
    }

    @Deployment(resources = {
            "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskVariablesTest.testExternalTaskVariablesLocal.bpmn20.xml" })
    public void testFetchNonExistingLocalVariables() {

        VariableMap globalVars = Variables.putValue("globalVar", "globalVal");

        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess", globalVars);

        final String workerId = "workerId";
        final String topicName = "testTopic";

        List<LockedExternalTask> lockedExternalTasks = externalTaskService.fetchAndLock(10, workerId)
                .topic(topicName, 60000).variables("globalVar", "nonExistingLocalVar").localVariables().execute();

        assertEquals(1, lockedExternalTasks.size());

        LockedExternalTask lockedExternalTask = lockedExternalTasks.get(0);
        VariableMap variables = lockedExternalTask.getVariables();
        assertEquals(0, variables.size());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testFetchVariables.bpmn20.xml")
    public void testFetchAllVariables() {
        // given
        runtimeService.startProcessInstanceByKey("subProcessExternalTask",
                Variables.createVariables().putValue("processVar1", 42).putValue("processVar2", 43));

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        LockedExternalTask task = externalTasks.get(0);
        verifyVariables(task);

        runtimeService.startProcessInstanceByKey("subProcessExternalTask",
                Variables.createVariables().putValue("processVar1", 42).putValue("processVar2", 43));

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .variables((String[]) null).execute();

        task = externalTasks.get(0);
        verifyVariables(task);

        runtimeService.startProcessInstanceByKey("subProcessExternalTask",
                Variables.createVariables().putValue("processVar1", 42).putValue("processVar2", 43));

        List<String> list = null;
        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).variables(list)
                .execute();

        task = externalTasks.get(0);
        verifyVariables(task);
    }

    private void verifyVariables(LockedExternalTask task) {
        VariableMap variables = task.getVariables();
        assertEquals(4, variables.size());

        assertEquals(42, variables.get("processVar1"));
        assertEquals(43, variables.get("processVar2"));
        assertEquals(44L, variables.get("subProcessVar"));
        assertEquals(45L, variables.get("taskVar"));
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchNonExistingVariable() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .variables("nonExistingVariable").execute();

        LockedExternalTask task = tasks.get(0);

        // then
        assertTrue(task.getVariables().isEmpty());
    }

    @Deployment
    public void testFetchMultipleTopics() {
        // given a process instance with external tasks for topics "topic1", "topic2", and "topic3"
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess");

        // when fetching tasks for two topics
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic("topic1", LOCK_TIME)
                .topic("topic2", LOCK_TIME * 2).execute();

        // then those two tasks are locked
        assertEquals(2, tasks.size());
        LockedExternalTask topic1Task = "topic1".equals(tasks.get(0).getTopicName()) ? tasks.get(0) : tasks.get(1);
        LockedExternalTask topic2Task = "topic2".equals(tasks.get(0).getTopicName()) ? tasks.get(0) : tasks.get(1);

        assertEquals("topic1", topic1Task.getTopicName());
        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME), topic1Task.getLockExpirationTime());

        assertEquals("topic2", topic2Task.getTopicName());
        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME * 2), topic2Task.getLockExpirationTime());

        // and the third task can still be fetched
        tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic("topic1", LOCK_TIME)
                .topic("topic2", LOCK_TIME * 2).topic("topic3", LOCK_TIME * 3).execute();

        assertEquals(1, tasks.size());

        LockedExternalTask topic3Task = tasks.get(0);
        assertEquals("topic3", topic3Task.getTopicName());
        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME * 3), topic3Task.getLockExpirationTime());
    }

    @Deployment
    public void testFetchMultipleTopicsWithVariables() {
        // given a process instance with external tasks for topics "topic1" and "topic2"
        // both have local variables "var1" and "var2"
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess",
                Variables.createVariables().putValue("var1", 0).putValue("var2", 0));

        // when
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic("topic1", LOCK_TIME)
                .variables("var1", "var2").topic("topic2", LOCK_TIME).variables("var1").execute();

        LockedExternalTask topic1Task = "topic1".equals(tasks.get(0).getTopicName()) ? tasks.get(0) : tasks.get(1);
        LockedExternalTask topic2Task = "topic2".equals(tasks.get(0).getTopicName()) ? tasks.get(0) : tasks.get(1);

        assertEquals("topic1", topic1Task.getTopicName());
        assertEquals("topic2", topic2Task.getTopicName());

        // then the correct variables have been fetched
        VariableMap topic1Variables = topic1Task.getVariables();
        assertEquals(2, topic1Variables.size());
        assertEquals(1L, topic1Variables.get("var1"));
        assertEquals(1L, topic1Variables.get("var2"));

        VariableMap topic2Variables = topic2Task.getVariables();
        assertEquals(1, topic2Variables.size());
        assertEquals(2L, topic2Variables.get("var1"));

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testFetchMultipleTopics.bpmn20.xml")
    public void testFetchMultipleTopicsMaxTasks() {
        // given
        for (int i = 0; i < 10; i++) {
            runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess");
        }

        // when
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic("topic1", LOCK_TIME)
                .topic("topic2", LOCK_TIME).topic("topic3", LOCK_TIME).execute();

        // then 5 tasks were returned in total, not per topic
        assertEquals(5, tasks.size());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchSuspendedTask() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when suspending the process instance
        runtimeService.suspendProcessInstanceById(processInstance.getId());

        // then the external task cannot be fetched
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        assertEquals(0, externalTasks.size());

        // when activating the process instance
        runtimeService.activateProcessInstanceById(processInstance.getId());

        // then the task can be fetched
        externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();

        assertEquals(1, externalTasks.size());
    }

    /**
     * Note: this does not test a hard API guarantee, i.e. the test is stricter than the API (Javadoc).
     * Its purpose is to ensure that the API implementation is less error-prone to use.
     *
     * Bottom line: if there is good reason to change behavior such that this test breaks, it may
     * be ok to change the test.
     */
    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchAndLockWithInitialBuilder() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        ExternalTaskQueryBuilder initialBuilder = externalTaskService.fetchAndLock(1, WORKER_ID);
        initialBuilder.topic(TOPIC_NAME, LOCK_TIME);

        // should execute regardless whether the initial builder is used or the builder returned by the
        // #topic invocation
        List<LockedExternalTask> tasks = initialBuilder.execute();

        // then
        assertEquals(1, tasks.size());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testComplete() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        externalTaskService.complete(externalTasks.get(0).getId(), WORKER_ID);

        // then
        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, externalTasks.size());

        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId());
        assertThat(activityInstance)
                .hasStructure(describeActivityInstanceTree(processInstance.getProcessDefinitionId())
                        .activity("afterExternalTask").done());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testCompleteWithVariables() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        externalTaskService.complete(externalTasks.get(0).getId(), WORKER_ID,
                Variables.createVariables().putValue("var", 42));

        // then
        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId());
        assertThat(activityInstance)
                .hasStructure(describeActivityInstanceTree(processInstance.getProcessDefinitionId())
                        .activity("afterExternalTask").done());

        assertEquals(42, runtimeService.getVariable(processInstance.getId(), "var"));
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testCompleteWithWrongWorkerId() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then it is not possible to complete the task with a different worker id
        try {
            externalTaskService.complete(externalTasks.get(0).getId(), "someCrazyWorkerId");
            fail("exception expected");
        } catch (BadUserRequestException e) {
            assertTextPresent("cannot be completed by worker 'someCrazyWorkerId'. It is locked by worker '"
                    + WORKER_ID + "'.", e.getMessage());
        }
    }

    public void testCompleteNonExistingTask() {
        try {
            externalTaskService.complete("nonExistingTaskId", WORKER_ID);
            fail("exception expected");
        } catch (NotFoundException e) {
            // not found exception lets client distinguish this from other failures
            assertTextPresent("Cannot find external task with id nonExistingTaskId", e.getMessage());
        }
    }

    public void testCompleteNullTaskId() {
        try {
            externalTaskService.complete(null, WORKER_ID);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("Cannot find external task with id " + null, e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testCompleteNullWorkerId() {
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        try {
            externalTaskService.complete(task.getId(), null);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("workerId is null", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testCompleteSuspendedTask() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        LockedExternalTask task = externalTasks.get(0);

        // when suspending the process instance
        runtimeService.suspendProcessInstanceById(processInstance.getId());

        // then the external task cannot be completed
        try {
            externalTaskService.complete(task.getId(), WORKER_ID);
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("ExternalTask with id '" + task.getId() + "' is suspended", e.getMessage());
        }

        assertProcessNotEnded(processInstance.getId());

        // when activating the process instance again
        runtimeService.activateProcessInstanceById(processInstance.getId());

        // then the task can be completed
        externalTaskService.complete(task.getId(), WORKER_ID);

        assertProcessEnded(processInstance.getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testLocking() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then the task is locked
        assertEquals(1, externalTasks.size());

        LockedExternalTask task = externalTasks.get(0);
        AssertUtil.assertEqualsSecondPrecision(nowPlus(LOCK_TIME), task.getLockExpirationTime());

        // and cannot be retrieved by another query
        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, externalTasks.size());

        // unless the expiration time expires
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(1, externalTasks.size());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testCompleteLockExpiredTask() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // and the lock expires without the task being reclaimed
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        // then the task can successfully be completed
        externalTaskService.complete(externalTasks.get(0).getId(), WORKER_ID);

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, externalTasks.size());
        assertProcessEnded(processInstance.getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testCompleteReclaimedLockExpiredTask() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // and the lock expires
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        // and it is reclaimed by another worker
        List<LockedExternalTask> reclaimedTasks = externalTaskService.fetchAndLock(1, "anotherWorkerId")
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then the first worker cannot complete the task
        try {
            externalTaskService.complete(externalTasks.get(0).getId(), WORKER_ID);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent(
                    "cannot be completed by worker '" + WORKER_ID + "'. It is locked by worker 'anotherWorkerId'.",
                    e.getMessage());
        }

        // and the second worker can
        externalTaskService.complete(reclaimedTasks.get(0).getId(), "anotherWorkerId");

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, externalTasks.size());
        assertProcessEnded(processInstance.getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testDeleteProcessInstance() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        runtimeService.deleteProcessInstance(processInstance.getId(), null);

        // then
        assertEquals(0,
                externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute().size());
        assertProcessEnded(processInstance.getId());
    }

    @Deployment
    public void testExternalTaskExecutionTreeExpansion() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("boundaryExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();
        LockedExternalTask externalTask = tasks.get(0);

        // when a non-interrupting boundary event is triggered meanwhile
        // such that the execution tree is expanded
        runtimeService.correlateMessage("Message");

        // then the external task can still be completed
        externalTaskService.complete(externalTask.getId(), WORKER_ID);

        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstance.getId());
        assertThat(activityInstance)
                .hasStructure(describeActivityInstanceTree(processInstance.getProcessDefinitionId())
                        .activity("afterBoundaryTask").done());

        Task afterBoundaryTask = taskService.createTaskQuery().singleResult();
        taskService.complete(afterBoundaryTask.getId());

        assertProcessEnded(processInstance.getId());
    }

    @Deployment
    public void testExternalTaskExecutionTreeCompaction() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("concurrentExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();
        LockedExternalTask externalTask = tasks.get(0);

        Task userTask = taskService.createTaskQuery().singleResult();

        // when the user task completes meanwhile, thereby trigger execution tree compaction
        taskService.complete(userTask.getId());

        // then the external task can still be completed
        externalTaskService.complete(externalTask.getId(), WORKER_ID);

        tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, tasks.size());

        assertProcessEnded(processInstance.getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUnlock() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        LockedExternalTask task = externalTasks.get(0);

        // when unlocking the task
        externalTaskService.unlock(task.getId());

        // then it can be acquired again
        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();

        assertEquals(1, externalTasks.size());
        LockedExternalTask reAcquiredTask = externalTasks.get(0);
        assertEquals(task.getId(), reAcquiredTask.getId());
    }

    public void testUnlockNullTaskId() {
        try {
            externalTaskService.unlock(null);
            fail("expected exception");
        } catch (ProcessEngineException e) {
            Assert.assertThat(e.getMessage(), containsString("externalTaskId is null"));
        }
    }

    public void testUnlockNonExistingTask() {
        try {
            externalTaskService.unlock("nonExistingId");
            fail("expected exception");
        } catch (NotFoundException e) {
            // not found exception lets client distinguish this from other failures
            assertTextPresent("Cannot find external task with id nonExistingId", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailure() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        // when submitting a failure (after a simulated processing time of three seconds)
        ClockUtil.setCurrentTime(nowPlus(3000L));

        String errorMessage = "errorMessage";
        externalTaskService.handleFailure(task.getId(), WORKER_ID, errorMessage, 5, 3000L);

        // then the task cannot be immediately acquired again
        tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, tasks.size());

        // and no incident exists because there are still retries left
        assertEquals(0, runtimeService.createIncidentQuery().count());

        // but when the retry time expires, the task is available again
        ClockUtil.setCurrentTime(nowPlus(4000L));

        tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(1, tasks.size());

        // and the retries and error message are accessible
        task = tasks.get(0);
        assertEquals(errorMessage, task.getErrorMessage());
        assertEquals(5, (int) task.getRetries());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureWithErrorDetails() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        // when submitting a failure (after a simulated processing time of three seconds)
        ClockUtil.setCurrentTime(nowPlus(3000L));

        String errorMessage;
        String exceptionStackTrace;
        try {
            RuntimeSqlException cause = new RuntimeSqlException("test cause");
            for (int i = 0; i < 10; i++) {
                cause = new RuntimeSqlException(cause);
            }
            throw cause;
        } catch (RuntimeException e) {
            exceptionStackTrace = ExceptionUtils.getStackTrace(e);
            errorMessage = e.getMessage();
            while (errorMessage.length() < 1000) {
                errorMessage = errorMessage + ":" + e.getMessage();
            }
        }
        Assert.assertThat(exceptionStackTrace, is(notNullValue()));
        //  make sure that stack trace is longer then errorMessage DB field length
        Assert.assertThat(exceptionStackTrace.length(), is(greaterThan(4000)));
        externalTaskService.handleFailure(task.getId(), WORKER_ID, errorMessage, exceptionStackTrace, 5, 3000L);
        ClockUtil.setCurrentTime(nowPlus(4000L));
        tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        Assert.assertThat(tasks.size(), is(1));

        // verify that exception is accessible properly
        task = tasks.get(0);
        Assert.assertThat(task.getErrorMessage(), is(errorMessage.substring(0, 666)));
        Assert.assertThat(task.getRetries(), is(5));
        Assert.assertThat(externalTaskService.getExternalTaskErrorDetails(task.getId()), is(exceptionStackTrace));
        Assert.assertThat(task.getErrorDetails(), is(exceptionStackTrace));
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureZeroRetries() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        // when reporting a failure and setting retries to 0
        ClockUtil.setCurrentTime(nowPlus(3000L));

        String errorMessage = "errorMessage";
        externalTaskService.handleFailure(task.getId(), WORKER_ID, errorMessage, 0, 3000L);

        // then the task cannot be fetched anymore even when the lock expires
        ClockUtil.setCurrentTime(nowPlus(4000L));

        tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, tasks.size());

        // and an incident has been created
        Incident incident = runtimeService.createIncidentQuery().singleResult();

        assertNotNull(incident);
        assertNotNull(incident.getId());
        assertEquals(errorMessage, incident.getIncidentMessage());
        assertEquals(task.getExecutionId(), incident.getExecutionId());
        assertEquals("externalTask", incident.getActivityId());
        assertEquals(incident.getId(), incident.getCauseIncidentId());
        assertEquals("failedExternalTask", incident.getIncidentType());
        assertEquals(task.getProcessDefinitionId(), incident.getProcessDefinitionId());
        assertEquals(task.getProcessInstanceId(), incident.getProcessInstanceId());
        assertEquals(incident.getId(), incident.getRootCauseIncidentId());
        AssertUtil.assertEqualsSecondPrecision(nowMinus(4000L), incident.getIncidentTimestamp());
        assertEquals(task.getId(), incident.getConfiguration());
        assertNull(incident.getJobDefinitionId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureAndDeleteProcessInstance() {
        // given a failed external task with incident
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        externalTaskService.handleFailure(task.getId(), WORKER_ID, "someError", 0, LOCK_TIME);

        // when
        runtimeService.deleteProcessInstance(processInstance.getId(), null);

        // then
        assertProcessEnded(processInstance.getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureThenComplete() {
        // given a failed external task with incident
        runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        externalTaskService.handleFailure(task.getId(), WORKER_ID, "someError", 0, LOCK_TIME);

        // when
        externalTaskService.complete(task.getId(), WORKER_ID);

        // then the task has been completed nonetheless
        Task followingTask = taskService.createTaskQuery().singleResult();
        assertNotNull(followingTask);
        assertEquals("afterExternalTask", followingTask.getTaskDefinitionKey());

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureWithWrongWorkerId() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then it is not possible to complete the task with a different worker id
        try {
            externalTaskService.handleFailure(externalTasks.get(0).getId(), "someCrazyWorkerId", "error", 5,
                    LOCK_TIME);
            fail("exception expected");
        } catch (BadUserRequestException e) {
            assertTextPresent("Failure of External Task " + externalTasks.get(0).getId()
                    + " cannot be reported by worker 'someCrazyWorkerId'. It is locked by worker '" + WORKER_ID
                    + "'.", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureNonExistingTask() {
        try {
            externalTaskService.handleFailure("nonExistingTaskId", WORKER_ID, "error", 5, LOCK_TIME);
            fail("exception expected");
        } catch (NotFoundException e) {
            // not found exception lets client distinguish this from other failures
            assertTextPresent("Cannot find external task with id nonExistingTaskId", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureNullTaskId() {
        try {
            externalTaskService.handleFailure(null, WORKER_ID, "error", 5, LOCK_TIME);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("Cannot find external task with id " + null, e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureNullWorkerId() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        try {
            externalTaskService.handleFailure(externalTasks.get(0).getId(), null, "error", 5, LOCK_TIME);
            fail("exception expected");
        } catch (NullValueException e) {
            assertTextPresent("workerId is null", e.getMessage());
        }

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureNegativeLockDuration() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        try {
            externalTaskService.handleFailure(externalTasks.get(0).getId(), WORKER_ID, "error", 5, -LOCK_TIME);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("retryDuration is not greater than or equal to 0", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureNegativeRetries() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then
        try {
            externalTaskService.handleFailure(externalTasks.get(0).getId(), WORKER_ID, "error", -5, LOCK_TIME);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("retries is not greater than or equal to 0", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureNullErrorMessage() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // when
        externalTaskService.handleFailure(externalTasks.get(0).getId(), WORKER_ID, null, 5, LOCK_TIME);

        // then the failure was reported successfully and the error message is null
        ExternalTask task = externalTaskService.createExternalTaskQuery().singleResult();

        assertEquals(5, (int) task.getRetries());
        assertNull(task.getErrorMessage());
        assertNull(externalTaskService.getExternalTaskErrorDetails(task.getId()));
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleFailureSuspendedTask() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        LockedExternalTask task = externalTasks.get(0);

        // when suspending the process instance
        runtimeService.suspendProcessInstanceById(processInstance.getId());

        // then a failure cannot be reported
        try {
            externalTaskService.handleFailure(externalTasks.get(0).getId(), WORKER_ID, "error", 5, LOCK_TIME);
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("ExternalTask with id '" + task.getId() + "' is suspended", e.getMessage());
        }

        assertProcessNotEnded(processInstance.getId());

        // when activating the process instance again
        runtimeService.activateProcessInstanceById(processInstance.getId());

        // then the failure can be reported successfully
        externalTaskService.handleFailure(externalTasks.get(0).getId(), WORKER_ID, "error", 5, LOCK_TIME);

        ExternalTask updatedTask = externalTaskService.createExternalTaskQuery().singleResult();
        assertEquals(5, (int) updatedTask.getRetries());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testSetRetries() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // when
        externalTaskService.setRetries(externalTasks.get(0).getId(), 5);

        // then
        ExternalTask task = externalTaskService.createExternalTaskQuery().singleResult();

        assertEquals(5, (int) task.getRetries());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testSetRetriesResolvesFailureIncident() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        LockedExternalTask lockedTask = externalTasks.get(0);
        externalTaskService.handleFailure(lockedTask.getId(), WORKER_ID, "error", 0, LOCK_TIME);

        Incident incident = runtimeService.createIncidentQuery().singleResult();

        // when
        externalTaskService.setRetries(lockedTask.getId(), 5);

        // then the incident is resolved
        assertEquals(0, runtimeService.createIncidentQuery().count());

        if (processEngineConfiguration.getHistoryLevel().getId() >= HistoryLevel.HISTORY_LEVEL_FULL.getId()) {

            HistoricIncident historicIncident = historyService.createHistoricIncidentQuery().singleResult();
            assertNotNull(historicIncident);
            assertEquals(incident.getId(), historicIncident.getId());
            assertTrue(historicIncident.isResolved());
        }

        // and the task can be fetched again
        ClockUtil.setCurrentTime(nowPlus(LOCK_TIME + 3000L));

        externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();

        assertEquals(1, externalTasks.size());
        assertEquals(lockedTask.getId(), externalTasks.get(0).getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testSetRetriesToZero() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        LockedExternalTask lockedTask = externalTasks.get(0);

        // when
        externalTaskService.setRetries(lockedTask.getId(), 0);

        // then
        Incident incident = runtimeService.createIncidentQuery().singleResult();
        assertNotNull(incident);
        assertEquals(lockedTask.getId(), incident.getConfiguration());

        // and resetting the retries removes the incident again
        externalTaskService.setRetries(lockedTask.getId(), 5);

        assertEquals(0, runtimeService.createIncidentQuery().count());

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testSetRetriesNegative() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        try {
            // when
            externalTaskService.setRetries(externalTasks.get(0).getId(), -5);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("retries is not greater than or equal to 0", e.getMessage());
        }
    }

    public void testSetRetriesNonExistingTask() {
        try {
            externalTaskService.setRetries("someExternalTaskId", 5);
            fail("expected exception");
        } catch (NotFoundException e) {
            // not found exception lets client distinguish this from other failures
            assertTextPresent("externalTask is null", e.getMessage());
        }
    }

    public void testSetRetriesNullTaskId() {
        try {
            externalTaskService.setRetries((String) null, 5);
            fail("expected exception");
        } catch (NullValueException e) {
            Assert.assertThat(e.getMessage(), containsString("externalTaskId is null"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testSetPriority() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // when
        externalTaskService.setPriority(externalTasks.get(0).getId(), 5);

        // then
        ExternalTask task = externalTaskService.createExternalTaskQuery().singleResult();

        assertEquals(5, (int) task.getPriority());
    }

    public void testSetPriorityNonExistingTask() {
        try {
            externalTaskService.setPriority("someExternalTaskId", 5);
            fail("expected exception");
        } catch (NotFoundException e) {
            // not found exception lets client distinguish this from other failures
            assertTextPresent("externalTask is null", e.getMessage());
        }
    }

    public void testSetPriorityNullTaskId() {
        try {
            externalTaskService.setPriority(null, 5);
            fail("expected exception");
        } catch (NullValueException e) {
            Assert.assertThat(e.getMessage(), containsString("externalTaskId is null"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskWithPriorityProcess.bpmn20.xml")
    public void testAfterSetPriorityFetchHigherTask() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskWithPriorityProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(2, WORKER_ID, true)
                .topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(2, externalTasks.size());
        LockedExternalTask task = externalTasks.get(1);
        assertEquals(0, task.getPriority());
        externalTaskService.setPriority(task.getId(), 9);
        // and the lock expires without the task being reclaimed
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        // then
        externalTasks = externalTaskService.fetchAndLock(1, "anotherWorkerId", true).topic(TOPIC_NAME, LOCK_TIME)
                .execute();
        assertEquals(externalTasks.get(0).getPriority(), 9);
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testSetPriorityLockExpiredTask() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // and the lock expires without the task being reclaimed
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        // then the priority can be set
        externalTaskService.setPriority(externalTasks.get(0).getId(), 9);

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID, true).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(1, externalTasks.size());
        assertEquals(externalTasks.get(0).getPriority(), 9);
    }

    @Deployment
    public void testCancelExternalTaskWithBoundaryEvent() {
        // given
        runtimeService.startProcessInstanceByKey("boundaryExternalTaskProcess");
        assertEquals(1, externalTaskService.createExternalTaskQuery().count());

        // when the external task is cancelled by a boundary event
        runtimeService.correlateMessage("Message");

        // then the external task instance has been removed
        assertEquals(0, externalTaskService.createExternalTaskQuery().count());

        Task afterBoundaryTask = taskService.createTaskQuery().singleResult();
        assertNotNull(afterBoundaryTask);
        assertEquals("afterBoundaryTask", afterBoundaryTask.getTaskDefinitionKey());

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnError() {
        //given
        runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");
        // when
        List<LockedExternalTask> externalTasks = helperHandleBpmnError(1, WORKER_ID, TOPIC_NAME, LOCK_TIME,
                "ERROR-OCCURED");
        //then
        assertEquals(0, externalTasks.size());
        assertEquals(0, externalTaskService.createExternalTaskQuery().count());
        Task afterBpmnError = taskService.createTaskQuery().singleResult();
        assertNotNull(afterBpmnError);
        assertEquals(afterBpmnError.getTaskDefinitionKey(), "afterBpmnError");
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnErrorWithoutDefinedBoundary() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        //when
        List<LockedExternalTask> externalTasks = helperHandleBpmnError(1, WORKER_ID, TOPIC_NAME, LOCK_TIME,
                "ERROR-OCCURED");

        //then
        assertEquals(0, externalTasks.size());
        assertEquals(0, externalTaskService.createExternalTaskQuery().count());
        Task afterBpmnError = taskService.createTaskQuery().singleResult();
        assertNull(afterBpmnError);
        assertProcessEnded(processInstance.getId());
    }

    /**
     * Helper method to handle a bmpn error on an external task, which is fetched with the given parameters.
     *
     * @param taskCount the count of task to fetch
     * @param workerID the worker id
     * @param topicName the topic name of the external task
     * @param lockTime the lock time for the fetch
     * @param errorCode the error code of the bpmn error
     * @return returns the locked external tasks after the bpmn error was handled
     */
    public List<LockedExternalTask> helperHandleBpmnError(int taskCount, String workerID, String topicName,
            long lockTime, String errorCode) {
        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(taskCount, workerID)
                .topic(topicName, lockTime).execute();

        externalTaskService.handleBpmnError(externalTasks.get(0).getId(), workerID, errorCode);

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        return externalTasks;
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnErrorLockExpiredTask() {
        //given
        runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");
        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // and the lock expires without the task being reclaimed
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        externalTaskService.handleBpmnError(externalTasks.get(0).getId(), WORKER_ID, "ERROR-OCCURED");

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();

        assertEquals(0, externalTasks.size());
        assertEquals(0, externalTaskService.createExternalTaskQuery().count());
        Task afterBpmnError = taskService.createTaskQuery().singleResult();
        assertNotNull(afterBpmnError);
        assertEquals(afterBpmnError.getTaskDefinitionKey(), "afterBpmnError");
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnErrorReclaimedLockExpiredTaskWithoutDefinedBoundary() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        handleBpmnErrorReclaimedLockExpiredTask();
        assertProcessEnded(processInstance.getId());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/twoExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnErrorReclaimedLockExpiredTaskWithBoundary() {
        // given
        runtimeService.startProcessInstanceByKey("twoExternalTaskProcess");
        //then
        handleBpmnErrorReclaimedLockExpiredTask();
    }

    /**
     * Helpher method which reclaims an external task after the lock is expired.
     */
    public void handleBpmnErrorReclaimedLockExpiredTask() {
        // when
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // and the lock expires
        ClockUtil.setCurrentTime(new DateTime(ClockUtil.getCurrentTime()).plus(LOCK_TIME * 2).toDate());

        // and it is reclaimed by another worker
        List<LockedExternalTask> reclaimedTasks = externalTaskService.fetchAndLock(1, "anotherWorkerId")
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        // then the first worker cannot complete the task
        try {
            externalTaskService.handleBpmnError(externalTasks.get(0).getId(), WORKER_ID, "ERROR-OCCURED");
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("Bpmn error of External Task " + externalTasks.get(0).getId()
                    + " cannot be reported by worker '" + WORKER_ID
                    + "'. It is locked by worker 'anotherWorkerId'.", e.getMessage());
        }

        // and the second worker can
        externalTaskService.complete(reclaimedTasks.get(0).getId(), "anotherWorkerId");

        externalTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME).execute();
        assertEquals(0, externalTasks.size());
    }

    public void testHandleBpmnErrorNonExistingTask() {
        try {
            externalTaskService.handleBpmnError("nonExistingTaskId", WORKER_ID, "ERROR-OCCURED");
            fail("exception expected");
        } catch (NotFoundException e) {
            // not found exception lets client distinguish this from other failures
            assertTextPresent("Cannot find external task with id nonExistingTaskId", e.getMessage());
        }
    }

    public void testHandleBpmnNullTaskId() {
        try {
            externalTaskService.handleBpmnError(null, WORKER_ID, "ERROR-OCCURED");
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("Cannot find external task with id " + null, e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnNullErrorCode() {
        //given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        //when
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        //then
        LockedExternalTask task = tasks.get(0);
        try {
            externalTaskService.handleBpmnError(task.getId(), WORKER_ID, null);
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("errorCode is null", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnErrorNullWorkerId() {
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, LOCK_TIME)
                .execute();

        LockedExternalTask task = tasks.get(0);

        try {
            externalTaskService.handleBpmnError(task.getId(), null, "ERROR-OCCURED");
            fail("exception expected");
        } catch (ProcessEngineException e) {
            assertTextPresent("workerId is null", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testHandleBpmnErrorSuspendedTask() {
        // given
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
        List<LockedExternalTask> externalTasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic(TOPIC_NAME, LOCK_TIME).execute();

        LockedExternalTask task = externalTasks.get(0);

        // when suspending the process instance
        runtimeService.suspendProcessInstanceById(processInstance.getId());

        // then the external task cannot be completed
        try {
            externalTaskService.handleBpmnError(task.getId(), WORKER_ID, "ERROR-OCCURED");
            fail("expected exception");
        } catch (ProcessEngineException e) {
            assertTextPresent("ExternalTask with id '" + task.getId() + "' is suspended", e.getMessage());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByExternalTaskIds() {
        // given
        startProcessInstance("oneExternalTaskProcess", 5);
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        List<String> externalTaskIds = Arrays.asList(tasks.get(0).getId(), tasks.get(1).getId(),
                tasks.get(2).getId(), tasks.get(3).getId(), tasks.get(4).getId());

        // when
        externalTaskService.updateRetries().externalTaskIds(externalTaskIds).set(5);

        // then
        tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByExternalTaskIdArray() {
        // given
        startProcessInstance("oneExternalTaskProcess", 5);
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        List<String> externalTaskIds = Arrays.asList(tasks.get(0).getId(), tasks.get(1).getId(),
                tasks.get(2).getId(), tasks.get(3).getId(), tasks.get(4).getId());

        // when
        externalTaskService.updateRetries()
                .externalTaskIds(externalTaskIds.toArray(new String[externalTaskIds.size()])).set(5);

        // then
        tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByProcessInstanceIds() {
        // given
        List<String> processInstances = startProcessInstance("oneExternalTaskProcess", 5);

        // when
        externalTaskService.updateRetries().processInstanceIds(processInstances).set(5);

        // then
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByProcessInstanceIdArray() {
        // given
        List<String> processInstances = startProcessInstance("oneExternalTaskProcess", 5);

        // when
        externalTaskService.updateRetries()
                .processInstanceIds(processInstances.toArray(new String[processInstances.size()])).set(5);

        // then
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByExternalTaskQuery() {
        // given
        startProcessInstance("oneExternalTaskProcess", 5);

        ExternalTaskQuery query = externalTaskService.createExternalTaskQuery();

        // when
        externalTaskService.updateRetries().externalTaskQuery(query).set(5);

        // then
        List<ExternalTask> tasks = query.list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByProcessInstanceQuery() {
        // given
        startProcessInstance("oneExternalTaskProcess", 5);

        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery()
                .processDefinitionKey("oneExternalTaskProcess");

        // when
        externalTaskService.updateRetries().processInstanceQuery(processInstanceQuery).set(5);

        // then
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @RequiredHistoryLevel(ProcessEngineConfiguration.HISTORY_AUDIT)
    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByHistoricProcessInstanceQuery() {
        // given
        startProcessInstance("oneExternalTaskProcess", 5);

        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey("oneExternalTaskProcess");

        // when
        externalTaskService.updateRetries().historicProcessInstanceQuery(query).set(5);

        // then
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(5, (int) task.getRetries());
        }
    }

    @RequiredHistoryLevel(ProcessEngineConfiguration.HISTORY_AUDIT)
    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testUpdateRetriesByAllParameters() {
        // given
        List<String> ids = startProcessInstance("oneExternalTaskProcess", 5);

        ExternalTask externalTask = externalTaskService.createExternalTaskQuery().processInstanceId(ids.get(0))
                .singleResult();

        ExternalTaskQuery externalTaskQuery = externalTaskService.createExternalTaskQuery()
                .processInstanceId(ids.get(1));

        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery()
                .processInstanceId(ids.get(2));

        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService
                .createHistoricProcessInstanceQuery().processInstanceId(ids.get(3));

        // when
        externalTaskService.updateRetries().externalTaskIds(externalTask.getId())
                .externalTaskQuery(externalTaskQuery).processInstanceQuery(processInstanceQuery)
                .historicProcessInstanceQuery(historicProcessInstanceQuery).processInstanceIds(ids.get(4)).set(5);

        // then
        List<ExternalTask> tasks = externalTaskService.createExternalTaskQuery().list();
        assertEquals(5, tasks.size());

        for (ExternalTask task : tasks) {
            assertEquals(Integer.valueOf(5), task.getRetries());
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTime() {
        final Date oldCurrentTime = ClockUtil.getCurrentTime();
        try {
            // given
            runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");
            ClockUtil.setCurrentTime(nowMinus(1000));
            List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID)
                    .topic(TOPIC_NAME, LOCK_TIME).execute();

            // when
            Date extendLockTime = new Date();
            ClockUtil.setCurrentTime(extendLockTime);

            externalTaskService.extendLock(lockedTasks.get(0).getId(), WORKER_ID, LOCK_TIME);

            // then
            ExternalTask taskWithExtendedLock = externalTaskService.createExternalTaskQuery().locked()
                    .singleResult();
            assertNotNull(taskWithExtendedLock);
            AssertUtil.assertEqualsSecondPrecision(new Date(extendLockTime.getTime() + LOCK_TIME),
                    taskWithExtendedLock.getLockExpirationTime());

        } finally {
            ClockUtil.setCurrentTime(oldCurrentTime);
        }

    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeThatExpired() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 1L)
                .execute();

        assertNotNull(lockedTasks);
        assertEquals(1, lockedTasks.size());

        ClockUtil.setCurrentTime(nowPlus(2));
        // when
        try {
            externalTaskService.extendLock(lockedTasks.get(0).getId(), WORKER_ID, 100);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            // then
            assertTrue(e.getMessage().contains("Cannot extend a lock that expired"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeWithoutLock() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        ExternalTask externalTask = externalTaskService.createExternalTaskQuery().singleResult();
        // when
        try {
            externalTaskService.extendLock(externalTask.getId(), WORKER_ID, 100);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            assertTrue(e.getMessage().contains("The lock of the External Task " + externalTask.getId()
                    + " cannot be extended by worker '" + WORKER_ID));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeWithNullLockTime() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 1L)
                .execute();

        assertNotNull(lockedTasks);
        assertEquals(1, lockedTasks.size());

        // when
        try {
            externalTaskService.extendLock(lockedTasks.get(0).getId(), WORKER_ID, 0);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            // then
            assertTrue(e.getMessage().contains("lockTime is not greater than 0"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeWithNegativeLockTime() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 1L)
                .execute();

        assertNotNull(lockedTasks);
        assertEquals(1, lockedTasks.size());

        // when
        try {
            externalTaskService.extendLock(lockedTasks.get(0).getId(), WORKER_ID, -1);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            // then
            assertTrue(e.getMessage().contains("lockTime is not greater than 0"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeWithNullWorkerId() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 1L)
                .execute();

        assertNotNull(lockedTasks);
        assertEquals(1, lockedTasks.size());

        // when
        try {
            externalTaskService.extendLock(lockedTasks.get(0).getId(), null, 100);
            fail("Exception expected");
        } catch (NullValueException e) {
            // then
            assertTrue(e.getMessage().contains("workerId is null"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeWithDifferentWorkerId() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 1L)
                .execute();

        assertNotNull(lockedTasks);
        assertEquals(1, lockedTasks.size());

        LockedExternalTask task = lockedTasks.get(0);
        // when
        try {
            externalTaskService.extendLock(task.getId(), "anAnotherWorkerId", 100);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            assertTrue(e.getMessage().contains("The lock of the External Task " + task.getId()
                    + " cannot be extended by worker 'anAnotherWorkerId'"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeWithNullExternalTask() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic(TOPIC_NAME, 1L)
                .execute();

        assertNotNull(lockedTasks);
        assertEquals(1, lockedTasks.size());

        // when
        try {
            externalTaskService.extendLock(null, WORKER_ID, 100);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            assertTrue(e.getMessage().contains("Cannot find external task with id null"));
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testExtendLockTimeForUnexistingExternalTask() {
        // when
        try {
            externalTaskService.extendLock("unexisting", WORKER_ID, 100);
            fail("Exception expected");
        } catch (BadUserRequestException e) {
            assertTrue(e.getMessage().contains("Cannot find external task with id unexisting"));
        }
    }

    public void testCompleteWithLocalVariables() {
        // given
        BpmnModelInstance instance = Bpmn.createExecutableProcess("Process").startEvent()
                .serviceTask("externalTask").camundaType("external").camundaTopic("foo").camundaTaskPriority("100")
                .camundaExecutionListenerClass(ExecutionListener.EVENTNAME_END, ReadLocalVariableListenerImpl.class)
                .userTask("user").endEvent().done();

        deployment(instance);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Process");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic("foo", 1L)
                .execute();

        // when
        externalTaskService.complete(lockedTasks.get(0).getId(), WORKER_ID, null,
                Variables.createVariables().putValue("abc", "bar"));

        // then
        VariableInstance variableInstance = runtimeService.createVariableInstanceQuery()
                .processInstanceIdIn(processInstance.getId()).singleResult();
        assertNull(variableInstance);
        if (processEngineConfiguration.getHistoryLevel() == HistoryLevel.HISTORY_LEVEL_AUDIT
                || processEngineConfiguration.getHistoryLevel() == HistoryLevel.HISTORY_LEVEL_FULL) {
            HistoricVariableInstance historicVariableInstance = historyService.createHistoricVariableInstanceQuery()
                    .activityInstanceIdIn(lockedTasks.get(0).getActivityInstanceId()).singleResult();
            assertNotNull(historicVariableInstance);
            assertEquals("abc", historicVariableInstance.getName());
            assertEquals("bar", historicVariableInstance.getValue());
        }
    }

    public void testCompleteWithNonLocalVariables() {
        // given
        BpmnModelInstance instance = Bpmn.createExecutableProcess("Process").startEvent()
                .serviceTask("externalTask").camundaType("external").camundaTopic("foo").camundaTaskPriority("100")
                .camundaExecutionListenerClass(ExecutionListener.EVENTNAME_END, ReadLocalVariableListenerImpl.class)
                .userTask("user").endEvent().done();

        deployment(instance);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Process");

        List<LockedExternalTask> lockedTasks = externalTaskService.fetchAndLock(1, WORKER_ID).topic("foo", 1L)
                .execute();

        // when
        externalTaskService.complete(lockedTasks.get(0).getId(), WORKER_ID,
                Variables.createVariables().putValue("abc", "bar"), null);

        // then
        VariableInstance variableInstance = runtimeService.createVariableInstanceQuery()
                .processInstanceIdIn(processInstance.getId()).singleResult();
        assertNotNull(variableInstance);
        assertEquals("bar", variableInstance.getValue());
        assertEquals("abc", variableInstance.getName());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/oneExternalTaskProcess.bpmn20.xml")
    public void testFetchWithEmptyListOfVariables() {
        // given
        runtimeService.startProcessInstanceByKey("oneExternalTaskProcess");

        // when
        List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(5, WORKER_ID)
                .topic("externalTaskTopic", LOCK_TIME).variables(new String[] {}).execute();

        // then
        assertEquals(1, tasks.size());

        LockedExternalTask task = tasks.get(0);
        assertNotNull(task.getId());
        assertEquals(0, task.getVariables().size());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/parallelExternalTaskProcess.bpmn20.xml")
    public void testQueryByBusinessKey() {
        // given
        String topicName1 = "topic1";
        String topicName2 = "topic2";
        String topicName3 = "topic3";

        String businessKey1 = "testBusinessKey1";
        String businessKey2 = "testBusinessKey2";

        Long lockDuration = 60L * 1000L;

        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess", businessKey1);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess", businessKey2);

        //when
        List<LockedExternalTask> topicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).businessKey(businessKey1).topic(topicName2, lockDuration)
                .businessKey(businessKey2).topic(topicName3, lockDuration).businessKey("fakeBusinessKey").execute();

        //then
        assertEquals(2, topicTasks.size());

        for (LockedExternalTask externalTask : topicTasks) {
            ProcessInstance pi = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(externalTask.getProcessInstanceId()).singleResult();
            if (externalTask.getTopicName().equals(topicName1)) {
                assertEquals(businessKey1, pi.getBusinessKey());
                assertEquals(businessKey1, externalTask.getBusinessKey());
            } else if (externalTask.getTopicName().equals(topicName2)) {
                assertEquals(businessKey2, pi.getBusinessKey());
                assertEquals(businessKey2, externalTask.getBusinessKey());
            } else {
                fail("No other topic name values should be available!");
            }
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/parallelExternalTaskProcess.bpmn20.xml")
    public void testQueryByBusinessKeyLocking() {
        // given
        String topicName1 = "topic1";
        String topicName2 = "topic2";
        String topicName3 = "topic3";

        String businessKey1 = "testBusinessKey1";
        String businessKey2 = "testBusinessKey2";

        Long lockDuration = 60L * 1000L;

        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess", businessKey1);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcess", businessKey2);

        //when
        List<LockedExternalTask> lockedTopicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).businessKey(businessKey1).topic(topicName2, lockDuration)
                .businessKey(businessKey2).topic(topicName3, lockDuration).businessKey("fakeBusinessKey").execute();

        List<LockedExternalTask> topicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).businessKey(businessKey1).topic(topicName2, 2 * lockDuration)
                .businessKey(businessKey2).topic(topicName3, 2 * lockDuration).businessKey(businessKey1).execute();

        //then
        assertEquals(2, lockedTopicTasks.size());
        assertEquals(1, topicTasks.size());

        LockedExternalTask externalTask = topicTasks.get(0);
        ProcessInstance pi = runtimeService.createProcessInstanceQuery()
                .processInstanceId(externalTask.getProcessInstanceId()).singleResult();

        assertEquals(businessKey1, pi.getBusinessKey());
        assertEquals(businessKey1, externalTask.getBusinessKey());
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testVariableValueTopicQuery.bpmn20.xml")
    public void testTopicQueryByVariableValue() {
        // given
        String topicName1 = "testTopic1";
        String topicName2 = "testTopic2";

        String variableName = "testVariable";
        String variableValue1 = "testValue1";
        String variableValue2 = "testValue2";

        Map<String, Object> variables = new HashMap<String, Object>();

        Long lockDuration = 60L * 1000L;

        //when
        variables.put(variableName, variableValue1);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues", variables);

        variables.put(variableName, variableValue2);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues", variables);

        List<LockedExternalTask> topicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).processInstanceVariableEquals(variableName, variableValue1)
                .topic(topicName2, lockDuration).processInstanceVariableEquals(variableName, variableValue2)
                .execute();

        //then
        assertEquals(2, topicTasks.size());

        for (LockedExternalTask externalTask : topicTasks) {
            if (externalTask.getTopicName().equals(topicName1)) {
                assertEquals(variableValue1, externalTask.getVariables().get(variableName));
            } else if (externalTask.getTopicName().equals(topicName2)) {
                assertEquals(variableValue2, externalTask.getVariables().get(variableName));
            } else {
                fail("No other topic name values should be available!");
            }
        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testVariableValueTopicQuery.bpmn20.xml")
    public void testTopicQueryByVariableValueLocking() {
        // given
        String topicName1 = "testTopic1";
        String topicName2 = "testTopic2";
        String topicName3 = "testTopic3";

        String variableName = "testVariable";
        String variableValue1 = "testValue1";
        String variableValue2 = "testValue2";

        Map<String, Object> variables = new HashMap<String, Object>();

        Long lockDuration = 60L * 1000L;

        //when
        variables.put(variableName, variableValue1);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues", variables);

        variables.put(variableName, variableValue2);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues", variables);

        List<LockedExternalTask> lockedTopicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).processInstanceVariableEquals(variableName, variableValue1)
                .topic(topicName2, lockDuration).processInstanceVariableEquals(variableName, variableValue2)
                .execute();

        List<LockedExternalTask> topicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, 2 * lockDuration).processInstanceVariableEquals(variableName, variableValue1)
                .topic(topicName2, 2 * lockDuration).processInstanceVariableEquals(variableName, variableValue2)
                .topic(topicName3, lockDuration).processInstanceVariableEquals(variableName, variableValue2)
                .execute();

        //then
        assertEquals(2, lockedTopicTasks.size());
        assertEquals(1, topicTasks.size());

        LockedExternalTask externalTask = topicTasks.get(0);
        assertEquals(topicName3, externalTask.getTopicName());
        assertEquals(variableValue2, externalTask.getVariables().get(variableName));
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testVariableValueTopicQuery.bpmn20.xml")
    public void testTopicQueryByVariableValues() {
        // given
        String topicName1 = "testTopic1";
        String topicName2 = "testTopic2";
        String topicName3 = "testTopic3";

        String variableName1 = "testVariable1";
        String variableName2 = "testVariable2";
        String variableName3 = "testVariable3";

        String variableValue1 = "testValue1";
        String variableValue2 = "testValue2";
        String variableValue3 = "testValue3";
        String variableValue4 = "testValue4";
        String variableValue5 = "testValue5";
        String variableValue6 = "testValue6";

        Map<String, Object> variables = new HashMap<String, Object>();

        Long lockDuration = 60L * 1000L;

        //when
        variables.put(variableName1, variableValue1);
        variables.put(variableName2, variableValue2);
        variables.put(variableName3, variableValue3);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues", variables);

        variables.put(variableName1, variableValue4);
        variables.put(variableName2, variableValue5);
        variables.put(variableName3, variableValue6);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues", variables);

        List<LockedExternalTask> topicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).processInstanceVariableEquals(variableName1, variableValue1)
                .processInstanceVariableEquals(variableName2, variableValue2).topic(topicName2, lockDuration)
                .processInstanceVariableEquals(variableName2, variableValue5)
                .processInstanceVariableEquals(variableName3, variableValue6).topic(topicName3, lockDuration)
                .processInstanceVariableEquals(variableName1, "fakeVariableValue").execute();

        //then
        assertEquals(2, topicTasks.size());

        for (LockedExternalTask externalTask : topicTasks) {
            // topic names are not always in the same order
            if (externalTask.getTopicName().equals(topicName1)) {
                assertEquals(variableValue1, externalTask.getVariables().get(variableName1));
                assertEquals(variableValue2, externalTask.getVariables().get(variableName2));
            } else if (externalTask.getTopicName().equals(topicName2)) {
                assertEquals(variableValue5, externalTask.getVariables().get(variableName2));
                assertEquals(variableValue6, externalTask.getVariables().get(variableName3));
            } else {
                fail("No other topic name values should be available!");
            }

        }
    }

    @Deployment(resources = "org/camunda/bpm/engine/test/api/externaltask/ExternalTaskServiceTest.testVariableValueTopicQuery.bpmn20.xml")
    public void testTopicQueryByBusinessKeyAndVariableValue() {
        // given
        String topicName1 = "testTopic1";
        String topicName2 = "testTopic2";
        String topicName3 = "testTopic3";

        String businessKey1 = "testBusinessKey1";
        String businessKey2 = "testBusinessKey2";

        String variableName = "testVariable1";
        String variableValue1 = "testValue1";
        String variableValue2 = "testValue2";

        Map<String, Object> variables = new HashMap<String, Object>();

        Long lockDuration = 60L * 1000L;

        //when
        variables.put(variableName, variableValue1);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues",
                businessKey1, variables);
        variables.put(variableName, variableValue2);
        runtimeService.startProcessInstanceByKey("parallelExternalTaskProcessTopicQueryVariableValues",
                businessKey2, variables);

        List<LockedExternalTask> topicTasks = externalTaskService.fetchAndLock(3, "externalWorkerId")
                .topic(topicName1, lockDuration).businessKey(businessKey1)
                .processInstanceVariableEquals(variableName, variableValue1).topic(topicName2, lockDuration)
                .businessKey(businessKey2).processInstanceVariableEquals(variableName, variableValue2)
                .topic(topicName3, lockDuration).businessKey("fakeBusinessKey").execute();

        //then
        assertEquals(2, topicTasks.size());

        for (LockedExternalTask externalTask : topicTasks) {
            ProcessInstance pi = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(externalTask.getProcessInstanceId()).singleResult();
            // topic names are not always in the same order
            if (externalTask.getTopicName().equals(topicName1)) {
                assertEquals(businessKey1, pi.getBusinessKey());
                assertEquals(variableValue1, externalTask.getVariables().get(variableName));
            } else if (externalTask.getTopicName().equals(topicName2)) {
                assertEquals(businessKey2, pi.getBusinessKey());
                assertEquals(variableValue2, externalTask.getVariables().get(variableName));
            } else {
                fail("No other topic name values should be available!");
            }
        }
    }

    protected Date nowPlus(long millis) {
        return new Date(ClockUtil.getCurrentTime().getTime() + millis);
    }

    protected Date nowMinus(long millis) {
        return new Date(ClockUtil.getCurrentTime().getTime() - millis);
    }

    protected List<String> startProcessInstance(String key, int instances) {
        List<String> ids = new ArrayList<String>();
        for (int i = 0; i < instances; i++) {
            ids.add(runtimeService.startProcessInstanceByKey(key, String.valueOf(i)).getId());
        }
        return ids;
    }

    public static class ReadLocalVariableListenerImpl implements ExecutionListener {

        @Override
        public void notify(DelegateExecution execution) throws Exception {
            String value = (String) execution.getVariable("abc");
            assertEquals("bar", value);
        }
    }

}