From 7734b0ffe73fe4b697833f29e018a28dc724bfa8 Mon Sep 17 00:00:00 2001 From: Romain Bioteau Date: Fri, 15 Nov 2024 10:39:58 +0100 Subject: [PATCH] feat(failure): bpm failure tables (#3241) Introduce 2 new tables in bonita DB schema: bpm_failure and arch_bpm_failure. Add associated PersistedObject and Service to create new failure. Closes BPM-308 --- .../engine/bpm/failure/FlowNodeFailureIT.java | 81 +++++++++++++++++++ .../execution/work/ExecuteFlowNodeWork.java | 16 +++- .../work/NotifyChildFinishedWork.java | 14 +++- .../execution/work/SetInFailCallable.java | 18 ++++- .../engine/service/ServiceAccessor.java | 3 + .../service/impl/SpringServiceAccessor.java | 6 ++ .../src/main/resources/bonita-community.xml | 24 ++++++ .../work/ExecuteFlowNodeWorkTest.java | 5 +- .../work/NotifyChildFinishedWorkTest.java | 5 +- .../execution/work/SetInFailCallableTest.java | 17 +++- .../instance/api/BpmFailureService.java | 31 +++++++ .../api/exceptions/FailureContext.java | 44 ++++++++++ .../SFlowNodeExecutionException.java | 7 +- .../instance/impl/BpmFailureServiceImpl.java | 60 ++++++++++++++ .../process/instance/model/SABpmFailure.java | 70 ++++++++++++++++ .../process/instance/model/SBpmFailure.java | 50 ++++++++++++ .../impl/hibernate/failure.queries.hbm.xml | 12 +++ .../src/main/resources/sql/h2/cleanTables.sql | 2 + .../main/resources/sql/h2/createTables.sql | 34 ++++++++ .../src/main/resources/sql/h2/dropTables.sql | 4 +- .../src/main/resources/sql/h2/initTables.sql | 2 + .../resources/sql/postgres/cleanTables.sql | 2 + .../resources/sql/postgres/createTables.sql | 34 ++++++++ .../resources/sql/postgres/dropTables.sql | 4 +- .../resources/sql/postgres/initTables.sql | 2 + .../platform/setup/ScriptExecutorIT.java | 2 +- 26 files changed, 533 insertions(+), 16 deletions(-) create mode 100644 bonita-integration-tests/bonita-integration-tests-local/src/test/java/org/bonitasoft/engine/bpm/failure/FlowNodeFailureIT.java create mode 100644 bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/BpmFailureService.java create mode 100644 bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/FailureContext.java create mode 100644 bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/impl/BpmFailureServiceImpl.java create mode 100644 bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SABpmFailure.java create mode 100644 bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SBpmFailure.java create mode 100644 bpm/bonita-core/bonita-process-instance/src/main/resources/org/bonitasoft/engine/core/failure/model/impl/hibernate/failure.queries.hbm.xml diff --git a/bonita-integration-tests/bonita-integration-tests-local/src/test/java/org/bonitasoft/engine/bpm/failure/FlowNodeFailureIT.java b/bonita-integration-tests/bonita-integration-tests-local/src/test/java/org/bonitasoft/engine/bpm/failure/FlowNodeFailureIT.java new file mode 100644 index 00000000000..a84fd86350c --- /dev/null +++ b/bonita-integration-tests/bonita-integration-tests-local/src/test/java/org/bonitasoft/engine/bpm/failure/FlowNodeFailureIT.java @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2024 Bonitasoft S.A. + * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble + * This library is free software; you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation + * version 2.1 of the License. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth + * Floor, Boston, MA 02110-1301, USA. + **/ +package org.bonitasoft.engine.bpm.failure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +import org.bonitasoft.engine.TestWithUser; +import org.bonitasoft.engine.bpm.flownode.FlowNodeInstance; +import org.bonitasoft.engine.bpm.process.ActivationState; +import org.bonitasoft.engine.bpm.process.DesignProcessDefinition; +import org.bonitasoft.engine.bpm.process.ProcessDefinition; +import org.bonitasoft.engine.bpm.process.ProcessDeploymentInfo; +import org.bonitasoft.engine.bpm.process.ProcessInstance; +import org.bonitasoft.engine.bpm.process.impl.ProcessDefinitionBuilder; +import org.bonitasoft.engine.expression.ExpressionBuilder; +import org.bonitasoft.engine.operation.LeftOperandBuilder; +import org.bonitasoft.engine.operation.OperatorType; +import org.bonitasoft.engine.service.ServiceAccessor; +import org.bonitasoft.engine.service.impl.ServiceAccessorFactory; +import org.junit.Before; +import org.junit.Test; + +public class FlowNodeFailureIT extends TestWithUser { + + private ServiceAccessor serviceAccessor; + + @Override + @Before + public void before() throws Exception { + super.before(); + serviceAccessor = ServiceAccessorFactory.getInstance().createServiceAccessor(); + } + + @Test + public void create_a_failure_on_flownode_operation_exception() throws Exception { + // Given a process failing on a flownode operation + final DesignProcessDefinition designProcessDefinition = new ProcessDefinitionBuilder() + .createNewInstance("My_Process_with_failed_flownode", PROCESS_VERSION) + .addActor(ACTOR_NAME) + .addAutomaticTask("step1") + .addOperation(new LeftOperandBuilder().createDataLeftOperand("myData"), + OperatorType.ASSIGNMENT, + "", + new ExpressionBuilder() + .createGroovyScriptExpression("my-failing-script", + "throw new RuntimeException('Failed !')", String.class.getName())) + .getProcess(); + final ProcessDefinition processDefinition = deployAndEnableProcessWithActor(designProcessDefinition, ACTOR_NAME, + user); + final ProcessDeploymentInfo processDeploymentInfo = getProcessAPI() + .getProcessDeploymentInfo(processDefinition.getId()); + assertEquals(ActivationState.ENABLED, processDeploymentInfo.getActivationState()); + + // When the flownode is executed + final ProcessInstance processInstance = getProcessAPI().startProcess(processDeploymentInfo.getProcessId()); + final FlowNodeInstance failFlowNodeInstance = waitForFlowNodeInFailedState(processInstance); + + // Then a failure is created + assertEquals("step1", failFlowNodeInstance.getName()); + var failureService = ServiceAccessorFactory.getInstance().createServiceAccessor().getBpmFailureService(); + var failures = serviceAccessor.getTransactionService() + .executeInTransaction(() -> failureService.getFlowNodeFailures(failFlowNodeInstance.getId(), 5)); + assertThat(failures).hasSize(1); + assertThat(failures.get(0).getErrorMessage()) + .isEqualTo("java.lang.RuntimeException: Failed !"); + disableAndDeleteProcess(processDefinition); + } + +} diff --git a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWork.java b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWork.java index e21e5e70218..822d416662e 100644 --- a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWork.java +++ b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWork.java @@ -16,6 +16,8 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; +import org.bonitasoft.engine.core.process.instance.api.exceptions.FailureContext; import org.bonitasoft.engine.core.process.instance.api.exceptions.SFlowNodeNotFoundException; import org.bonitasoft.engine.core.process.instance.api.exceptions.SFlowNodeReadException; import org.bonitasoft.engine.core.process.instance.model.SFlowNodeInstance; @@ -59,6 +61,7 @@ public String getDescription() { public CompletableFuture work(final Map context) throws Exception { final ServiceAccessor serviceAccessor = getServiceAccessor(context); SFlowNodeInstance flowNodeInstance = retrieveAndVerifyFlowNodeInstance(serviceAccessor); + context.put("flowNodeInstance", flowNodeInstance); serviceAccessor.getFlowNodeExecutor().executeFlowNode(flowNodeInstance, null, null); return CompletableFuture.completedFuture(null); } @@ -98,7 +101,18 @@ public void handleFailure(final Throwable e, final Map context) FailedStateSetter failedStateSetter = new FailedStateSetter(waitingEventsInterrupter, serviceAccessor.getActivityInstanceService(), serviceAccessor.getFlowNodeStateManager()); - userTransactionService.executeInTransaction(new SetInFailCallable(failedStateSetter, flowNodeInstanceId)); + var flowNodeInstance = (SFlowNodeInstance) context.get("flowNodeInstance"); + userTransactionService.executeInTransaction(new SetInFailCallable(failedStateSetter, + flowNodeInstance, + serviceAccessor.getBpmFailureService(), + failureContext(e))); + } + + private FailureContext failureContext(Throwable e) { + if (e instanceof FailureContext failureContext) { + return failureContext; + } + return () -> new BpmFailureService.Failure(FailureContext.UNKNOWN_SCOPE, FailureContext.EMPTY_CONTEXT, e); } @Override diff --git a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWork.java b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWork.java index 9cab121b515..ef896b58eb6 100644 --- a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWork.java +++ b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWork.java @@ -19,6 +19,8 @@ import java.util.concurrent.CompletableFuture; import org.bonitasoft.engine.commons.exceptions.SBonitaException; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; +import org.bonitasoft.engine.core.process.instance.api.exceptions.FailureContext; import org.bonitasoft.engine.core.process.instance.api.exceptions.SFlowNodeNotFoundException; import org.bonitasoft.engine.core.process.instance.api.exceptions.SFlowNodeReadException; import org.bonitasoft.engine.core.process.instance.model.SFlowNodeInstance; @@ -68,6 +70,7 @@ public CompletableFuture work(final Map context) throws Ex Thread.currentThread().setContextClassLoader(processClassloader); ServiceAccessor serviceAccessor = getServiceAccessor(context); SFlowNodeInstance flowNodeInstance = retrieveAndVerifyFlowNodeInstance(serviceAccessor); + context.put("flowNodeInstance", flowNodeInstance); final ContainerRegistry containerRegistry = serviceAccessor.getContainerRegistry(); containerRegistry.nodeReachedState(flowNodeInstance); } finally { @@ -119,7 +122,16 @@ public void handleFailure(final Throwable e, final Map context) FailedStateSetter failedStateSetter = new FailedStateSetter(waitingEventsInterrupter, serviceAccessor.getActivityInstanceService(), serviceAccessor.getFlowNodeStateManager()); - userTransactionService.executeInTransaction(new SetInFailCallable(failedStateSetter, flowNodeInstanceId)); + SFlowNodeInstance flowNodeInstance = (SFlowNodeInstance) context.get("flowNodeInstance"); + userTransactionService.executeInTransaction(new SetInFailCallable(failedStateSetter, flowNodeInstance, + serviceAccessor.getBpmFailureService(), failureContext(e))); + } + + private FailureContext failureContext(Throwable e) { + if (e instanceof FailureContext failureContext) { + return failureContext; + } + return () -> new BpmFailureService.Failure(FailureContext.UNKNOWN_SCOPE, FailureContext.EMPTY_CONTEXT, e); } @Override diff --git a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/SetInFailCallable.java b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/SetInFailCallable.java index b6c28891e20..ed3417f4a4a 100644 --- a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/SetInFailCallable.java +++ b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/execution/work/SetInFailCallable.java @@ -15,22 +15,32 @@ import java.util.concurrent.Callable; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; +import org.bonitasoft.engine.core.process.instance.api.exceptions.FailureContext; +import org.bonitasoft.engine.core.process.instance.model.SFlowNodeInstance; + /** * @author Baptiste Mesta */ public class SetInFailCallable implements Callable { private final FailedStateSetter failedStateSetter; - private final long flowNodeInstanceId; + private final SFlowNodeInstance flowNodeInstance; + private final BpmFailureService bpmFailureService; + private final FailureContext failureContext; - SetInFailCallable(FailedStateSetter failedStateSetter, final long flowNodeInstanceId) { + SetInFailCallable(FailedStateSetter failedStateSetter, final SFlowNodeInstance flowNodeInstance, + BpmFailureService bpmFailureService, FailureContext failureContext) { this.failedStateSetter = failedStateSetter; - this.flowNodeInstanceId = flowNodeInstanceId; + this.flowNodeInstance = flowNodeInstance; + this.bpmFailureService = bpmFailureService; + this.failureContext = failureContext; } @Override public Void call() throws Exception { - failedStateSetter.setAsFailed(flowNodeInstanceId); + failedStateSetter.setAsFailed(flowNodeInstance.getId()); + bpmFailureService.createFlowNodeFailure(flowNodeInstance, failureContext.createFailure()); return null; } diff --git a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/ServiceAccessor.java b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/ServiceAccessor.java index c2b541d2a01..c099edbe902 100644 --- a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/ServiceAccessor.java +++ b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/ServiceAccessor.java @@ -45,6 +45,7 @@ import org.bonitasoft.engine.core.process.comment.api.SCommentService; import org.bonitasoft.engine.core.process.definition.ProcessDefinitionService; import org.bonitasoft.engine.core.process.instance.api.ActivityInstanceService; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; import org.bonitasoft.engine.core.process.instance.api.GatewayInstanceService; import org.bonitasoft.engine.core.process.instance.api.ProcessInstanceService; import org.bonitasoft.engine.core.process.instance.api.RefBusinessDataService; @@ -134,6 +135,8 @@ public interface ServiceAccessor { ActivityInstanceService getActivityInstanceService(); + BpmFailureService getBpmFailureService(); + BPMInstancesCreator getBPMInstancesCreator(); FlowNodeExecutor getFlowNodeExecutor(); diff --git a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/impl/SpringServiceAccessor.java b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/impl/SpringServiceAccessor.java index 7a55275d0b8..528749679e9 100644 --- a/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/impl/SpringServiceAccessor.java +++ b/bpm/bonita-core/bonita-process-engine/src/main/java/org/bonitasoft/engine/service/impl/SpringServiceAccessor.java @@ -46,6 +46,7 @@ import org.bonitasoft.engine.core.process.comment.api.SCommentService; import org.bonitasoft.engine.core.process.definition.ProcessDefinitionService; import org.bonitasoft.engine.core.process.instance.api.ActivityInstanceService; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; import org.bonitasoft.engine.core.process.instance.api.GatewayInstanceService; import org.bonitasoft.engine.core.process.instance.api.ProcessInstanceService; import org.bonitasoft.engine.core.process.instance.api.RefBusinessDataService; @@ -190,6 +191,11 @@ public ActivityInstanceService getActivityInstanceService() { return beanAccessor.getService(ActivityInstanceService.class); } + @Override + public BpmFailureService getBpmFailureService() { + return beanAccessor.getService(BpmFailureService.class); + } + @Override public BPMInstancesCreator getBPMInstancesCreator() { return beanAccessor.getService(BPMInstancesCreator.class); diff --git a/bpm/bonita-core/bonita-process-engine/src/main/resources/bonita-community.xml b/bpm/bonita-core/bonita-process-engine/src/main/resources/bonita-community.xml index 297c5da1bdb..1ba21205ae2 100644 --- a/bpm/bonita-core/bonita-process-engine/src/main/resources/bonita-community.xml +++ b/bpm/bonita-core/bonita-process-engine/src/main/resources/bonita-community.xml @@ -648,6 +648,26 @@ + + + + org.bonitasoft.engine.core.process.instance.model.SBpmFailure + + + + + + + + + org.bonitasoft.engine.core.process.instance.model.SABpmFailure + + + + + + + @@ -724,6 +744,8 @@ org/bonitasoft/engine/core/contract/data/model/impl/hibernate/contract.queries.hbm.xml org/bonitasoft/engine/parameter/parameter.queries.hbm.xml + + org/bonitasoft/engine/core/failure/model/impl/hibernate/failure.queries.hbm.xml @@ -1012,6 +1034,8 @@ org.bonitasoft.engine.core.process.instance.model.archive.business.data.SAProcessMultiRefBusinessDataInstance org.bonitasoft.engine.queriablelogger.model.SQueriableLog org.bonitasoft.engine.queriablelogger.model.SQueriableLogParameter + org.bonitasoft.engine.core.process.instance.model.SBpmFailure + org.bonitasoft.engine.core.process.instance.model.SABpmFailure diff --git a/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWorkTest.java b/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWorkTest.java index 35932040a0b..d8eadf2feda 100644 --- a/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWorkTest.java +++ b/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/ExecuteFlowNodeWorkTest.java @@ -16,7 +16,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.bonitasoft.engine.core.process.instance.api.ActivityInstanceService; @@ -54,7 +54,8 @@ public class ExecuteFlowNodeWorkTest { @Before public void before() throws Exception { - context = Collections.singletonMap(TenantAwareBonitaWork.SERVICE_ACCESSOR, serviceAccessor); + context = new HashMap<>(); + context.put(TenantAwareBonitaWork.SERVICE_ACCESSOR, serviceAccessor); doReturn(activityInstanceService).when(serviceAccessor).getActivityInstanceService(); doReturn(flowNodeExecutor).when(serviceAccessor).getFlowNodeExecutor(); sHumanTaskInstance = new SUserTaskInstance(); diff --git a/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWorkTest.java b/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWorkTest.java index b6b4c9f25ff..0e200458226 100644 --- a/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWorkTest.java +++ b/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/NotifyChildFinishedWorkTest.java @@ -16,7 +16,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.bonitasoft.engine.classloader.ClassLoaderService; @@ -58,7 +58,8 @@ public class NotifyChildFinishedWorkTest { @Before public void before() throws SClassLoaderException { doReturn(this.getClass().getClassLoader()).when(classLoaderService).getClassLoader(any()); - context = Collections.singletonMap(TenantAwareBonitaWork.SERVICE_ACCESSOR, serviceAccessor); + context = new HashMap<>(); + context.put(TenantAwareBonitaWork.SERVICE_ACCESSOR, serviceAccessor); doReturn(classLoaderService).when(serviceAccessor).getClassLoaderService(); doReturn(flowNodeInstanceService).when(serviceAccessor).getActivityInstanceService(); diff --git a/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/SetInFailCallableTest.java b/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/SetInFailCallableTest.java index 889a7e2b0f1..e42e6b7fcd5 100644 --- a/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/SetInFailCallableTest.java +++ b/bpm/bonita-core/bonita-process-engine/src/test/java/org/bonitasoft/engine/execution/work/SetInFailCallableTest.java @@ -14,7 +14,11 @@ package org.bonitasoft.engine.execution.work; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; +import org.bonitasoft.engine.core.process.instance.api.exceptions.FailureContext; +import org.bonitasoft.engine.core.process.instance.model.SFlowNodeInstance; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,15 +31,24 @@ public class SetInFailCallableTest { @Mock private FailedStateSetter failedStateSetter; - private final long FLOW_NODE_INSTANCE_ID = 15L; + @Mock + private SFlowNodeInstance flowNodeInstance; + + @Mock + private BpmFailureService failureService; + + @Mock + private FailureContext failureContext; public static final long PROCESS_DEFINITION_ID = 25L; + private static final long FLOW_NODE_INSTANCE_ID = 123L; private SetInFailCallable setInFailCallable; @Before public void setUp() throws Exception { - setInFailCallable = new SetInFailCallable(failedStateSetter, FLOW_NODE_INSTANCE_ID); + when(flowNodeInstance.getId()).thenReturn(FLOW_NODE_INSTANCE_ID); + setInFailCallable = new SetInFailCallable(failedStateSetter, flowNodeInstance, failureService, failureContext); } @Test diff --git a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/BpmFailureService.java b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/BpmFailureService.java new file mode 100644 index 00000000000..238dbb39a88 --- /dev/null +++ b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/BpmFailureService.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2024 Bonitasoft S.A. + * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble + * This library is free software; you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation + * version 2.1 of the License. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth + * Floor, Boston, MA 02110-1301, USA. + **/ +package org.bonitasoft.engine.core.process.instance.api; + +import java.util.List; + +import org.bonitasoft.engine.core.process.instance.model.SBpmFailure; +import org.bonitasoft.engine.core.process.instance.model.SFlowNodeInstance; +import org.bonitasoft.engine.persistence.SBonitaReadException; +import org.bonitasoft.engine.services.SPersistenceException; + +public interface BpmFailureService { + + void createFlowNodeFailure(SFlowNodeInstance flowNodeInstance, + Failure failure) throws SPersistenceException; + + List getFlowNodeFailures(long flowNodeInstanceId, int maxResults) throws SBonitaReadException; + + record Failure(String scope, String context, Throwable throwable){} +} diff --git a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/FailureContext.java b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/FailureContext.java new file mode 100644 index 00000000000..5a6e172fa4c --- /dev/null +++ b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/FailureContext.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2024 Bonitasoft S.A. + * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble + * This library is free software; you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation + * version 2.1 of the License. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth + * Floor, Boston, MA 02110-1301, USA. + **/ +package org.bonitasoft.engine.core.process.instance.api.exceptions; + +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; + +public interface FailureContext { + + static final String UNKNOWN_SCOPE = "UNKNOWN"; + static final String EMPTY_CONTEXT = ""; + + /** + * Describe the scope of the failure with a human-readable failure category. + * + * @return The scope of the failure + */ + default String getFailureScope() { + return UNKNOWN_SCOPE; + } + + /** + * Additional context information about the failure. The returned string is formatted using + * the following pattern: "context1//context2//context3" + * + * @return The context of the failure + */ + default String getFailureContext() { + return EMPTY_CONTEXT; + } + + BpmFailureService.Failure createFailure(); + +} diff --git a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/SFlowNodeExecutionException.java b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/SFlowNodeExecutionException.java index 95e81077417..e28f5c72943 100644 --- a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/SFlowNodeExecutionException.java +++ b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/api/exceptions/SFlowNodeExecutionException.java @@ -14,6 +14,7 @@ package org.bonitasoft.engine.core.process.instance.api.exceptions; import org.bonitasoft.engine.commons.exceptions.SBonitaException; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; /** * An unexpected error happened during the execution of the flow node @@ -21,7 +22,7 @@ * * @author Celine Souchet */ -public class SFlowNodeExecutionException extends SBonitaException { +public class SFlowNodeExecutionException extends SBonitaException implements FailureContext { private static final long serialVersionUID = 5549874111741638842L; @@ -37,4 +38,8 @@ public SFlowNodeExecutionException(final Throwable cause) { super(cause); } + @Override + public BpmFailureService.Failure createFailure() { + return new BpmFailureService.Failure(getFailureScope(), getFailureContext(), this); + } } diff --git a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/impl/BpmFailureServiceImpl.java b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/impl/BpmFailureServiceImpl.java new file mode 100644 index 00000000000..a3b1fc34b22 --- /dev/null +++ b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/impl/BpmFailureServiceImpl.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2024 Bonitasoft S.A. + * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble + * This library is free software; you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation + * version 2.1 of the License. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth + * Floor, Boston, MA 02110-1301, USA. + **/ +package org.bonitasoft.engine.core.process.instance.impl; + +import java.util.List; +import java.util.Map; + +import org.bonitasoft.engine.commons.ExceptionUtils; +import org.bonitasoft.engine.core.process.instance.api.BpmFailureService; +import org.bonitasoft.engine.core.process.instance.model.SBpmFailure; +import org.bonitasoft.engine.core.process.instance.model.SFlowNodeInstance; +import org.bonitasoft.engine.persistence.QueryOptions; +import org.bonitasoft.engine.persistence.SBonitaReadException; +import org.bonitasoft.engine.persistence.SelectListDescriptor; +import org.bonitasoft.engine.services.PersistenceService; +import org.bonitasoft.engine.services.SPersistenceException; +import org.springframework.stereotype.Service; + +@Service +public class BpmFailureServiceImpl implements BpmFailureService { + + private final PersistenceService persistenceService; + + public BpmFailureServiceImpl(PersistenceService persistenceService) { + this.persistenceService = persistenceService; + } + + @Override + public void createFlowNodeFailure(SFlowNodeInstance flowNodeInstance, Failure failure) + throws SPersistenceException { + persistenceService.insert(SBpmFailure.builder() + .flowNodeInstanceId(flowNodeInstance.getId()) + .processInstanceId(flowNodeInstance.getParentProcessInstanceId()) + .processDefinitionId(flowNodeInstance.getProcessDefinitionId()) + .scope(failure.scope()) + .context(failure.context()) + .errorMessage(ExceptionUtils.printRootCauseOnly(failure.throwable())) + .stackTrace(ExceptionUtils.printLightWeightStacktrace(failure.throwable(), 20)) + .build()); + } + + @Override + public List getFlowNodeFailures(long flowNodeInstanceId, int maxResults) throws SBonitaReadException { + final QueryOptions queryOptions = new QueryOptions(0, maxResults); + final Map parameters = Map.ofEntries(Map.entry("flowNodeInstanceId", flowNodeInstanceId)); + return persistenceService.selectList(new SelectListDescriptor("getFlowNodeFailure", parameters, + SBpmFailure.class, queryOptions)); + } +} diff --git a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SABpmFailure.java b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SABpmFailure.java new file mode 100644 index 00000000000..0468a5f2524 --- /dev/null +++ b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SABpmFailure.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2024 Bonitasoft S.A. + * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble + * This library is free software; you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation + * version 2.1 of the License. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth + * Floor, Boston, MA 02110-1301, USA. + **/ +package org.bonitasoft.engine.core.process.instance.model; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bonitasoft.engine.persistence.ArchivedPersistentObject; +import org.bonitasoft.engine.persistence.PersistentObject; +import org.hibernate.annotations.Type; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "arch_bpm_failure") +public class SABpmFailure implements ArchivedPersistentObject { + + @Id + private long id; + private long processDefinitionId; + private long processInstanceId; + private long flowNodeInstanceId; + private String scope; + private String context; + private String errorMessage; + @Type(type = "materialized_clob") + private String stackTrace; + private long failureDate; + private long archiveDate; + private long sourceObjectId; + + public SABpmFailure(final SBpmFailure bpmFailure) { + this.sourceObjectId = bpmFailure.getId(); + this.processDefinitionId = bpmFailure.getProcessDefinitionId(); + this.processInstanceId = bpmFailure.getProcessInstanceId(); + this.flowNodeInstanceId = bpmFailure.getFlowNodeInstanceId(); + this.scope = bpmFailure.getScope(); + this.context = bpmFailure.getContext(); + this.errorMessage = bpmFailure.getErrorMessage(); + this.stackTrace = bpmFailure.getStackTrace(); + } + + @Override + public Class getPersistentObjectInterface() { + return SBpmFailure.class; + } + + @Override + public void setTenantId(long id) { + // Ignore tenant id + } +} diff --git a/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SBpmFailure.java b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SBpmFailure.java new file mode 100644 index 00000000000..192c2ccc7a7 --- /dev/null +++ b/bpm/bonita-core/bonita-process-instance/src/main/java/org/bonitasoft/engine/core/process/instance/model/SBpmFailure.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2024 Bonitasoft S.A. + * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble + * This library is free software; you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation + * version 2.1 of the License. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License along with this + * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth + * Floor, Boston, MA 02110-1301, USA. + **/ +package org.bonitasoft.engine.core.process.instance.model; + +import java.time.Instant; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bonitasoft.engine.persistence.PlatformPersistentObject; +import org.hibernate.annotations.Type; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "bpm_failure") +public class SBpmFailure implements PlatformPersistentObject { + + @Id + private long id; + private long processDefinitionId; + private long processInstanceId; + private long flowNodeInstanceId; + private String scope; + private String context; + private String errorMessage; + @Type(type = "materialized_clob") + private String stackTrace; + @Builder.Default + private long failureDate = Instant.now().toEpochMilli(); + +} diff --git a/bpm/bonita-core/bonita-process-instance/src/main/resources/org/bonitasoft/engine/core/failure/model/impl/hibernate/failure.queries.hbm.xml b/bpm/bonita-core/bonita-process-instance/src/main/resources/org/bonitasoft/engine/core/failure/model/impl/hibernate/failure.queries.hbm.xml new file mode 100644 index 00000000000..df5213b6b59 --- /dev/null +++ b/bpm/bonita-core/bonita-process-instance/src/main/resources/org/bonitasoft/engine/core/failure/model/impl/hibernate/failure.queries.hbm.xml @@ -0,0 +1,12 @@ + + + + + + SELECT f + FROM org.bonitasoft.engine.core.process.instance.model.SBpmFailure AS f + WHERE f.flowNodeInstanceId = :flowNodeInstanceId + ORDER BY f.failureDate DESC + + + diff --git a/platform/platform-resources/src/main/resources/sql/h2/cleanTables.sql b/platform/platform-resources/src/main/resources/sql/h2/cleanTables.sql index 4a04ca2b484..07ee6e7ccc3 100644 --- a/platform/platform-resources/src/main/resources/sql/h2/cleanTables.sql +++ b/platform/platform-resources/src/main/resources/sql/h2/cleanTables.sql @@ -57,6 +57,8 @@ DELETE FROM platformCommand; DELETE FROM form_mapping; DELETE FROM page_mapping; DELETE FROM proc_parameter; +DELETE FROM arch_bpm_failure; +DELETE FROM bpm_failure; -- do NOT clear directly PLATFORM table, Hibernate needs to update its cache to know the platform has been deleted \ No newline at end of file diff --git a/platform/platform-resources/src/main/resources/sql/h2/createTables.sql b/platform/platform-resources/src/main/resources/sql/h2/createTables.sql index 50421277bb4..8093eabde1d 100644 --- a/platform/platform-resources/src/main/resources/sql/h2/createTables.sql +++ b/platform/platform-resources/src/main/resources/sql/h2/createTables.sql @@ -1045,3 +1045,37 @@ CREATE TABLE icon ( content LONGBLOB NOT NULL, CONSTRAINT pk_icon PRIMARY KEY (tenantId, id) ); + +CREATE TABLE bpm_failure ( + id BIGINT NOT NULL, + processDefinitionId BIGINT NOT NULL, + processInstanceId BIGINT NOT NULL, + flowNodeInstanceId BIGINT, + scope VARCHAR(255), + context VARCHAR(1024), + errorMessage VARCHAR(1024), + stackTrace TEXT, + failureDate BIGINT NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX idx_flownode_instance_id ON bpm_failure (flowNodeInstanceId); +CREATE INDEX idx_process_instance_id ON bpm_failure (processInstanceId); +CREATE INDEX idx_process_definition_id ON bpm_failure (processDefinitionId); + +CREATE TABLE arch_bpm_failure ( + id BIGINT NOT NULL, + processDefinitionId BIGINT NOT NULL, + processInstanceId BIGINT NOT NULL, + flowNodeInstanceId BIGINT, + scope VARCHAR(255), + context VARCHAR(1024), + errorMessage VARCHAR(1024), + stackTrace TEXT, + failureDate BIGINT NOT NULL, + archiveDate BIGINT NOT NULL, + sourceObjectId BIGINT NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX idx_arch_flownode_instance_id ON arch_bpm_failure (flowNodeInstanceId); +CREATE INDEX idx_arch_process_instance_id ON arch_bpm_failure (processInstanceId); +CREATE INDEX idx_arch_process_definition_id ON arch_bpm_failure (processDefinitionId); \ No newline at end of file diff --git a/platform/platform-resources/src/main/resources/sql/h2/dropTables.sql b/platform/platform-resources/src/main/resources/sql/h2/dropTables.sql index b5c9288d054..17bfc6931ee 100644 --- a/platform/platform-resources/src/main/resources/sql/h2/dropTables.sql +++ b/platform/platform-resources/src/main/resources/sql/h2/dropTables.sql @@ -65,4 +65,6 @@ DROP TABLE proc_parameter; DROP TABLE bar_resource; DROP TABLE temporary_content; DROP TABLE tenant_resource; -DROP TABLE icon; \ No newline at end of file +DROP TABLE icon; +DROP TABLE arch_bpm_failure; +DROP TABLE bpm_failure; \ No newline at end of file diff --git a/platform/platform-resources/src/main/resources/sql/h2/initTables.sql b/platform/platform-resources/src/main/resources/sql/h2/initTables.sql index 7e3d54500e5..8b1e59d825f 100644 --- a/platform/platform-resources/src/main/resources/sql/h2/initTables.sql +++ b/platform/platform-resources/src/main/resources/sql/h2/initTables.sql @@ -3,6 +3,8 @@ INSERT INTO sequence VALUES (-1, 2, 1); INSERT INTO sequence VALUES (-1, 3, 1); INSERT INTO sequence VALUES (-1, 4, 1); INSERT INTO sequence VALUES (-1, 5, 1); +INSERT INTO sequence VALUES (-1, 6, 1); +INSERT INTO sequence VALUES (-1, 7, 1); INSERT INTO sequence VALUES(1, 10, 1); INSERT INTO sequence VALUES(1, 11, 1); INSERT INTO sequence VALUES(1, 20, 1); diff --git a/platform/platform-resources/src/main/resources/sql/postgres/cleanTables.sql b/platform/platform-resources/src/main/resources/sql/postgres/cleanTables.sql index 7befb1b98b8..842da29f22b 100644 --- a/platform/platform-resources/src/main/resources/sql/postgres/cleanTables.sql +++ b/platform/platform-resources/src/main/resources/sql/postgres/cleanTables.sql @@ -56,5 +56,7 @@ DELETE FROM platformCommand; DELETE FROM form_mapping; DELETE FROM page_mapping; DELETE FROM proc_parameter; +DELETE FROM arch_bpm_failure; +DELETE FROM bpm_failure; -- do NOT clear directly PLATFORM table, Hibernate needs to update its cache to know the platform has been deleted \ No newline at end of file diff --git a/platform/platform-resources/src/main/resources/sql/postgres/createTables.sql b/platform/platform-resources/src/main/resources/sql/postgres/createTables.sql index 909a8d57413..27349a9cda1 100644 --- a/platform/platform-resources/src/main/resources/sql/postgres/createTables.sql +++ b/platform/platform-resources/src/main/resources/sql/postgres/createTables.sql @@ -1044,3 +1044,37 @@ CREATE TABLE icon ( content BYTEA NOT NULL, CONSTRAINT pk_icon PRIMARY KEY (tenantId, id) ); + +CREATE TABLE bpm_failure ( + id INT8 NOT NULL, + processDefinitionId INT8 NOT NULL, + processInstanceId INT8 NOT NULL, + flowNodeInstanceId INT8, + scope VARCHAR(255), + context VARCHAR(1024), + errorMessage VARCHAR(1024), + stackTrace TEXT, + failureDate INT8 NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX idx_flownode_instance_id ON bpm_failure (flowNodeInstanceId); +CREATE INDEX idx_process_instance_id ON bpm_failure (processInstanceId); +CREATE INDEX idx_process_definition_id ON bpm_failure (processDefinitionId); + +CREATE TABLE arch_bpm_failure ( + id INT8 NOT NULL, + processDefinitionId INT8 NOT NULL, + processInstanceId INT8 NOT NULL, + flowNodeInstanceId INT8, + scope VARCHAR(255), + context VARCHAR(1024), + errorMessage VARCHAR(1024), + stackTrace TEXT, + failureDate INT8 NOT NULL, + archiveDate INT8 NOT NULL, + sourceObjectId INT8 NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX idx_arch_flownode_instance_id ON arch_bpm_failure (flowNodeInstanceId); +CREATE INDEX idx_arch_process_instance_id ON arch_bpm_failure (processInstanceId); +CREATE INDEX idx_arch_process_definition_id ON arch_bpm_failure (processDefinitionId); \ No newline at end of file diff --git a/platform/platform-resources/src/main/resources/sql/postgres/dropTables.sql b/platform/platform-resources/src/main/resources/sql/postgres/dropTables.sql index b76bc8d063e..79da222373f 100644 --- a/platform/platform-resources/src/main/resources/sql/postgres/dropTables.sql +++ b/platform/platform-resources/src/main/resources/sql/postgres/dropTables.sql @@ -65,4 +65,6 @@ DROP TABLE IF EXISTS proc_parameter; DROP TABLE IF EXISTS bar_resource; DROP TABLE IF EXISTS temporary_content; DROP TABLE IF EXISTS tenant_resource; -DROP TABLE IF EXISTS icon; \ No newline at end of file +DROP TABLE IF EXISTS icon; +DROP TABLE IF EXISTS arch_bpm_failure; +DROP TABLE IF EXISTS bpm_failure; \ No newline at end of file diff --git a/platform/platform-resources/src/main/resources/sql/postgres/initTables.sql b/platform/platform-resources/src/main/resources/sql/postgres/initTables.sql index 7e3d54500e5..8b1e59d825f 100644 --- a/platform/platform-resources/src/main/resources/sql/postgres/initTables.sql +++ b/platform/platform-resources/src/main/resources/sql/postgres/initTables.sql @@ -3,6 +3,8 @@ INSERT INTO sequence VALUES (-1, 2, 1); INSERT INTO sequence VALUES (-1, 3, 1); INSERT INTO sequence VALUES (-1, 4, 1); INSERT INTO sequence VALUES (-1, 5, 1); +INSERT INTO sequence VALUES (-1, 6, 1); +INSERT INTO sequence VALUES (-1, 7, 1); INSERT INTO sequence VALUES(1, 10, 1); INSERT INTO sequence VALUES(1, 11, 1); INSERT INTO sequence VALUES(1, 20, 1); diff --git a/platform/platform-setup/src/test/java/org/bonitasoft/platform/setup/ScriptExecutorIT.java b/platform/platform-setup/src/test/java/org/bonitasoft/platform/setup/ScriptExecutorIT.java index 94027a6ac71..848e038a93b 100644 --- a/platform/platform-setup/src/test/java/org/bonitasoft/platform/setup/ScriptExecutorIT.java +++ b/platform/platform-setup/src/test/java/org/bonitasoft/platform/setup/ScriptExecutorIT.java @@ -85,7 +85,7 @@ private void removeSetupFolderProperty() { public void should_be_able_to_create_platform_tables() throws Exception { scriptExecutor.createAndInitializePlatformIfNecessary(); final Integer sequences = jdbcTemplate.queryForObject("select count(*) from sequence", Integer.class); - assertThat(sequences).isEqualTo(65); + assertThat(sequences).isEqualTo(67); final int platformRows = JdbcTestUtils.countRowsInTable(jdbcTemplate, "platform"); assertThat(platformRows).isEqualTo(1);