bpm: a flowable:class execution or task listener reaches the client bean container too (#7222) - #7335
Merged
Merged
Conversation
…ean container too (#7222) #7058 wired a client JavaDelegate through the bean container on both service-task paths, but only the activity-behaviour factory was registered. Flowable creates a listener through a different factory: ProcessEngineConfigurationImpl.initListenerFactory builds its own DefaultListenerFactory carrying a stock DefaultClassDelegateFactory, and createClassDelegateExecutionListener / createClassDelegateTaskListener call that one. So a <flowable:executionListener class="..."> or <flowable:taskListener class="..."> written the way the docs teach - a constructor collaborator, or an @Inject field - either failed at reflection (no no-arg constructor) or ran with every injected field null, silently: the #7058 symptom one artefact type over. ResilientClassDelegateFactory.create(String, List), the overload the listener path uses, was dead code under this configuration. BpmFlowableConfig now also registers setListenerFactory(new DefaultListenerFactory( classDelegateFactory)) with the same factory instance, so both listener kinds come out as ResilientClassDelegates and share instantiateDelegate - the client-bean seam. The engine keeps a pre-set listener factory and only injects the expression manager into it, so expression listeners are unaffected. A listener deliberately gets no intent step resilience: execute() is the service-task entry point and Flowable's notify paths never reach it, and a listener is not a step. Verified: new ResilientListenerFactoryTest (both listener kinds against a real engine, plus the defect case - a stock factory does not yield the resilient delegate), the engine-bpm-flowable unit suite (38 tests), and a listener process added to JavaDelegateInjectionIT, which is green with the fix and fails without it with "couldn't instantiate class delegateinjection.CtorInjectedExecutionListener / NoSuchMethodException: <init>()". The IT's runtime-variable assertion was reading the historic endpoint's "variableName" key against the runtime endpoint's "name", which made assertNoRuntimeVariable vacuous; fixed with it. Docs in engine-java/CLAUDE.md and engine-intent/CLAUDE.md, whose sentences over-stated the delegate seam's reach, now scope and extend it. Fixes #7222 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cause
#7189 (#7058) wires a client
JavaDelegatethrough the bean container on both service-task paths, butBpmFlowableConfigregistered only the activity-behaviour factory. Flowable creates a listener through a different one:ProcessEngineConfigurationImpl.initListenerFactorybuilds its ownDefaultListenerFactorycarrying a stockDefaultClassDelegateFactory, andcreateClassDelegateExecutionListener/createClassDelegateTaskListenercall that.So a
<flowable:executionListener class="...">or<flowable:taskListener class="...">written the way the new docs teach — a constructor collaborator, or an@Injectfield — either failed at reflection (no no-arg constructor) or ran with every injected fieldnull, silently. That is the #7058 symptom one artefact type over, and it madeResilientClassDelegateFactory.create(String, List)— the overload the listener path uses — dead code under this configuration.Change
BpmFlowableConfigadditionally registerssetListenerFactory(new DefaultListenerFactory(classDelegateFactory))with the same factory instance, so both listener kinds come out asResilientClassDelegates and shareinstantiateDelegate, the client-bean seam. The engine keeps a pre-set listener factory and only injects the expression manager into it, so expression listeners are unaffected and nothing else about the engine changes.A listener deliberately gets no intent step resilience:
execute()is the service-task entry point and Flowable'snotifypaths never reach it — a listener is not a step, and nothing in the DSL emits one.The two doc sentences the issue flags (
ResilientClassDelegate's javadoc andengine-java/CLAUDE.md) said the seam covered everyflowable:classdelegate; they now say what it covers and why the listener needed a second registration, inengine-java/CLAUDE.mdandengine-intent/CLAUDE.md.Verification
ResilientListenerFactoryTest— against a real (in-memory) engine: both listener kinds come out asResilientClassDelegates, the configured factory survivesinitListenerFactoryand gets its expression manager, and the defect case is pinned alongside (without the registration the listener is a stockClassDelegate).engine-bpm-flowableunit suite — 38 tests, green.JavaDelegateInjectionITgains a listener process (listeners.bpmn+ a constructor-injectedExecutionListenerand an@Inject-fieldTaskListener). All 4 tests green with the fix; the new one fails without it withcouldn't instantiate class delegateinjection.CtorInjectedExecutionListener/NoSuchMethodException: <init>()— checked by reverting the one production line and re-running.mvn formatter:validateover the reactor with the cache wiped, and therelease-profile javadoc build onengine-bpm-flowable.Not run locally: the rest of the IT suite.
One adjacent fix rode along, because the new assertion needed it: the IT's runtime-variable helpers were querying
variableName, which is the historic endpoint's key — the runtime endpoint serves Flowable's own variable entities, whose key isname. That madeassertNoRuntimeVariablevacuous.Fixes #7222
🤖 Generated with Claude Code