Skip to content

Commit

Permalink
feat(failure): bpm failure tables (#3241)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rbioteau authored Nov 15, 2024
1 parent 8120f03 commit 7734b0f
Show file tree
Hide file tree
Showing 26 changed files with 533 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,6 +61,7 @@ public String getDescription() {
public CompletableFuture<Void> work(final Map<String, Object> 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);
}
Expand Down Expand Up @@ -98,7 +101,18 @@ public void handleFailure(final Throwable e, final Map<String, Object> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,6 +70,7 @@ public CompletableFuture<Void> work(final Map<String, Object> 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 {
Expand Down Expand Up @@ -119,7 +122,16 @@ public void handleFailure(final Throwable e, final Map<String, Object> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void> {

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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -134,6 +135,8 @@ public interface ServiceAccessor {

ActivityInstanceService getActivityInstanceService();

BpmFailureService getBpmFailureService();

BPMInstancesCreator getBPMInstancesCreator();

FlowNodeExecutor getFlowNodeExecutor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,26 @@
<constructor-arg name="sequenceId" value="5" />
<constructor-arg name="rangeSize" value="${bonita.platform.sequence.5:${bonita.platform.sequence.defaultRangeSize}}" />
</bean>
<bean class="org.bonitasoft.engine.sequence.SequenceMapping">
<constructor-arg name="classNames">
<set>
<value>org.bonitasoft.engine.core.process.instance.model.SBpmFailure</value>
</set>
</constructor-arg>
<constructor-arg name="sequenceId" value="6" />
<constructor-arg name="rangeSize" value="${bonita.platform.sequence.defaultRangeSize}" />
</bean>
<bean class="org.bonitasoft.engine.sequence.SequenceMapping">
<constructor-arg name="classNames">
<set>
<value>org.bonitasoft.engine.core.process.instance.model.SABpmFailure</value>
</set>
</constructor-arg>
<constructor-arg name="sequenceId" value="7" />
<constructor-arg name="rangeSize" value="${bonita.platform.sequence.defaultRangeSize}" />
</bean>



<bean name="sequenceManager" class="org.bonitasoft.engine.sequence.SequenceManagerImpl">
<constructor-arg name="sequenceMappingProvider" ref="sequenceMappingProvider" />
Expand Down Expand Up @@ -724,6 +744,8 @@
<value>org/bonitasoft/engine/core/contract/data/model/impl/hibernate/contract.queries.hbm.xml</value>
<!-- parameters -->
<value>org/bonitasoft/engine/parameter/parameter.queries.hbm.xml</value>
<!-- BPM Failures -->
<value>org/bonitasoft/engine/core/failure/model/impl/hibernate/failure.queries.hbm.xml</value>
</set>
</property>
<property name="classAliasMappings">
Expand Down Expand Up @@ -1012,6 +1034,8 @@
<value>org.bonitasoft.engine.core.process.instance.model.archive.business.data.SAProcessMultiRefBusinessDataInstance</value>
<value>org.bonitasoft.engine.queriablelogger.model.SQueriableLog</value>
<value>org.bonitasoft.engine.queriablelogger.model.SQueriableLogParameter</value>
<value>org.bonitasoft.engine.core.process.instance.model.SBpmFailure</value>
<value>org.bonitasoft.engine.core.process.instance.model.SABpmFailure</value>
</set>
</property>
</bean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SBpmFailure> getFlowNodeFailures(long flowNodeInstanceId, int maxResults) throws SBonitaReadException;

record Failure(String scope, String context, Throwable throwable){}
}
Loading

0 comments on commit 7734b0f

Please sign in to comment.