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:
0264408
2026-09-17 10:30:09 +08:00
co-authored by Cursor
parent 7d17157633
commit 0288a8b3dd
29 changed files with 1295 additions and 27 deletions
@@ -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