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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 446cc76aa55b CAMEL-26277: camel-xmpp - a consumer with a participant 
routes only the chat messages from that participant (#29106)
446cc76aa55b is described below

commit 446cc76aa55b172d820698872730aea547bff841
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 18:07:35 2026 +0100

    CAMEL-26277: camel-xmpp - a consumer with a participant routes only the 
chat messages from that participant (#29106)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/components/xmpp.json  |   2 +-
 .../apache/camel/catalog/docs/xmpp-component.adoc  |   6 ++
 .../org/apache/camel/component/xmpp/xmpp.json      |   2 +-
 .../camel-xmpp/src/main/docs/xmpp-component.adoc   |   6 ++
 .../apache/camel/component/xmpp/XmppConsumer.java  |  14 ++-
 .../apache/camel/component/xmpp/XmppEndpoint.java  |  11 +-
 .../xmpp/XmppConsumerParticipantTest.java          | 114 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   7 ++
 .../builder/endpoint/StaticEndpointBuilders.java   |  14 ++-
 .../endpoint/dsl/XmppEndpointBuilderFactory.java   |  14 ++-
 10 files changed, 178 insertions(+), 12 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xmpp.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xmpp.json
index 4137ca97a77c..8fab88356f24 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xmpp.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xmpp.json
@@ -40,7 +40,7 @@
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Hostname for the chat server" },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"common", "label": "", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 5222, 
"description": "Port number for the chat server" },
-    "participant": { "index": 2, "kind": "path", "displayName": "Participant", 
"group": "common", "label": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "JID (Jabber ID) of person to receive messages. 
room parameter has precedence over participant." },
+    "participant": { "index": 2, "kind": "path", "displayName": "Participant", 
"group": "common", "label": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "JID (Jabber ID) of the person to chat with: 
the producer sends the messages to this person, and the consumer only routes 
the chat messages from this person (without a participant, the consumer routes 
the chat messages of every sende [...]
     "login": { "index": 3, "kind": "parameter", "displayName": "Login", 
"group": "common", "label": "common", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": true, "description": "Whether to login the user." },
     "nickname": { "index": 4, "kind": "parameter", "displayName": "Nickname", 
"group": "common", "label": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Use nickname when joining room. If room is 
specified and nickname is not, user will be used for the nickname." },
     "pubsub": { "index": 5, "kind": "parameter", "displayName": "Pubsub", 
"group": "common", "label": "common", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "description": "Accept pubsub packets on input, 
default is false" },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmpp-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmpp-component.adoc
index 7354f9322955..da4f038211e6 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmpp-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/xmpp-component.adoc
@@ -55,6 +55,12 @@ settings) share one XMPP connection, also across endpoints 
with different partic
 `connectionConfig` option, endpoints share a connection only when they refer 
to the same `ConnectionConfiguration`
 instance, for example the same bean in the registry, because that class has no 
`equals()`.
 
+=== Receiving chat messages
+
+A consumer with a participant routes only the chat messages from that 
participant (any resource of its JID). A consumer
+without a participant routes the chat messages of every sender. The JID of the 
sender is in the `CamelXmppFrom` header.
+A consumer with a `room` routes the messages of the room, and the chat 
messages of every sender.
+
 === Headers and setting Subject or Language
 
 Camel sets the message IN headers as properties on the XMPP message. You
diff --git 
a/components/camel-xmpp/src/generated/resources/META-INF/org/apache/camel/component/xmpp/xmpp.json
 
b/components/camel-xmpp/src/generated/resources/META-INF/org/apache/camel/component/xmpp/xmpp.json
index 4137ca97a77c..8fab88356f24 100644
--- 
a/components/camel-xmpp/src/generated/resources/META-INF/org/apache/camel/component/xmpp/xmpp.json
+++ 
b/components/camel-xmpp/src/generated/resources/META-INF/org/apache/camel/component/xmpp/xmpp.json
@@ -40,7 +40,7 @@
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Hostname for the chat server" },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"common", "label": "", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 5222, 
"description": "Port number for the chat server" },
-    "participant": { "index": 2, "kind": "path", "displayName": "Participant", 
"group": "common", "label": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "JID (Jabber ID) of person to receive messages. 
room parameter has precedence over participant." },
+    "participant": { "index": 2, "kind": "path", "displayName": "Participant", 
"group": "common", "label": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "JID (Jabber ID) of the person to chat with: 
the producer sends the messages to this person, and the consumer only routes 
the chat messages from this person (without a participant, the consumer routes 
the chat messages of every sende [...]
     "login": { "index": 3, "kind": "parameter", "displayName": "Login", 
"group": "common", "label": "common", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": true, "description": "Whether to login the user." },
     "nickname": { "index": 4, "kind": "parameter", "displayName": "Nickname", 
"group": "common", "label": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Use nickname when joining room. If room is 
specified and nickname is not, user will be used for the nickname." },
     "pubsub": { "index": 5, "kind": "parameter", "displayName": "Pubsub", 
"group": "common", "label": "common", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "description": "Accept pubsub packets on input, 
default is false" },
diff --git a/components/camel-xmpp/src/main/docs/xmpp-component.adoc 
b/components/camel-xmpp/src/main/docs/xmpp-component.adoc
index 7354f9322955..da4f038211e6 100644
--- a/components/camel-xmpp/src/main/docs/xmpp-component.adoc
+++ b/components/camel-xmpp/src/main/docs/xmpp-component.adoc
@@ -55,6 +55,12 @@ settings) share one XMPP connection, also across endpoints 
with different partic
 `connectionConfig` option, endpoints share a connection only when they refer 
to the same `ConnectionConfiguration`
 instance, for example the same bean in the registry, because that class has no 
`equals()`.
 
+=== Receiving chat messages
+
+A consumer with a participant routes only the chat messages from that 
participant (any resource of its JID). A consumer
+without a participant routes the chat messages of every sender. The JID of the 
sender is in the `CamelXmppFrom` header.
+A consumer with a `room` routes the messages of the room, and the chat 
messages of every sender.
+
 === Headers and setting Subject or Language
 
 Camel sets the message IN headers as properties on the XMPP message. You
diff --git 
a/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppConsumer.java
 
b/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppConsumer.java
index 90bd4310f448..0ca2e70dc82e 100644
--- 
a/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppConsumer.java
+++ 
b/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppConsumer.java
@@ -72,6 +72,8 @@ public class XmppConsumer extends DefaultConsumer implements 
IncomingChatMessage
         }
     };
     private ChatManager chatManager;
+    // the participant whose chat messages are routed, or null to route the 
chat messages of every sender
+    private volatile EntityBareJid participantJid;
     // package-private for tests
     XMPPTCPConnection connection;
     private volatile ScheduledExecutorService scheduledExecutor;
@@ -98,6 +100,10 @@ public class XmppConsumer extends DefaultConsumer 
implements IncomingChatMessage
             }
         }
 
+        if (endpoint.getRoom() == null && endpoint.hasParticipant()) {
+            // the chat manager of the account passes the chat messages of 
every sender to every consumer of the account
+            participantJid = 
JidCreate.entityBareFrom(endpoint.resolveParticipant(connection));
+        }
         chatManager = ChatManager.getInstanceFor(connection);
         chatManager.addIncomingListener(this);
 
@@ -284,6 +290,12 @@ public class XmppConsumer extends DefaultConsumer 
implements IncomingChatMessage
 
     @Override
     public void newIncomingMessage(EntityBareJid from, Message message, Chat 
chat) {
+        EntityBareJid participant = participantJid;
+        if (participant != null && !participant.equals(from)) {
+            LOG.trace("Ignoring XMPP chat message for {} from {}, which is not 
the participant {}",
+                    endpoint.getUser(), from, participant);
+            return;
+        }
         processMessage(message);
     }
 
@@ -302,7 +314,7 @@ public class XmppConsumer extends DefaultConsumer 
implements IncomingChatMessage
     public void processMessage(Chat chat, Message message) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("Received XMPP message for {} from {} : {}",
-                    endpoint.getUser(), endpoint.getParticipant(), 
message.getBody());
+                    endpoint.getUser(), message.getFrom(), message.getBody());
         }
 
         Exchange exchange = createExchange(message);
diff --git 
a/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppEndpoint.java
 
b/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppEndpoint.java
index d2c7f8772dd0..e54704139ffb 100644
--- 
a/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppEndpoint.java
+++ 
b/components/camel-xmpp/src/main/java/org/apache/camel/component/xmpp/XmppEndpoint.java
@@ -487,12 +487,21 @@ public class XmppEndpoint extends DefaultEndpoint 
implements HeaderFilterStrateg
     }
 
     /**
-     * JID (Jabber ID) of person to receive messages. room parameter has 
precedence over participant.
+     * JID (Jabber ID) of the person to chat with: the producer sends the 
messages to this person, and the consumer only
+     * routes the chat messages from this person (without a participant, the 
consumer routes the chat messages of every
+     * sender). room parameter has precedence over participant.
      */
     public void setParticipant(String participant) {
         this.participant = participant;
     }
 
+    /**
+     * Whether a participant is configured on this endpoint ({@link 
#getParticipant()} falls back to the user).
+     */
+    public boolean hasParticipant() {
+        return participant != null;
+    }
+
     public String getNickname() {
         return nickname != null ? nickname : getUser();
     }
diff --git 
a/components/camel-xmpp/src/test/java/org/apache/camel/component/xmpp/XmppConsumerParticipantTest.java
 
b/components/camel-xmpp/src/test/java/org/apache/camel/component/xmpp/XmppConsumerParticipantTest.java
new file mode 100644
index 000000000000..703b564fa5e9
--- /dev/null
+++ 
b/components/camel-xmpp/src/test/java/org/apache/camel/component/xmpp/XmppConsumerParticipantTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.apache.camel.component.xmpp;
+
+import java.net.InetAddress;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.jivesoftware.smack.ConnectionConfiguration;
+import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+
+/**
+ * A consumer with a participant routes only the chat messages from that 
participant, although the consumers of one
+ * account share its connection, which receives the chat messages of every 
sender. A consumer without a participant
+ * routes the chat messages of every sender.
+ */
+class XmppConsumerParticipantTest extends CamelTestSupport {
+
+    private static final String CAROL = "carol@" + FakeXmppServer.DOMAIN;
+    private static final String DAVE = "dave@" + FakeXmppServer.DOMAIN;
+    private static final String ERIN = "erin@" + FakeXmppServer.DOMAIN;
+    private static final String FRANK = "frank@" + FakeXmppServer.DOMAIN;
+
+    private FakeXmppServer server;
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        server = new FakeXmppServer();
+        CamelContext context = super.createCamelContext();
+        // the test server has no TLS
+        context.getRegistry().bind("connectionConfig", 
XMPPTCPConnectionConfiguration.builder()
+                .setHostAddress(InetAddress.getLoopbackAddress())
+                .setPort(server.getPort())
+                .setXmppDomain(FakeXmppServer.DOMAIN)
+                .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
+                .build());
+        return context;
+    }
+
+    @AfterEach
+    void closeServer() throws Exception {
+        // stop the routes before the server, so the consumers do not try to 
connect to a server that is closed
+        context.stop();
+        if (server != null) {
+            server.close();
+        }
+    }
+
+    @Test
+    void consumerRoutesOnlyTheChatMessagesOfItsParticipant() throws Exception {
+        MockEndpoint carol = getMockEndpoint("mock:carol");
+        carol.expectedBodiesReceived("from carol");
+        
carol.message(0).header(XmppConstants.FROM).convertToString().isEqualTo(CAROL + 
"/phone");
+        // with doc the body is the Smack message
+        MockEndpoint dave = getMockEndpoint("mock:dave");
+        dave.expectedMessageCount(1);
+        
dave.message(0).header(XmppConstants.FROM).convertToString().isEqualTo(DAVE + 
"/phone");
+        // a participant without a domain is in the domain of the server
+        MockEndpoint erin = getMockEndpoint("mock:erin");
+        erin.expectedBodiesReceived("from erin");
+        MockEndpoint all = getMockEndpoint("mock:all");
+        all.expectedBodiesReceivedInAnyOrder("from carol", "from dave", "from 
erin", "from frank");
+
+        await().atMost(10, TimeUnit.SECONDS).until(() -> 
server.boundSessions() == 1);
+        server.sendChat(CAROL, "from carol");
+        server.sendChat(DAVE, "from dave");
+        server.sendChat(ERIN, "from erin");
+        server.sendChat(FRANK, "from frank");
+
+        all.assertIsSatisfied();
+        // the consumers with a participant must not route the chat messages 
of the other senders
+        carol.setAssertPeriod(500);
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    private String uri(String participant) {
+        return "xmpp://[email protected]:" + server.getPort() + "/" + participant
+               + "?password=secret&connectionConfig=#connectionConfig";
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from(uri(CAROL)).routeId("carol").to("mock:carol");
+                from(uri(DAVE) + "&doc=true").routeId("dave").to("mock:dave");
+                from(uri("erin")).routeId("erin").to("mock:erin");
+                from(uri("")).routeId("all").to("mock:all");
+            }
+        };
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 6ad1a45e7755..9deef6cc1d84 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -3035,6 +3035,13 @@ they refer to the same `ConnectionConfiguration` 
instance (for example the same
 `XmppEndpoint.createConnection()` now creates a new connection on every call, 
instead of returning the connection of
 the endpoint, and an endpoint no longer disconnects a connection when it stops.
 
+=== camel-xmpp - a consumer with a participant routes only the chat messages 
from that participant
+
+A consumer with a participant (for example 
`xmpp://[email protected]/[email protected]`) and no `room` now routes only
+the chat messages from that participant. Before, it routed the chat messages 
of every sender to the account, and every
+consumer of the account routed each of them. To route the chat messages of 
every sender, leave out the participant
+(`xmpp://[email protected]`), and read the sender from the `CamelXmppFrom` 
header.
+
 === camel-google-bigquery - a grouped exchange with a body that is not a row 
fails as a whole
 
 When one exchange of a grouped exchange (`Exchange.GROUPED_EXCHANGE`) sent to 
`google-bigquery` has a body that is not a
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
index cc1a6ef5ae39..8af302c2b0a2 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
@@ -18509,8 +18509,11 @@ public class StaticEndpointBuilders {
      * Default value: 5222
      * 
      * Path parameter: participant
-     * JID (Jabber ID) of person to receive messages. room parameter has
-     * precedence over participant.
+     * JID (Jabber ID) of the person to chat with: the producer sends the
+     * messages to this person, and the consumer only routes the chat messages
+     * from this person (without a participant, the consumer routes the chat
+     * messages of every sender). room parameter has precedence over
+     * participant.
      * 
      * @param path host:port/participant
      * @return the dsl builder
@@ -18536,8 +18539,11 @@ public class StaticEndpointBuilders {
      * Default value: 5222
      * 
      * Path parameter: participant
-     * JID (Jabber ID) of person to receive messages. room parameter has
-     * precedence over participant.
+     * JID (Jabber ID) of the person to chat with: the producer sends the
+     * messages to this person, and the consumer only routes the chat messages
+     * from this person (without a participant, the consumer routes the chat
+     * messages of every sender). room parameter has precedence over
+     * participant.
      * 
      * @param componentName to use a custom component name for the endpoint
      * instead of the default name
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/XmppEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/XmppEndpointBuilderFactory.java
index af77eb51b480..4a621724cdc8 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/XmppEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/XmppEndpointBuilderFactory.java
@@ -1296,8 +1296,11 @@ public interface XmppEndpointBuilderFactory {
          * Default value: 5222
          * 
          * Path parameter: participant
-         * JID (Jabber ID) of person to receive messages. room parameter has
-         * precedence over participant.
+         * JID (Jabber ID) of the person to chat with: the producer sends the
+         * messages to this person, and the consumer only routes the chat
+         * messages from this person (without a participant, the consumer 
routes
+         * the chat messages of every sender). room parameter has precedence
+         * over participant.
          * 
          * @param path host:port/participant
          * @return the dsl builder
@@ -1323,8 +1326,11 @@ public interface XmppEndpointBuilderFactory {
          * Default value: 5222
          * 
          * Path parameter: participant
-         * JID (Jabber ID) of person to receive messages. room parameter has
-         * precedence over participant.
+         * JID (Jabber ID) of the person to chat with: the producer sends the
+         * messages to this person, and the consumer only routes the chat
+         * messages from this person (without a participant, the consumer 
routes
+         * the chat messages of every sender). room parameter has precedence
+         * over participant.
          * 
          * @param componentName to use a custom component name for the endpoint
          * instead of the default name

Reply via email to