org.camunda.bpm.engine.test.api.history.HistoryCleanupTest.java Source code

Java tutorial

Introduction

Here is the source code for org.camunda.bpm.engine.test.api.history.HistoryCleanupTest.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.history;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.apache.commons.lang.time.DateUtils;
import org.camunda.bpm.engine.CaseService;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.ManagementService;
import org.camunda.bpm.engine.ProcessEngineConfiguration;
import org.camunda.bpm.engine.ProcessEngineException;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.history.HistoricCaseInstance;
import org.camunda.bpm.engine.history.HistoricDecisionInstance;
import org.camunda.bpm.engine.history.HistoricIncident;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.impl.interceptor.Command;
import org.camunda.bpm.engine.impl.interceptor.CommandContext;
import org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper;
import org.camunda.bpm.engine.impl.jobexecutor.historycleanup.HistoryCleanupJobHandlerConfiguration;
import org.camunda.bpm.engine.impl.metrics.Meter;
import org.camunda.bpm.engine.impl.persistence.entity.HistoricIncidentEntity;
import org.camunda.bpm.engine.impl.persistence.entity.JobEntity;
import org.camunda.bpm.engine.impl.persistence.entity.SuspensionState;
import org.camunda.bpm.engine.impl.util.ClockUtil;
import org.camunda.bpm.engine.impl.util.ExceptionUtil;
import org.camunda.bpm.engine.impl.util.json.JSONObject;
import org.camunda.bpm.engine.management.Metrics;
import org.camunda.bpm.engine.repository.CaseDefinition;
import org.camunda.bpm.engine.repository.DecisionDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.CaseInstance;
import org.camunda.bpm.engine.runtime.Job;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.test.Deployment;
import org.camunda.bpm.engine.test.RequiredHistoryLevel;
import org.camunda.bpm.engine.test.dmn.businessruletask.TestPojo;
import org.camunda.bpm.engine.test.util.ProcessEngineBootstrapRule;
import org.camunda.bpm.engine.test.util.ProcessEngineTestRule;
import org.camunda.bpm.engine.test.util.ProvidedProcessEngineRule;
import org.camunda.bpm.engine.variable.VariableMap;
import org.camunda.bpm.engine.variable.Variables;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.RuleChain;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

/**
 * @author Svetlana Dorokhova
 */
@RequiredHistoryLevel(ProcessEngineConfiguration.HISTORY_FULL)
public class HistoryCleanupTest {

    private static final int PROCESS_INSTANCES_COUNT = 3;
    private static final int DECISIONS_IN_PROCESS_INSTANCES = 3;
    private static final int DECISION_INSTANCES_COUNT = 10;
    private static final int CASE_INSTANCES_COUNT = 4;
    private static final int HISTORY_TIME_TO_LIVE = 5;
    private static final int DAYS_IN_THE_PAST = -6;
    protected static final String ONE_TASK_PROCESS = "oneTaskProcess";
    protected static final String DECISION = "decision";
    protected static final String ONE_TASK_CASE = "case";

    protected ProcessEngineBootstrapRule bootstrapRule = new ProcessEngineBootstrapRule() {
        public ProcessEngineConfiguration configureEngine(ProcessEngineConfigurationImpl configuration) {
            configuration.setHistoryCleanupBatchSize(20);
            configuration.setHistoryCleanupBatchThreshold(10);
            configuration.setDefaultNumberOfRetries(5);
            return configuration;
        }
    };

    protected ProvidedProcessEngineRule engineRule = new ProvidedProcessEngineRule(bootstrapRule);
    public ProcessEngineTestRule testRule = new ProcessEngineTestRule(engineRule);

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    private HistoryService historyService;
    private RuntimeService runtimeService;
    private ManagementService managementService;
    private CaseService caseService;
    private RepositoryService repositoryService;
    private ProcessEngineConfigurationImpl processEngineConfiguration;

    @Rule
    public RuleChain ruleChain = RuleChain.outerRule(bootstrapRule).around(engineRule).around(testRule);

    @Before
    public void init() {
        runtimeService = engineRule.getRuntimeService();
        historyService = engineRule.getHistoryService();
        managementService = engineRule.getManagementService();
        caseService = engineRule.getCaseService();
        repositoryService = engineRule.getRepositoryService();
        processEngineConfiguration = engineRule.getProcessEngineConfiguration();
        testRule.deploy("org/camunda/bpm/engine/test/api/oneTaskProcess.bpmn20.xml",
                "org/camunda/bpm/engine/test/api/dmn/Example.dmn",
                "org/camunda/bpm/engine/test/api/cmmn/oneTaskCaseWithHistoryTimeToLive.cmmn");
    }

