CAMEL-24192: camel-lra - separate compensation/completion URI allowlists and prune on route removal#24904
Conversation
…sts and prune on route removal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
gnodet
left a comment
There was a problem hiding this comment.
Review: CAMEL-24192 — Separate LRA compensation/completion URI allowlists
Overall: Clean security improvement — splits the flat sagaURIs allowlist into type-specific sets so compensation callbacks can only invoke compensation endpoints and vice versa, and adds automatic cleanup when routes are removed.
What this PR does well
-
Security hardening — The original single
Set<String>allowed cross-type callback invocation (a completion callback could dispatch to a compensation URI). Splitting intocompensationURIsandcompletionURIswith type-specific validation inverifyRequest(Exchange, boolean)closes this gap. -
Correct cleanup design —
unregisterSteps()properly handles shared URIs: it collects URIs still referenced by other routes before removing, preventing premature pruning. TheLifecycleStrategyhook fires ononRoutesRemove()to trigger cleanup automatically. -
Backward-compatible
getRegisteredURIs()— Returns the union of both sets, so existing callers are unaffected. -
API fix — Changed
Optional.map()toOptional.ifPresent()for side-effect operations inregisterStep()(the old.map(this.sagaURIs::add)misusedmapfor side effects). -
Good test coverage — 6 tests covering separation, union, pruning, shared-URI retention, and unknown-route no-op.
-
Import cleanup — Wildcard
java.util.*replaced with specific imports inLRASagaRoutes.
Non-blocking observations
-
Thread safety in
unregisterSteps()— There's a narrow TOCTOU window betweenstepsByRouteId.remove(routeId)and iteratingstepsByRouteId.values(): a concurrentregisterStep()could add a step referencing the same URI between those two operations. In practice this isn't a problem since route registration and removal are sequential during lifecycle transitions, but worth noting. -
Lifecycle strategy cleanup — Consider removing the lifecycle strategy in
doStop()(viacamelContext.removeLifecycleStrategy(this.lifecycleStrategy)) alongside clearing the maps, and resettingthis.lifecycleStrategy = nullso it gets re-registered on restart. Currently the null-guard prevents double-registration, but explicit removal would be cleaner for restart scenarios. -
Assertion style — Tests use JUnit
assertTrue/assertFalse/assertEquals. The project preference is AssertJ (assertThat(...).contains(...),.doesNotContain(...),.isEmpty()), which would also give better failure messages for set membership checks.
Claude Code on behalf of gnodet — AI-generated review
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 550 tested, 29 compile-only — current: 550 all testedMaveniverse Scalpel detected 579 affected modules (current approach: 550).
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
|
Claude Code on behalf of davsclaus Thanks for the review! Addressed observation #2 — lifecycle strategy is now explicitly removed in On the other observations:
|
gnodet
left a comment
There was a problem hiding this comment.
Re-review after 2nd commit (d84f42e): Lifecycle strategy cleanup in doStop() looks good 👍
The new commit properly deregisters the LifecycleStrategy from the CamelContext during doStop():
if (this.lifecycleStrategy != null) {
camelContext.getLifecycleStrategies().remove(this.lifecycleStrategy);
this.lifecycleStrategy = null;
}This prevents the strategy from lingering if the service is stopped while the CamelContext continues running. Clean follow-up. My earlier approval stands.
Claude Code on behalf of gnodet — AI-generated review
Summary
Claude Code on behalf of davsclaus
Splits the single shared
sagaURIsallowlist inLRASagaServiceinto separate compensation and completion URI sets, and adds automatic cleanup when routes are removed.Problem: The LRA Saga service maintained one flat
Set<String>for both compensation and completion callback URIs. This meant (1) a completion callback could invoke a compensation endpoint and vice versa, and (2) URIs from dynamically removed routes remained authorized for the entire context lifetime (memory leak + stale authorization).Changes:
CamelSagaStep— addedrouteIdfield so steps can be tracked by the route that registered themSagaReifier— setsrouteIdon the step before callingregisterStep()LRASagaService— replacedsagaURIswith separatecompensationURIsandcompletionURIssets; addedstepsByRouteIdtracking map; registered aLifecycleStrategySupportthat prunes URIs when routes are removed; shared URIs are only pruned when all referencing routes are removedLRASagaRoutes— splitverifyRequest()intoverifyCompensationRequest()andverifyCompletionRequest()so each callback type validates only against its own allowlistLRASagaServiceAllowlistTest— covers URI separation, pruning, shared URI retention, and no-op for unknown routesNo interface changes —
CamelSagaService.registerStep(CamelSagaStep)signature is preserved.InMemorySagaServiceis unaffected.Test plan
LRASagaServiceAllowlistTest— 6 tests covering separation, pruning, shared URIs, union, and no-opmvn verifyincomponents/camel-lra)camel-core-reifiercompiles with the newstep.setRouteId()call🤖 Generated with Claude Code