Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(engine): add operation log for sync delete process instance #4028

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@

import static org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull;

import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.camunda.bpm.engine.BadUserRequestException;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.exception.NotFoundException;
Expand All @@ -31,6 +31,7 @@
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.impl.context.Context;
import org.camunda.bpm.engine.impl.history.HistoryLevel;
import org.camunda.bpm.engine.impl.history.SynchronousOperationLogProducer;
import org.camunda.bpm.engine.impl.history.event.HistoryEvent;
import org.camunda.bpm.engine.impl.history.event.HistoryEventProcessor;
import org.camunda.bpm.engine.impl.history.event.HistoryEventTypes;
Expand All @@ -47,7 +48,7 @@
* Provide common logic for process instance deletion operations.
* Permissions checking and single process instance removal included.
*/
public abstract class AbstractDeleteProcessInstanceCmd {
public abstract class AbstractDeleteProcessInstanceCmd implements SynchronousOperationLogProducer<ProcessInstance>{

protected boolean externallyTerminated;
protected String deleteReason;
Expand All @@ -56,17 +57,21 @@ public abstract class AbstractDeleteProcessInstanceCmd {
protected boolean failIfNotExists = true;
protected boolean skipIoMappings;

protected List<ProcessInstance> deletedInstances = new ArrayList<>();

protected void checkDeleteProcessInstance(ExecutionEntity execution, CommandContext commandContext) {
for (CommandChecker checker : commandContext.getProcessEngineConfiguration().getCommandCheckers()) {
checker.checkDeleteProcessInstance(execution);
}
}

protected void deleteProcessInstances(final CommandContext commandContext, List<String> processInstanceIds) {
processInstanceIds.forEach(processInstance -> deleteProcessInstance(commandContext, processInstance));
processInstanceIds.forEach(processInstance -> deleteProcessInstance(commandContext, processInstance, false));
// create user operation log
produceOperationLog(commandContext, deletedInstances);
}

protected void deleteProcessInstance(final CommandContext commandContext, String processInstanceId) {
protected void deleteProcessInstance(final CommandContext commandContext, String processInstanceId, boolean writeOpLogImmediately) {
ensureNotNull(BadUserRequestException.class, "processInstanceId is null", "processInstanceId", processInstanceId);

// fetch process instance
Expand Down Expand Up @@ -107,9 +112,11 @@ protected void deleteProcessInstance(final CommandContext commandContext, String
}

// create user operation log
commandContext.getOperationLogManager()
.logProcessInstanceOperation(UserOperationLogEntry.OPERATION_TYPE_DELETE, processInstanceId,
null, null, Collections.singletonList(PropertyChange.EMPTY_CHANGE));
if(writeOpLogImmediately) {
produceOperationLog(commandContext, List.of(execution));
} else {
deletedInstances.add(execution);
}
}

public void triggerHistoryEvent(List<ProcessInstance> subProcesslist) {
Expand All @@ -131,4 +138,31 @@ public HistoryEvent createHistoryEvent(HistoryEventProducer producer) {
}
}

@Override
public Map<ProcessInstance, List<PropertyChange>> getPropChangesForOperation(List<ProcessInstance> results) {
return null;
Comment on lines +142 to +143
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Depending on how often this will be just return null, this could be a default method in the interface.

}

@Override
public List<PropertyChange> getSummarizingPropChangesForOperation(List<ProcessInstance> results) {
ArrayList<PropertyChange> propChanges = new ArrayList<>();
propChanges.add(new PropertyChange("nrOfInstances", null, results.size()));
return propChanges;
}

@Override
public void createOperationLogEntry(CommandContext commandContext, ProcessInstance result,
List<PropertyChange> propChanges, boolean isSummary) {

String processInstanceId = null;
String processDefinitionId = null;
if (!isSummary) {
processInstanceId = result.getId();
processDefinitionId = result.getProcessDefinitionId();
}

commandContext.getOperationLogManager()
.logProcessInstanceOperation(UserOperationLogEntry.OPERATION_TYPE_DELETE, processInstanceId,
processDefinitionId, null, propChanges);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public DeleteProcessInstanceCmd(String processInstanceId, String deleteReason, b
}

public Void execute(CommandContext commandContext) {
deleteProcessInstance(commandContext, processInstanceId);
deleteProcessInstance(commandContext, processInstanceId, true);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* 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.test.api.runtime;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

import java.util.List;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.ProcessEngineConfiguration;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.history.UserOperationLogEntry;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.test.ProcessEngineRule;
import org.camunda.bpm.engine.test.RequiredHistoryLevel;
import org.camunda.bpm.engine.test.util.ProcessEngineTestRule;
import org.camunda.bpm.engine.test.util.ProvidedProcessEngineRule;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;

@RequiredHistoryLevel(ProcessEngineConfiguration.HISTORY_FULL)
public class DeleteProcessInstanceUserOperationLogTest {

private static final String PROPERTY = "property";
private static final String NEW_VALUE = "newValue";
private static final String PROCESS_INSTANCE_ID = "processInstanceId";
private static final String PROCESS_DEFINITION_ID = "processDefinitionId";

protected ProcessEngineRule engineRule = new ProvidedProcessEngineRule();
protected ProcessEngineTestRule testRule = new ProcessEngineTestRule(engineRule);

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

protected ProcessEngineConfigurationImpl processEngineConfiguration;
protected RuntimeService runtimeService;
protected HistoryService historyService;
protected IdentityService identityService;
protected RepositoryService repositoryService;

protected Long defaultLogEntriesPerSyncOperationLimit;

protected BpmnModelInstance instance;
protected Deployment deployment;

@Before
public void setup() {
processEngineConfiguration = engineRule.getProcessEngineConfiguration();
runtimeService = engineRule.getRuntimeService();
historyService = engineRule.getHistoryService();
repositoryService = engineRule.getRepositoryService();
identityService = engineRule.getIdentityService();
defaultLogEntriesPerSyncOperationLimit = processEngineConfiguration.getLogEntriesPerSyncOperationLimit();

this.instance = Bpmn.createExecutableProcess("process1")
.startEvent("start")
.userTask("user1")
.sequenceFlowId("seq")
.userTask("user2")
.endEvent("end")
.done();
}

@After
public void tearDown() {
processEngineConfiguration.setLogEntriesPerSyncOperationLimit(defaultLogEntriesPerSyncOperationLimit);
}

@Test
public void shouldProduceSingleOperationLog() {
// given
processEngineConfiguration.setLogEntriesPerSyncOperationLimit(1);
testRule.deploy(instance);
engineRule.getIdentityService().setAuthenticatedUserId("userId");
ProcessInstance instance1 = runtimeService.startProcessInstanceByKey("process1");

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

// then
UserOperationLogEntry logEntry = historyService.createUserOperationLogQuery().operationType(UserOperationLogEntry.OPERATION_TYPE_DELETE).singleResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Widescreen monitor ftw. You could add some line breaks 😀 (Below too.)

Suggested change
UserOperationLogEntry logEntry = historyService.createUserOperationLogQuery().operationType(UserOperationLogEntry.OPERATION_TYPE_DELETE).singleResult();
UserOperationLogEntry logEntry = historyService.createUserOperationLogQuery()
.operationType(UserOperationLogEntry.OPERATION_TYPE_DELETE)
.singleResult();

assertThat(logEntry.getProcessInstanceId()).isEqualTo(instance1.getId());
assertThat(logEntry.getProcessDefinitionId()).isEqualTo(instance1.getProcessDefinitionId());
assertThat(logEntry.getProperty()).isNull();
assertThat(logEntry.getNewValue()).isNull();
}

@Test
public void shouldProduceMultipleOperationLogs() {
// given
processEngineConfiguration.setLogEntriesPerSyncOperationLimit(3);
testRule.deploy(instance);
engineRule.getIdentityService().setAuthenticatedUserId("userId");
ProcessInstance instance1 = runtimeService.startProcessInstanceByKey("process1");
ProcessInstance instance2 = runtimeService.startProcessInstanceByKey("process1");
ProcessInstance instance3 = runtimeService.startProcessInstanceByKey("process1");

// when
runtimeService.deleteProcessInstances(List.of(instance1.getId(), instance2.getId(), instance3.getId()), null, false, true);

// then
List<UserOperationLogEntry> logs = historyService.createUserOperationLogQuery().operationType(UserOperationLogEntry.OPERATION_TYPE_DELETE).list();
assertThat(logs).hasSize(3);
assertThat(logs)
.extracting(PROPERTY, NEW_VALUE, PROCESS_INSTANCE_ID, PROCESS_DEFINITION_ID)
.containsExactlyInAnyOrder(
tuple(null, null, instance1.getProcessInstanceId(), instance1.getProcessDefinitionId()),
tuple(null, null, instance2.getProcessInstanceId(), instance2.getProcessDefinitionId()),
tuple(null, null, instance3.getProcessInstanceId(), instance3.getProcessDefinitionId()));
}

@Test
public void shouldProduceSummaryOperationLog() {
// given
processEngineConfiguration.setLogEntriesPerSyncOperationLimit(1);
testRule.deploy(instance);
engineRule.getIdentityService().setAuthenticatedUserId("userId");
ProcessInstance instance1 = runtimeService.startProcessInstanceByKey("process1");
ProcessInstance instance2 = runtimeService.startProcessInstanceByKey("process1");
ProcessInstance instance3 = runtimeService.startProcessInstanceByKey("process1");

// when
runtimeService.deleteProcessInstances(List.of(instance1.getId(), instance2.getId(), instance3.getId()), null, false, true);

// then
UserOperationLogEntry logEntry = historyService.createUserOperationLogQuery().operationType(UserOperationLogEntry.OPERATION_TYPE_DELETE).singleResult();
assertThat(logEntry.getProcessInstanceId()).isNull();
assertThat(logEntry.getProcessDefinitionId()).isNull();
assertThat(logEntry.getProperty()).isEqualTo("nrOfInstances");
assertThat(logEntry.getNewValue()).isEqualTo("3");
}
}