provides: "GET /api/v1/diagrams/{hash} with content negotiation"
exports: ["DiagramRenderController"]
key_links:
- from: "DiagramRenderController"
to: "DiagramRepository"
via: "findByContentHash to load RouteGraph"
pattern: "findByContentHash"
- from: "DiagramRenderController"
to: "DiagramRenderer"
via: "renderSvg or layoutJson based on Accept header"
pattern: "renderSvg|layoutJson"
- from: "ElkDiagramRenderer"
to: "ELK RecursiveGraphLayoutEngine"
via: "layout computation"
pattern: "RecursiveGraphLayoutEngine"
---
<objective>
Implement route diagram rendering with Eclipse ELK for layout and JFreeSVG for SVG output, exposed via a REST endpoint with content negotiation.
Purpose: Users need to visualize route diagrams from stored RouteGraph definitions. The server renders color-coded, top-to-bottom SVG diagrams or returns JSON layout data for client-side rendering. This is independent of the search work and can run in parallel.
Output: DiagramRenderer interface in core, ElkDiagramRenderer implementation in app, DiagramRenderController with Accept header content negotiation, integration and unit tests.
-`PositionedNode` record: id (String), label (String), type (String — NodeType name), x (double), y (double), width (double), height (double), children (List<PositionedNode> — for compound/swimlane groups). JSON-serializable for the JSON layout response.
-`PositionedEdge` record: sourceId (String), targetId (String), label (String), points (List<double[]> — waypoints for edge routing). The points list contains [x,y] pairs from source to target.
-`DiagramLayout` record: width (double), height (double), nodes (List<PositionedNode>), edges (List<PositionedEdge>). This is the JSON layout response format.
-`DiagramRenderer` interface with two methods:
-`String renderSvg(RouteGraph graph)` — returns SVG XML string
-`DiagramLayout layoutJson(RouteGraph graph)` — returns positioned layout data
Both methods take a RouteGraph and produce output. The interface lives in core so it can be swapped (e.g., for a different renderer).
- Unit test: ElkDiagramRenderer.renderSvg with a simple 3-node graph (from->process->to) produces valid SVG containing svg element, rect elements for nodes, line/path elements for edges
- Unit test: ElkDiagramRenderer.renderSvg produces SVG where endpoint nodes have blue fill (#3B82F6 or rgb equivalent)
- Unit test: ElkDiagramRenderer.layoutJson returns DiagramLayout with correct node count and positive coordinates
- Unit test: Nested processors (e.g., CHOICE with WHEN children) are laid out as compound nodes with children inside parent bounds
- Integration test: GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns 200 with content-type image/svg+xml and body starting with '<svg' or '<?xml'
- Integration test: GET /api/v1/diagrams/{hash} with Accept: application/json returns 200 with JSON containing 'nodes' and 'edges' arrays
- Integration test: GET /api/v1/diagrams/{nonexistent-hash} returns 404
- Integration test: GET /api/v1/diagrams/{hash} with no Accept preference defaults to SVG
- Convert RouteGraph to ELK graph: create ElkNode root, set properties for LayeredOptions.ALGORITHM_ID, Direction.DOWN (top-to-bottom per user decision), spacing 40px node-node, 20px edge-node.
- For each RouteNode: create ElkNode with estimated width (based on label length * 8 + 32, min 80) and height (40). Set identifier to node.id.
- For compound/nesting nodes (CHOICE, SPLIT, TRY_CATCH, DO_TRY, LOOP, MULTICAST, AGGREGATE): create these as compound ElkNodes. Identify children by examining RouteEdge topology — nodes whose only incoming edge is from the compound node AND have no outgoing edge leaving the compound scope are children. Alternatively, use the NodeType hierarchy: WHEN/OTHERWISE are children of CHOICE, DO_CATCH/DO_FINALLY are children of DO_TRY. Create child ElkNodes inside the parent compound node. Set compound node padding (top: 30 for label, sides: 10).
- For each RouteEdge: create ElkEdge connecting source to target ElkNodes.
- Run layout: `new RecursiveGraphLayoutEngine().layout(rootNode, new BasicProgressMonitor())`.
- Extract positions from computed layout into DiagramLayout (nodes with x/y/w/h, edges with routed waypoints).
**SVG rendering (renderSvg):**
- Run layout phase to get DiagramLayout.
- Create `SVGGraphics2D` with layout width + margins and layout height + margins (add 20px padding each side).
- Draw edges first (behind nodes): gray (#9CA3AF) lines with 2px stroke following edge waypoints. Draw arrowheads at endpoints.
-`GET /api/v1/diagrams/{contentHash}/render` — renders the diagram
- Inject DiagramRepository and DiagramRenderer.
- Look up RouteGraph via `diagramRepository.findByContentHash(contentHash)`. If empty, return 404.
- Content negotiation via Accept header:
-`image/svg+xml` or `*/*` or no Accept: call `renderer.renderSvg(graph)`, return ResponseEntity with content-type `image/svg+xml` and SVG body.
-`application/json`: call `renderer.layoutJson(graph)`, return ResponseEntity with content-type `application/json`.
- Use `@RequestMapping(produces = {...})` or manual Accept header parsing to handle content negotiation. Manual parsing is simpler: read `request.getHeader("Accept")`, check for "application/json", default to SVG.
4. Create `ElkDiagramRendererTest` (unit test, no Spring context):
- Build a simple RouteGraph with 3 nodes (from-endpoint, process-bean, to-endpoint) and 2 edges.
- Test renderSvg produces valid SVG string containing `<svg`, `<rect` or `<path`, node labels.
- Test layoutJson returns DiagramLayout with 3 nodes, all with positive x/y coordinates.
- Build a RouteGraph with CHOICE -> WHEN, OTHERWISE compound structure. Verify compound node layout has children.
<done>Diagram rendering produces color-coded top-to-bottom SVG and JSON layout, content negotiation works via Accept header, compound nodes group nested processors, all tests pass</done>
-`mvn clean verify` passes (all existing tests still green)
- SVG output contains color-coded nodes matching the NodeType color scheme
</verification>
<success_criteria>
- GET /api/v1/diagrams/{hash}/render returns SVG with color-coded nodes (blue endpoints, green processors, red error handlers, purple EIPs, cyan cross-route)
- GET /api/v1/diagrams/{hash}/render with Accept: application/json returns JSON layout with node positions
- Nodes laid out top-to-bottom via ELK layered algorithm
- Compound nodes group nested processors (CHOICE/WHEN, TRY/CATCH) in swimlane containers
- Non-existent hash returns 404
- Default (no Accept header) returns SVG
</success_criteria>
<output>
After completion, create `.planning/phases/02-transaction-search-diagrams/02-02-SUMMARY.md`