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
