Skip to content

Customizable BPMN EventDefinitions and CMMN EventListeners#4206

Open
filiphr wants to merge 9 commits intoflowable:mainfrom
filiphr:start-event-lifecycle-refactor
Open

Customizable BPMN EventDefinitions and CMMN EventListeners#4206
filiphr wants to merge 9 commits intoflowable:mainfrom
filiphr:start-event-lifecycle-refactor

Conversation

@filiphr
Copy link
Copy Markdown
Contributor

@filiphr filiphr commented May 4, 2026

Summary

Adds end-to-end extensibility for custom BPMN EventDefinitions and CMMN
EventListeners, and refactors the dispatch / deploy lifecycle / placement
validation paths around it so the built-ins go through the same code paths
as the customs.

What's new (extensibility surface)

  • BPMN custom EventDefinitions: addCustomChildElementParser registers
    a parser at engine init so custom <flowable:*> children parse into a
    typed EventDefinition; addCustomEventDefinitionWriter registers the
    matching XML writer for round-trip serialization. A new
    CustomBpmnEventDefinition marker interface flags subclasses that route
    through the extension-elements path.
  • CMMN custom EventListeners: a discriminator registry on
    GenericEventListenerXmlConverter keyed by flowable:eventType, with a
    customEventListenerTypeFactories slot on CmmnEngineConfiguration.
    flowable:eventType="event" is now a built-in discriminator value that
    produces an EventRegistryEventListener directly (alongside signal,
    variable, intent, reactivate); the legacy
    <flowable:eventType>X</flowable:eventType> extension element still
    works.
  • EventDefinition#getSupportedLocations(): each subtype declares the
    immutable set of placements (start / event-sub-process start /
    intermediate catch / boundary / end / intermediate throw) it's valid at;
    all six placement validators consult this set instead of hard-coded
    instanceof chains.

Refactors that fall out of the design

  • Behavior owns its full lifecycle:
    • Event-sub-process StartEvents implement
      EventSubProcessStartEventActivityBehavior and own
      initializeEventSubProcessStart(...). The five built-in registering
      behaviors (Message, Signal, Timer, EventRegistry, VariableListener)
      absorb the bodies of the corresponding handle... helpers in
      ProcessInstanceHelper, which collapses to a single delegated call.
    • Process-level StartEvents implement
      ProcessLevelStartEventActivityBehavior and own deploy(...) /
      undeploy(...). BpmnDeploymentHelper iterates start events; the
      EventSubscriptionManager and TimerManager classes are deleted.
    • DeploymentProcessDefinitionDeletionManagerImpl delegates restore-
      after-delete to behavior.deploy(...) with an
      isRestoringPreviousVersion() flag in the deploy context.
  • Parse handler dispatch unification: StartEventParseHandler,
    IntermediateThrowEventParseHandler, and EndEventParseHandler no
    longer have instanceof cascades — each routes through
    parseElement(eventDefinition) like the boundary / catch handlers
    already did.
  • CMMN parity: a CaseDefinitionStartLifecycleHandler attached to
    the Case at parse time mirrors the BPMN process-level pattern. The
    built-in EventRegistryCaseDefinitionStartLifecycleHandler is
    installed by default when case.startEventType is set.
  • Bulk-flush event subscription cleanup: new
    findEventSubscriptionsByTypesAndProcessDefinitionId /
    findEventSubscriptionsByTypesAndScopeDefinitionId finders run a
    single SQL with EVENT_TYPE_ IN (...), so deploy-time cleanup is
    one DB round-trip regardless of how many handler types the previous
    definition declared.

Breaking changes

EventRegistryEventListener no longer extends GenericEventListener
(both extend EventListener directly now). Downstream code that does
instanceof GenericEventListener or
findPlanItemDefinitionsOfType(GenericEventListener.class) to enumerate
event-registry listeners must switch to EventRegistryEventListener (or
match both, where the runtime distinction matters).

