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