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

davsclaus pushed a commit to branch CAMEL-26277-xmpp-participant-filter
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 6daa562f32f3b83cf52e215b9746e5b1059cd17d
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 15:41:14 2026 +0100

    CAMEL-26277: camel-xmpp - a consumer with a participant routes only the 
chat messages from that participant
    
    The consumer listened on the chat manager of the account, which passes the 
chat
    messages of every sender, and did not check the sender against the 
participant.
    As the consumers of an account share its connection, every consumer of the
    account routed every chat message. A consumer with a participant and no 
room now
    routes only the chat messages whose bare JID is the participant's. A 
consumer
    without a participant, and a room consumer, still route the chats of every 
sender.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[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 7c82ebd18930..17b230ca1a82 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
@@ -3003,6 +3003,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