feat: add optional Spring REST adapter and catalog SPI
Expose OrdoEngine over conditionally registered HTTP endpoints in autoconfigure, keep JDBC wiring in spring.jdbc, and share definition JSON via ProcessDefinitionParser.toJson. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Host-registered keys for designers and REST dropdowns; not used by the engine at runtime. */
|
||||
public interface OrdoCatalog {
|
||||
List<Item> conditions();
|
||||
|
||||
List<Item> actions();
|
||||
|
||||
List<Item> assignees();
|
||||
|
||||
record Item(String key, String label) {
|
||||
}
|
||||
|
||||
static OrdoCatalog empty() {
|
||||
return new OrdoCatalog() {
|
||||
@Override
|
||||
public List<Item> conditions() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Item> actions() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Item> assignees() {
|
||||
return List.of();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -10,11 +11,13 @@ import java.time.DateTimeException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Parses a structural JSON process graph into a {@link ProcessDefinition}. */
|
||||
public final class ProcessDefinitionParser {
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper()
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
private ProcessDefinitionParser() {
|
||||
}
|
||||
@@ -28,6 +31,15 @@ public final class ProcessDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
public static String toJson(ProcessDefinition definition) {
|
||||
Objects.requireNonNull(definition, "definition must not be null");
|
||||
try {
|
||||
return MAPPER.writeValueAsString(fromDefinition(definition));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("invalid process definition json", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ProcessDefinition fromJson(InputStream json) {
|
||||
Objects.requireNonNull(json, "json must not be null");
|
||||
try {
|
||||
@@ -66,6 +78,27 @@ public final class ProcessDefinitionParser {
|
||||
return new ProcessDefinition(document.id(), document.name(), steps, transitions);
|
||||
}
|
||||
|
||||
private static DefinitionDocument fromDefinition(ProcessDefinition definition) {
|
||||
List<StepDocument> steps = new ArrayList<>(definition.steps().size());
|
||||
for (ApprovalStep step : definition.steps()) {
|
||||
DueDocument due = null;
|
||||
if (step.due() != null) {
|
||||
StepDue stepDue = step.due();
|
||||
due = new DueDocument(stepDue.after().toString(), stepDue.then().name().toLowerCase(Locale.ROOT),
|
||||
stepDue.to(), stepDue.action());
|
||||
}
|
||||
steps.add(new StepDocument(step.id(), step.name(), step.candidates(), step.policy(), step.kind(),
|
||||
step.actionKey(), due));
|
||||
}
|
||||
List<TransitionDocument> transitions = new ArrayList<>(definition.transitions().size());
|
||||
for (StepTransition transition : definition.transitions()) {
|
||||
transitions.add(new TransitionDocument(transition.fromStepId(), transition.toStepId(),
|
||||
transition.conditionKey(), transition.priority()));
|
||||
}
|
||||
return new DefinitionDocument(definition.id(), definition.name(), definition.version(),
|
||||
definition.steps().get(0).id(), steps, transitions);
|
||||
}
|
||||
|
||||
private static void rotateStartStep(List<ApprovalStep> steps, String startStepId) {
|
||||
if (startStepId == null || startStepId.isBlank()) {
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ProcessDefinitionParserTest {
|
||||
|
||||
@@ -37,8 +38,12 @@ class ProcessDefinitionParserTest {
|
||||
StepTransition.end("hr")
|
||||
));
|
||||
|
||||
assertEquals(expected, ProcessDefinitionParser.fromJson(LEAVE_REQUEST_JSON));
|
||||
assertEquals(0, ProcessDefinitionParser.fromJson(LEAVE_REQUEST_JSON).version());
|
||||
ProcessDefinition definition = ProcessDefinitionParser.fromJson(LEAVE_REQUEST_JSON);
|
||||
assertEquals(expected, definition);
|
||||
assertEquals(0, definition.version());
|
||||
ProcessDefinition roundTrip = ProcessDefinitionParser.fromJson(ProcessDefinitionParser.toJson(definition));
|
||||
assertTrue(definition.sameGraph(roundTrip));
|
||||
assertEquals("manager", roundTrip.steps().get(0).id());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user