From c11c7d2a9920396bad62bef2cf737771a1cd0514 Mon Sep 17 00:00:00 2001
From: bespaltovyj <32296746+bespaltovyj@users.noreply.github.com>
Date: Mon, 18 Oct 2021 10:34:30 +0400
Subject: [PATCH] feat(engine): support composite incident handlers
* adds new engine flag for enabling composite incident handlers so multiple
incident handlers can be triggered for the same type
related to CAM-13569
closes #1561
---
.../cfg/ProcessEngineConfigurationImpl.java | 51 +++-
.../incident/CompositeIncidentHandler.java | 125 ++++++++++
.../CompositeIncidentHandlerTest.java | 222 ++++++++++++++++++
.../mgmt/IncidentMultipleProcessingTest.java | 192 +++++++++++++++
4 files changed, 586 insertions(+), 4 deletions(-)
create mode 100644 engine/src/main/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandler.java
create mode 100644 engine/src/test/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandlerTest.java
create mode 100644 engine/src/test/java/org/camunda/bpm/engine/test/api/mgmt/IncidentMultipleProcessingTest.java
diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java b/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java
index 1b5abe74b6f..9f2e6f242e6 100644
--- a/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java
+++ b/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java
@@ -210,6 +210,7 @@
import org.camunda.bpm.engine.impl.identity.ReadOnlyIdentityProvider;
import org.camunda.bpm.engine.impl.identity.WritableIdentityProvider;
import org.camunda.bpm.engine.impl.identity.db.DbIdentityServiceProvider;
+import org.camunda.bpm.engine.impl.incident.CompositeIncidentHandler;
import org.camunda.bpm.engine.impl.incident.DefaultIncidentHandler;
import org.camunda.bpm.engine.impl.incident.IncidentHandler;
import org.camunda.bpm.engine.impl.interceptor.CommandContextFactory;
@@ -761,6 +762,22 @@ public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfig
protected boolean isExecutionTreePrefetchEnabled = true;
+ /**
+ * If true, the incident handlers init as {@link CompositeIncidentHandler} and
+ * multiple incident handlers can be added for the same Incident type.
+ * However, only the result from the "main" incident handler will be returned.
+ *
+ * All {@link customIncidentHandlers} will be added as sub handlers to {@link CompositeIncidentHandler} for same handler type.
+ *
+ * By default, main handler is {@link DefaultIncidentHandler}.
+ * To override the main handler you need create {@link CompositeIncidentHandler} with your main IncidentHandler and
+ * init {@link incidentHandlers} before setting up the engine.
+ *
+ * @see CompositeIncidentHandler
+ * @see #initIncidentHandlers
+ */
+ protected boolean isCompositeIncidentHandlersEnabled = false;
+
/**
* If true the process engine will attempt to acquire an exclusive lock before
* creating a deployment.
@@ -1325,14 +1342,21 @@ protected void initIncidentHandlers() {
incidentHandlers = new HashMap<>();
DefaultIncidentHandler failedJobIncidentHandler = new DefaultIncidentHandler(Incident.FAILED_JOB_HANDLER_TYPE);
- incidentHandlers.put(failedJobIncidentHandler.getIncidentHandlerType(), failedJobIncidentHandler);
+ DefaultIncidentHandler failedExternalTaskIncidentHandler = new DefaultIncidentHandler(
+ Incident.EXTERNAL_TASK_HANDLER_TYPE);
+
+ if (isCompositeIncidentHandlersEnabled) {
+ addIncidentHandler(new CompositeIncidentHandler(failedJobIncidentHandler));
+ addIncidentHandler(new CompositeIncidentHandler(failedExternalTaskIncidentHandler));
+ } else {
+ addIncidentHandler(failedJobIncidentHandler);
+ addIncidentHandler(failedExternalTaskIncidentHandler);
+ }
- DefaultIncidentHandler failedExternalTaskIncidentHandler = new DefaultIncidentHandler(Incident.EXTERNAL_TASK_HANDLER_TYPE);
- incidentHandlers.put(failedExternalTaskIncidentHandler.getIncidentHandlerType(), failedExternalTaskIncidentHandler);
}
if (customIncidentHandlers != null) {
for (IncidentHandler incidentHandler : customIncidentHandlers) {
- incidentHandlers.put(incidentHandler.getIncidentHandlerType(), incidentHandler);
+ addIncidentHandler(incidentHandler);
}
}
}
@@ -3791,6 +3815,16 @@ public IncidentHandler getIncidentHandler(String incidentType) {
return incidentHandlers.get(incidentType);
}
+ public void addIncidentHandler(IncidentHandler incidentHandler) {
+ IncidentHandler existsHandler = incidentHandlers.get(incidentHandler.getIncidentHandlerType());
+
+ if (existsHandler instanceof CompositeIncidentHandler) {
+ ((CompositeIncidentHandler) existsHandler).add(incidentHandler);
+ } else {
+ incidentHandlers.put(incidentHandler.getIncidentHandlerType(), incidentHandler);
+ }
+ }
+
public Map getIncidentHandlers() {
return incidentHandlers;
}
@@ -4114,6 +4148,15 @@ public ProcessEngineConfigurationImpl setStandaloneTasksEnabled(boolean standalo
return this;
}
+ public boolean isCompositeIncidentHandlersEnabled() {
+ return isCompositeIncidentHandlersEnabled;
+ }
+
+ public ProcessEngineConfigurationImpl setCompositeIncidentHandlersEnabled(boolean compositeIncidentHandlersEnabled) {
+ this.isCompositeIncidentHandlersEnabled = compositeIncidentHandlersEnabled;
+ return this;
+ }
+
public ScriptFactory getScriptFactory() {
return scriptFactory;
}
diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandler.java b/engine/src/main/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandler.java
new file mode 100644
index 00000000000..602f9f4bf06
--- /dev/null
+++ b/engine/src/main/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandler.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. Camunda licenses this file to you under the Apache License,
+ * Version 2.0; 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.impl.incident;
+
+import org.camunda.bpm.engine.ProcessEngineException;
+import org.camunda.bpm.engine.impl.util.EnsureUtil;
+import org.camunda.bpm.engine.runtime.Incident;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * A composite incident handler that handles incidents of a certain type by the multiple handlers.
+ * The result of handling depends on main handler.
+ *
+ * @see #mainIncidentHandler
+ *
+ * @see IncidentHandler
+ */
+public class CompositeIncidentHandler implements IncidentHandler {
+
+ protected IncidentHandler mainIncidentHandler;
+ protected final List incidentHandlers = new ArrayList<>();
+
+ /**
+ * Constructor that takes a list of {@link IncidentHandler} that consume
+ * the incident.
+ *
+ * @param mainIncidentHandler the main incident handler {@link IncidentHandler} that consume the incident and return result.
+ * @param incidentHandlers the list of {@link IncidentHandler} that consume the incident.
+ */
+ public CompositeIncidentHandler(IncidentHandler mainIncidentHandler, final List incidentHandlers) {
+ initializeIncidentsHandlers(mainIncidentHandler, incidentHandlers);
+ }
+
+ /**
+ * Constructor that takes a varargs parameter {@link IncidentHandler} that
+ * consume the incident.
+ *
+ * @param mainIncidentHandler the main incident handler {@link IncidentHandler} that consume the incident and return result.
+ * @param incidentHandlers the list of {@link IncidentHandler} that consume the incident.
+ */
+ public CompositeIncidentHandler(IncidentHandler mainIncidentHandler, final IncidentHandler... incidentHandlers) {
+ EnsureUtil.ensureNotNull("Incident handlers", (Object[]) incidentHandlers);
+ initializeIncidentsHandlers(mainIncidentHandler, Arrays.asList(incidentHandlers));
+ }
+
+ /**
+ * Initialize {@link #incidentHandlers} with data transfered from constructor
+ *
+ * @param incidentHandlers
+ */
+ protected void initializeIncidentsHandlers(IncidentHandler mainIncidentHandler,
+ final List incidentHandlers) {
+ EnsureUtil.ensureNotNull("Incident handler", mainIncidentHandler);
+ this.mainIncidentHandler = mainIncidentHandler;
+
+ EnsureUtil.ensureNotNull("Incident handlers", incidentHandlers);
+ for (IncidentHandler incidentHandler : incidentHandlers) {
+ add(incidentHandler);
+ }
+ }
+
+ /**
+ * Adds the {@link IncidentHandler} to the list of
+ * {@link IncidentHandler} that consume the incident.
+ *
+ * @param incidentHandler the {@link IncidentHandler} that consume the incident.
+ */
+ public void add(final IncidentHandler incidentHandler) {
+ EnsureUtil.ensureNotNull("Incident handler", incidentHandler);
+ String incidentHandlerType = getIncidentHandlerType();
+ if (!incidentHandlerType.equals(incidentHandler.getIncidentHandlerType())) {
+ throw new ProcessEngineException(
+ "Incorrect incident type handler in composite handler with type: " + incidentHandlerType);
+ }
+ this.incidentHandlers.add(incidentHandler);
+ }
+
+ @Override
+ public String getIncidentHandlerType() {
+ return mainIncidentHandler.getIncidentHandlerType();
+ }
+
+ @Override
+ public Incident handleIncident(IncidentContext context, String message) {
+ Incident incident = mainIncidentHandler.handleIncident(context, message);
+ for (IncidentHandler incidentHandler : this.incidentHandlers) {
+ incidentHandler.handleIncident(context, message);
+ }
+ return incident;
+ }
+
+ @Override
+ public void resolveIncident(IncidentContext context) {
+ mainIncidentHandler.resolveIncident(context);
+ for (IncidentHandler incidentHandler : this.incidentHandlers) {
+ incidentHandler.resolveIncident(context);
+ }
+ }
+
+ @Override
+ public void deleteIncident(IncidentContext context) {
+ mainIncidentHandler.deleteIncident(context);
+ for (IncidentHandler incidentHandler : this.incidentHandlers) {
+ incidentHandler.deleteIncident(context);
+ }
+ }
+}
diff --git a/engine/src/test/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandlerTest.java b/engine/src/test/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandlerTest.java
new file mode 100644
index 00000000000..ba5134ef680
--- /dev/null
+++ b/engine/src/test/java/org/camunda/bpm/engine/impl/incident/CompositeIncidentHandlerTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. Camunda licenses this file to you under the Apache License,
+ * Version 2.0; 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.impl.incident;
+
+import org.camunda.bpm.engine.ProcessEngineException;
+import org.camunda.bpm.engine.exception.NullValueException;
+import org.camunda.bpm.engine.runtime.Incident;
+import org.junit.Test;
+import org.mockito.internal.verification.Times;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CompositeIncidentHandlerTest {
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerWithMainIncidentHandlerAddNullHandler() {
+ CompositeIncidentHandler compositeIncidentHandler = new CompositeIncidentHandler(new DefaultIncidentHandler(""));
+ try {
+ compositeIncidentHandler.add(null);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handler is null");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithNullMainHandler() {
+ try {
+ new CompositeIncidentHandler(null);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handler is null");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithNullVarargs() {
+ IncidentHandler incidentHandler = null;
+ try {
+ new CompositeIncidentHandler(null, incidentHandler);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handlers contains null value");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithNullList() {
+ List incidentHandler = null;
+ try {
+ new CompositeIncidentHandler(null, incidentHandler);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handler is null");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithMainHandlersAndNullVarargValue() {
+ IncidentHandler mainIncidentHandler = new DefaultIncidentHandler("failedJob");
+ IncidentHandler incidentHandler = null;
+ try {
+ new CompositeIncidentHandler(mainIncidentHandler, incidentHandler);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handlers contains null value");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithMainHandlersAndNullVarargs() {
+ IncidentHandler mainIncidentHandler = new DefaultIncidentHandler("failedJob");
+ IncidentHandler[] incidentHandler = null;
+ try {
+ new CompositeIncidentHandler(mainIncidentHandler, incidentHandler);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handlers is null");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithMainHandlersAndNullList() {
+ IncidentHandler mainIncidentHandler = new DefaultIncidentHandler("failedJob");
+
+ List incidentHandler = null;
+ try {
+ new CompositeIncidentHandler(mainIncidentHandler, incidentHandler);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handlers is null");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerArgumentConstructorWithMainHandlersAndListWithNulls() {
+ IncidentHandler mainIncidentHandler = new DefaultIncidentHandler("failedJob");
+
+ List incidentHandler = new ArrayList<>();
+ incidentHandler.add(null);
+ incidentHandler.add(null);
+ try {
+ new CompositeIncidentHandler(mainIncidentHandler, incidentHandler);
+ fail("NullValueException expected");
+ } catch (NullValueException e) {
+ assertThat(e.getMessage()).containsIgnoringCase("Incident handler is null");
+ }
+ }
+
+ @Test
+ public void shouldUseCompositeIncidentHandlerWithAnotherIncidentType() {
+ CompositeIncidentHandler compositeIncidentHandler = new CompositeIncidentHandler(
+ new DefaultIncidentHandler("failedJob"));
+ try {
+ compositeIncidentHandler.add(new DefaultIncidentHandler("failedExternalTask"));
+ fail("Non expected message expected");
+ } catch (ProcessEngineException e) {
+ assertThat(e.getMessage()).containsIgnoringCase(
+ "Incorrect incident type handler in composite handler with type: failedJob");
+ }
+ }
+
+ @Test
+ public void shouldCallAllHandlersWhenCreatingIncident() {
+ IncidentHandler mainHandler = mock(IncidentHandler.class);
+ Incident incident = mock(Incident.class);
+
+ when(mainHandler.getIncidentHandlerType()).thenReturn("failedJob");
+ when(mainHandler.handleIncident(any(), any())).thenReturn(incident);
+
+ CompositeIncidentHandler compositeIncidentHandler = new CompositeIncidentHandler(mainHandler);
+
+ IncidentHandler subHandler = mock(IncidentHandler.class);
+
+ when(subHandler.getIncidentHandlerType()).thenReturn("failedJob");
+ when(subHandler.handleIncident(any(), any())).thenReturn(null);
+
+ compositeIncidentHandler.add(subHandler);
+ compositeIncidentHandler.add(subHandler);
+ compositeIncidentHandler.add(subHandler);
+
+ IncidentContext incidentContext = mock(IncidentContext.class);
+
+ Incident result = compositeIncidentHandler.handleIncident(incidentContext, "Incident message");
+
+ assertThat(result).isNotNull();
+ assertThat(result).isEqualTo(incident);
+
+ verify(mainHandler).handleIncident(eq(incidentContext), eq("Incident message"));
+ verify(subHandler, new Times(3)).handleIncident(eq(incidentContext), eq("Incident message"));
+ }
+
+ @Test
+ public void shouldCallAllHandlersWhenDeletingIncident() {
+ IncidentHandler mainHandler = mock(IncidentHandler.class);
+
+ when(mainHandler.getIncidentHandlerType()).thenReturn("failedJob");
+
+ CompositeIncidentHandler compositeIncidentHandler = new CompositeIncidentHandler(mainHandler);
+
+ IncidentHandler subHandler = mock(IncidentHandler.class);
+ when(subHandler.getIncidentHandlerType()).thenReturn("failedJob");
+ compositeIncidentHandler.add(subHandler);
+ compositeIncidentHandler.add(subHandler);
+ compositeIncidentHandler.add(subHandler);
+
+ IncidentContext incidentContext = mock(IncidentContext.class);
+
+ compositeIncidentHandler.deleteIncident(incidentContext);
+
+ verify(mainHandler).deleteIncident(eq(incidentContext));
+ verify(subHandler, new Times(3)).deleteIncident(eq(incidentContext));
+ }
+
+ @Test
+ public void shouldCallAllHandlersWhenResolvingIncident() {
+ IncidentHandler mainHandler = mock(IncidentHandler.class);
+
+ when(mainHandler.getIncidentHandlerType()).thenReturn("failedJob");
+
+ CompositeIncidentHandler compositeIncidentHandler = new CompositeIncidentHandler(mainHandler);
+
+ IncidentHandler subHandler = mock(IncidentHandler.class);
+
+ when(subHandler.getIncidentHandlerType()).thenReturn("failedJob");
+
+ compositeIncidentHandler.add(subHandler);
+ compositeIncidentHandler.add(subHandler);
+ compositeIncidentHandler.add(subHandler);
+
+ IncidentContext incidentContext = mock(IncidentContext.class);
+
+ compositeIncidentHandler.resolveIncident(incidentContext);
+
+ verify(mainHandler).resolveIncident(eq(incidentContext));
+ verify(subHandler, new Times(3)).resolveIncident(eq(incidentContext));
+ }
+}
+
diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/api/mgmt/IncidentMultipleProcessingTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/api/mgmt/IncidentMultipleProcessingTest.java
new file mode 100644
index 00000000000..adc63d47544
--- /dev/null
+++ b/engine/src/test/java/org/camunda/bpm/engine/test/api/mgmt/IncidentMultipleProcessingTest.java
@@ -0,0 +1,192 @@
+package org.camunda.bpm.engine.test.api.mgmt;
+
+import org.camunda.bpm.engine.ManagementService;
+import org.camunda.bpm.engine.RuntimeService;
+import org.camunda.bpm.engine.impl.incident.CompositeIncidentHandler;
+import org.camunda.bpm.engine.impl.incident.IncidentContext;
+import org.camunda.bpm.engine.impl.incident.IncidentHandler;
+import org.camunda.bpm.engine.runtime.Incident;
+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.ProcessEngineRule;
+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.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IncidentMultipleProcessingTest {
+
+ private static final StubIncidentHandler JOB_HANDLER = new StubIncidentHandler(Incident.FAILED_JOB_HANDLER_TYPE);
+
+ @ClassRule
+ public static ProcessEngineBootstrapRule processEngineBootstrapRule = new ProcessEngineBootstrapRule(
+ configuration -> {
+ configuration.setCompositeIncidentHandlersEnabled(true);
+ configuration.setCustomIncidentHandlers(Collections.singletonList(JOB_HANDLER));
+ });
+ protected ProcessEngineRule engineRule = new ProvidedProcessEngineRule(processEngineBootstrapRule);
+ protected ProcessEngineTestRule testRule = new ProcessEngineTestRule(engineRule);
+
+ @Rule
+ public RuleChain ruleChain = RuleChain.outerRule(engineRule).around(testRule);
+
+ private RuntimeService runtimeService;
+ private ManagementService managementService;
+
+ @Before
+ public void init() {
+ JOB_HANDLER.reset();
+
+ runtimeService = engineRule.getRuntimeService();
+ managementService = engineRule.getManagementService();
+ }
+
+ @Test
+ public void jobHandlerShouldBeCompositeHandler() {
+ IncidentHandler incidentHandler = engineRule.getProcessEngineConfiguration().getIncidentHandler(Incident.FAILED_JOB_HANDLER_TYPE);
+
+ assertThat(incidentHandler).isNotNull();
+ assertThat(incidentHandler).isInstanceOf(CompositeIncidentHandler.class);
+ }
+
+ @Test
+ public void externalTaskHandlerShouldBeCompositeHandler() {
+ IncidentHandler incidentHandler = engineRule.getProcessEngineConfiguration().getIncidentHandler(Incident.EXTERNAL_TASK_HANDLER_TYPE);
+
+ assertThat(incidentHandler).isNotNull();
+ assertThat(incidentHandler).isInstanceOf(CompositeIncidentHandler.class);
+ }
+
+ @Deployment(resources = { "org/camunda/bpm/engine/test/api/mgmt/IncidentTest.testShouldCreateOneIncident.bpmn" })
+ @Test
+ public void shouldCreateOneIncident() {
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("failingProcess");
+
+ testRule.executeAvailableJobs();
+
+ List incidents = runtimeService.createIncidentQuery().processInstanceId(processInstance.getId()).list();
+
+ assertThat(incidents).hasSize(1);
+
+ assertThat(JOB_HANDLER.getCreateEvents()).hasSize(1);
+ assertThat(JOB_HANDLER.getResolveEvents()).isEmpty();
+ assertThat(JOB_HANDLER.getDeleteEvents()).isEmpty();
+ }
+
+ @Deployment(resources = { "org/camunda/bpm/engine/test/api/mgmt/IncidentTest.testShouldCreateOneIncident.bpmn" })
+ @Test
+ public void shouldResolveIncidentAfterJobRetriesRefresh() {
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("failingProcess");
+
+ testRule.executeAvailableJobs();
+
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ Incident incident = runtimeService.createIncidentQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(incident).isNotNull();
+
+ managementService.setJobRetries(job.getId(), 1);
+
+ incident = runtimeService.createIncidentQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(incident).isNull();
+
+ assertThat(JOB_HANDLER.getCreateEvents()).hasSize(1);
+ // incidents resolved when job retries update
+ assertThat(JOB_HANDLER.getResolveEvents()).hasSize(1);
+ assertThat(JOB_HANDLER.getDeleteEvents()).isEmpty();
+ }
+
+ @Deployment(resources = { "org/camunda/bpm/engine/test/api/mgmt/IncidentTest.testShouldCreateOneIncident.bpmn" })
+ @Test
+ public void shouldDeleteIncidentAfterJobHasBeenDeleted() {
+ // start failing process
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("failingProcess");
+
+ testRule.executeAvailableJobs();
+
+ // get the job
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ // there exists one incident to failed
+ Incident incident = runtimeService.createIncidentQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(incident).isNotNull();
+
+ // delete the job
+ managementService.deleteJob(job.getId());
+
+ // the incident has been deleted too.
+ incident = runtimeService.createIncidentQuery().incidentId(incident.getId()).singleResult();
+ assertThat(incident).isNull();
+
+ assertThat(JOB_HANDLER.getCreateEvents()).hasSize(1);
+ assertThat(JOB_HANDLER.getResolveEvents()).isEmpty();
+ assertThat(JOB_HANDLER.getDeleteEvents()).hasSize(1);
+ }
+
+ public static class StubIncidentHandler implements IncidentHandler {
+
+ private String incidentType;
+
+ private List createEvents = new ArrayList<>();
+ private List resolveEvents = new ArrayList<>();
+ private List deleteEvents = new ArrayList<>();
+
+ public StubIncidentHandler(String type) {
+ this.incidentType = type;
+ }
+
+ @Override
+ public String getIncidentHandlerType() {
+ return incidentType;
+ }
+
+ @Override
+ public Incident handleIncident(IncidentContext context, String message) {
+ createEvents.add(context);
+ return null;
+ }
+
+ @Override
+ public void resolveIncident(IncidentContext context) {
+ resolveEvents.add(context);
+ }
+
+ @Override
+ public void deleteIncident(IncidentContext context) {
+ deleteEvents.add(context);
+ }
+
+ public List getCreateEvents() {
+ return createEvents;
+ }
+
+ public List getResolveEvents() {
+ return resolveEvents;
+ }
+
+ public List getDeleteEvents() {
+ return deleteEvents;
+ }
+
+ public void reset() {
+ createEvents.clear();
+ resolveEvents.clear();
+ deleteEvents.clear();
+ }
+
+ }
+
+}