Skip to content

Commit b92e083

Browse files
committed
Decouple EventRegistryEventListener from GenericEventListener
Move the eventType field from GenericEventListener onto EventRegistryEventListener and have it extend EventListener directly so the type hierarchy reflects runtime semantics. The legacy extension element path now promotes eagerly in ExtensionElementsXMLConverter serialization for the decoupled type. Breaking change: instanceof GenericEventListener and findPlanItemDefinitions OfType(GenericEventListener.class) no longer match event-registry listeners.
1 parent 470dd23 commit b92e083

12 files changed

Lines changed: 141 additions & 113 deletions

File tree

modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ExtensionElementsXMLConverter.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static org.flowable.cmmn.model.ImplementationType.IMPLEMENTATION_TYPE_CLASS;
2020
import static org.flowable.cmmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION;
2121

22+
import java.util.List;
23+
2224
import javax.xml.stream.Location;
2325
import javax.xml.stream.XMLStreamReader;
2426

@@ -38,6 +40,7 @@
3840
import org.flowable.cmmn.model.FlowableHttpRequestHandler;
3941
import org.flowable.cmmn.model.FlowableHttpResponseHandler;
4042
import org.flowable.cmmn.model.FlowableListener;
43+
import org.flowable.cmmn.model.EventRegistryEventListener;
4144
import org.flowable.cmmn.model.GenericEventListener;
4245
import org.flowable.cmmn.model.HasInParameters;
4346
import org.flowable.cmmn.model.HasLifecycleListeners;
@@ -48,11 +51,13 @@
4851
import org.flowable.cmmn.model.ImplementationType;
4952
import org.flowable.cmmn.model.ParentCompletionRule;
5053
import org.flowable.cmmn.model.PlanItemControl;
54+
import org.flowable.cmmn.model.PlanItemDefinition;
5155
import org.flowable.cmmn.model.ReactivateEventListener;
5256
import org.flowable.cmmn.model.ReactivationRule;
5357
import org.flowable.cmmn.model.RepetitionRule;
5458
import org.flowable.cmmn.model.SendEventServiceTask;
5559
import org.flowable.cmmn.model.ServiceTask;
60+
import org.flowable.cmmn.model.Stage;
5661
import org.flowable.cmmn.model.VariableAggregationDefinition;
5762
import org.flowable.common.engine.api.FlowableException;
5863
import org.slf4j.Logger;
@@ -423,16 +428,55 @@ protected void readEventType(XMLStreamReader xtr, ConversionHelper conversionHel
423428
} else if (currentCmmnElement instanceof SendEventServiceTask sendEventServiceTask) {
424429
sendEventServiceTask.setEventType(eventType);
425430

431+
} else if (currentCmmnElement instanceof EventRegistryEventListener registryListener) {
432+
registryListener.setEventType(eventType);
433+
426434
} else if (currentCmmnElement instanceof GenericEventListener genericEventListener) {
427-
// Promotion of GenericEventListener to the typed EventRegistryEventListener happens in
428-
// GenericEventListenerXmlConverter#elementEnd; here we only set the value on the listener.
429-
genericEventListener.setEventType(eventType);
435+
// Legacy XML form: a plain <eventListener> with a <flowable:eventType> extension element. Promote
436+
// it to a typed EventRegistryEventListener so factory and parse-handler dispatch can match on
437+
// class. The new flowable:eventType="event" attribute path skips this branch — its factory
438+
// already produced an EventRegistryEventListener directly.
439+
EventRegistryEventListener promoted = promoteToEventRegistryEventListener(genericEventListener, conversionHelper);
440+
promoted.setEventType(eventType);
430441

431442
} else {
432443
LOGGER.warn("Unsupported eventType detected for element {}", currentCmmnElement);
433444
}
434445
}
435446

