This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-25901 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 31a5cc6f981d5a1bdb1d684e41628d783ee94533 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Oct 21 07:48:18 2026 +0200 CAMEL-25901: camel-jms - docs: a request-reply example in Java, XML and YAML, and the AI tools say to use InOut instead of setting JMSReplyTo - jms docs: "Sending a request and waiting for the reply" (Java, XML, YAML) after the first example: the caller sends with pattern InOut and variableReceive, the service route's last body is the reply, and Camel sets JMSReplyTo; it is the second jms sample. The filter example moves to "Routing JMS messages"; "Request-reply over JMS" points to it. - The header check: a header name with Reply that is not set (CamelJmsReplyTo) gets the request-reply hint (pattern: InOut on the to:, do not set JMSReplyTo), not "did you mean JMSReplyTo?". Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- .../apache/camel/catalog/docs/jms-component.adoc | 86 +++++++++++++++++++++- .../camel-jms/src/main/docs/jms-component.adoc | 86 +++++++++++++++++++++- .../dsl/jbang/core/commands/ai/HeaderChecks.java | 12 ++- .../commands/ai/SourceValidatorEndpointTest.java | 19 +++++ 4 files changed, 200 insertions(+), 3 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jms-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jms-component.adoc index aaba25bf786a..4b1a7e9c1af3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jms-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jms-component.adoc @@ -323,6 +323,88 @@ YAML:: ---- ==== +=== Sending a request and waiting for the reply + +To send a request and wait for its reply, the caller sends with the `InOut` exchange pattern, and the service is a +route that consumes the queue: the body at the end of its route is the reply. Camel creates a temporary reply queue +and sets `JMSReplyTo` itself, so do not set the `replyTo` option or a `JMSReplyTo` header for this. With +`variableReceive`, the reply goes into a variable, and the body is kept: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("timer:order?repeatCount=1") + .setBody(constant("{\"orderId\": \"ORD-1001\", \"sku\": \"CAMEL-MUG\"}")) + .setExchangePattern(ExchangePattern.InOut) + .toV("jms:queue:stock", null, "stockReply") + .log("Order ${body} got the reply ${variable.stockReply}"); + +from("jms:queue:stock") + .log("Stock service got ${body}") + .setBody(constant("{\"reserved\": true}")); +---- + +XML:: ++ +[source,xml] +---- +<route> + <from uri="timer:order?repeatCount=1"/> + <setBody> + <constant>{"orderId": "ORD-1001", "sku": "CAMEL-MUG"}</constant> + </setBody> + <to uri="jms:queue:stock" pattern="InOut" variableReceive="stockReply"/> + <log message="Order ${body} got the reply ${variable.stockReply}"/> +</route> +<route> + <from uri="jms:queue:stock"/> + <log message="Stock service got ${body}"/> + <setBody> + <constant>{"reserved": true}</constant> + </setBody> +</route> +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: timer:order + parameters: + repeatCount: 1 + steps: + - setBody: + expression: + constant: + expression: '{"orderId": "ORD-1001", "sku": "CAMEL-MUG"}' + - to: + uri: jms:queue:stock + pattern: InOut + variableReceive: stockReply + - log: + message: "Order ${body} got the reply ${variable.stockReply}" +- route: + from: + uri: jms:queue:stock + steps: + - log: + message: "Stock service got ${body}" + - setBody: + expression: + constant: + expression: '{"reserved": true}' +---- +==== + +The reply queue can also be shared or exclusive: see <<Request-reply over JMS>> below. + +=== Routing JMS messages + You can use any of the EIP patterns so the route can be context based. For example, here's how to filter an order topic for the big spenders: @@ -1107,7 +1189,9 @@ YAML:: Camel supports Request Reply over JMS. In essence the MEP of the Exchange should be `InOut` when you send a -message to a JMS queue. +message to a JMS queue. The example in <<Sending a request and waiting for the reply>> shows the caller and the service. +Camel sets the `JMSReplyTo` header and waits for the reply: do not set the `replyTo` option or a `JMSReplyTo` header to +get a reply. Camel offers a number of options to configure request/reply over JMS that influence performance and clustered environments. The table below diff --git a/components/camel-jms/src/main/docs/jms-component.adoc b/components/camel-jms/src/main/docs/jms-component.adoc index aaba25bf786a..4b1a7e9c1af3 100644 --- a/components/camel-jms/src/main/docs/jms-component.adoc +++ b/components/camel-jms/src/main/docs/jms-component.adoc @@ -323,6 +323,88 @@ YAML:: ---- ==== +=== Sending a request and waiting for the reply + +To send a request and wait for its reply, the caller sends with the `InOut` exchange pattern, and the service is a +route that consumes the queue: the body at the end of its route is the reply. Camel creates a temporary reply queue +and sets `JMSReplyTo` itself, so do not set the `replyTo` option or a `JMSReplyTo` header for this. With +`variableReceive`, the reply goes into a variable, and the body is kept: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("timer:order?repeatCount=1") + .setBody(constant("{\"orderId\": \"ORD-1001\", \"sku\": \"CAMEL-MUG\"}")) + .setExchangePattern(ExchangePattern.InOut) + .toV("jms:queue:stock", null, "stockReply") + .log("Order ${body} got the reply ${variable.stockReply}"); + +from("jms:queue:stock") + .log("Stock service got ${body}") + .setBody(constant("{\"reserved\": true}")); +---- + +XML:: ++ +[source,xml] +---- +<route> + <from uri="timer:order?repeatCount=1"/> + <setBody> + <constant>{"orderId": "ORD-1001", "sku": "CAMEL-MUG"}</constant> + </setBody> + <to uri="jms:queue:stock" pattern="InOut" variableReceive="stockReply"/> + <log message="Order ${body} got the reply ${variable.stockReply}"/> +</route> +<route> + <from uri="jms:queue:stock"/> + <log message="Stock service got ${body}"/> + <setBody> + <constant>{"reserved": true}</constant> + </setBody> +</route> +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: timer:order + parameters: + repeatCount: 1 + steps: + - setBody: + expression: + constant: + expression: '{"orderId": "ORD-1001", "sku": "CAMEL-MUG"}' + - to: + uri: jms:queue:stock + pattern: InOut + variableReceive: stockReply + - log: + message: "Order ${body} got the reply ${variable.stockReply}" +- route: + from: + uri: jms:queue:stock + steps: + - log: + message: "Stock service got ${body}" + - setBody: + expression: + constant: + expression: '{"reserved": true}' +---- +==== + +The reply queue can also be shared or exclusive: see <<Request-reply over JMS>> below. + +=== Routing JMS messages + You can use any of the EIP patterns so the route can be context based. For example, here's how to filter an order topic for the big spenders: @@ -1107,7 +1189,9 @@ YAML:: Camel supports Request Reply over JMS. In essence the MEP of the Exchange should be `InOut` when you send a -message to a JMS queue. +message to a JMS queue. The example in <<Sending a request and waiting for the reply>> shows the caller and the service. +Camel sets the `JMSReplyTo` header and waits for the reply: do not set the `replyTo` option or a `JMSReplyTo` header to +get a reply. Camel offers a number of options to configure request/reply over JMS that influence performance and clustered environments. The table below diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java index 47b8bd33f75a..8a158d844107 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java @@ -108,6 +108,8 @@ final class HeaderChecks { * a beginner or a model writes CamelFromEndpoint or CamelEndpointUri for "which endpoint did this come from" and * was told only that the value would be null. */ + private static final Pattern REPLY_NAME = Pattern.compile("Reply"); + static final List<Map.Entry<Pattern, String>> INTENT_HINTS = List.of( Map.entry(Pattern.compile("Endpoint|Uri|Source|From|Route|Origin"), "For the endpoint or route a message came from use ${exchange.fromEndpoint} or ${routeId}; for the" @@ -115,6 +117,11 @@ final class HeaderChecks { Map.entry(Pattern.compile("Size|Length|Count"), "The body size is ${body.length()} for a String body or ${bodyAs(byte[]).length}; the number of" + " headers is ${headers.size()}."), + // a reply header read or set by hand: Camel does request-reply itself (r20 runs built replies by hand) + Map.entry(REPLY_NAME, + "For request-reply over JMS, send with pattern: InOut on the to: and Camel sets JMSReplyTo and waits" + + " for the reply (the body at the end of the service route); do not set" + + " JMSReplyTo or replyTo yourself."), Map.entry(Pattern.compile("Timestamp|Time|Date"), "The current time is ${date:now:yyyy-MM-dd HH:mm:ss} and the time the message was created is" + " ${date:exchangeCreated:yyyy-MM-dd HH:mm:ss}; ${messageTimestamp} is set only by" @@ -293,7 +300,10 @@ final class HeaderChecks { String scheme = best != null && owner.containsKey(best.split(", ")[0]) ? owner.get(best.split(", ")[0]) : null; StringBuilder sb = new StringBuilder("Line ").append(i + 1).append(": header ").append(name) .append(" is not set by ").append(String.join(", ", schemes)).append(" (the value would be null)"); - if (best != null) { + if (best != null && REPLY_NAME.matcher(name).find()) { + // "did you mean JMSReplyTo?" led to replies set by hand: request-reply is done by Camel + sb.append(" ").append(intentHint(name)); + } else if (best != null) { sb.append(": did you mean ").append(best).append("?"); } else { String hint = intentHint(name); diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java index 5ffd282fe596..c8e8ae3f6c67 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java @@ -587,6 +587,25 @@ class SourceValidatorEndpointTest { .contains("did you mean Exchange.FILE_NAME"); } + /** "did you mean JMSReplyTo?" led to replies set by hand: Camel does request-reply with pattern InOut (r20). */ + @Test + void aReplyHeaderGetsTheRequestReplyHint() { + List<String> msgs = SourceValidator.validateKnownHeaders(""" + - from: + uri: jms:queue:orders + steps: + - setHeader: + name: CamelJmsReplyTo + constant: stock-reply + - to: + uri: jms:queue:stock + """, catalog); + assertThat(msgs).singleElement().satisfies(m -> assertThat(m) + .contains("header CamelJmsReplyTo is not set by jms") + .contains("send with pattern: InOut on the to: and Camel sets JMSReplyTo and waits for the reply") + .doesNotContain("did you mean")); + } + @Test void withIncludeMetadataAHeaderLookupFindsTheExchangeProperty() { // Simple's ${header.x} falls back to the exchange properties, so it reads the counter
