Skip to content

Commit 665f7ff

Browse files
committed
Add hooks for custom BPMN EventDefinitions and CMMN EventListeners
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.
1 parent 888eef6 commit 665f7ff

57 files changed

Lines changed: 1842 additions & 433 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/BaseBpmnXMLConverter.java

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.flowable.bpmn.model.CancelEventDefinition;
3636
import org.flowable.bpmn.model.CompensateEventDefinition;
3737
import org.flowable.bpmn.model.ConditionalEventDefinition;
38+
import org.flowable.bpmn.model.CustomBpmnEventDefinition;
3839
import org.flowable.bpmn.model.DataAssociation;
3940
import org.flowable.bpmn.model.DataObject;
4041
import org.flowable.bpmn.model.ErrorEventDefinition;
@@ -60,7 +61,6 @@
6061
import org.flowable.bpmn.model.TimerEventDefinition;
6162
import org.flowable.bpmn.model.UserTask;
6263
import org.flowable.bpmn.model.ValuedDataObject;
63-
import org.flowable.bpmn.model.VariableListenerEventDefinition;
6464
import org.slf4j.Logger;
6565
import org.slf4j.LoggerFactory;
6666

@@ -438,7 +438,13 @@ protected boolean writeListeners(BaseElement element, boolean didWriteExtensionS
438438
}
439439

