This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26177-dhis2-body in repository https://gitbox.apache.org/repos/asf/camel.git
commit 950ba043bc62e483432e138d39e6d31cea129b09 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 23:38:49 2026 +0100 CAMEL-26177: camel-dhis2 - post/put send bytes, a stream or a file as the JSON it holds, delete does not send a leftover body, and the get/collection consumer sends one message per item The producer passed the body as is to the DHIS2 client, which serialized it with Jackson: bytes went out as a base64 string, a file as its path, and a stream failed. Such bodies are now converted to their JSON text first. The body was also taken as the resource of every call, so delete sent any leftover body as its payload; only post and put now take the body, and delete takes a resource from inBody=resource or the CamelDhis2.resource header. The get/collection consumer sent one message with an Iterator body; it now sends one message per Dhis2Resource. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/components/dhis2.json | 4 +- .../apache/camel/catalog/docs/dhis2-component.adoc | 10 ++ .../org/apache/camel/component/dhis2/dhis2.json | 4 +- .../src/main/docs/dhis2-component.adoc | 10 ++ .../camel/component/dhis2/Dhis2Consumer.java | 13 ++ .../camel/component/dhis2/Dhis2Endpoint.java | 26 +-- .../camel/component/dhis2/Dhis2Producer.java | 35 +++- .../camel/component/dhis2/Dhis2BodyTest.java | 186 +++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 13 ++ 9 files changed, 283 insertions(+), 18 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dhis2.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dhis2.json index a673f1194e4c..701d4a3c6416 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dhis2.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dhis2.json @@ -36,8 +36,8 @@ "username": { "index": 8, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.dhis2.Dhis2Configuration", "configurationField": "configuration", "description": "Username of the DHIS2 user to operate as" } }, "bodyTypes": { - "consumerOut": { "carriers": [ "stream" ], "byOption": [ { "option": "methodName", "value": "collection", "carriers": [ "iterator" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "null" ] } ], "description": "One message per poll with the return value of the method: the JSON response of get\/resource as a stream; get\/collection gives an Iterator of Dhis2Resource, which are Maps of the JSON fields, in one message, not split: split the body to get one message per [...] - "producerIn": { "carriers": [ "text", "map", "list", "pojo", "null" ], "byOption": [ { "option": "apiName", "value": "GET", "carriers": [ "any" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "any" ] } ], "description": "post, put and delete send the body as the JSON resource of the request, also without inBody: text as it is, and a Map, a List or a POJO, such as a DHIS2 model class, serialized to JSON by Jackson; no body sends none. Bytes and a file are seriali [...] + "consumerOut": { "carriers": [ "stream" ], "byOption": [ { "option": "methodName", "value": "collection", "carriers": [ "map" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "null" ] } ], "description": "The return value of the method: one message per poll with the JSON response of get\/resource as a stream; get\/collection gives one message per item, a Dhis2Resource, which is a Map of the JSON fields" }, + "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "map", "list", "pojo", "null" ], "byOption": [ { "option": "apiName", "value": "GET", "carriers": [ "any" ] }, { "option": "apiName", "value": "DELETE", "carriers": [ "any" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "any" ] } ], "description": "post and put send the body as the JSON resource of the request, also without inBody: text, bytes, a stream or a file as the JSON it holds, and a Map, a [...] "producerOut": { "carriers": [ "stream" ], "byOption": [ { "option": "methodName", "value": "collection", "carriers": [ "iterator" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "null" ] } ], "description": "The JSON response of DHIS2 as a stream, such as the resource of get\/resource or the import summary of post, put and delete: unmarshal it with json. get\/collection gives an Iterator of Dhis2Resource, read once, which are Maps of the JSON fields that conver [...] }, "properties": { diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dhis2-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dhis2-component.adoc index 93e20c3392d7..b7852cea4762 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dhis2-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dhis2-component.adoc @@ -47,6 +47,16 @@ producers, strip these internal headers at the trust boundary -- for example wit override the API call. See xref:manual::security-model.adoc[the Camel security model] for details. ==== +== Message body + +`post/resource` and `put/resource` send the message body as the JSON resource of the request: text, bytes, a stream +or a file as the JSON they hold, and a `Map`, a `List` or a POJO, such as a DHIS2 model class, serialized to JSON. +`delete/resource` does not send the message body: set `inBody=resource` or the `CamelDhis2.resource` header to send a +resource with the delete. + +The `get/collection` consumer sends one message per item of the collection, a `Dhis2Resource`, which is a `Map` of +the JSON fields of the item. + == Examples * Fetch an organisation unit by ID: diff --git a/components/camel-dhis2/camel-dhis2-component/src/generated/resources/META-INF/org/apache/camel/component/dhis2/dhis2.json b/components/camel-dhis2/camel-dhis2-component/src/generated/resources/META-INF/org/apache/camel/component/dhis2/dhis2.json index a673f1194e4c..701d4a3c6416 100644 --- a/components/camel-dhis2/camel-dhis2-component/src/generated/resources/META-INF/org/apache/camel/component/dhis2/dhis2.json +++ b/components/camel-dhis2/camel-dhis2-component/src/generated/resources/META-INF/org/apache/camel/component/dhis2/dhis2.json @@ -36,8 +36,8 @@ "username": { "index": 8, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.dhis2.Dhis2Configuration", "configurationField": "configuration", "description": "Username of the DHIS2 user to operate as" } }, "bodyTypes": { - "consumerOut": { "carriers": [ "stream" ], "byOption": [ { "option": "methodName", "value": "collection", "carriers": [ "iterator" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "null" ] } ], "description": "One message per poll with the return value of the method: the JSON response of get\/resource as a stream; get\/collection gives an Iterator of Dhis2Resource, which are Maps of the JSON fields, in one message, not split: split the body to get one message per [...] - "producerIn": { "carriers": [ "text", "map", "list", "pojo", "null" ], "byOption": [ { "option": "apiName", "value": "GET", "carriers": [ "any" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "any" ] } ], "description": "post, put and delete send the body as the JSON resource of the request, also without inBody: text as it is, and a Map, a List or a POJO, such as a DHIS2 model class, serialized to JSON by Jackson; no body sends none. Bytes and a file are seriali [...] + "consumerOut": { "carriers": [ "stream" ], "byOption": [ { "option": "methodName", "value": "collection", "carriers": [ "map" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "null" ] } ], "description": "The return value of the method: one message per poll with the JSON response of get\/resource as a stream; get\/collection gives one message per item, a Dhis2Resource, which is a Map of the JSON fields" }, + "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "map", "list", "pojo", "null" ], "byOption": [ { "option": "apiName", "value": "GET", "carriers": [ "any" ] }, { "option": "apiName", "value": "DELETE", "carriers": [ "any" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "any" ] } ], "description": "post and put send the body as the JSON resource of the request, also without inBody: text, bytes, a stream or a file as the JSON it holds, and a Map, a [...] "producerOut": { "carriers": [ "stream" ], "byOption": [ { "option": "methodName", "value": "collection", "carriers": [ "iterator" ] }, { "option": "apiName", "value": "RESOURCE_TABLES", "carriers": [ "null" ] } ], "description": "The JSON response of DHIS2 as a stream, such as the resource of get\/resource or the import summary of post, put and delete: unmarshal it with json. get\/collection gives an Iterator of Dhis2Resource, read once, which are Maps of the JSON fields that conver [...] }, "properties": { diff --git a/components/camel-dhis2/camel-dhis2-component/src/main/docs/dhis2-component.adoc b/components/camel-dhis2/camel-dhis2-component/src/main/docs/dhis2-component.adoc index 93e20c3392d7..b7852cea4762 100644 --- a/components/camel-dhis2/camel-dhis2-component/src/main/docs/dhis2-component.adoc +++ b/components/camel-dhis2/camel-dhis2-component/src/main/docs/dhis2-component.adoc @@ -47,6 +47,16 @@ producers, strip these internal headers at the trust boundary -- for example wit override the API call. See xref:manual::security-model.adoc[the Camel security model] for details. ==== +== Message body + +`post/resource` and `put/resource` send the message body as the JSON resource of the request: text, bytes, a stream +or a file as the JSON they hold, and a `Map`, a `List` or a POJO, such as a DHIS2 model class, serialized to JSON. +`delete/resource` does not send the message body: set `inBody=resource` or the `CamelDhis2.resource` header to send a +resource with the delete. + +The `get/collection` consumer sends one message per item of the collection, a `Dhis2Resource`, which is a `Map` of +the JSON fields of the item. + == Examples * Fetch an organisation unit by ID: diff --git a/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Consumer.java b/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Consumer.java index ef598539028d..4d45ae87a710 100644 --- a/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Consumer.java +++ b/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Consumer.java @@ -16,6 +16,8 @@ */ package org.apache.camel.component.dhis2; +import java.util.Iterator; + import org.apache.camel.Processor; import org.apache.camel.component.dhis2.internal.Dhis2ApiName; import org.apache.camel.support.component.AbstractApiConsumer; @@ -26,4 +28,15 @@ public class Dhis2Consumer extends AbstractApiConsumer<Dhis2ApiName, Dhis2Config super(endpoint, processor); } + @Override + @SuppressWarnings("unchecked") + public Object splitResult(Object result) { + if (result instanceof Iterator<?> iterator) { + // one message per resource of get/collection, read as the pages come + Iterator<Object> resources = (Iterator<Object>) iterator; + return (Iterable<Object>) () -> resources; + } + return super.splitResult(result); + } + } diff --git a/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Endpoint.java b/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Endpoint.java index 6da7f925cafc..91d52dafb172 100644 --- a/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Endpoint.java +++ b/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Endpoint.java @@ -50,24 +50,26 @@ import org.hisp.dhis.integration.sdk.api.Dhis2Client; @BodyTypes(consumerOut = @BodyType(value = Carrier.STREAM, byOption = { @BodyType.When(option = "methodName", value = "collection", - carriers = Carrier.ITERATOR), + carriers = Carrier.MAP), @BodyType.When(option = "apiName", value = "RESOURCE_TABLES", carriers = Carrier.NULL) }, - description = "One message per poll with the return value of the method: the JSON" - + " response of get/resource as a stream; get/collection gives an Iterator" - + " of Dhis2Resource, which are Maps of the JSON fields, in one message," - + " not split: split the body to get one message per item"), - producerIn = @BodyType(value = { Carrier.TEXT, Carrier.MAP, Carrier.LIST, Carrier.POJO, Carrier.NULL }, + description = "The return value of the method: one message per poll with the JSON" + + " response of get/resource as a stream; get/collection gives one" + + " message per item, a Dhis2Resource, which is a Map of the JSON fields"), + producerIn = @BodyType(value = { + Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, Carrier.MAP, Carrier.LIST, Carrier.POJO, + Carrier.NULL }, byOption = { @BodyType.When(option = "apiName", value = "GET", carriers = Carrier.ANY), + @BodyType.When(option = "apiName", value = "DELETE", carriers = Carrier.ANY), @BodyType.When(option = "apiName", value = "RESOURCE_TABLES", carriers = Carrier.ANY) }, - description = "post, put and delete send the body as the JSON resource of the request," - + " also without inBody: text as it is, and a Map, a List or a POJO, such as" - + " a DHIS2 model class, serialized to JSON by Jackson; no body sends none." - + " Bytes and a file are serialized as a JSON string and a stream fails:" - + " convertBodyTo String first. A body left by an earlier step is sent too," - + " also by delete. get and resourceTables do not use the body"), + description = "post and put send the body as the JSON resource of the request, also" + + " without inBody: text, bytes, a stream or a file as the JSON it holds," + + " and a Map, a List or a POJO, such as a DHIS2 model class, serialized to" + + " JSON by Jackson; no body sends none. delete sends a resource only when" + + " given by inBody=resource or the CamelDhis2.resource header. get and" + + " resourceTables do not use the body"), producerOut = @BodyType(value = Carrier.STREAM, byOption = { @BodyType.When(option = "methodName", value = "collection", diff --git a/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Producer.java b/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Producer.java index 7b36e0c3a173..a8f78a97bbff 100644 --- a/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Producer.java +++ b/components/camel-dhis2/camel-dhis2-component/src/main/java/org/apache/camel/component/dhis2/Dhis2Producer.java @@ -16,9 +16,16 @@ */ package org.apache.camel.component.dhis2; +import java.io.File; +import java.io.InputStream; +import java.io.Reader; +import java.nio.file.Path; import java.util.Map; import org.apache.camel.Exchange; +import org.apache.camel.NoTypeConversionAvailableException; +import org.apache.camel.StreamCache; +import org.apache.camel.WrappedFile; import org.apache.camel.component.dhis2.internal.Dhis2ApiName; import org.apache.camel.component.dhis2.internal.Dhis2PropertiesHelper; import org.apache.camel.support.component.AbstractApiProducer; @@ -26,6 +33,8 @@ import org.apache.camel.support.component.ApiMethod; public class Dhis2Producer extends AbstractApiProducer<Dhis2ApiName, Dhis2Configuration> { + private static final String RESOURCE = "resource"; + public Dhis2Producer(Dhis2Endpoint endpoint) { super(endpoint, Dhis2PropertiesHelper.getHelper(endpoint.getCamelContext())); } @@ -33,10 +42,32 @@ public class Dhis2Producer extends AbstractApiProducer<Dhis2ApiName, Dhis2Config @Override protected ApiMethod findMethod(Exchange exchange, Map<String, Object> properties) { ApiMethod apiMethod = super.findMethod(exchange, properties); - if (!properties.containsKey("resource")) { - properties.put("resource", exchange.getIn().getBody()); + if (apiMethod == null) { + return null; + } + // the body is the resource of post and put; delete sends one only when given by inBody, a header or an option + Dhis2ApiName apiName = endpoint.getApiName(); + if (!properties.containsKey(RESOURCE) && (apiName == Dhis2ApiName.POST || apiName == Dhis2ApiName.PUT)) { + properties.put(RESOURCE, exchange.getIn().getBody()); + } + Object resource = properties.get(RESOURCE); + if (isJsonPayload(resource)) { + // send the JSON as it is: the DHIS2 client would serialize bytes as a base64 string and a file as its path + try { + properties.put(RESOURCE, exchange.getContext().getTypeConverter() + .mandatoryConvertTo(String.class, exchange, resource)); + } catch (NoTypeConversionAvailableException e) { + exchange.setException(e); + return null; + } } return apiMethod; } + private static boolean isJsonPayload(Object resource) { + return resource instanceof byte[] || resource instanceof InputStream || resource instanceof Reader + || resource instanceof File || resource instanceof Path || resource instanceof WrappedFile + || resource instanceof StreamCache; + } + } diff --git a/components/camel-dhis2/camel-dhis2-component/src/test/java/org/apache/camel/component/dhis2/Dhis2BodyTest.java b/components/camel-dhis2/camel-dhis2-component/src/test/java/org/apache/camel/component/dhis2/Dhis2BodyTest.java new file mode 100644 index 000000000000..8901e4f7fc0e --- /dev/null +++ b/components/camel-dhis2/camel-dhis2-component/src/test/java/org/apache/camel/component/dhis2/Dhis2BodyTest.java @@ -0,0 +1,186 @@ +/* + * 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.dhis2; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.dhis2.api.Dhis2Resource; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +/** + * Sends the producer bodies to a local HTTP server that stands in for the DHIS2 Web API, and polls a collection from + * it. + */ +public class Dhis2BodyTest extends CamelTestSupport { + + private static final String JSON = "{\"name\":\"Acme\",\"shortName\":\"Acme\"}"; + + private final List<String> requests = new CopyOnWriteArrayList<>(); + + private HttpServer server; + + @TempDir + Path tempDir; + + @Override + protected CamelContext createCamelContext() throws Exception { + server = HttpServer.create(new InetSocketAddress("localhost", 0), 0); + server.createContext("/api", this::handle); + server.start(); + + CamelContext context = super.createCamelContext(); + Dhis2Configuration configuration = new Dhis2Configuration(); + configuration.setBaseApiUrl("http://localhost:" + server.getAddress().getPort() + "/api"); + configuration.setUsername("admin"); + configuration.setPassword("district"); + Dhis2Component component = new Dhis2Component(context); + component.setConfiguration(configuration); + context.addComponent("dhis2", component); + return context; + } + + @AfterEach + void stopServer() { + if (server != null) { + server.stop(0); + } + } + + private void handle(HttpExchange exchange) throws IOException { + String body; + try (InputStream is = exchange.getRequestBody()) { + body = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } + requests.add(exchange.getRequestMethod() + " " + exchange.getRequestURI().getPath() + " " + body); + + String response; + if (exchange.getRequestURI().getPath().endsWith("/organisationUnits")) { + response = "{\"organisationUnits\":[{\"id\":\"a\",\"name\":\"A\"},{\"id\":\"b\",\"name\":\"B\"}]}"; + } else { + response = "{\"status\":\"OK\"}"; + } + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, bytes.length); + try (var os = exchange.getResponseBody()) { + os.write(bytes); + } + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("dhis2:get/collection?path=organisationUnits&arrayName=organisationUnits&delay=60000") + .routeId("poll").autoStartup(false) + .to("mock:resources"); + } + }; + } + + private String lastRequest() { + return requests.get(requests.size() - 1); + } + + @Test + void postSendsTextAsItIs() { + template.requestBody("dhis2:post/resource?path=organisationUnits/x", JSON); + assertEquals("POST /api/organisationUnits/x " + JSON, lastRequest()); + } + + @Test + void postSendsBytesAsJson() { + template.requestBody("dhis2:post/resource?path=organisationUnits/x", JSON.getBytes(StandardCharsets.UTF_8)); + assertEquals("POST /api/organisationUnits/x " + JSON, lastRequest()); + } + + @Test + void putSendsStreamAsJson() { + template.requestBody("dhis2:put/resource?path=organisationUnits/x", + new ByteArrayInputStream(JSON.getBytes(StandardCharsets.UTF_8))); + assertEquals("PUT /api/organisationUnits/x " + JSON, lastRequest()); + } + + @Test + void putSendsFileAsJson() throws IOException { + File file = Files.writeString(tempDir.resolve("unit.json"), JSON).toFile(); + template.requestBody("dhis2:put/resource?path=organisationUnits/x", file); + assertEquals("PUT /api/organisationUnits/x " + JSON, lastRequest()); + } + + @Test + void postSendsMapAsJson() { + template.requestBody("dhis2:post/resource?path=organisationUnits/x", Map.of("name", "Acme")); + assertEquals("POST /api/organisationUnits/x {\"name\":\"Acme\"}", lastRequest()); + } + + @Test + void deleteDoesNotSendLeftoverBody() { + template.requestBody("dhis2:delete/resource?path=organisationUnits/x", JSON); + assertEquals("DELETE /api/organisationUnits/x ", lastRequest()); + } + + @Test + void deleteSendsBodyGivenByInBody() { + template.requestBody("dhis2:delete/resource?path=organisationUnits/x&inBody=resource", + JSON.getBytes(StandardCharsets.UTF_8)); + assertEquals("DELETE /api/organisationUnits/x " + JSON, lastRequest()); + } + + @Test + void deleteSendsResourceGivenByHeader() { + template.requestBodyAndHeader("dhis2:delete/resource?path=organisationUnits/x", null, "CamelDhis2.resource", + JSON); + assertEquals("DELETE /api/organisationUnits/x " + JSON, lastRequest()); + } + + @Test + void collectionConsumerSendsOneMessagePerResource() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:resources"); + mock.expectedMessageCount(2); + + context.getRouteController().startRoute("poll"); + + mock.assertIsSatisfied(); + Dhis2Resource first = assertInstanceOf(Dhis2Resource.class, mock.getExchanges().get(0).getIn().getBody()); + Dhis2Resource second = assertInstanceOf(Dhis2Resource.class, mock.getExchanges().get(1).getIn().getBody()); + assertEquals("A", first.get("name")); + assertEquals("B", second.get("name")); + } +} 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..91ae1ef394ac 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 @@ -2825,6 +2825,19 @@ channel of its endpoint, for every operation. To choose a different channel for `CamelPubNubOverrideChannel` header (`PubNubConstants.OVERRIDE_CHANNEL`). Routes that set `CamelPubNubChannel` to pick the channel of a pubnub producer must set `CamelPubNubOverrideChannel` instead. +=== camel-dhis2 - the delete body, and one message per item of the collection consumer + +The `delete/resource` producer no longer sends the message body as the resource of the request. Previously any body, +also one left by an earlier step, was sent as the payload of the DELETE. To send a resource with the delete, set +`inBody=resource` or the `CamelDhis2.resource` header. + +The `post/resource` and `put/resource` producers now send a body of bytes, a stream or a file as the JSON it holds. +Previously bytes were sent as a base64 JSON string, a file as its path and a stream failed. + +The `get/collection` consumer now sends one message per item of the collection, a `Dhis2Resource`, as the other API +components do for a list. Previously it sent one message per poll with an `Iterator` of the items as the body: remove +a `split` of the body after the consumer. + === camel-docker - the events consumer subscribes again when its event stream ends The `docker:events` consumer subscribes again when its event stream fails or ends (for example when the Docker daemon