447+
private static EventRegistryEventListener promoteToEventRegistryEventListener(GenericEventListener original, ConversionHelper conversionHelper) {
448+
EventRegistryEventListener replacement = new EventRegistryEventListener();
449+
replacement.setValues(original);
450+
451+
// Replace in (1) the conversion helper's flat planItemDefinitions list, (2) the parent stage's
452+
// planItemDefinitions list AND its by-id map (Stage maintains both — findPlanItemDefinitionInStageOrUpwards
453+
// reads from the map at deploy time to resolve PlanItem.planItemDefinition), and (3) the top of the
454+
// current-element stack so super.elementEnd pops the typed instance.
455+
replaceInList(conversionHelper.getPlanItemDefinitions(), original, replacement);
456+
457+
Stage parentStage = original.getParentStage();
458+
if (parentStage != null) {
459+
replaceInList(parentStage.getPlanItemDefinitions(), original, replacement);
460+
if (replacement.getId() != null) {
461+
parentStage.getPlanItemDefinitionMap().put(replacement.getId(), replacement);
462+
}
463+
}
464+
465+
conversionHelper.removeCurrentCmmnElement();
466+
conversionHelper.setCurrentCmmnElement(replacement);
467+
return replacement;
468+
}
469+
470+
private static void replaceInList(List<PlanItemDefinition> list, PlanItemDefinition original, PlanItemDefinition replacement) {
471+
if (list == null) {
472+
return;
473+
}
474+
int index = list.indexOf(original);
475+
if (index >= 0) {
476+
list.set(index, replacement);
477+
}
478+
}
479+
436480
protected void readVariableAggregationDefinition(XMLStreamReader xtr, ConversionHelper conversionHelper) {
437481
CmmnElement currentCmmnElement = conversionHelper.getCurrentCmmnElement();
438482

modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/GenericEventListenerXmlConverter.java

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,19 @@
1313
package org.flowable.cmmn.converter;
1414

1515
import java.util.HashMap;
16-
import java.util.List;
1716
import java.util.Map;
1817
import java.util.Set;
1918

2019
import javax.xml.stream.XMLStreamReader;
2120

2221
import org.apache.commons.lang3.StringUtils;
2322
import org.flowable.cmmn.model.BaseElement;
24-
import org.flowable.cmmn.model.CmmnElement;
2523
import org.flowable.cmmn.model.EventListener;
2624
import org.flowable.cmmn.model.EventRegistryEventListener;
2725
import org.flowable.cmmn.model.GenericEventListener;
2826
import org.flowable.cmmn.model.IntentEventListener;
29-
import org.flowable.cmmn.model.PlanItemDefinition;
3027
import org.flowable.cmmn.model.ReactivateEventListener;
3128
import org.flowable.cmmn.model.SignalEventListener;
32-
import org.flowable.cmmn.model.Stage;
3329
import org.flowable.cmmn.model.VariableEventListener;
3430
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
3531
import org.slf4j.Logger;
@@ -127,51 +123,4 @@ protected EventListener convertCommonAttributes(XMLStreamReader xtr, EventListen
127123
return listener;
128124
}
129125

130-
@Override
131-
protected void elementEnd(XMLStreamReader xtr, ConversionHelper conversionHelper) {
132-
// The legacy <flowable:eventType> child sets eventType on the GenericEventListener via
133-
// ExtensionElementsXMLConverter#readEventType while we're still inside <eventListener>. By the time
134-
// we reach this end-element callback all child extension elements have been processed, so we can
135-
// promote the plain GenericEventListener to a typed EventRegistryEventListener. Promotion lets
136-
// factory and parse-handler dispatch match on instanceof EventRegistryEventListener directly.
137-
CmmnElement currentElement = conversionHelper.getCurrentCmmnElement();
138-
if (currentElement != null
139-
&& currentElement.getClass() == GenericEventListener.class
140-
&& StringUtils.isNotEmpty(((GenericEventListener) currentElement).getEventType())) {
141-
promoteToEventRegistryEventListener((GenericEventListener) currentElement, conversionHelper);
142-
}
143-
super.elementEnd(xtr, conversionHelper);
144-
}
145-
146-
private static void promoteToEventRegistryEventListener(GenericEventListener original, ConversionHelper conversionHelper) {
147-
EventRegistryEventListener replacement = new EventRegistryEventListener();
148-
replacement.setValues(original);
149-
150-
// Replace in (1) the conversion helper's flat planItemDefinitions list, (2) the parent stage's
151-
// planItemDefinitions list AND its by-id map (Stage maintains both — findPlanItemDefinitionInStageOrUpwards
152-
// reads from the map at deploy time to resolve PlanItem.planItemDefinition), and (3) the top of the
153-
// current-element stack so super.elementEnd pops the typed instance.
154-
replaceInList(conversionHelper.getPlanItemDefinitions(), original, replacement);
155-
156-
Stage parentStage = original.getParentStage();
157-
if (parentStage != null) {
158-
replaceInList(parentStage.getPlanItemDefinitions(), original, replacement);
159-
if (replacement.getId() != null) {
160-
parentStage.getPlanItemDefinitionMap().put(replacement.getId(), replacement);
161-
}
162-
}
163-
164-
conversionHelper.removeCurrentCmmnElement();
165-
conversionHelper.setCurrentCmmnElement(replacement);
166-
}
167-
168-
private static void replaceInList(List<PlanItemDefinition> list, PlanItemDefinition original, PlanItemDefinition replacement) {
169-
if (list == null) {
170-
return;
171-
}
172-
int index = list.indexOf(original);
173-
if (index >= 0) {
174-
list.set(index, replacement);
175-
}
176-
}
177126
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* Licensed under the Apache License, Version 2.0 (the "License");
2+
* you may not use this file except in compliance with the License.
3+
* You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
package org.flowable.cmmn.converter.export;
14+
15+
import javax.xml.stream.XMLStreamWriter;
16+
17+
import org.apache.commons.lang3.StringUtils;
18+
import org.flowable.cmmn.converter.CmmnXmlConstants;
19+
import org.flowable.cmmn.model.EventRegistryEventListener;
20+
import org.flowable.cmmn.model.ExtensionElement;
21+
22+
public class EventRegistryEventListenerExport extends AbstractPlanItemDefinitionExport<EventRegistryEventListener> {
23+
24+
@Override
25+
protected Class<? extends EventRegistryEventListener> getExportablePlanItemDefinitionClass() {
26+
return EventRegistryEventListener.class;
27+
}
28+
29+
@Override
30+
protected String getPlanItemDefinitionXmlElementValue(EventRegistryEventListener planItemDefinition) {
31+
return ELEMENT_GENERIC_EVENT_LISTENER;
32+
}
33+
34+
@Override
35+
protected void writePlanItemDefinitionSpecificAttributes(EventRegistryEventListener listener, XMLStreamWriter xtw) throws Exception {
36+
super.writePlanItemDefinitionSpecificAttributes(listener, xtw);
37+
38+
if (StringUtils.isNotEmpty(listener.getAvailableConditionExpression())) {
39+
xtw.writeAttribute(FLOWABLE_EXTENSIONS_NAMESPACE,
40+
CmmnXmlConstants.ATTRIBUTE_EVENT_LISTENER_AVAILABLE_CONDITION,
41+
listener.getAvailableConditionExpression());
42+
}
43+
44+
// Re-emit the event-key as the legacy <flowable:eventType>X</flowable:eventType> extension element
45+
// so the round-trip is symmetric with the legacy XML form. Skip when an extensionElements entry was
46+
// already present (e.g. preserved from the original parse).
47+
if (StringUtils.isNotEmpty(listener.getEventType()) && listener.getExtensionElements().get("eventType") == null) {
48+
ExtensionElement extensionElement = new ExtensionElement();
49+
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
50+
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
51+
extensionElement.setName("eventType");
52+
extensionElement.setElementText(listener.getEventType());
53+
listener.addExtensionElement(extensionElement);
54+
}
55+
}
56+
}

modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/GenericEventListenerExport.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import org.apache.commons.lang3.StringUtils;
1818
import org.flowable.cmmn.converter.CmmnXmlConstants;
19-
import org.flowable.cmmn.model.ExtensionElement;
2019
import org.flowable.cmmn.model.GenericEventListener;
2120

2221
/**
@@ -44,15 +43,6 @@ protected void writePlanItemDefinitionSpecificAttributes(GenericEventListener ge
4443
CmmnXmlConstants.ATTRIBUTE_EVENT_LISTENER_AVAILABLE_CONDITION,
4544
genericEventListener.getAvailableConditionExpression());
4645
}
47-
48-
if (StringUtils.isNotEmpty(genericEventListener.getEventType()) && genericEventListener.getExtensionElements().get("eventType") == null) {
49-
ExtensionElement extensionElement = new ExtensionElement();
50-
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
51-
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
52-
extensionElement.setName("eventType");
53-
extensionElement.setElementText(genericEventListener.getEventType());
54-
genericEventListener.addExtensionElement(extensionElement);
55-
}
5646
}
5747

5848
}

modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/PlanItemDefinitionExport.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class PlanItemDefinitionExport implements CmmnXmlConstants {
4242
addPlanItemDefinitionExport(new ExternalWorkerServiceTaskExport());
4343
addPlanItemDefinitionExport(new MilestoneExport());
4444
addPlanItemDefinitionExport(new GenericEventListenerExport());
45+
addPlanItemDefinitionExport(new EventRegistryEventListenerExport());
4546
addPlanItemDefinitionExport(new SignalEventListenerExport());
4647
addPlanItemDefinitionExport(new IntentEventListenerExport());
4748
addPlanItemDefinitionExport(new ReactivationEventListenerExport());

modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/EventListenerWithEventTypeXmlConverterTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import java.util.List;
2020

2121
import org.flowable.cmmn.model.CmmnModel;
22+
import org.flowable.cmmn.model.EventRegistryEventListener;
2223
import org.flowable.cmmn.model.ExtensionElement;
23-
import org.flowable.cmmn.model.GenericEventListener;
2424
import org.flowable.cmmn.model.PlanItemDefinition;
2525
import org.flowable.test.cmmn.converter.util.CmmnXmlConverterTest;
2626

@@ -32,12 +32,12 @@ public class EventListenerWithEventTypeXmlConverterTest {
3232
@CmmnXmlConverterTest("org/flowable/test/cmmn/converter/event-listener-with-event-type.cmmn")
3333
public void validateModel(CmmnModel cmmnModel) {
3434
PlanItemDefinition planItemDefinition = cmmnModel.findPlanItemDefinition("eventListener");
35-
assertThat(planItemDefinition).isInstanceOf(GenericEventListener.class);
35+
assertThat(planItemDefinition).isInstanceOf(EventRegistryEventListener.class);
3636

37-
GenericEventListener genericEventListener = (GenericEventListener) planItemDefinition;
38-
assertThat(genericEventListener.getEventType()).isEqualTo("myEvent");
37+
EventRegistryEventListener listener = (EventRegistryEventListener) planItemDefinition;
38+
assertThat(listener.getEventType()).isEqualTo("myEvent");
3939

40-
List<ExtensionElement> correlationParameters = genericEventListener.getExtensionElements().getOrDefault("eventCorrelationParameter", new ArrayList<>());
40+
List<ExtensionElement> correlationParameters = listener.getExtensionElements().getOrDefault("eventCorrelationParameter", new ArrayList<>());
4141
assertThat(correlationParameters)
4242
.extracting(extensionElement -> extensionElement.getAttributeValue(null, "name"),
4343
extensionElement -> extensionElement.getAttributeValue(null, "value"))

modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/EventRegistryEventListenerCmmnXmlConverterTest.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ class EventRegistryEventListenerCmmnXmlConverterTest {
2424

2525
@CmmnXmlConverterTest("org/flowable/test/cmmn/converter/eventListenerLegacyEventType.cmmn")
2626
void legacyEventTypeExtensionElement_promotesToEventRegistryEventListener(CmmnModel cmmnModel) {
27-
// XML with <flowable:eventType>X</flowable:eventType> on a GenericEventListener should produce an
28-
// EventRegistryEventListener (typed).
27+
// XML with <flowable:eventType>X</flowable:eventType> on a plain <eventListener> should still
28+
// produce an EventRegistryEventListener (the legacy promotion path).
2929
PlanItemDefinition planItemDefinition = cmmnModel.getPrimaryCase().getPlanModel()
3030
.findPlanItemDefinitionInStageOrDownwards("eventListener");
3131

3232
assertThat(planItemDefinition).isExactlyInstanceOf(EventRegistryEventListener.class);
3333
EventRegistryEventListener listener = (EventRegistryEventListener) planItemDefinition;
3434
assertThat(listener.getEventType()).isEqualTo("orderShipped");
35-
// Inheritance from GenericEventListener is preserved so existing instanceof checks still match.
36-
assertThat(listener).isInstanceOf(GenericEventListener.class);
35+
assertThat(listener).isNotInstanceOf(GenericEventListener.class);
3736
}
3837

3938
@CmmnXmlConverterTest("org/flowable/test/cmmn/converter/eventListenerEventTypeAttribute.cmmn")
@@ -46,7 +45,6 @@ void eventTypeAttribute_buildsEventRegistryEventListenerDirectly(CmmnModel cmmnM
4645
assertThat(planItemDefinition).isExactlyInstanceOf(EventRegistryEventListener.class);
4746
EventRegistryEventListener listener = (EventRegistryEventListener) planItemDefinition;
4847
assertThat(listener.getEventType()).isEqualTo("orderShipped");
49-
assertThat(listener).isInstanceOf(GenericEventListener.class);
5048
}
5149

5250
@CmmnXmlConverterTest("org/flowable/test/cmmn/converter/eventListenerUnrecognizedType.cmmn")

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/parser/CmmnActivityBehaviorFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.flowable.cmmn.model.CaseTask;
3939
import org.flowable.cmmn.model.DecisionTask;
4040
import org.flowable.cmmn.model.ExternalWorkerServiceTask;
41+
import org.flowable.cmmn.model.EventRegistryEventListener;
4142
import org.flowable.cmmn.model.GenericEventListener;
4243
import org.flowable.cmmn.model.HumanTask;
4344
import org.flowable.cmmn.model.IntentEventListener;
@@ -99,7 +100,7 @@ public interface CmmnActivityBehaviorFactory {
99100

100101
GenericEventListenerActivityBehaviour createGenericEventListenerActivityBehavior(PlanItem planItem, GenericEventListener genericEventListener);
101102

102-
EventRegistryEventListenerActivityBehaviour createEventRegistryEventListenerActivityBehaviour(PlanItem planItem, GenericEventListener genericEventListener);
103+
EventRegistryEventListenerActivityBehaviour createEventRegistryEventListenerActivityBehaviour(PlanItem planItem, EventRegistryEventListener eventRegistryEventListener);
103104

104105
VariableEventListenerActivityBehaviour createVariableEventListenerActivityBehaviour(PlanItem planItem, VariableEventListener variableEventListener);
105106

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/parser/DefaultCmmnActivityBehaviorFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.flowable.cmmn.model.DecisionTask;
4545
import org.flowable.cmmn.model.ExternalWorkerServiceTask;
4646
import org.flowable.cmmn.model.FieldExtension;
47+
import org.flowable.cmmn.model.EventRegistryEventListener;
4748
import org.flowable.cmmn.model.GenericEventListener;
4849
import org.flowable.cmmn.model.HumanTask;
4950
import org.flowable.cmmn.model.IntentEventListener;
@@ -145,8 +146,8 @@ public GenericEventListenerActivityBehaviour createGenericEventListenerActivityB
145146
}
146147

147148
@Override
148-
public EventRegistryEventListenerActivityBehaviour createEventRegistryEventListenerActivityBehaviour(PlanItem planItem, GenericEventListener genericEventListener) {
149-
return new EventRegistryEventListenerActivityBehaviour(createExpression(genericEventListener.getEventType()));
149+
public EventRegistryEventListenerActivityBehaviour createEventRegistryEventListenerActivityBehaviour(PlanItem planItem, EventRegistryEventListener eventRegistryEventListener) {
150+
return new EventRegistryEventListenerActivityBehaviour(createExpression(eventRegistryEventListener.getEventType()));
150151
}
151152

152153
@Override

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/parser/handler/GenericEventListenerParseHandler.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import java.util.Collection;
1616
import java.util.Collections;
1717

18-
import org.apache.commons.lang3.StringUtils;
1918
import org.flowable.cmmn.engine.impl.parser.CmmnParseResult;
2019
import org.flowable.cmmn.engine.impl.parser.CmmnParserImpl;
2120
import org.flowable.cmmn.model.BaseElement;
@@ -34,15 +33,7 @@ public Collection<Class<? extends BaseElement>> getHandledTypes() {
3433

3534
@Override
3635
protected void executePlanItemParse(CmmnParserImpl cmmnParser, CmmnParseResult cmmnParseResult, PlanItem planItem, GenericEventListener genericEventListener) {
37-
// Programmatic backwards-compat: a model built in Java with setEventType(...) on a plain
38-
// GenericEventListener still gets the event-registry behavior. The XML-parsing path swaps the type to
39-
// EventRegistryEventListener (handled by its own parse handler), so this branch only fires for
40-
// programmatic construction.
41-
if (StringUtils.isEmpty(genericEventListener.getEventType())) {
42-
planItem.setBehavior(cmmnParser.getActivityBehaviorFactory().createGenericEventListenerActivityBehavior(planItem, genericEventListener));
43-
} else {
44-
planItem.setBehavior(cmmnParser.getActivityBehaviorFactory().createEventRegistryEventListenerActivityBehaviour(planItem, genericEventListener));
45-
}
36+
planItem.setBehavior(cmmnParser.getActivityBehaviorFactory().createGenericEventListenerActivityBehavior(planItem, genericEventListener));
4637
}
4738

4839
}

0 commit comments

Comments
 (0)