    @After
    public void clearDatabase() {
        //reset configuration changes
        String defaultStartTime = processEngineConfiguration.getHistoryCleanupBatchWindowStartTime();
        String defaultEndTime = processEngineConfiguration.getHistoryCleanupBatchWindowEndTime();
        int defaultBatchSize = processEngineConfiguration.getHistoryCleanupBatchSize();

        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(defaultStartTime);
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(defaultEndTime);
        processEngineConfiguration.setHistoryCleanupBatchSize(defaultBatchSize);

        processEngineConfiguration.getCommandExecutorTxRequired().execute(new Command<Void>() {
            public Void execute(CommandContext commandContext) {

                List<Job> jobs = managementService.createJobQuery().list();
                if (jobs.size() > 0) {
                    assertEquals(1, jobs.size());
                    String jobId = jobs.get(0).getId();
                    commandContext.getJobManager().deleteJob((JobEntity) jobs.get(0));
                    commandContext.getHistoricJobLogManager().deleteHistoricJobLogByJobId(jobId);
                }

                List<HistoricIncident> historicIncidents = historyService.createHistoricIncidentQuery().list();
                for (HistoricIncident historicIncident : historicIncidents) {
                    commandContext.getDbEntityManager().delete((HistoricIncidentEntity) historicIncident);
                }

                commandContext.getMeterLogManager().deleteAll();

                return null;
            }
        });

        List<HistoricProcessInstance> historicProcessInstances = historyService.createHistoricProcessInstanceQuery()
                .list();
        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
            historyService.deleteHistoricProcessInstance(historicProcessInstance.getId());
        }

        List<HistoricDecisionInstance> historicDecisionInstances = historyService
                .createHistoricDecisionInstanceQuery().list();
        for (HistoricDecisionInstance historicDecisionInstance : historicDecisionInstances) {
            historyService.deleteHistoricDecisionInstanceByInstanceId(historicDecisionInstance.getId());
        }

        List<HistoricCaseInstance> historicCaseInstances = historyService.createHistoricCaseInstanceQuery().list();
        for (HistoricCaseInstance historicCaseInstance : historicCaseInstances) {
            historyService.deleteHistoricCaseInstance(historicCaseInstance.getId());
        }

