This is an automated email from the ASF dual-hosted git repository.

fjtiradosarti pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-runtimes.git


The following commit(s) were added to refs/heads/main by this push:
     new a7044d0480 [incubator-kie-issues#1457] Allow grouping of events (#3654)
a7044d0480 is described below

commit a7044d04807968e98e12e6b18d18f4768bf21d1b
Author: Francisco Javier Tirado Sarti 
<[email protected]>
AuthorDate: Fri Oct 4 17:44:55 2024 +0200

    [incubator-kie-issues#1457] Allow grouping of events (#3654)
    
    * [Fix apache/incubator-kie-issues#1457] Allow grouping of events
    
    * Add test for coverage
    
    * [Fix apache/incubator-kie-issues#1457] Grouping of event serialization
    
    * [Fix apache/incubator-kie-issues#1457] Adding default constructor
    
    * [Fix apache/incubator-kie-issues#1457] Adding TYPE constant
    
    ---------
    
    Co-authored-by: gmunozfe <[email protected]>
---
 .../org/kie/kogito/event/AbstractDataEvent.java    |  16 +-
 .../process/MultipleProcessInstanceDataEvent.java  |  34 ++
 .../event/process/ProcessInstanceDataEvent.java    |   6 +
 .../MultipleUserTaskInstanceDataEvent.java         |  34 ++
 .../event/usertask/UserTaskInstanceDataEvent.java  |   5 +
 kogito-build/kogito-dependencies-bom/pom.xml       |   2 +-
 quarkus/addons/events/process/runtime/pom.xml      |  11 +
 ...r.java => AbstractMessagingEventPublisher.java} | 104 +++---
 .../process/GroupingMessagingEventPublisher.java   |  72 ++++
 .../process/ReactiveMessagingEventPublisher.java   | 135 +------
 .../GroupingMessagingEventPublisherTest.java       | 392 +++++++++++++++++++++
 11 files changed, 629 insertions(+), 182 deletions(-)

diff --git 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java
 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java
index 2fcdd70419..065111c851 100644
--- 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java
+++ 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java
@@ -177,6 +177,15 @@ public abstract class AbstractDataEvent<T> implements 
DataEvent<T> {
     protected AbstractDataEvent() {
     }
 
+    protected AbstractDataEvent(String type, URI source, T body) {
+        this.specVersion = SpecVersion.parse(SPEC_VERSION);
+        this.id = UUID.randomUUID().toString();
+        this.source = source;
+        this.type = type;
+        this.time = ZonedDateTime.now().toOffsetDateTime();
+        this.data = body;
+    }
+
     protected AbstractDataEvent(String type,
             String source,
             T body,
@@ -201,12 +210,7 @@ public abstract class AbstractDataEvent<T> implements 
DataEvent<T> {
             String subject,
             String dataContentType,
             String dataSchema) {
-        this.specVersion = SpecVersion.parse(SPEC_VERSION);
-        this.id = UUID.randomUUID().toString();
-        this.source = 
Optional.ofNullable(source).map(URI::create).orElse(null);
-        this.type = type;
-        this.time = ZonedDateTime.now().toOffsetDateTime();
-        this.data = body;
+        this(type, Optional.ofNullable(source).map(URI::create).orElse(null), 
body);
         setKogitoProcessInstanceId(kogitoProcessInstanceId);
         setKogitoRootProcessInstanceId(kogitoRootProcessInstanceId);
         setKogitoProcessId(kogitoProcessId);
diff --git 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java
 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java
new file mode 100644
index 0000000000..7db8c0e765
--- /dev/null
+++ 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.event.process;
+
+import java.net.URI;
+import java.util.Collection;
+
+public class MultipleProcessInstanceDataEvent extends 
ProcessInstanceDataEvent<Collection<ProcessInstanceDataEvent<?>>> {
+
+    public static final String TYPE = "MultipleProcessInstanceDataEvent";
+
+    public MultipleProcessInstanceDataEvent() {
+    }
+
+    public MultipleProcessInstanceDataEvent(URI source, 
Collection<ProcessInstanceDataEvent<?>> body) {
+        super(TYPE, source, body);
+    }
+}
diff --git 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java
 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java
index 8069df7ee3..31131563dc 100644
--- 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java
+++ 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java
@@ -18,6 +18,8 @@
  */
 package org.kie.kogito.event.process;
 
+import java.net.URI;
+
 import org.kie.kogito.event.AbstractDataEvent;
 
 public class ProcessInstanceDataEvent<T> extends AbstractDataEvent<T> {
@@ -29,6 +31,10 @@ public class ProcessInstanceDataEvent<T> extends 
AbstractDataEvent<T> {
         setData(body);
     }
 
+    protected ProcessInstanceDataEvent(String type, URI source, T body) {
+        super(type, source, body);
+    }
+
     public ProcessInstanceDataEvent(String type,
             String source,
             T body,
diff --git 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java
 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java
new file mode 100644
index 0000000000..b2b62c61d8
--- /dev/null
+++ 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.event.usertask;
+
+import java.net.URI;
+import java.util.Collection;
+
+public class MultipleUserTaskInstanceDataEvent extends 
UserTaskInstanceDataEvent<Collection<UserTaskInstanceDataEvent<?>>> {
+
+    public static final String TYPE = "MultipleUserTaskInstanceDataEvent";
+
+    public MultipleUserTaskInstanceDataEvent() {
+    }
+
+    public MultipleUserTaskInstanceDataEvent(URI source, 
Collection<UserTaskInstanceDataEvent<?>> body) {
+        super(TYPE, source, body);
+    }
+}
diff --git 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java
 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java
index 98fc652809..c4b3e0af5c 100644
--- 
a/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java
+++ 
b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java
@@ -18,6 +18,7 @@
  */
 package org.kie.kogito.event.usertask;
 
+import java.net.URI;
 import java.util.Set;
 
 import org.kie.kogito.event.AbstractDataEvent;
@@ -48,6 +49,10 @@ public class UserTaskInstanceDataEvent<T> extends 
AbstractDataEvent<T> {
         setData(body);
     }
 
+    protected UserTaskInstanceDataEvent(String type, URI source, T body) {
+        super(type, source, body);
+    }
+
     public UserTaskInstanceDataEvent(String type,
             String source,
             T body,
diff --git a/kogito-build/kogito-dependencies-bom/pom.xml 
b/kogito-build/kogito-dependencies-bom/pom.xml
index 0c815fc235..26fae00aa0 100644
--- a/kogito-build/kogito-dependencies-bom/pom.xml
+++ b/kogito-build/kogito-dependencies-bom/pom.xml
@@ -52,7 +52,7 @@
     
<version.io.quarkiverse.jackson-jq>2.0.2</version.io.quarkiverse.jackson-jq>
     
<version.io.quarkiverse.openapi.generator>2.4.1</version.io.quarkiverse.openapi.generator>
     <version.io.quarkiverse.asyncapi>0.3.0</version.io.quarkiverse.asyncapi>
-    
<version.io.quarkiverse.reactivemessaging.http>2.2.0</version.io.quarkiverse.reactivemessaging.http>
+    
<version.io.quarkiverse.reactivemessaging.http>2.4.1</version.io.quarkiverse.reactivemessaging.http>
     
<version.io.quarkiverse.embedded.postgresql>0.2.3</version.io.quarkiverse.embedded.postgresql>
     
<version.com.github.haifengl.smile>1.5.2</version.com.github.haifengl.smile>
     <version.com.github.javaparser>3.25.8</version.com.github.javaparser>
diff --git a/quarkus/addons/events/process/runtime/pom.xml 
b/quarkus/addons/events/process/runtime/pom.xml
index dc5d417218..50ef92684c 100644
--- a/quarkus/addons/events/process/runtime/pom.xml
+++ b/quarkus/addons/events/process/runtime/pom.xml
@@ -78,6 +78,17 @@
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
+    
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-junit5-mockito</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git 
a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
 
b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java
similarity index 59%
copy from 
quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
copy to 
quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java
index c232a1eb47..f8092c8357 100644
--- 
a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
+++ 
b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java
@@ -19,9 +19,10 @@
 package org.kie.kogito.events.process;
 
 import java.util.Collection;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 import org.eclipse.microprofile.reactive.messaging.Channel;
 import org.eclipse.microprofile.reactive.messaging.Message;
@@ -42,12 +43,10 @@ import 
io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage;
 import jakarta.annotation.PostConstruct;
 import jakarta.enterprise.inject.Instance;
 import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
 
-@Singleton
-public class ReactiveMessagingEventPublisher implements EventPublisher {
+public abstract class AbstractMessagingEventPublisher implements 
EventPublisher {
 
-    private static final Logger logger = 
LoggerFactory.getLogger(ReactiveMessagingEventPublisher.class);
+    private static final Logger logger = 
LoggerFactory.getLogger(AbstractMessagingEventPublisher.class);
 
     @Inject
     ObjectMapper json;
@@ -56,17 +55,17 @@ public class ReactiveMessagingEventPublisher implements 
EventPublisher {
     @Channel(PROCESS_INSTANCES_TOPIC_NAME)
     @OnOverflow(Strategy.UNBOUNDED_BUFFER)
     MutinyEmitter<String> processInstancesEventsEmitter;
-    private BiConsumer<MutinyEmitter<String>, Message<String>> 
processInstanceConsumer;
+    private AbstractMessageEmitter processInstanceConsumer;
 
     @Inject
     @Channel(PROCESS_DEFINITIONS_TOPIC_NAME)
     MutinyEmitter<String> processDefinitionEventsEmitter;
-    private BiConsumer<MutinyEmitter<String>, Message<String>> 
processDefinitionConsumer;
+    private AbstractMessageEmitter processDefinitionConsumer;
 
     @Inject
     @Channel(USER_TASK_INSTANCES_TOPIC_NAME)
     MutinyEmitter<String> userTasksEventsEmitter;
-    private BiConsumer<MutinyEmitter<String>, Message<String>> 
userTaskConsumer;
+    private AbstractMessageEmitter userTaskConsumer;
     @Inject
     EventsRuntimeConfig eventsRuntimeConfig;
 
@@ -78,29 +77,28 @@ public class ReactiveMessagingEventPublisher implements 
EventPublisher {
     @PostConstruct
     public void init() {
         decoratorProvider = decoratorProviderInstance.isResolvable() ? 
decoratorProviderInstance.get() : null;
-        processInstanceConsumer = 
eventsRuntimeConfig.isProcessInstancesPropagateError() ? new 
BlockingMessageEmitter() : new ReactiveMessageEmitter();
-        processDefinitionConsumer = 
eventsRuntimeConfig.isProcessDefinitionPropagateError() ? new 
BlockingMessageEmitter() : new ReactiveMessageEmitter();
-        userTaskConsumer = eventsRuntimeConfig.isUserTasksPropagateError() ? 
new BlockingMessageEmitter() : new ReactiveMessageEmitter();
+        processDefinitionConsumer = 
eventsRuntimeConfig.isProcessInstancesPropagateError() ? new 
BlockingMessageEmitter(processDefinitionEventsEmitter, 
PROCESS_DEFINITIONS_TOPIC_NAME)
+                : new ReactiveMessageEmitter(processDefinitionEventsEmitter, 
PROCESS_DEFINITIONS_TOPIC_NAME);
+        processInstanceConsumer = 
eventsRuntimeConfig.isProcessDefinitionPropagateError() ? new 
BlockingMessageEmitter(processInstancesEventsEmitter, 
PROCESS_INSTANCES_TOPIC_NAME)
+                : new ReactiveMessageEmitter(processInstancesEventsEmitter, 
PROCESS_INSTANCES_TOPIC_NAME);
+        userTaskConsumer = eventsRuntimeConfig.isUserTasksPropagateError() ? 
new BlockingMessageEmitter(userTasksEventsEmitter, 
USER_TASK_INSTANCES_TOPIC_NAME)
+                : new ReactiveMessageEmitter(userTasksEventsEmitter, 
USER_TASK_INSTANCES_TOPIC_NAME);
     }
 
-    @Override
-    public void publish(DataEvent<?> event) {
-
+    protected Optional<AbstractMessageEmitter> getConsumer(DataEvent<?> event) 
{
+        if (event == null) {
+            return Optional.empty();
+        }
         switch (event.getType()) {
             case "ProcessDefinitionEvent":
-                if (eventsRuntimeConfig.isProcessDefinitionEventsEnabled()) {
-                    publishToTopic(processDefinitionConsumer, event, 
processDefinitionEventsEmitter, PROCESS_DEFINITIONS_TOPIC_NAME);
-                }
-                break;
+                return eventsRuntimeConfig.isProcessDefinitionEventsEnabled() 
? Optional.of(processDefinitionConsumer) : Optional.empty();
+
             case "ProcessInstanceErrorDataEvent":
             case "ProcessInstanceNodeDataEvent":
             case "ProcessInstanceSLADataEvent":
             case "ProcessInstanceStateDataEvent":
             case "ProcessInstanceVariableDataEvent":
-                if (eventsRuntimeConfig.isProcessInstancesEventsEnabled()) {
-                    publishToTopic(processInstanceConsumer, event, 
processInstancesEventsEmitter, PROCESS_INSTANCES_TOPIC_NAME);
-                }
-                break;
+                return eventsRuntimeConfig.isProcessInstancesEventsEnabled() ? 
Optional.of(processInstanceConsumer) : Optional.empty();
 
             case "UserTaskInstanceAssignmentDataEvent":
             case "UserTaskInstanceAttachmentDataEvent":
@@ -108,12 +106,10 @@ public class ReactiveMessagingEventPublisher implements 
EventPublisher {
             case "UserTaskInstanceDeadlineDataEvent":
             case "UserTaskInstanceStateDataEvent":
             case "UserTaskInstanceVariableDataEvent":
-                if (eventsRuntimeConfig.isUserTasksEventsEnabled()) {
-                    publishToTopic(userTaskConsumer, event, 
userTasksEventsEmitter, USER_TASK_INSTANCES_TOPIC_NAME);
-                }
-                break;
+                return eventsRuntimeConfig.isUserTasksEventsEnabled() ? 
Optional.of(userTaskConsumer) : Optional.empty();
+
             default:
-                logger.debug("Unknown type of event '{}', ignoring for this 
publisher", event.getType());
+                return Optional.empty();
         }
     }
 
@@ -124,49 +120,69 @@ public class ReactiveMessagingEventPublisher implements 
EventPublisher {
         }
     }
 
-    protected void publishToTopic(BiConsumer<MutinyEmitter<String>, 
Message<String>> consumer, DataEvent<?> event, MutinyEmitter<String> emitter, 
String topic) {
-        logger.debug("About to publish event {} to topic {}", event, topic);
+    protected void publishToTopic(AbstractMessageEmitter emitter, Object 
event) {
+        logger.debug("About to publish event {} to topic {}", event, 
emitter.topic);
         Message<String> message = null;
         try {
             String eventString = json.writeValueAsString(event);
             logger.debug("Event payload '{}'", eventString);
             message = decorateMessage(ContextAwareMessage.of(eventString));
         } catch (Exception e) {
-            logger.error("Error while creating event to topic {} for event 
{}", topic, event);
+            logger.error("Error while creating event to topic {} for event 
{}", emitter.topic, event);
         }
         if (message != null) {
-            consumer.accept(emitter, message);
+            emitter.accept(message);
         }
     }
 
-    protected CompletionStage<Void> onAck(Message<String> message) {
-        logger.debug("Successfully published message {}", 
message.getPayload());
-        return CompletableFuture.completedFuture(null);
+    protected Message<String> decorateMessage(Message<String> message) {
+        return decoratorProvider != null ? decoratorProvider.decorate(message) 
: message;
     }
 
-    protected CompletionStage<Void> onNack(Throwable reason, Message<String> 
message) {
-        logger.error("Error while publishing message {}", message, reason);
-        return CompletableFuture.completedFuture(null);
-    }
+    protected static abstract class AbstractMessageEmitter implements 
Consumer<Message<String>> {
 
-    protected Message<String> decorateMessage(Message<String> message) {
-        return decoratorProvider != null ? decoratorProvider.decorate(message) 
: message;
+        protected final String topic;
+        protected final MutinyEmitter<String> emitter;
+
+        protected AbstractMessageEmitter(MutinyEmitter<String> emitter, String 
topic) {
+            this.emitter = emitter;
+            this.topic = topic;
+        }
     }
 
-    private class BlockingMessageEmitter implements 
BiConsumer<MutinyEmitter<String>, Message<String>> {
+    private static class BlockingMessageEmitter extends AbstractMessageEmitter 
{
+        protected BlockingMessageEmitter(MutinyEmitter<String> emitter, String 
topic) {
+            super(emitter, topic);
+        }
+
         @Override
-        public void accept(MutinyEmitter<String> emitter, Message<String> 
message) {
+        public void accept(Message<String> message) {
             emitter.sendMessageAndAwait(message);
             logger.debug("Successfully published message {}", 
message.getPayload());
         }
     }
 
-    private class ReactiveMessageEmitter implements 
BiConsumer<MutinyEmitter<String>, Message<String>> {
+    private static class ReactiveMessageEmitter extends AbstractMessageEmitter 
{
+        protected ReactiveMessageEmitter(MutinyEmitter<String> emitter, String 
topic) {
+            super(emitter, topic);
+        }
+
         @Override
-        public void accept(MutinyEmitter<String> emitter, Message<String> 
message) {
+        public void accept(Message<String> message) {
             emitter.sendMessageAndForget(message
                     .withAck(() -> onAck(message))
                     .withNack(reason -> onNack(reason, message)));
         }
+
+        private CompletionStage<Void> onAck(Message<String> message) {
+            logger.debug("Successfully published message {}", 
message.getPayload());
+            return CompletableFuture.completedFuture(null);
+        }
+
+        private CompletionStage<Void> onNack(Throwable reason, Message<String> 
message) {
+            logger.error("Error while publishing message {}", message, reason);
+            return CompletableFuture.completedFuture(null);
+        }
+
     }
 }
diff --git 
a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java
 
b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java
new file mode 100644
index 0000000000..ee1f284b3a
--- /dev/null
+++ 
b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.events.process;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.kie.kogito.event.DataEvent;
+import org.kie.kogito.event.process.MultipleProcessInstanceDataEvent;
+import org.kie.kogito.event.process.ProcessInstanceDataEvent;
+import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent;
+import org.kie.kogito.event.usertask.UserTaskInstanceDataEvent;
+
+import io.quarkus.arc.properties.IfBuildProperty;
+
+import jakarta.inject.Singleton;
+
+@Singleton
+@IfBuildProperty(name = "kogito.events.grouping", stringValue = "true")
+public class GroupingMessagingEventPublisher extends 
AbstractMessagingEventPublisher {
+
+    @Override
+    public void publish(DataEvent<?> event) {
+        publish(Collections.singletonList(event));
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public void publish(Collection<DataEvent<?>> events) {
+        Map<AbstractMessageEmitter, Collection> eventsByChannel = new 
HashMap<>();
+        for (DataEvent<?> event : events) {
+            getConsumer(event).ifPresent(c -> 
eventsByChannel.computeIfAbsent(c, k -> new ArrayList<>()).add(event));
+        }
+        eventsByChannel.entrySet().forEach(this::publishEvents);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private void publishEvents(Map.Entry<AbstractMessageEmitter, Collection> 
entry) {
+        DataEvent<?> firstEvent = (DataEvent<?>) 
entry.getValue().iterator().next();
+        URI source = firstEvent.getSource();
+        if (firstEvent instanceof UserTaskInstanceDataEvent) {
+            publishToTopic(entry.getKey(), new 
MultipleUserTaskInstanceDataEvent(source, 
(Collection<UserTaskInstanceDataEvent<?>>) entry.getValue()));
+        } else if (firstEvent instanceof ProcessInstanceDataEvent) {
+            publishToTopic(entry.getKey(), new 
MultipleProcessInstanceDataEvent(source, 
(Collection<ProcessInstanceDataEvent<?>>) entry.getValue()));
+        } else {
+            for (DataEvent<?> event : (Collection<DataEvent<?>>) 
entry.getValue()) {
+                publishToTopic(entry.getKey(), event);
+            }
+        }
+    }
+}
diff --git 
a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
 
b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
index c232a1eb47..c6aa4424f0 100644
--- 
a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
+++ 
b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java
@@ -19,102 +19,20 @@
 package org.kie.kogito.events.process;
 
 import java.util.Collection;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.function.BiConsumer;
 
-import org.eclipse.microprofile.reactive.messaging.Channel;
-import org.eclipse.microprofile.reactive.messaging.Message;
-import org.eclipse.microprofile.reactive.messaging.OnOverflow;
-import org.eclipse.microprofile.reactive.messaging.OnOverflow.Strategy;
-import 
org.kie.kogito.addon.quarkus.common.reactive.messaging.MessageDecoratorProvider;
 import org.kie.kogito.event.DataEvent;
-import org.kie.kogito.event.EventPublisher;
-import org.kie.kogito.events.config.EventsRuntimeConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.quarkus.arc.properties.UnlessBuildProperty;
 
-import io.smallrye.reactive.messaging.MutinyEmitter;
-import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage;
-
-import jakarta.annotation.PostConstruct;
-import jakarta.enterprise.inject.Instance;
-import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 
 @Singleton
-public class ReactiveMessagingEventPublisher implements EventPublisher {
-
-    private static final Logger logger = 
LoggerFactory.getLogger(ReactiveMessagingEventPublisher.class);
-
-    @Inject
-    ObjectMapper json;
-
-    @Inject
-    @Channel(PROCESS_INSTANCES_TOPIC_NAME)
-    @OnOverflow(Strategy.UNBOUNDED_BUFFER)
-    MutinyEmitter<String> processInstancesEventsEmitter;
-    private BiConsumer<MutinyEmitter<String>, Message<String>> 
processInstanceConsumer;
-
-    @Inject
-    @Channel(PROCESS_DEFINITIONS_TOPIC_NAME)
-    MutinyEmitter<String> processDefinitionEventsEmitter;
-    private BiConsumer<MutinyEmitter<String>, Message<String>> 
processDefinitionConsumer;
-
-    @Inject
-    @Channel(USER_TASK_INSTANCES_TOPIC_NAME)
-    MutinyEmitter<String> userTasksEventsEmitter;
-    private BiConsumer<MutinyEmitter<String>, Message<String>> 
userTaskConsumer;
-    @Inject
-    EventsRuntimeConfig eventsRuntimeConfig;
-
-    @Inject
-    Instance<MessageDecoratorProvider> decoratorProviderInstance;
-
-    private MessageDecoratorProvider decoratorProvider;
-
-    @PostConstruct
-    public void init() {
-        decoratorProvider = decoratorProviderInstance.isResolvable() ? 
decoratorProviderInstance.get() : null;
-        processInstanceConsumer = 
eventsRuntimeConfig.isProcessInstancesPropagateError() ? new 
BlockingMessageEmitter() : new ReactiveMessageEmitter();
-        processDefinitionConsumer = 
eventsRuntimeConfig.isProcessDefinitionPropagateError() ? new 
BlockingMessageEmitter() : new ReactiveMessageEmitter();
-        userTaskConsumer = eventsRuntimeConfig.isUserTasksPropagateError() ? 
new BlockingMessageEmitter() : new ReactiveMessageEmitter();
-    }
+@UnlessBuildProperty(name = "kogito.events.grouping", stringValue = "true", 
enableIfMissing = true)
+public class ReactiveMessagingEventPublisher extends 
AbstractMessagingEventPublisher {
 
     @Override
     public void publish(DataEvent<?> event) {
-
-        switch (event.getType()) {
-            case "ProcessDefinitionEvent":
-                if (eventsRuntimeConfig.isProcessDefinitionEventsEnabled()) {
-                    publishToTopic(processDefinitionConsumer, event, 
processDefinitionEventsEmitter, PROCESS_DEFINITIONS_TOPIC_NAME);
-                }
-                break;
-            case "ProcessInstanceErrorDataEvent":
-            case "ProcessInstanceNodeDataEvent":
-            case "ProcessInstanceSLADataEvent":
-            case "ProcessInstanceStateDataEvent":
-            case "ProcessInstanceVariableDataEvent":
-                if (eventsRuntimeConfig.isProcessInstancesEventsEnabled()) {
-                    publishToTopic(processInstanceConsumer, event, 
processInstancesEventsEmitter, PROCESS_INSTANCES_TOPIC_NAME);
-                }
-                break;
-
-            case "UserTaskInstanceAssignmentDataEvent":
-            case "UserTaskInstanceAttachmentDataEvent":
-            case "UserTaskInstanceCommentDataEvent":
-            case "UserTaskInstanceDeadlineDataEvent":
-            case "UserTaskInstanceStateDataEvent":
-            case "UserTaskInstanceVariableDataEvent":
-                if (eventsRuntimeConfig.isUserTasksEventsEnabled()) {
-                    publishToTopic(userTaskConsumer, event, 
userTasksEventsEmitter, USER_TASK_INSTANCES_TOPIC_NAME);
-                }
-                break;
-            default:
-                logger.debug("Unknown type of event '{}', ignoring for this 
publisher", event.getType());
-        }
+        getConsumer(event).ifPresent(emitter -> publishToTopic(emitter, 
event));
     }
 
     @Override
@@ -124,49 +42,4 @@ public class ReactiveMessagingEventPublisher implements 
EventPublisher {
         }
     }
 
-    protected void publishToTopic(BiConsumer<MutinyEmitter<String>, 
Message<String>> consumer, DataEvent<?> event, MutinyEmitter<String> emitter, 
String topic) {
-        logger.debug("About to publish event {} to topic {}", event, topic);
-        Message<String> message = null;
-        try {
-            String eventString = json.writeValueAsString(event);
-            logger.debug("Event payload '{}'", eventString);
-            message = decorateMessage(ContextAwareMessage.of(eventString));
-        } catch (Exception e) {
-            logger.error("Error while creating event to topic {} for event 
{}", topic, event);
-        }
-        if (message != null) {
-            consumer.accept(emitter, message);
-        }
-    }
-
-    protected CompletionStage<Void> onAck(Message<String> message) {
-        logger.debug("Successfully published message {}", 
message.getPayload());
-        return CompletableFuture.completedFuture(null);
-    }
-
-    protected CompletionStage<Void> onNack(Throwable reason, Message<String> 
message) {
-        logger.error("Error while publishing message {}", message, reason);
-        return CompletableFuture.completedFuture(null);
-    }
-
-    protected Message<String> decorateMessage(Message<String> message) {
-        return decoratorProvider != null ? decoratorProvider.decorate(message) 
: message;
-    }
-
-    private class BlockingMessageEmitter implements 
BiConsumer<MutinyEmitter<String>, Message<String>> {
-        @Override
-        public void accept(MutinyEmitter<String> emitter, Message<String> 
message) {
-            emitter.sendMessageAndAwait(message);
-            logger.debug("Successfully published message {}", 
message.getPayload());
-        }
-    }
-
-    private class ReactiveMessageEmitter implements 
BiConsumer<MutinyEmitter<String>, Message<String>> {
-        @Override
-        public void accept(MutinyEmitter<String> emitter, Message<String> 
message) {
-            emitter.sendMessageAndForget(message
-                    .withAck(() -> onAck(message))
-                    .withNack(reason -> onNack(reason, message)));
-        }
-    }
 }
diff --git 
a/quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java
 
b/quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java
new file mode 100644
index 0000000000..0d784d3a53
--- /dev/null
+++ 
b/quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.kie.kogito.events.process;
+
+import java.util.*;
+
+import org.eclipse.microprofile.reactive.messaging.Message;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import 
org.kie.kogito.addon.quarkus.common.reactive.messaging.MessageDecoratorProvider;
+import org.kie.kogito.event.DataEvent;
+import org.kie.kogito.event.process.MultipleProcessInstanceDataEvent;
+import org.kie.kogito.event.process.ProcessInstanceDataEvent;
+import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent;
+import org.kie.kogito.event.usertask.UserTaskInstanceDataEvent;
+import org.kie.kogito.events.config.EventsRuntimeConfig;
+import 
org.kie.kogito.events.process.AbstractMessagingEventPublisher.AbstractMessageEmitter;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.smallrye.reactive.messaging.MutinyEmitter;
+
+import jakarta.enterprise.inject.Instance;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+@SuppressWarnings("unchecked")
+public class GroupingMessagingEventPublisherTest {
+
+    @Mock
+    private ObjectMapper json;
+
+    @Mock
+    private MutinyEmitter<String> processInstancesEventsEmitter;
+
+    @Mock
+    private MutinyEmitter<String> processDefinitionEventsEmitter;
+
+    @Mock
+    private MutinyEmitter<String> userTasksEventsEmitter;
+
+    @Mock
+    private EventsRuntimeConfig eventsRuntimeConfig;
+
+    @Mock
+    private MessageDecoratorProvider decoratorProvider;
+
+    @Mock
+    private Message<String> decoratedMessage;
+
+    @Mock
+    private Instance<MessageDecoratorProvider> decoratorProviderInstance;
+
+    @Mock
+    private AbstractMessagingEventPublisher.AbstractMessageEmitter 
processInstanceConsumer;
+
+    @Mock
+    private AbstractMessagingEventPublisher.AbstractMessageEmitter 
userTaskConsumer;
+
+    @Mock
+    private AbstractMessagingEventPublisher.AbstractMessageEmitter 
processDefinitionConsumer;
+
+    @Spy
+    @InjectMocks
+    private GroupingMessagingEventPublisher groupingMessagingEventPublisher;
+
+    @Spy
+    @InjectMocks
+    private ReactiveMessagingEventPublisher reactiveMessagingEventPublisher;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+
+        when(decoratorProviderInstance.isResolvable()).thenReturn(true);
+        when(decoratorProviderInstance.get()).thenReturn(decoratorProvider);
+
+        
when(eventsRuntimeConfig.isProcessInstancesPropagateError()).thenReturn(false);
+        
when(eventsRuntimeConfig.isProcessDefinitionPropagateError()).thenReturn(false);
+        
when(eventsRuntimeConfig.isUserTasksPropagateError()).thenReturn(false);
+
+        
when(eventsRuntimeConfig.isProcessInstancesEventsEnabled()).thenReturn(true);
+        when(eventsRuntimeConfig.isUserTasksEventsEnabled()).thenReturn(true);
+    }
+
+    @Test
+    public void testGroupingMessagingEventPublisher_publish() throws Exception 
{
+        DataEvent<String> event = mock(DataEvent.class);
+        when(event.getType()).thenReturn("ProcessInstanceErrorDataEvent");
+
+        // Test initialization
+        groupingMessagingEventPublisher.init();
+        
when(decoratorProvider.decorate(any(Message.class))).thenReturn(decoratedMessage);
+
+        // Mock the message behavior
+        mockMessageForBothAckNack(decoratedMessage);
+
+        // Call method
+        groupingMessagingEventPublisher.publish(event);
+
+        // Verify that the consumer has been invoked
+        verify(processInstancesEventsEmitter).sendMessageAndForget(any());
+    }
+
+    @Test
+    public void testReactiveMessagingEventPublisher_publish() throws Exception 
{
+        DataEvent<String> event = mock(DataEvent.class);
+        when(event.getType()).thenReturn("ProcessInstanceErrorDataEvent");
+
+        // Test initialization
+        reactiveMessagingEventPublisher.init();
+        
when(decoratorProvider.decorate(any(Message.class))).thenReturn(decoratedMessage);
+
+        // Mock the message behavior
+        mockMessageForBothAckNack(decoratedMessage);
+
+        // Call method
+        reactiveMessagingEventPublisher.publish(event);
+
+        // Verify that the consumer has been invoked
+        verify(processInstancesEventsEmitter).sendMessageAndForget(any());
+    }
+
+    @Test
+    public void testPublishGroupingByChannel() {
+        // Create mock events
+        DataEvent<String> processInstanceEvent = 
mock(ProcessInstanceDataEvent.class);
+        
when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent");
+
+        DataEvent<String> userTaskEvent = 
mock(UserTaskInstanceDataEvent.class);
+        
when(userTaskEvent.getType()).thenReturn("UserTaskInstanceStateDataEvent");
+
+        // Mock getConsumer() to return different emitters based on event type
+        
doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent);
+        
doReturn(Optional.of(userTaskConsumer)).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent);
+
+        // Create a collection of events with different types (ProcessInstance 
and UserTask)
+        Collection<DataEvent<?>> events = Arrays.asList(processInstanceEvent, 
userTaskEvent);
+
+        // Spy on the publisher's internal method to verify the calls
+        
doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any());
+
+        // Invoke the method to test
+        groupingMessagingEventPublisher.publish(events);
+
+        // Capture and verify that the correct emitter was used for each event
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(processInstanceConsumer), 
any(MultipleProcessInstanceDataEvent.class));
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(userTaskConsumer), 
any(MultipleUserTaskInstanceDataEvent.class));
+    }
+
+    @Test
+    public void testPublishMultipleEventsGroupedByChannel() {
+        // Create multiple events of different types
+        DataEvent<String> processInstanceEvent1 = 
mock(ProcessInstanceDataEvent.class);
+        DataEvent<String> processInstanceEvent2 = 
mock(ProcessInstanceDataEvent.class);
+        DataEvent<String> userTaskEvent1 = 
mock(UserTaskInstanceDataEvent.class);
+        DataEvent<String> userTaskEvent2 = 
mock(UserTaskInstanceDataEvent.class);
+
+        
when(processInstanceEvent1.getType()).thenReturn("ProcessInstanceStateDataEvent");
+        
when(processInstanceEvent2.getType()).thenReturn("ProcessInstanceStateDataEvent");
+        
when(userTaskEvent1.getType()).thenReturn("UserTaskInstanceStateDataEvent");
+        
when(userTaskEvent2.getType()).thenReturn("UserTaskInstanceStateDataEvent");
+
+        // Mock getConsumer() to return corresponding emitters for event types
+        
doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent1);
+        
doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent2);
+        
doReturn(Optional.of(userTaskConsumer)).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent1);
+        
doReturn(Optional.of(userTaskConsumer)).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent2);
+
+        // Create a collection of events that would be grouped by channel
+        Collection<DataEvent<?>> events = Arrays.asList(processInstanceEvent1, 
processInstanceEvent2, userTaskEvent1, userTaskEvent2);
+
+        // Spy on the internal publishToTopic to verify grouping
+        
doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any());
+
+        // Invoke the method to test
+        groupingMessagingEventPublisher.publish(events);
+
+        // Verify that two grouped publishToTopic calls are made: one for 
processInstanceConsumer, one for userTaskConsumer
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(processInstanceConsumer), 
any(MultipleProcessInstanceDataEvent.class));
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(userTaskConsumer), 
any(MultipleUserTaskInstanceDataEvent.class));
+
+        // Verify that the right number of events was grouped and passed to 
each emitter
+        ArgumentCaptor<MultipleProcessInstanceDataEvent> captorPI = 
ArgumentCaptor.forClass(MultipleProcessInstanceDataEvent.class);
+
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(processInstanceConsumer), captorPI.capture());
+        MultipleProcessInstanceDataEvent groupedProcessInstanceEvents = 
captorPI.getValue();
+        assertEquals(2, groupedProcessInstanceEvents.getData().size()); // 
both processInstanceEvents are grouped
+
+        ArgumentCaptor<MultipleUserTaskInstanceDataEvent> captorUT = 
ArgumentCaptor.forClass(MultipleUserTaskInstanceDataEvent.class);
+
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(userTaskConsumer), captorUT.capture());
+        MultipleUserTaskInstanceDataEvent groupedUserTaskEvents = 
captorUT.getValue();
+        assertEquals(2, groupedUserTaskEvents.getData().size()); // both 
userTaskEvents are grouped
+    }
+
+    @Test
+    public void testPublishEmptyEventsCollection() {
+        Collection<DataEvent<?>> events = Collections.emptyList();
+
+        // Spy on the internal publishToTopic to verify no calls are made
+        
doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any());
+
+        groupingMessagingEventPublisher.publish(events);
+
+        // Verify that publishToTopic is never called
+        verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), 
anyCollection());
+    }
+
+    @Test
+    public void testNoConsumersFound() {
+        DataEvent<String> processInstanceEvent = mock(DataEvent.class);
+        
when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent");
+
+        DataEvent<String> userTaskEvent = mock(DataEvent.class);
+        
when(userTaskEvent.getType()).thenReturn("UserTaskInstanceStateDataEvent");
+
+        // Mock getConsumer() to return empty optionals (no consumers found)
+        
doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent);
+        
doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent);
+
+        // Create a collection of events
+        Collection<DataEvent<?>> events = Arrays.asList(processInstanceEvent, 
userTaskEvent);
+
+        // Spy on the publisher's internal method to verify no calls are made
+        
doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any());
+
+        // Invoke the method to test
+        groupingMessagingEventPublisher.publish(events);
+
+        // Verify that publishToTopic is never called since no consumers were 
found
+        verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), 
anyCollection());
+    }
+
+    @Test
+    void testPublishToTopic_ExceptionHandling() throws Exception {
+        DataEvent<String> event = mock(DataEvent.class);
+        when(event.getType()).thenReturn("ProcessInstanceErrorDataEvent");
+
+        groupingMessagingEventPublisher.init();
+        when(decoratorProvider.decorate(any(Message.class))).thenThrow(new 
RuntimeException("Serialization error"));
+
+        // Mock the message behavior
+        mockMessageForBothAckNack(decoratedMessage);
+
+        // Call method
+        groupingMessagingEventPublisher.publish(event);
+
+        // Check that emitter.sendMessageAndForget was never called
+        verify(processInstancesEventsEmitter, 
never()).sendMessageAndForget(any());
+    }
+
+    @Test
+    public void testPublishUnsupportedEventType() {
+        DataEvent<String> unsupportedEvent = mock(DataEvent.class);
+        when(unsupportedEvent.getType()).thenReturn("UnsupportedEvent");
+
+        
doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(unsupportedEvent);
+
+        Collection<DataEvent<?>> events = 
Collections.singletonList(unsupportedEvent);
+
+        groupingMessagingEventPublisher.publish(events);
+
+        // Verify no publishing occurred since no consumer exists for 
unsupported event
+        verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), 
anyCollection());
+    }
+
+    @Test
+    public void testEventsDisabledInConfig() {
+        DataEvent<String> processInstanceEvent = mock(DataEvent.class);
+        
when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent");
+
+        DataEvent<String> userTaskEvent = mock(DataEvent.class);
+        
when(userTaskEvent.getType()).thenReturn("UserTaskInstanceStateDataEvent");
+
+        // Disable process and user task events in the config
+        
when(eventsRuntimeConfig.isProcessInstancesEventsEnabled()).thenReturn(false);
+        when(eventsRuntimeConfig.isUserTasksEventsEnabled()).thenReturn(false);
+
+        Collection<DataEvent<?>> events = Arrays.asList(processInstanceEvent, 
userTaskEvent);
+
+        groupingMessagingEventPublisher.publish(events);
+
+        // Verify no publishing occurred since events are disabled
+        verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), 
anyCollection());
+    }
+
+    @Test
+    public void testNullEventInCollection() {
+        DataEvent<String> validEvent = mock(ProcessInstanceDataEvent.class);
+        when(validEvent.getType()).thenReturn("ProcessInstanceStateDataEvent");
+
+        Collection<DataEvent<?>> events = Arrays.asList(validEvent, null); // 
One valid event and one null event
+
+        // Return a mock consumer for the valid event
+        
doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(validEvent);
+
+        // Call the method
+        groupingMessagingEventPublisher.publish(events);
+
+        // Verify the valid event is processed
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(processInstanceConsumer), 
any(MultipleProcessInstanceDataEvent.class));
+    }
+
+    @Test
+    public void testDecorateMessage() {
+        Message<String> rawMessage = mock(Message.class);
+        
when(decoratorProvider.decorate(rawMessage)).thenReturn(decoratedMessage);
+
+        reactiveMessagingEventPublisher.init();
+
+        Message<String> result = 
reactiveMessagingEventPublisher.decorateMessage(rawMessage);
+        assertEquals(decoratedMessage, result);
+
+        verify(decoratorProvider).decorate(rawMessage);
+    }
+
+    @Test
+    public void testPublishToTopicWithDecorator() throws Exception {
+        Object event = new Object();
+        when(json.writeValueAsString(event)).thenReturn("eventString");
+
+        reactiveMessagingEventPublisher.init();
+
+        // Mock the message emitter
+        AbstractMessagingEventPublisher.AbstractMessageEmitter mockEmitter = 
mock(AbstractMessagingEventPublisher.AbstractMessageEmitter.class);
+
+        // Ensure decorated message is used
+        
when(decoratorProvider.decorate(any(Message.class))).thenReturn(decoratedMessage);
+
+        // Spy on the reactiveMessagingEventPublisher to allow publishToTopic
+        reactiveMessagingEventPublisher.publishToTopic(mockEmitter, event);
+
+        // Verify that the message was decorated and sent
+        verify(decoratorProvider).decorate(any(Message.class));
+        verify(mockEmitter).accept(decoratedMessage);
+    }
+
+    @Test
+    public void testPublishWithMultipleEventTypesSomeWithoutConsumers() {
+        DataEvent<String> processInstanceEvent = 
mock(ProcessInstanceDataEvent.class);
+        
when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent");
+
+        DataEvent<String> unsupportedEvent = mock(DataEvent.class);
+        when(unsupportedEvent.getType()).thenReturn("UnsupportedEvent");
+
+        
doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent);
+        
doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(unsupportedEvent);
+
+        Collection<DataEvent<?>> events = Arrays.asList(processInstanceEvent, 
unsupportedEvent);
+
+        groupingMessagingEventPublisher.publish(events);
+
+        // Ensure that only the supported event was published
+        verify(groupingMessagingEventPublisher, 
times(1)).publishToTopic(eq(processInstanceConsumer), 
any(MultipleProcessInstanceDataEvent.class));
+        verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), 
eq(Collections.singletonList(unsupportedEvent)));
+    }
+
+    private void mockMessageForBothAckNack(Message<String> message) {
+        when(message.withAck(any())).thenReturn(message);
+        when(message.withNack(any())).thenReturn(message);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to