This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26188-cometd-body in repository https://gitbox.apache.org/repos/asf/camel.git
commit 1556dffa8019f831a26d57ffa02f0b52f4ed80aa Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 23:55:35 2026 +0100 CAMEL-26188: camel-cometd - publish a stream or file body as its text, and a null body as a JSON null The producer and the reply of an InOut consumer set the body as is as the Bayeux data, so a stream, a Reader or a file reached the clients as the text of its toString. They are now read as text before publishing. A null body called setData(null), which leaves out the data field: the clients took the message for the reply of a publish and never delivered it to their subscribers. The data field is now set to a JSON null. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/catalog/components/cometd.json | 6 +- .../apache/camel/catalog/components/cometds.json | 6 +- .../org/apache/camel/component/cometd/cometd.json | 6 +- .../org/apache/camel/component/cometd/cometds.json | 6 +- .../camel/component/cometd/CometdBinding.java | 26 ++- .../camel/component/cometd/CometdEndpoint.java | 23 ++- .../component/cometd/CometdProducerBodyTest.java | 201 +++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 10 + 8 files changed, 263 insertions(+), 21 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json index 5737e8a07f07..8dfb68350a75 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json @@ -41,9 +41,9 @@ "subscription": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The subscription", "constantName": "org.apache.camel.component.cometd.CometdBinding#COMETD_SUBSCRIPTION_HEADER_NAME" } }, "bodyTypes": { - "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is" }, - "consumerReply": { "carriers": [ "text", "value", "map", "list" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString" }, - "producerIn": { "carriers": [ "text", "value", "map", "list" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, + "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is (a stream or a file as its text)" }, + "consumerReply": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. A stream or a file is read as text, and null is sent as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString" }, + "producerIn": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. A stream or a file is read as text, and null is published as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, "producerOut": { "carriers": [ "unchanged" ] } }, "properties": { diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json index f20d00b45f43..bb734eaa3df7 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json @@ -41,9 +41,9 @@ "subscription": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The subscription", "constantName": "org.apache.camel.component.cometd.CometdBinding#COMETD_SUBSCRIPTION_HEADER_NAME" } }, "bodyTypes": { - "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is" }, - "consumerReply": { "carriers": [ "text", "value", "map", "list" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString" }, - "producerIn": { "carriers": [ "text", "value", "map", "list" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, + "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is (a stream or a file as its text)" }, + "consumerReply": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. A stream or a file is read as text, and null is sent as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString" }, + "producerIn": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. A stream or a file is read as text, and null is published as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, "producerOut": { "carriers": [ "unchanged" ] } }, "properties": { diff --git a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json index 5737e8a07f07..8dfb68350a75 100644 --- a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json +++ b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json @@ -41,9 +41,9 @@ "subscription": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The subscription", "constantName": "org.apache.camel.component.cometd.CometdBinding#COMETD_SUBSCRIPTION_HEADER_NAME" } }, "bodyTypes": { - "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is" }, - "consumerReply": { "carriers": [ "text", "value", "map", "list" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString" }, - "producerIn": { "carriers": [ "text", "value", "map", "list" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, + "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is (a stream or a file as its text)" }, + "consumerReply": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. A stream or a file is read as text, and null is sent as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString" }, + "producerIn": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. A stream or a file is read as text, and null is published as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, "producerOut": { "carriers": [ "unchanged" ] } }, "properties": { diff --git a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json index f20d00b45f43..bb734eaa3df7 100644 --- a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json +++ b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json @@ -41,9 +41,9 @@ "subscription": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The subscription", "constantName": "org.apache.camel.component.cometd.CometdBinding#COMETD_SUBSCRIPTION_HEADER_NAME" } }, "bodyTypes": { - "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is" }, - "consumerReply": { "carriers": [ "text", "value", "map", "list" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString" }, - "producerIn": { "carriers": [ "text", "value", "map", "list" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. Bytes become an array of numbers; a stream, a file or another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, + "consumerOut": { "carriers": [ "text", "value", "map", "list", "null" ], "description": "The data of the Bayeux message, parsed from JSON: a String, a number or a boolean, a Map for an object, a List for an array, or null. From a cometd producer in the same JVM the body that producer sent, as is (a stream or a file as its text)" }, + "consumerReply": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "Only when the exchange is InOut (exchangePattern=InOut, or set in the route): the body the route ends with is sent back to the client that published, as JSON. A stream or a file is read as text, and null is sent as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString" }, + "producerIn": { "carriers": [ "text", "value", "map", "list", "stream", "file", "null" ], "description": "The data published to the channel, sent as JSON to the subscribed clients: text, a number or a boolean, a Map or a List. A stream or a file is read as text, and null is published as a JSON null. Bytes become an array of numbers; another object is sent as the text of its toString: convert it first. The headers go in the ext field of the message" }, "producerOut": { "carriers": [ "unchanged" ] } }, "properties": { diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java index 814954d26253..4313aad98a77 100644 --- a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java @@ -16,6 +16,10 @@ */ package org.apache.camel.component.cometd; +import java.io.File; +import java.io.InputStream; +import java.io.Reader; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -24,6 +28,8 @@ import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.Message; +import org.apache.camel.StreamCache; +import org.apache.camel.WrappedFile; import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.Metadata; import org.apache.camel.support.DefaultHeaderFilterStrategy; @@ -35,6 +41,8 @@ import org.cometd.server.BayeuxServerImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.cometd.bayeux.Message.DATA_FIELD; + /** * A Strategy used to convert between a Camel {@link Exchange} and to and from a Cometd messages */ @@ -75,10 +83,26 @@ public class CometdBinding { mutable.setClientId(serverSession.getId()); } addHeadersToMessage(mutable, camelMessage); - mutable.setData(camelMessage.getBody()); + Object body = camelMessage.getBody(); + if (body == null) { + // publish a JSON null: setData(null) leaves out the data field, and the clients take a message without + // data for the reply of a publish, so their subscribers never receive it + mutable.put(DATA_FIELD, null); + } else { + if (isStreamOrFile(body)) { + // the JSON of the message has the toString of a stream or a file, so publish its content as text + body = camelMessage.getBody(String.class); + } + mutable.setData(body); + } return mutable; } + private static boolean isStreamOrFile(Object body) { + return body instanceof StreamCache || body instanceof InputStream || body instanceof Reader + || body instanceof File || body instanceof Path || body instanceof WrappedFile; + } + public Message createCamelMessage( CamelContext camelContext, ServerSession remote, ServerMessage cometdMessage, Object data) { if (cometdMessage != null) { diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java index e0a3c215dfbc..2c846c0b2d78 100644 --- a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java @@ -46,17 +46,24 @@ import org.apache.camel.util.ObjectHelper; @BodyTypes(consumerOut = @BodyType(value = { Carrier.TEXT, Carrier.VALUE, Carrier.MAP, Carrier.LIST, Carrier.NULL }, description = "The data of the Bayeux message, parsed from JSON: a String, a number or a" + " boolean, a Map for an object, a List for an array, or null. From a cometd" - + " producer in the same JVM the body that producer sent, as is"), - consumerReply = @BodyType(value = { Carrier.TEXT, Carrier.VALUE, Carrier.MAP, Carrier.LIST }, + + " producer in the same JVM the body that producer sent, as is (a stream or" + + " a file as its text)"), + consumerReply = @BodyType(value = { + Carrier.TEXT, Carrier.VALUE, Carrier.MAP, Carrier.LIST, Carrier.STREAM, + Carrier.FILE, Carrier.NULL }, description = "Only when the exchange is InOut (exchangePattern=InOut, or set in the" + " route): the body the route ends with is sent back to the client that" - + " published, as JSON. Bytes become an array of numbers; a stream, a file" - + " or another object is sent as the text of its toString"), - producerIn = @BodyType(value = { Carrier.TEXT, Carrier.VALUE, Carrier.MAP, Carrier.LIST }, + + " published, as JSON. A stream or a file is read as text, and null is" + + " sent as a JSON null. Bytes become an array of numbers; another object" + + " is sent as the text of its toString"), + producerIn = @BodyType(value = { + Carrier.TEXT, Carrier.VALUE, Carrier.MAP, Carrier.LIST, Carrier.STREAM, + Carrier.FILE, Carrier.NULL }, description = "The data published to the channel, sent as JSON to the subscribed clients:" - + " text, a number or a boolean, a Map or a List. Bytes become an array of" - + " numbers; a stream, a file or another object is sent as the text of its" - + " toString: convert it first. The headers go in the ext field of the message"), + + " text, a number or a boolean, a Map or a List. A stream or a file is read" + + " as text, and null is published as a JSON null. Bytes become an array of" + + " numbers; another object is sent as the text of its toString: convert it" + + " first. The headers go in the ext field of the message"), producerOut = @BodyType(Carrier.UNCHANGED)) public class CometdEndpoint extends DefaultEndpoint implements EndpointServiceLocation { diff --git a/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometdProducerBodyTest.java b/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometdProducerBodyTest.java new file mode 100644 index 000000000000..2140027a0aa6 --- /dev/null +++ b/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometdProducerBodyTest.java @@ -0,0 +1,201 @@ +/* + * 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.cometd; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.ExchangePattern; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.converter.stream.ByteArrayInputStreamCache; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.cometd.bayeux.Message; +import org.cometd.client.BayeuxClient; +import org.cometd.client.http.jetty.JettyHttpClientTransport; +import org.eclipse.jetty.client.HttpClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The body a cometd producer publishes, and the body a cometd consumer replies with, as a BayeuxClient receives them. + */ +public class CometdProducerBodyTest extends CamelTestSupport { + + private static final String CHANNEL = "/test/body"; + private static final String SERVICE_CHANNEL = "/service/reply"; + + @RegisterExtension + AvailablePortFinder.Port port = AvailablePortFinder.find(); + + @TempDir + Path tempDir; + + private final BlockingQueue<Message> received = new LinkedBlockingQueue<>(); + private HttpClient httpClient; + private BayeuxClient client; + + @BeforeEach + void connectClient() throws Exception { + httpClient = new HttpClient(); + httpClient.start(); + client = new BayeuxClient( + "http://127.0.0.1:" + port.getPort() + "/cometd", new JettyHttpClientTransport(null, httpClient)); + client.handshake(); + assertTrue(client.waitFor(5000, BayeuxClient.State.CONNECTED)); + + CountDownLatch subscribed = new CountDownLatch(2); + client.getChannel(CHANNEL).subscribe((channel, message) -> received.add(message), + message -> subscribed.countDown()); + client.getChannel(SERVICE_CHANNEL).subscribe((channel, message) -> received.add(message), + message -> subscribed.countDown()); + assertTrue(subscribed.await(5, TimeUnit.SECONDS), "Should subscribe within timeout"); + } + + @AfterEach + void disconnectClient() throws Exception { + client.disconnect(); + client.waitFor(5000, BayeuxClient.State.DISCONNECTED); + httpClient.stop(); + } + + @Test + void testTextBody() throws Exception { + template.sendBody("direct:input", "Hello World"); + + assertEquals("Hello World", receive().getData()); + } + + @Test + void testInputStreamBody() throws Exception { + template.sendBody("direct:input", new ByteArrayInputStream("Hello Stream".getBytes(StandardCharsets.UTF_8))); + + assertEquals("Hello Stream", receive().getData()); + } + + @Test + void testStreamCacheBody() throws Exception { + template.sendBody("direct:input", new ByteArrayInputStreamCache( + new ByteArrayInputStream("Hello Stream Cache".getBytes(StandardCharsets.UTF_8)))); + + assertEquals("Hello Stream Cache", receive().getData()); + } + + @Test + void testReaderBody() throws Exception { + template.sendBody("direct:input", new StringReader("Hello Reader")); + + assertEquals("Hello Reader", receive().getData()); + } + + @Test + void testFileBody() throws Exception { + Path file = Files.writeString(tempDir.resolve("hello.txt"), "Hello File"); + + template.sendBody("direct:input", file.toFile()); + assertEquals("Hello File", receive().getData()); + + template.sendBody("direct:input", file); + assertEquals("Hello File", receive().getData()); + } + + @Test + void testFileConsumerBody() throws Exception { + Files.writeString(tempDir.resolve("hello.txt"), "Hello File Consumer"); + + context.getRouteController().startRoute("file"); + + assertEquals("Hello File Consumer", receive().getData()); + } + + @Test + void testNullBody() throws Exception { + template.sendBody("direct:input", null); + + Message message = receive(); + assertEquals(CHANNEL, message.getChannel()); + assertTrue(message.containsKey(Message.DATA_FIELD)); + assertNull(message.getData()); + } + + @Test + void testNullReply() throws Exception { + client.getChannel(SERVICE_CHANNEL).publish("Hello"); + + Message message = receive(); + assertEquals(SERVICE_CHANNEL, message.getChannel()); + assertTrue(message.containsKey(Message.DATA_FIELD)); + assertNull(message.getData()); + } + + @Test + void testStreamReply() throws Exception { + client.getChannel(SERVICE_CHANNEL).publish("stream"); + + assertEquals("Reply stream", receive().getData()); + } + + private Message receive() throws InterruptedException { + Message message = received.poll(5, TimeUnit.SECONDS); + assertNotNull(message, "Should receive a message within timeout"); + return message; + } + + @Override + protected RouteBuilder createRouteBuilder() { + String options = "?baseResource=file:./target/test-classes/webapp&timeout=240000&interval=0&maxInterval=30000" + + "&multiFrameInterval=1500&jsonCommented=true&logLevel=2"; + String producer = "cometd://127.0.0.1:" + port.getPort() + CHANNEL + options; + String consumer = "cometd://127.0.0.1:" + port.getPort() + SERVICE_CHANNEL + options; + + return new RouteBuilder() { + @Override + public void configure() { + from("direct:input").to(producer); + + File dir = tempDir.toFile(); + fromF("file:%s?noop=true&initialDelay=0&delay=10", dir.getAbsolutePath()).routeId("file").autoStartup(false) + .to(producer); + + from(consumer) + .setExchangePattern(ExchangePattern.InOut) + .choice() + .when(body().isEqualTo("stream")) + .process(exchange -> exchange.getMessage().setBody( + new ByteArrayInputStream("Reply stream".getBytes(StandardCharsets.UTF_8)))) + .otherwise() + .process(exchange -> exchange.getMessage().setBody(null)); + } + }; + } +} 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 10f5a47c1b89..b3dfa7b63373 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 @@ -711,6 +711,16 @@ with `defaultFrom`. When the validation of an `SMSMessage` fails, the `InvalidPayloadRuntimeException` now names the invalid fields in its message, and its cause is an `org.apache.camel.ValidationException`. +=== camel-cometd - stream, file and null bodies are published + +The cometd producer, and the reply of an InOut cometd consumer, now publish a stream, a `Reader` or a file body +(`File`, `Path` or the body of the file consumer) as its content as text. Before, the clients received the text of its +`toString`, such as `ByteArrayInputStreamCache@...` or the path of the file. A cometd consumer in the same JVM now +receives such a body as a `String`. + +A `null` body is now published as a JSON `null` that the subscribed clients receive. Before, the message had no data, +so the clients took it for the reply of a publish and never received it, while the exchange succeeded. + === camel-aws - the HTTP client is no longer looked up on the classpath The camel-aws components now set the HTTP client of the AWS SDK clients they create for their endpoints: the Apache 5
