feat: replace process definition graphs when no instance is running
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import com.jetlumen.ordo.api.TaskAction;
|
||||
import com.jetlumen.ordo.api.TaskStatus;
|
||||
import com.jetlumen.ordo.api.TransactionExecutor;
|
||||
import com.jetlumen.ordo.api.exception.DefinitionAlreadyExistsException;
|
||||
import com.jetlumen.ordo.api.exception.DefinitionInUseException;
|
||||
import com.jetlumen.ordo.api.exception.DefinitionNotFoundException;
|
||||
import com.jetlumen.ordo.api.exception.NoRouteFoundException;
|
||||
import com.jetlumen.ordo.api.exception.TaskAlreadyCompletedException;
|
||||
@@ -73,6 +74,18 @@ public final class DefaultOrdoEngine implements OrdoEngine {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void replace(ProcessDefinition definition) {
|
||||
Objects.requireNonNull(definition, "definition must not be null");
|
||||
transactionExecutor.execute(() -> {
|
||||
if (instanceRepository.existsRunning(definition.id())) {
|
||||
throw new DefinitionInUseException(definition.id());
|
||||
}
|
||||
definitionRepository.upsert(definition);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ProcessInstance start(String definitionId, String initiator, ProcessContext context) {
|
||||
requireText(initiator, "initiator");
|
||||
|
||||
@@ -55,6 +55,11 @@ public final class InMemoryOrdoEngine implements OrdoEngine {
|
||||
delegate.register(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replace(ProcessDefinition definition) {
|
||||
delegate.replace(definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessInstance start(String definitionId, String initiator, ProcessContext context) {
|
||||
return delegate.start(definitionId, initiator, context);
|
||||
|
||||
+5
@@ -16,6 +16,11 @@ public final class InMemoryProcessDefinitionRepository implements ProcessDefinit
|
||||
return definitions.putIfAbsent(definition.id(), definition) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void upsert(ProcessDefinition definition) {
|
||||
definitions.put(definition.id(), definition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Optional<ProcessDefinition> findById(String definitionId) {
|
||||
return Optional.ofNullable(definitions.get(definitionId));
|
||||
|
||||
+8
@@ -1,6 +1,7 @@
|
||||
package com.jetlumen.ordo.core.repository;
|
||||
|
||||
import com.jetlumen.ordo.api.ProcessInstance;
|
||||
import com.jetlumen.ordo.api.ProcessStatus;
|
||||
import com.jetlumen.ordo.api.repository.ProcessInstanceRepository;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -25,4 +26,11 @@ public final class InMemoryProcessInstanceRepository implements ProcessInstanceR
|
||||
public synchronized Optional<ProcessInstance> findById(String instanceId) {
|
||||
return Optional.ofNullable(instances.get(instanceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean existsRunning(String definitionId) {
|
||||
return instances.values().stream()
|
||||
.anyMatch(instance -> instance.definitionId().equals(definitionId)
|
||||
&& instance.status() == ProcessStatus.RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.jetlumen.ordo.api.RoutingCondition;
|
||||
import com.jetlumen.ordo.api.StepTransition;
|
||||
import com.jetlumen.ordo.api.TaskStatus;
|
||||
import com.jetlumen.ordo.api.exception.DefinitionAlreadyExistsException;
|
||||
import com.jetlumen.ordo.api.exception.DefinitionInUseException;
|
||||
import com.jetlumen.ordo.api.exception.DefinitionNotFoundException;
|
||||
import com.jetlumen.ordo.api.exception.NoRouteFoundException;
|
||||
import com.jetlumen.ordo.api.exception.TaskAlreadyCompletedException;
|
||||
@@ -86,6 +87,46 @@ class InMemoryOrdoEngineTest {
|
||||
"leave", "Another leave request", List.of(ApprovalStep.single("lead", "Lead approval", "lee")))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceInsertsWhenTheDefinitionIsMissing() {
|
||||
InMemoryOrdoEngine empty = new InMemoryOrdoEngine();
|
||||
empty.replace(ProcessDefinition.linear("expense", "Expense request", List.of(
|
||||
ApprovalStep.single("director", "Director approval", "diana"))));
|
||||
|
||||
var instance = empty.start("expense", "alice");
|
||||
assertEquals("diana", empty.findTasks(instance.id()).getFirst().assignee());
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceSwapsTheGraphWhenNoInstanceIsRunning() {
|
||||
engine.replace(ProcessDefinition.linear("leave", "Leave request v2", List.of(
|
||||
ApprovalStep.single("director", "Director approval", "diana"))));
|
||||
|
||||
var instance = engine.start("leave", "alice");
|
||||
ApprovalTask task = engine.findTasks(instance.id()).getFirst();
|
||||
assertEquals("director", task.stepId());
|
||||
assertEquals("diana", task.assignee());
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceIsRejectedWhileAnInstanceIsRunning() {
|
||||
engine.start("leave", "alice");
|
||||
assertThrows(DefinitionInUseException.class, () -> engine.replace(ProcessDefinition.linear(
|
||||
"leave", "Leave request v2", List.of(ApprovalStep.single("director", "Director approval", "diana")))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceSucceedsAfterInstancesReachATerminalStatus() {
|
||||
var instance = engine.start("leave", "alice");
|
||||
engine.reject(engine.findTasks(instance.id()).getFirst().id(), "maria");
|
||||
|
||||
engine.replace(ProcessDefinition.linear("leave", "Leave request v2", List.of(
|
||||
ApprovalStep.single("director", "Director approval", "diana"))));
|
||||
|
||||
var next = engine.start("leave", "bob");
|
||||
assertEquals("diana", engine.findTasks(next.id()).getFirst().assignee());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsBlankRuntimeArguments() {
|
||||
assertThrows(IllegalArgumentException.class, () -> engine.start("leave", " "));
|
||||
|
||||
Reference in New Issue
Block a user