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