From 20c8e1784303b11ca9dd124c5fd975a62552bc86 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:02:47 +0200 Subject: [PATCH] feat: add server-side ExecutionChunk and FlatProcessorRecord DTOs Co-Authored-By: Claude Opus 4.6 (1M context) --- cameleer3-server-core/pom.xml | 5 + .../core/storage/model/ExecutionChunk.java | 42 +++++++ .../storage/model/FlatProcessorRecord.java | 42 +++++++ .../ExecutionChunkDeserializationTest.java | 109 ++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/ExecutionChunk.java create mode 100644 cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/FlatProcessorRecord.java create mode 100644 cameleer3-server-core/src/test/java/com/cameleer3/server/core/storage/model/ExecutionChunkDeserializationTest.java diff --git a/cameleer3-server-core/pom.xml b/cameleer3-server-core/pom.xml index 5e2e517c..fcc2542d 100644 --- a/cameleer3-server-core/pom.xml +++ b/cameleer3-server-core/pom.xml @@ -37,6 +37,11 @@ spring-security-core provided + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + test + org.junit.jupiter junit-jupiter diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/ExecutionChunk.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/ExecutionChunk.java new file mode 100644 index 00000000..20066f09 --- /dev/null +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/ExecutionChunk.java @@ -0,0 +1,42 @@ +package com.cameleer3.server.core.storage.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +/** + * Chunk document: exchange envelope + list of FlatProcessorRecords. + * Mirrors cameleer3-common ExecutionChunk — replace with common lib import when available. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ExecutionChunk( + String exchangeId, + String applicationName, + String agentId, + String routeId, + String correlationId, + String status, + Instant startTime, + Instant endTime, + Long durationMs, + String engineLevel, + String errorMessage, + String errorStackTrace, + String errorType, + String errorCategory, + String rootCauseType, + String rootCauseMessage, + Map attributes, + String traceId, + String spanId, + String originalExchangeId, + String replayExchangeId, + int chunkSeq, + @JsonProperty("final") boolean isFinal, + List processors +) {} diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/FlatProcessorRecord.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/FlatProcessorRecord.java new file mode 100644 index 00000000..deb89221 --- /dev/null +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/FlatProcessorRecord.java @@ -0,0 +1,42 @@ +package com.cameleer3.server.core.storage.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.time.Instant; +import java.util.Map; + +/** + * Flat processor execution record with seq/parentSeq for tree reconstruction. + * Mirrors cameleer3-common FlatProcessorRecord — replace with common lib import when available. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public record FlatProcessorRecord( + int seq, + Integer parentSeq, + String parentProcessorId, + String processorId, + String processorType, + Integer iteration, + Integer iterationSize, + String status, + Instant startTime, + long durationMs, + String resolvedEndpointUri, + String inputBody, + String outputBody, + Map inputHeaders, + Map outputHeaders, + String errorMessage, + String errorStackTrace, + String errorType, + String errorCategory, + String rootCauseType, + String rootCauseMessage, + Map attributes, + String circuitBreakerState, + Boolean fallbackTriggered, + Boolean filterMatched, + Boolean duplicateMessage +) {} diff --git a/cameleer3-server-core/src/test/java/com/cameleer3/server/core/storage/model/ExecutionChunkDeserializationTest.java b/cameleer3-server-core/src/test/java/com/cameleer3/server/core/storage/model/ExecutionChunkDeserializationTest.java new file mode 100644 index 00000000..de0a50cf --- /dev/null +++ b/cameleer3-server-core/src/test/java/com/cameleer3/server/core/storage/model/ExecutionChunkDeserializationTest.java @@ -0,0 +1,109 @@ +package com.cameleer3.server.core.storage.model; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class ExecutionChunkDeserializationTest { + + private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()); + + @Test + void roundTrip_fullChunk() throws Exception { + ExecutionChunk chunk = new ExecutionChunk( + "ex-1", "order-service", "pod-1", "order-route", + "corr-1", "COMPLETED", + Instant.parse("2026-03-31T10:00:00Z"), + Instant.parse("2026-03-31T10:00:01Z"), 1000L, + "REGULAR", + null, null, null, null, null, null, + Map.of("orderId", "ORD-1"), + "trace-1", "span-1", null, null, + 2, true, + List.of(new FlatProcessorRecord( + 1, null, null, "log1", "log", + null, null, "COMPLETED", + Instant.parse("2026-03-31T10:00:00.100Z"), 5L, + null, "body", null, null, null, + null, null, null, null, null, null, + null, null, null, null, null))); + + String json = MAPPER.writeValueAsString(chunk); + ExecutionChunk deserialized = MAPPER.readValue(json, ExecutionChunk.class); + + assertThat(deserialized.exchangeId()).isEqualTo("ex-1"); + assertThat(deserialized.isFinal()).isTrue(); + assertThat(deserialized.chunkSeq()).isEqualTo(2); + assertThat(deserialized.processors()).hasSize(1); + assertThat(deserialized.processors().get(0).seq()).isEqualTo(1); + assertThat(deserialized.processors().get(0).processorId()).isEqualTo("log1"); + assertThat(deserialized.attributes()).containsEntry("orderId", "ORD-1"); + } + + @Test + void roundTrip_runningChunkWithIterations() throws Exception { + ExecutionChunk chunk = new ExecutionChunk( + "ex-2", "app", "agent-1", "route-1", + "ex-2", "RUNNING", + Instant.parse("2026-03-31T10:00:00Z"), + null, null, "REGULAR", + null, null, null, null, null, null, + null, null, null, null, null, + 0, false, + List.of( + new FlatProcessorRecord( + 1, null, null, "split1", "split", + null, 3, "COMPLETED", + Instant.parse("2026-03-31T10:00:00Z"), 100L, + null, null, null, null, null, + null, null, null, null, null, null, + null, null, null, null, null), + new FlatProcessorRecord( + 2, 1, "split1", "log1", "log", + 0, null, "COMPLETED", + Instant.parse("2026-03-31T10:00:00Z"), 5L, + null, "item-0", null, null, null, + null, null, null, null, null, null, + null, null, null, null, null), + new FlatProcessorRecord( + 3, 1, "split1", "log1", "log", + 1, null, "COMPLETED", + Instant.parse("2026-03-31T10:00:00Z"), 5L, + null, "item-1", null, null, null, + null, null, null, null, null, null, + null, null, null, null, null))); + + String json = MAPPER.writeValueAsString(chunk); + ExecutionChunk deserialized = MAPPER.readValue(json, ExecutionChunk.class); + + assertThat(deserialized.isFinal()).isFalse(); + assertThat(deserialized.processors()).hasSize(3); + + FlatProcessorRecord split = deserialized.processors().get(0); + assertThat(split.iterationSize()).isEqualTo(3); + assertThat(split.parentSeq()).isNull(); + + FlatProcessorRecord child0 = deserialized.processors().get(1); + assertThat(child0.parentSeq()).isEqualTo(1); + assertThat(child0.parentProcessorId()).isEqualTo("split1"); + assertThat(child0.iteration()).isEqualTo(0); + } + + @Test + void deserialize_unknownFieldsIgnored() throws Exception { + String json = """ + {"exchangeId":"ex-1","routeId":"r1","status":"COMPLETED", + "startTime":"2026-03-31T10:00:00Z","chunkSeq":0,"final":true, + "futureField":"ignored","processors":[]} + """; + ExecutionChunk chunk = MAPPER.readValue(json, ExecutionChunk.class); + assertThat(chunk.exchangeId()).isEqualTo("ex-1"); + assertThat(chunk.isFinal()).isTrue(); + } +}