filiphr added 9 commits May 4, 2026 16:07
Allow consumers to register custom EventDefinition / EventListener types
end-to-end: parser + writer registration hooks on the BPMN side, a
discriminator registry on the CMMN side, and an EventDefinition.getSupported
Locations() set that the placement validators consult instead of hard-coded
instanceof chains. The legacy <flowable:eventType> child is parsed into a
typed EventRegistryEventDefinition. Tests cover the full register-deploy-
execute path on both engines.
…se handlers

Replace the inline instanceof cascade in StartEventParseHandler with a
single parseElement(eventDefinition) delegation, matching the pattern in
BoundaryEventParseHandler and IntermediateCatchEventParseHandler. Each
per-EventDefinition handler now installs the event-sub-process behavior
alongside its IntermediateCatch / Boundary cases, so custom EventDefinitions
go through the same path as the built-ins.
…Event behavior

Each event-sub-process StartEvent ActivityBehavior now owns its full
lifecycle via a new EventSubProcessStartEventActivityBehavior interface
(initializeEventSubProcessStart(context)). The five built-in subscription
registering behaviors (Message, Signal, Timer, EventRegistry,
VariableListener) absorb the bodies of the corresponding handle... helpers
from ProcessInstanceHelper, which collapses to a single delegated call.
The context exposes createEventScopeChildExecution(),
createEventSubscriptionBuilder(execution), and record callbacks that
preserve the two-pass message/signal-waiting dispatch.
…yBehavior

Each process-level start event now owns its full deploy lifecycle via a
new ProcessLevelStartEventActivityBehavior interface (deploy + undeploy),
plus typed deploy/undeploy contexts and four built-in behaviors (Message,
Signal, Timer, EventRegistry). BpmnDeploymentHelper iterates start events
and the EventSubscriptionManager / TimerManager classes are deleted. A
new findEventSubscriptionsByTypesAndProcessDefinitionId finder runs a
single SQL with EVENT_TYPE_ IN (...), so cleanup is one DB round-trip
regardless of how many handler types the previous process definition
declared.
…dlers

Replace IntermediateThrowEventParseHandler's instanceof cascade with a
parseElement(eventDefinition) delegation. Signal and Escalation parse
handlers gain a ThrowEvent branch (Compensate already had one); the
None-throw path stays inline.
Replace EndEventParseHandler's instanceof cascade for Error / Escalation
/ Terminate / Cancel with a parseElement(eventDefinition) delegation.
The per-EventDefinition handlers gain EndEvent branches and a new
TerminateEventDefinitionParseHandler covers Terminate's only valid
placement. None-end stays inline.
DeploymentProcessDefinitionDeletionManagerImpl now iterates the previous
process definition's top-level start events and calls behavior.deploy()
instead of dispatching by instanceof. The deploy context gains an
isRestoringPreviousVersion() flag so MessageStartEventActivityBehavior
can skip its duplicate-subscription guard — the just-deleted PD's row is
still in the in-session cache because the bulk delete is queued, not
flushed.
Mirror the BPMN ProcessLevelStartEventActivityBehavior pattern on the
CMMN side: a CaseDefinitionStartLifecycleHandler attached to the Case
at parse time owns its deploy/undeploy. The deploy context has an
isRestoringPreviousVersion() flag for parity, and an
EventRegistryCaseDefinitionStartLifecycleHandler is installed by default
when case.startEventType is set. A new
findEventSubscriptionsByTypesAndScopeDefinitionId finder lets CmmnDeployer
issue a single bulk-delete after the undeploy iteration.
…Locations()

Align EndEventValidator and IntermediateThrowEventValidator with the four
already-refactored placement validators so all six EventDefinitionLocation
values are enforced. Built-ins keep their existing behavior; custom
EventDefinitions declaring END_EVENT or INTERMEDIATE_THROW_EVENT support
are now valid at those placements.
@filiphr filiphr force-pushed the start-event-lifecycle-refactor branch from b92e083 to c6d7dbb Compare May 4, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant