From cf6c4bd60cf4180f7a2dd0bab6f4078b48135f94 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:34:45 +0100 Subject: [PATCH] feat: add snapshot-by-processorId endpoint for robust processor lookup Add GET /executions/{id}/processors/by-id/{processorId}/snapshot endpoint that fetches processor snapshot data by processorId instead of positional index, which is fragile when the tree structure changes. The existing index-based endpoint remains unchanged for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../server/app/controller/DetailController.java | 14 +++++++++++++- .../server/app/storage/PostgresExecutionStore.java | 7 +++++++ .../server/core/detail/DetailService.java | 12 ++++++++++++ .../server/core/storage/ExecutionStore.java | 2 ++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java index 2bd6ea55..dc705523 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java @@ -49,7 +49,7 @@ public class DetailController { } @GetMapping("/{executionId}/processors/{index}/snapshot") - @Operation(summary = "Get exchange snapshot for a specific processor") + @Operation(summary = "Get exchange snapshot for a specific processor by index") @ApiResponse(responseCode = "200", description = "Snapshot data") @ApiResponse(responseCode = "404", description = "Snapshot not found") public ResponseEntity> getProcessorSnapshot( @@ -69,4 +69,16 @@ public class DetailController { return ResponseEntity.ok(snapshot); } + + @GetMapping("/{executionId}/processors/by-id/{processorId}/snapshot") + @Operation(summary = "Get exchange snapshot for a specific processor by processorId") + @ApiResponse(responseCode = "200", description = "Snapshot data") + @ApiResponse(responseCode = "404", description = "Snapshot not found") + public ResponseEntity> processorSnapshotById( + @PathVariable String executionId, + @PathVariable String processorId) { + return detailService.getProcessorSnapshot(executionId, processorId) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java index 5060b718..61de0828 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java @@ -121,6 +121,13 @@ public class PostgresExecutionStore implements ExecutionStore { PROCESSOR_MAPPER, executionId); } + @Override + public Optional findProcessorById(String executionId, String processorId) { + String sql = "SELECT * FROM processor_executions WHERE execution_id = ? AND processor_id = ? LIMIT 1"; + List results = jdbc.query(sql, PROCESSOR_MAPPER, executionId, processorId); + return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0)); + } + private static final RowMapper EXECUTION_MAPPER = (rs, rowNum) -> new ExecutionRecord( rs.getString("execution_id"), rs.getString("route_id"), diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java index c12d3be6..236aafc8 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java @@ -38,6 +38,18 @@ public class DetailService { }); } + public Optional> getProcessorSnapshot(String executionId, String processorId) { + return executionStore.findProcessorById(executionId, processorId) + .map(p -> { + Map snapshot = new LinkedHashMap<>(); + if (p.inputBody() != null) snapshot.put("inputBody", p.inputBody()); + if (p.outputBody() != null) snapshot.put("outputBody", p.outputBody()); + if (p.inputHeaders() != null) snapshot.put("inputHeaders", p.inputHeaders()); + if (p.outputHeaders() != null) snapshot.put("outputHeaders", p.outputHeaders()); + return snapshot; + }); + } + List buildTree(List processors) { if (processors.isEmpty()) return List.of(); diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java index 75971586..181dd8f9 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java @@ -16,6 +16,8 @@ public interface ExecutionStore { List findProcessors(String executionId); + Optional findProcessorById(String executionId, String processorId); + record ExecutionRecord( String executionId, String routeId, String agentId, String applicationName, String status, String correlationId, String exchangeId,