440440
protected void writeEventDefinitions(Event parentEvent, List<EventDefinition> eventDefinitions, BpmnModel model, XMLStreamWriter xtw) throws Exception {
441+
// Custom (non-BPMN-spec) event definitions are emitted in writeExtensionChildElements via
442+
// BpmnXMLUtil.writeCustomEventDefinitionExtensionElements. Only the BPMN-spec types are written here
443+
// as direct children of the event element.
441444
for (EventDefinition eventDefinition : eventDefinitions) {
445+
if (eventDefinition instanceof CustomBpmnEventDefinition) {
446+
continue;
447+
}
442448
if (eventDefinition instanceof TimerEventDefinition) {
443449
writeTimerDefinition(parentEvent, (TimerEventDefinition) eventDefinition, model, xtw);
444450

@@ -653,32 +659,6 @@ protected void writeTerminateDefinition(Event parentEvent, TerminateEventDefinit
653659
xtw.writeEndElement();
654660
}
655661

656-
protected boolean writeVariableListenerDefinition(Event parentEvent, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
657-
if (parentEvent.getEventDefinitions().size() == 1) {
658-
EventDefinition eventDefinition = parentEvent.getEventDefinitions().iterator().next();
659-
if (eventDefinition instanceof VariableListenerEventDefinition variableListenerEventDefinition) {
660-
if (!didWriteExtensionStartElement) {
661-
xtw.writeStartElement(ELEMENT_EXTENSIONS);
662-
didWriteExtensionStartElement = true;
663-
}
664-
665-
xtw.writeStartElement(FLOWABLE_EXTENSIONS_PREFIX, ELEMENT_EVENT_VARIABLELISTENERDEFINITION, FLOWABLE_EXTENSIONS_NAMESPACE);
666-
667-
if (StringUtils.isNotEmpty(variableListenerEventDefinition.getVariableName())) {
668-
writeDefaultAttribute(ATTRIBUTE_VARIABLE_NAME, variableListenerEventDefinition.getVariableName(), xtw);
669-
}
670-
671-
if (StringUtils.isNotEmpty(variableListenerEventDefinition.getVariableChangeType())) {
672-
writeDefaultAttribute(ATTRIBUTE_VARIABLE_CHANGE_TYPE, variableListenerEventDefinition.getVariableChangeType(), xtw);
673-
}
674-
675-
xtw.writeEndElement();
676-
}
677-
}
678-
679-
return didWriteExtensionStartElement;
680-
}
681-
682662
protected void writeDefaultAttribute(String attributeName, String value, XMLStreamWriter xtw) throws Exception {
683663
BpmnXMLUtil.writeDefaultAttribute(attributeName, value, xtw);
684664
}

modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/BoundaryEventXMLConverter.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import javax.xml.stream.XMLStreamWriter;
2222

2323
import org.apache.commons.lang3.StringUtils;
24-
import org.flowable.bpmn.constants.BpmnXMLConstants;
2524
import org.flowable.bpmn.converter.child.BaseChildElementParser;
2625
import org.flowable.bpmn.converter.child.InParameterParser;
2726
import org.flowable.bpmn.converter.child.VariableListenerEventDefinitionParser;
@@ -32,7 +31,6 @@
3231
import org.flowable.bpmn.model.ErrorEventDefinition;
3332
import org.flowable.bpmn.model.EventDefinition;
3433
import org.flowable.bpmn.model.ExtensionAttribute;
35-
import org.flowable.bpmn.model.ExtensionElement;
3634

3735
/**
3836
* @author Tijs Rademakers
@@ -108,23 +106,14 @@ protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, X
108106
if (!(eventDef instanceof ErrorEventDefinition)) {
109107
writeDefaultAttribute(ATTRIBUTE_BOUNDARY_CANCELACTIVITY, String.valueOf(boundaryEvent.isCancelActivity()).toLowerCase(), xtw);
110108
}
111-
112-
} else if (!boundaryEvent.getExtensionElements().isEmpty()) {
113-
List<ExtensionElement> eventTypeExtensionElements = boundaryEvent.getExtensionElements().get(BpmnXMLConstants.ELEMENT_EVENT_TYPE);
114-
if (eventTypeExtensionElements != null && !eventTypeExtensionElements.isEmpty()) {
115-
String eventTypeValue = eventTypeExtensionElements.get(0).getElementText();
116-
if (StringUtils.isNotEmpty(eventTypeValue)) {
117-
writeDefaultAttribute(ATTRIBUTE_BOUNDARY_CANCELACTIVITY, String.valueOf(boundaryEvent.isCancelActivity()).toLowerCase(), xtw);
118-
}
119-
}
120109
}
121110
}
122111

123112
@Override
124113
protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
125114
BoundaryEvent boundaryEvent = (BoundaryEvent) element;
126115
didWriteExtensionStartElement = BpmnXMLUtil.writeIOParameters(ELEMENT_IN_PARAMETERS, boundaryEvent.getInParameters(), didWriteExtensionStartElement, xtw);
127-
didWriteExtensionStartElement = writeVariableListenerDefinition(boundaryEvent, didWriteExtensionStartElement, xtw);
116+
didWriteExtensionStartElement = BpmnXMLUtil.writeCustomEventDefinitionExtensionElements(boundaryEvent, didWriteExtensionStartElement, xtw);
128117
return didWriteExtensionStartElement;
129118
}
130119

modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/CatchEventXMLConverter.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, X
7272
@Override
7373
protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
7474
IntermediateCatchEvent catchEvent = (IntermediateCatchEvent) element;
75-
didWriteExtensionStartElement = writeVariableListenerDefinition(catchEvent, didWriteExtensionStartElement, xtw);
76-
return didWriteExtensionStartElement;
75+
return BpmnXMLUtil.writeCustomEventDefinitionExtensionElements(catchEvent, didWriteExtensionStartElement, xtw);
7776
}
7877

7978
@Override
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.bpmn.converter;
14+
15+
import javax.xml.stream.XMLStreamWriter;
16+
17+
import org.flowable.bpmn.model.Event;
18+
import org.flowable.bpmn.model.EventDefinition;
19+
20+
/**
21+
* Hook for serializing a {@link org.flowable.bpmn.model.CustomBpmnEventDefinition} back to XML — the
22+
* write-side counterpart to {@link org.flowable.bpmn.converter.child.BaseChildElementParser} on the read
23+
* side. A custom {@link EventDefinition} requires one parser (read) and one writer (write) to round-trip
24+
* through {@code BpmnXMLConverter}. The writer is invoked inside an already-opened
25+
* {@code <extensionElements>} wrapper.
26+
*/
27+
@FunctionalInterface
28+
public interface CustomEventDefinitionXmlWriter {
29+
30+
void write(Event parentEvent, EventDefinition eventDefinition, XMLStreamWriter xtw) throws Exception;
31+
}

modules/flowable-bpmn-converter/src/main/java/org/flowable/bpmn/converter/StartEventXMLConverter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, X
117117
writeQualifiedAttribute(ATTRIBUTE_SAME_DEPLOYMENT, "false", xtw);
118118
}
119119

120-
if ((startEvent.getEventDefinitions() != null && startEvent.getEventDefinitions().size() > 0) ||
121-
(startEvent.getExtensionElements() != null && startEvent.getExtensionElements().containsKey(ELEMENT_EVENT_TYPE))) {
120+
if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) {
122121
writeDefaultAttribute(ATTRIBUTE_EVENT_START_INTERRUPTING, String.valueOf(startEvent.isInterrupting()), xtw);
123122
}
124123
}
@@ -127,7 +126,7 @@ protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, X
127126
protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
128127
StartEvent startEvent = (StartEvent) element;
129128
didWriteExtensionStartElement = BpmnXMLUtil.writeIOParameters(ELEMENT_IN_PARAMETERS, startEvent.getInParameters(), didWriteExtensionStartElement, xtw);
130-
didWriteExtensionStartElement = writeVariableListenerDefinition(startEvent, didWriteExtensionStartElement, xtw);
129+
didWriteExtensionStartElement = BpmnXMLUtil.writeCustomEventDefinitionExtensionElements(startEvent, didWriteExtensionStartElement, xtw);
131130
didWriteExtensionStartElement = writeFormProperties(startEvent, didWriteExtensionStartElement, xtw);
132131
return didWriteExtensionStartElement;
133132
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.bpmn.converter.child;
14+
15+
import javax.xml.stream.XMLStreamReader;
16+
17+
import org.apache.commons.lang3.StringUtils;
18+
import org.flowable.bpmn.model.BaseElement;
19+
import org.flowable.bpmn.model.BoundaryEvent;
20+
import org.flowable.bpmn.model.BpmnModel;
21+
import org.flowable.bpmn.model.Event;
22+
import org.flowable.bpmn.model.EventRegistryEventDefinition;
23+
import org.flowable.bpmn.model.IntermediateCatchEvent;
24+
import org.flowable.bpmn.model.StartEvent;
25+
26+
/**
27+
* Reads {@code <flowable:eventType>X</flowable:eventType>} as a child of an event host (start /
28+
* intermediate-catch / boundary) and produces a typed {@link EventRegistryEventDefinition} on the host's
29+
* {@code eventDefinitions} list. The {@link EventRegistryEventDefinition} is the single source of truth in
30+
* the model — the legacy XML form is re-emitted on serialization by the host's XML writer, no parallel
31+
* extension-element representation is kept.
32+
* <p>
33+
* On non-event hosts (e.g. {@code receiveTask}, {@code sendEventServiceTask}, {@code process}) this parser
34+
* declines via {@link #accepts(BaseElement)} so {@code BpmnXMLUtil.parseChildElements} falls through to the
35+
* generic extension-element path and existing behavior is preserved.
36+
*/
37+
public class EventRegistryEventTypeParser extends BaseChildElementParser {
38+
39+
@Override
40+
public String getElementName() {
41+
return ELEMENT_EVENT_TYPE;
42+
}
43+
44+
@Override
45+
public boolean accepts(BaseElement element) {
46+
return element instanceof IntermediateCatchEvent
47+
|| element instanceof BoundaryEvent
48+
|| element instanceof StartEvent;
49+
}
50+
51+
@Override
52+
public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, BpmnModel model) throws Exception {
53+
if (!(parentElement instanceof Event event)) {
54+
return;
55+
}
56+
// An empty <flowable:eventType> on an event host is almost always an authoring mistake — warn so it
57+
// surfaces at parse time rather than failing silently at runtime.
58+
String eventTypeValue = xtr.getElementText();
59+
if (StringUtils.isEmpty(eventTypeValue)) {
60+
LOGGER.warn("Empty <flowable:eventType> extension element on event '{}'; ignoring (no event-registry subscription will be created)",
61+
event.getId());
62+
return;
63+
}
64+
if (event.getEventDefinitions().stream().anyMatch(EventRegistryEventDefinition.class::isInstance)) {
65+
LOGGER.warn("Multiple <flowable:eventType> extension elements on event '{}'; only the first is used and the duplicate value '{}' is ignored",
66+
event.getId(), eventTypeValue);
67+
return;
68+
}
69+
event.addEventDefinition(new EventRegistryEventDefinition(eventTypeValue));
70+
}
71+
}

0 commit comments

Comments
 (0)