        clearMetrics();

    }

    protected void clearMetrics() {
        Collection<Meter> meters = processEngineConfiguration.getMetricsRegistry().getMeters().values();
        for (Meter meter : meters) {
            meter.getAndClear();
        }
        managementService.deleteMetrics(null);
    }

    @Test
    public void testHistoryCleanupManualRun() {
        //given
        prepareData(15);

        ClockUtil.setCurrentTime(new Date());
        //when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        //then
        assertResult(0);
    }

    @Test
    public void testHistoryCleanupMetrics() {
        //given
        processEngineConfiguration.setHistoryCleanupMetricsEnabled(true);
        prepareData(15);

        ClockUtil.setCurrentTime(new Date());
        //when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        //then
        final long removedProcessInstances = managementService.createMetricsQuery()
                .name(Metrics.HISTORY_CLEANUP_REMOVED_PROCESS_INSTANCES).sum();
        final long removedDecisionInstances = managementService.createMetricsQuery()
                .name(Metrics.HISTORY_CLEANUP_REMOVED_CASE_INSTANCES).sum();
        final long removedCaseInstances = managementService.createMetricsQuery()
                .name(Metrics.HISTORY_CLEANUP_REMOVED_DECISION_INSTANCES).sum();

        assertTrue(removedProcessInstances > 0);
        assertTrue(removedDecisionInstances > 0);
        assertTrue(removedCaseInstances > 0);

        assertEquals(15, removedProcessInstances + removedCaseInstances + removedDecisionInstances);
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupOnlyDecisionInstancesRemoved() {
        // given
        prepareInstances(null, HISTORY_TIME_TO_LIVE, null);

        ClockUtil.setCurrentTime(new Date());
        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(PROCESS_INSTANCES_COUNT, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(0, historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(CASE_INSTANCES_COUNT, historyService.createHistoricCaseInstanceQuery().count());
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupOnlyProcessInstancesRemoved() {
        // given
        prepareInstances(HISTORY_TIME_TO_LIVE, null, null);

        ClockUtil.setCurrentTime(new Date());
        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(0, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(DECISION_INSTANCES_COUNT + DECISIONS_IN_PROCESS_INSTANCES,
                historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(CASE_INSTANCES_COUNT, historyService.createHistoricCaseInstanceQuery().count());
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupOnlyCaseInstancesRemoved() {
        // given
        prepareInstances(null, null, HISTORY_TIME_TO_LIVE);

        ClockUtil.setCurrentTime(new Date());

        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(PROCESS_INSTANCES_COUNT, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(DECISION_INSTANCES_COUNT + DECISIONS_IN_PROCESS_INSTANCES,
                historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(0, historyService.createHistoricCaseInstanceQuery().count());
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupOnlyDecisionInstancesNotRemoved() {
        // given
        prepareInstances(HISTORY_TIME_TO_LIVE, null, HISTORY_TIME_TO_LIVE);

        ClockUtil.setCurrentTime(new Date());
        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(0, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(DECISION_INSTANCES_COUNT + DECISIONS_IN_PROCESS_INSTANCES,
                historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(0, historyService.createHistoricCaseInstanceQuery().count());
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupOnlyProcessInstancesNotRemoved() {
        // given
        prepareInstances(null, HISTORY_TIME_TO_LIVE, HISTORY_TIME_TO_LIVE);

        ClockUtil.setCurrentTime(new Date());
        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(PROCESS_INSTANCES_COUNT, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(0, historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(0, historyService.createHistoricCaseInstanceQuery().count());
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupOnlyCaseInstancesNotRemoved() {
        // given
        prepareInstances(HISTORY_TIME_TO_LIVE, HISTORY_TIME_TO_LIVE, null);

        ClockUtil.setCurrentTime(new Date());

        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(0, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(0, historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(CASE_INSTANCES_COUNT, historyService.createHistoricCaseInstanceQuery().count());
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupEverythingRemoved() {
        // given
        prepareInstances(HISTORY_TIME_TO_LIVE, HISTORY_TIME_TO_LIVE, HISTORY_TIME_TO_LIVE);

        ClockUtil.setCurrentTime(new Date());
        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertResult(0);
    }

    @Test
    @Deployment(resources = {
            "org/camunda/bpm/engine/test/dmn/businessruletask/DmnBusinessRuleTaskTest.testDecisionRef.bpmn20.xml",
            "org/camunda/bpm/engine/test/api/history/testDmnWithPojo.dmn11.xml",
            "org/camunda/bpm/engine/test/api/authorization/oneTaskCase.cmmn" })
    public void testHistoryCleanupNothingRemoved() {
        // given
        prepareInstances(null, null, null);

        ClockUtil.setCurrentTime(new Date());
        // when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        // then
        assertEquals(PROCESS_INSTANCES_COUNT, historyService.createHistoricProcessInstanceQuery().count());
        assertEquals(DECISION_INSTANCES_COUNT + DECISIONS_IN_PROCESS_INSTANCES,
                historyService.createHistoricDecisionInstanceQuery().count());
        assertEquals(CASE_INSTANCES_COUNT, historyService.createHistoricCaseInstanceQuery().count());
    }

    private void prepareInstances(Integer processInstanceTimeToLive, Integer decisionTimeToLive,
            Integer caseTimeToLive) {
        //update time to live
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("testProcess").list();
        assertEquals(1, processDefinitions.size());
        repositoryService.updateProcessDefinitionHistoryTimeToLive(processDefinitions.get(0).getId(),
                processInstanceTimeToLive);

        final List<DecisionDefinition> decisionDefinitions = repositoryService.createDecisionDefinitionQuery()
                .decisionDefinitionKey("testDecision").list();
        assertEquals(1, decisionDefinitions.size());
        repositoryService.updateDecisionDefinitionHistoryTimeToLive(decisionDefinitions.get(0).getId(),
                decisionTimeToLive);

        List<CaseDefinition> caseDefinitions = repositoryService.createCaseDefinitionQuery()
                .caseDefinitionKey("oneTaskCase").list();
        assertEquals(1, caseDefinitions.size());
        repositoryService.updateCaseDefinitionHistoryTimeToLive(caseDefinitions.get(0).getId(), caseTimeToLive);

        Date oldCurrentTime = ClockUtil.getCurrentTime();
        ClockUtil.setCurrentTime(DateUtils.addDays(new Date(), DAYS_IN_THE_PAST));

        //create 3 process instances
        List<String> processInstanceIds = new ArrayList<String>();
        Map<String, Object> variables = Variables.createVariables().putValue("pojo", new TestPojo("okay", 13.37));
        for (int i = 0; i < PROCESS_INSTANCES_COUNT; i++) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testProcess", variables);
            processInstanceIds.add(processInstance.getId());
        }
        runtimeService.deleteProcessInstances(processInstanceIds, null, true, true);

        //+10 standalone decisions
        for (int i = 0; i < DECISION_INSTANCES_COUNT; i++) {
            engineRule.getDecisionService().evaluateDecisionByKey("testDecision").variables(variables).evaluate();
        }

        // create 4 case instances
        for (int i = 0; i < CASE_INSTANCES_COUNT; i++) {
            CaseInstance caseInstance = caseService.createCaseInstanceByKey("oneTaskCase",
                    Variables.createVariables().putValue("pojo", new TestPojo("okay", 13.37 + i)));
            caseService.terminateCaseExecution(caseInstance.getId());
            caseService.closeCaseInstance(caseInstance.getId());
        }

        ClockUtil.setCurrentTime(oldCurrentTime);

    }

    @Test
    public void testHistoryCleanupWithinBatchWindow() {
        //given
        prepareData(15);

        //we're within batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(now));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, HISTORY_TIME_TO_LIVE)));
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync(false).getId();

        managementService.executeJob(jobId);

        //then
        assertResult(0);
    }

    @Test
    public void testHistoryCleanupJobNullTTL() {
        //given
        removeHistoryTimeToLive();

        prepareData(15);

        ClockUtil.setCurrentTime(new Date());
        //when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        //then
        assertResult(15);
    }

    private void removeHistoryTimeToLive() {
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(ONE_TASK_PROCESS).list();
        assertEquals(1, processDefinitions.size());
        repositoryService.updateProcessDefinitionHistoryTimeToLive(processDefinitions.get(0).getId(), null);

        final List<DecisionDefinition> decisionDefinitions = repositoryService.createDecisionDefinitionQuery()
                .decisionDefinitionKey(DECISION).list();
        assertEquals(1, decisionDefinitions.size());
        repositoryService.updateDecisionDefinitionHistoryTimeToLive(decisionDefinitions.get(0).getId(), null);

        final List<CaseDefinition> caseDefinitions = repositoryService.createCaseDefinitionQuery()
                .caseDefinitionKey(ONE_TASK_CASE).list();
        assertEquals(1, caseDefinitions.size());
        repositoryService.updateCaseDefinitionHistoryTimeToLive(caseDefinitions.get(0).getId(), null);
    }

    @Test
    @Deployment(resources = { "org/camunda/bpm/engine/test/api/twoTasksProcess.bpmn20.xml" })
    public void testHistoryCleanupJobDefaultTTL() {
        //given
        prepareBPMNData(15, "twoTasksProcess");

        ClockUtil.setCurrentTime(new Date());
        //when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        //then
        assertResult(15);
    }

    @Test
    public void testFindHistoryCleanupJob() {
        //given
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        //when
        Job historyCleanupJob = historyService.findHistoryCleanupJob();

        //then
        assertNotNull(historyCleanupJob);
        assertEquals(jobId, historyCleanupJob.getId());
    }

    @Test
    public void testRescheduleForNever() {
        //given

        //force creation of job
        historyService.cleanUpHistoryAsync(true);
        JobEntity historyCleanupJob = (JobEntity) historyService.findHistoryCleanupJob();
        assertNotNull(historyCleanupJob);
        assertNotNull(historyCleanupJob.getDuedate());

        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(null);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(null);
        processEngineConfiguration.initHistoryCleanup();

        ClockUtil.setCurrentTime(new Date());

        //when
        historyService.cleanUpHistoryAsync(false);

        //then
        historyCleanupJob = (JobEntity) historyService.findHistoryCleanupJob();
        assertEquals(SuspensionState.SUSPENDED.getStateCode(), historyCleanupJob.getSuspensionState());
        assertNull(historyCleanupJob.getDuedate());

    }

    @Test
    public void testHistoryCleanupJobResolveIncident() {
        //given
        String jobId = historyService.cleanUpHistoryAsync(true).getId();
        imitateFailedJob(jobId);

        assertEquals(5, processEngineConfiguration.getDefaultNumberOfRetries());
        //when
        //call to cleanup history means that incident was resolved
        jobId = historyService.cleanUpHistoryAsync(true).getId();

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        assertEquals(5, jobEntity.getRetries());
        assertEquals(null, jobEntity.getExceptionByteArrayId());
        assertEquals(null, jobEntity.getExceptionMessage());

    }

    private void imitateFailedJob(final String jobId) {
        processEngineConfiguration.getCommandExecutorTxRequired().execute(new Command<Void>() {
            public Void execute(CommandContext commandContext) {
                JobEntity jobEntity = getJobEntity(jobId);
                jobEntity.setRetries(0);
                jobEntity.setExceptionMessage("Something bad happened");
                jobEntity.setExceptionStacktrace(
                        ExceptionUtil.getExceptionStacktrace(new RuntimeException("Something bad happened")));
                return null;
            }
        });
    }

    @Test
    public void testLessThanThresholdManualRun() {
        //given
        prepareData(5);

        ClockUtil.setCurrentTime(new Date());
        //when
        String jobId = historyService.cleanUpHistoryAsync(true).getId();

        managementService.executeJob(jobId);

        //then
        assertEquals(0,
                historyService.createHistoricProcessInstanceQuery().processDefinitionKey(ONE_TASK_PROCESS).count());

        JobEntity jobEntity = getJobEntity(jobId);
        assertEquals(SuspensionState.SUSPENDED.getStateCode(), jobEntity.getSuspensionState());
    }

    @Test
    public void testNotEnoughTimeToDeleteEverything() {
        //given
        //we have something to cleanup
        prepareData(40);
        //we call history cleanup within batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(now));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, HISTORY_TIME_TO_LIVE)));
        processEngineConfiguration.initHistoryCleanup();
        String jobId = historyService.cleanUpHistoryAsync().getId();
        //job is executed once within batch window
        managementService.executeJob(jobId);

        //when
        //time passed -> outside batch window
        ClockUtil.setCurrentTime(DateUtils.addHours(now, 6));
        //the job is called for the second time
        managementService.executeJob(jobId);

        //then
        //second execution was not able to delete rest data
        assertResult(20);
    }

    @Test
    public void testManualRunDoesNotRespectBatchWindow() {
        //given
        //we have something to cleanup
        int processInstanceCount = 40;
        prepareData(processInstanceCount);

        //we call history cleanup outside batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, 1))); //now + 1 hour
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, HISTORY_TIME_TO_LIVE))); //now + 5 hours
        processEngineConfiguration.initHistoryCleanup();

        //when
        //job is executed before batch window start
        String jobId = historyService.cleanUpHistoryAsync(true).getId();
        managementService.executeJob(jobId);

        //the job is called for the second time after batch window end
        ClockUtil.setCurrentTime(DateUtils.addHours(now, 6)); //now + 6 hours
        managementService.executeJob(jobId);

        //then
        assertResult(0);
    }

    @Test
    public void testLessThanThresholdWithinBatchWindow() {
        //given
        prepareData(5);

        //we're within batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(now));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, HISTORY_TIME_TO_LIVE)));
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();

        managementService.executeJob(jobId);

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till current time + delay
        Date nextRun = getNextRunWithDelay(ClockUtil.getCurrentTime(), 0);
        assertTrue(jobEntity.getDuedate().equals(nextRun) || jobEntity.getDuedate().after(nextRun));
        Date nextRunMax = DateUtils.addSeconds(ClockUtil.getCurrentTime(),
                HistoryCleanupJobHandlerConfiguration.MAX_DELAY);
        assertTrue(jobEntity.getDuedate().before(nextRunMax));

        //countEmptyRuns incremented
        assertEquals(1, configuration.getCountEmptyRuns());

        //data is still removed
        assertResult(0);
    }

    private Date getNextRunWithDelay(Date date, int countEmptyRuns) {
        //ignore milliseconds because MySQL does not support them, and it's not important for test
        Date result = DateUtils.setMilliseconds(DateUtils.addSeconds(date,
                Math.min((int) (Math.pow(2., countEmptyRuns) * HistoryCleanupJobHandlerConfiguration.START_DELAY),
                        HistoryCleanupJobHandlerConfiguration.MAX_DELAY)),
                0);
        return result;
    }

    private JobEntity getJobEntity(String jobId) {
        return (JobEntity) managementService.createJobQuery().jobId(jobId).list().get(0);
    }

    @Test
    public void testLessThanThresholdWithinBatchWindowAgain() {
        //given
        prepareData(5);

        //we're within batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(now));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, 1)));
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();
        for (int i = 1; i <= 6; i++) {
            managementService.executeJob(jobId);
        }

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till current time + (2 power count)*delay
        Date nextRun = getNextRunWithDelay(ClockUtil.getCurrentTime(), HISTORY_TIME_TO_LIVE);
        assertTrue(jobEntity.getDuedate().equals(nextRun) || jobEntity.getDuedate().after(nextRun));
        Date nextRunMax = DateUtils.addSeconds(ClockUtil.getCurrentTime(),
                HistoryCleanupJobHandlerConfiguration.MAX_DELAY);
        assertTrue(jobEntity.getDuedate().before(nextRunMax));

        //countEmptyRuns incremented
        assertEquals(6, configuration.getCountEmptyRuns());

        //data is still removed
        assertResult(0);
    }

    @Test
    public void testLessThanThresholdWithinBatchWindowMaxDelayReached() {
        //given
        prepareData(5);

        //we're within batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(now));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(now, 2)));
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();
        for (int i = 1; i <= 11; i++) {
            managementService.executeJob(jobId);
        }

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till current time + max delay
        Date nextRun = getNextRunWithDelay(ClockUtil.getCurrentTime(), 10);
        assertTrue(jobEntity.getDuedate().equals(nextRun) || jobEntity.getDuedate().after(nextRun));
        assertTrue(jobEntity.getDuedate().before(getNextRunWithinBatchWindow(now)));

        //countEmptyRuns incremented
        assertEquals(11, configuration.getCountEmptyRuns());

        //data is still removed
        assertResult(0);
    }

    @Test
    public void testLessThanThresholdCloseToBatchWindowEndTime() {
        //given
        prepareData(5);

        //we're within batch window
        Date now = new Date();
        ClockUtil.setCurrentTime(now);
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(now));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addMinutes(now, 30)));
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();
        for (int i = 1; i <= 9; i++) {
            managementService.executeJob(jobId);
        }

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till next batch window start time
        Date nextRun = getNextRunWithinBatchWindow(ClockUtil.getCurrentTime());
        assertTrue(jobEntity.getDuedate().equals(nextRun));

        //countEmptyRuns canceled
        assertEquals(0, configuration.getCountEmptyRuns());

        //data is still removed
        assertResult(0);
    }

    @Test
    public void testLessThanThresholdOutsideBatchWindow() {
        //given
        prepareData(5);

        //we're outside batch window
        Date twoHoursAgo = new Date();
        processEngineConfiguration
                .setHistoryCleanupBatchWindowStartTime(new SimpleDateFormat("HH:mm").format(twoHoursAgo));
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime(
                new SimpleDateFormat("HH:mm").format(DateUtils.addHours(twoHoursAgo, 1)));
        processEngineConfiguration.initHistoryCleanup();
        ClockUtil.setCurrentTime(DateUtils.addHours(twoHoursAgo, 2));

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();
        for (int i = 1; i <= 3; i++) {
            managementService.executeJob(jobId);
        }

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till next batch window start
        Date nextRun = getNextRunWithinBatchWindow(ClockUtil.getCurrentTime());
        assertTrue(jobEntity.getDuedate().equals(nextRun));

        //countEmptyRuns canceled
        assertEquals(0, configuration.getCountEmptyRuns());

        //nothing was removed
        assertResult(5);
    }

    @Test
    public void testLessThanThresholdOutsideBatchWindowAfterMidnight() {
        //given
        prepareData(5);

        //we're outside batch window, batch window passes midnight
        Date date = new Date();
        ClockUtil.setCurrentTime(DateUtils.setMinutes(DateUtils.setHours(date, 1), 10)); //01:10
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00");
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:00");
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();
        managementService.executeJob(jobId);

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till next batch window start
        Date nextRun = getNextRunWithinBatchWindow(ClockUtil.getCurrentTime());
        assertTrue(jobEntity.getDuedate().equals(nextRun));
        assertTrue(nextRun.after(ClockUtil.getCurrentTime()));

        //countEmptyRuns canceled
        assertEquals(0, configuration.getCountEmptyRuns());

        //nothing was removed
        assertResult(5);
    }

    @Test
    public void testLessThanThresholdOutsideBatchWindowBeforeMidnight() {
        //given
        prepareData(5);

        //we're outside batch window, batch window passes midnight
        Date date = new Date();
        ClockUtil.setCurrentTime(DateUtils.setMinutes(DateUtils.setHours(date, 22), 10)); //22:10
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00");
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:00");
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();
        managementService.executeJob(jobId);

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till next batch window start
        Date nextRun = getNextRunWithinBatchWindow(ClockUtil.getCurrentTime());
        assertTrue(jobEntity.getDuedate().equals(nextRun));
        assertTrue(nextRun.after(ClockUtil.getCurrentTime()));

        //countEmptyRuns cancelled
        assertEquals(0, configuration.getCountEmptyRuns());

        //nothing was removed
        assertResult(5);
    }

    @Test
    public void testLessThanThresholdWithinBatchWindowBeforeMidnight() {
        //given
        prepareData(5);

        //we're within batch window, but batch window passes midnight
        Date date = new Date();
        ClockUtil.setCurrentTime(DateUtils.setMinutes(DateUtils.setHours(date, 23), 10)); //23:10
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00");
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:00");
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();

        ExecuteJobHelper.executeJob(jobId, processEngineConfiguration.getCommandExecutorTxRequired());

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till current time + delay
        Date nextRun = getNextRunWithDelay(ClockUtil.getCurrentTime(), 0);
        assertTrue(jobEntity.getDuedate().equals(nextRun) || jobEntity.getDuedate().after(nextRun));
        Date nextRunMax = DateUtils.addSeconds(ClockUtil.getCurrentTime(),
                HistoryCleanupJobHandlerConfiguration.MAX_DELAY);
        assertTrue(jobEntity.getDuedate().before(nextRunMax));

        //countEmptyRuns incremented
        assertEquals(1, configuration.getCountEmptyRuns());

        //data is still removed
        assertResult(0);
    }

    @Test
    public void testLessThanThresholdWithinBatchWindowAfterMidnight() {
        //given
        prepareData(5);

        //we're within batch window, but batch window passes midnight
        Date date = new Date();
        ClockUtil.setCurrentTime(DateUtils.setMinutes(DateUtils.setHours(date, 0), 10)); //00:10
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00");
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:00");
        processEngineConfiguration.initHistoryCleanup();

        //when
        String jobId = historyService.cleanUpHistoryAsync().getId();

        ExecuteJobHelper.executeJob(jobId, processEngineConfiguration.getCommandExecutorTxRequired());

        //then
        JobEntity jobEntity = getJobEntity(jobId);
        HistoryCleanupJobHandlerConfiguration configuration = getConfiguration(jobEntity);

        //job rescheduled till current time + delay
        Date nextRun = getNextRunWithDelay(ClockUtil.getCurrentTime(), 0);
        assertTrue(jobEntity.getDuedate().equals(nextRun) || jobEntity.getDuedate().after(nextRun));
        Date nextRunMax = DateUtils.addSeconds(ClockUtil.getCurrentTime(),
                HistoryCleanupJobHandlerConfiguration.MAX_DELAY);
        assertTrue(jobEntity.getDuedate().before(nextRunMax));

        //countEmptyRuns incremented
        assertEquals(1, configuration.getCountEmptyRuns());

        //data is still removed
        assertResult(0);
    }

    @Test
    public void testConfiguration() {
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00+0200");
        processEngineConfiguration.initHistoryCleanup();
        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT+2:00"));
        Date startTime = processEngineConfiguration.getHistoryCleanupBatchWindowStartTimeAsDate();
        c.setTime(startTime);
        assertEquals(23, c.get(Calendar.HOUR_OF_DAY));
        assertEquals(0, c.get(Calendar.MINUTE));
        assertEquals(0, c.get(Calendar.SECOND));

        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00");
        processEngineConfiguration.initHistoryCleanup();
        c = Calendar.getInstance();
        startTime = processEngineConfiguration.getHistoryCleanupBatchWindowStartTimeAsDate();
        c.setTime(startTime);
        assertEquals(23, c.get(Calendar.HOUR_OF_DAY));
        assertEquals(0, c.get(Calendar.MINUTE));
        assertEquals(0, c.get(Calendar.SECOND));

        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:35-0800");
        processEngineConfiguration.initHistoryCleanup();
        c = Calendar.getInstance(TimeZone.getTimeZone("GMT-8:00"));
        Date endTime = processEngineConfiguration.getHistoryCleanupBatchWindowEndTimeAsDate();
        c.setTime(endTime);
        assertEquals(1, c.get(Calendar.HOUR_OF_DAY));
        assertEquals(35, c.get(Calendar.MINUTE));
        assertEquals(0, c.get(Calendar.SECOND));

        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:35");
        processEngineConfiguration.initHistoryCleanup();
        c = Calendar.getInstance();
        endTime = processEngineConfiguration.getHistoryCleanupBatchWindowEndTimeAsDate();
        c.setTime(endTime);
        assertEquals(1, c.get(Calendar.HOUR_OF_DAY));
        assertEquals(35, c.get(Calendar.MINUTE));
        assertEquals(0, c.get(Calendar.SECOND));

        processEngineConfiguration.setHistoryCleanupBatchSize(500);
        processEngineConfiguration.initHistoryCleanup();
        assertEquals(processEngineConfiguration.getHistoryCleanupBatchSize(), 500);
    }

    @Test
    public void testConfigurationFailureWrongStartTime() {
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23");
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("01:00");

        thrown.expect(ProcessEngineException.class);
        thrown.expectMessage("historyCleanupBatchWindowStartTime");

        processEngineConfiguration.initHistoryCleanup();
    }

    @Test
    public void testConfigurationFailureWrongEndTime() {
        processEngineConfiguration.setHistoryCleanupBatchWindowStartTime("23:00");
        processEngineConfiguration.setHistoryCleanupBatchWindowEndTime("wrongValue");

        thrown.expect(ProcessEngineException.class);
        thrown.expectMessage("historyCleanupBatchWindowEndTime");

        processEngineConfiguration.initHistoryCleanup();
    }

    @Test
    public void testConfigurationFailureWrongBatchSize() {
        processEngineConfiguration.setHistoryCleanupBatchSize(501);

        thrown.expect(ProcessEngineException.class);
        thrown.expectMessage("historyCleanupBatchSize");

        processEngineConfiguration.initHistoryCleanup();
    }

    @Test
    public void testConfigurationFailureWrongBatchSize2() {
        processEngineConfiguration.setHistoryCleanupBatchSize(-5);

        thrown.expect(ProcessEngineException.class);
        thrown.expectMessage("historyCleanupBatchSize");

        processEngineConfiguration.initHistoryCleanup();
    }

    @Test
    public void testConfigurationFailureWrongBatchThreshold() {
        processEngineConfiguration.setHistoryCleanupBatchThreshold(-1);

        thrown.expect(ProcessEngineException.class);
        thrown.expectMessage("historyCleanupBatchThreshold");

        processEngineConfiguration.initHistoryCleanup();
    }

    private Date getNextRunWithinBatchWindow(Date currentTime) {
        Date batchWindowStartTime = processEngineConfiguration.getHistoryCleanupBatchWindowStartTimeAsDate();
        return getNextRunWithinBatchWindow(currentTime, batchWindowStartTime);
    }

    public Date getNextRunWithinBatchWindow(Date date, Date batchWindowStartTime) {
        Date todayPossibleRun = updateTime(date, batchWindowStartTime);
        if (todayPossibleRun.after(date)) {
            return todayPossibleRun;
        } else {
            //tomorrow
            return DateUtils.addDays(todayPossibleRun, 1);
        }
    }

    private Date updateTime(Date now, Date newTime) {
        Date result = now;

        Calendar newTimeCalendar = Calendar.getInstance();
        newTimeCalendar.setTime(newTime);

        result = DateUtils.setHours(result, newTimeCalendar.get(Calendar.HOUR_OF_DAY));
        result = DateUtils.setMinutes(result, newTimeCalendar.get(Calendar.MINUTE));
        result = DateUtils.setSeconds(result, newTimeCalendar.get(Calendar.SECOND));
        result = DateUtils.setMilliseconds(result, newTimeCalendar.get(Calendar.MILLISECOND));
        return result;
    }

    private HistoryCleanupJobHandlerConfiguration getConfiguration(JobEntity jobEntity) {
        String jobHandlerConfigurationRaw = jobEntity.getJobHandlerConfigurationRaw();
        return HistoryCleanupJobHandlerConfiguration.fromJson(new JSONObject(jobHandlerConfigurationRaw));
    }

    private void prepareData(int instanceCount) {
        int createdInstances = instanceCount / 3;
        prepareBPMNData(createdInstances, ONE_TASK_PROCESS);
        prepareDMNData(createdInstances);
        prepareCMMNData(instanceCount - 2 * createdInstances);
    }

    private void prepareBPMNData(int instanceCount, String businesskey) {
        Date oldCurrentTime = ClockUtil.getCurrentTime();
        ClockUtil.setCurrentTime(DateUtils.addDays(new Date(), DAYS_IN_THE_PAST));
        final List<String> ids = prepareHistoricProcesses(businesskey, getVariables(), instanceCount);
        runtimeService.deleteProcessInstances(ids, null, true, true);
        ClockUtil.setCurrentTime(oldCurrentTime);
    }

    private void prepareDMNData(int instanceCount) {
        Date oldCurrentTime = ClockUtil.getCurrentTime();
        ClockUtil.setCurrentTime(DateUtils.addDays(new Date(), DAYS_IN_THE_PAST));
        for (int i = 0; i < instanceCount; i++) {
            engineRule.getDecisionService().evaluateDecisionByKey(DECISION).variables(getDMNVariables()).evaluate();
        }
        ClockUtil.setCurrentTime(oldCurrentTime);
    }

    private void prepareCMMNData(int instanceCount) {
        Date oldCurrentTime = ClockUtil.getCurrentTime();
        ClockUtil.setCurrentTime(DateUtils.addDays(new Date(), DAYS_IN_THE_PAST));

        for (int i = 0; i < instanceCount; i++) {
            CaseInstance caseInstance = caseService.createCaseInstanceByKey(ONE_TASK_CASE);
            caseService.terminateCaseExecution(caseInstance.getId());
            caseService.closeCaseInstance(caseInstance.getId());
        }
        ClockUtil.setCurrentTime(oldCurrentTime);
    }

    private List<String> prepareHistoricProcesses(String businessKey, VariableMap variables,
            Integer processInstanceCount) {
        List<String> processInstanceIds = new ArrayList<String>();

        for (int i = 0; i < processInstanceCount; i++) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(businessKey, variables);
            processInstanceIds.add(processInstance.getId());
        }

        return processInstanceIds;
    }

    private VariableMap getVariables() {
        return Variables.createVariables().putValue("aVariableName", "aVariableValue")
                .putValue("anotherVariableName", "anotherVariableValue");
    }

    protected VariableMap getDMNVariables() {
        return Variables.createVariables().putValue("status", "silver").putValue("sum", 723);
    }

    private void assertResult(long expectedInstanceCount) {
        long count = historyService.createHistoricProcessInstanceQuery().count()
                + historyService.createHistoricDecisionInstanceQuery().count()
                + historyService.createHistoricCaseInstanceQuery().count();
        assertEquals(expectedInstanceCount, count);
    }

}