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 0f11a4a21c33 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 (#29006)
0f11a4a21c33 is described below

commit 0f11a4a21c33cb43e4f5411783ccbc0dc8c6ad0d
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 11:40:34 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 (#29006)
    
    … 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 78a71e410aaa..973afcb364bb 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
@@ -3057,6 +3057,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

Reply via email to