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 7b14a24bcb4b CAMEL-26178: camel-coap - a body that does not convert to
a byte array fails instead of being sent as an empty payload (#29004)
7b14a24bcb4b is described below
commit 7b14a24bcb4b45aad92d51832b8467a880e760eb
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 07:39:05 2026 +0100
CAMEL-26178: camel-coap - a body that does not convert to a byte array
fails instead of being sent as an empty payload (#29004)
…y fails instead of being sent as an empty payload
The producer read the POST and PUT payload with getBody(byte[].class), so a
Map, a POJO
or a number was sent as an empty payload without an error. It now uses
getMandatoryBody
and fails the exchange with an InvalidPayloadException; no body still sends
an empty payload.
The consumer answered such a reply body with 2.05 Content and an empty
payload. It now
uses getMandatoryBody for a non-null reply body, so the existing catch
answers 5.00 as
the component page documents. The body types, the component page and the
upgrade guide
are updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../apache/camel/catalog/components/coap+tcp.json | 4 +-
.../org/apache/camel/catalog/components/coap.json | 4 +-
.../apache/camel/catalog/components/coaps+tcp.json | 4 +-
.../org/apache/camel/catalog/components/coaps.json | 4 +-
.../apache/camel/catalog/docs/coap-component.adoc | 7 +-
.../META-INF/org/apache/camel/coap/coap+tcp.json | 4 +-
.../META-INF/org/apache/camel/coap/coap.json | 4 +-
.../META-INF/org/apache/camel/coap/coaps+tcp.json | 4 +-
.../META-INF/org/apache/camel/coap/coaps.json | 4 +-
.../camel-coap/src/main/docs/coap-component.adoc | 7 +-
.../org/apache/camel/coap/CamelCoapResource.java | 4 +-
.../java/org/apache/camel/coap/CoAPEndpoint.java | 6 +-
.../java/org/apache/camel/coap/CoAPProducer.java | 16 ++-
.../camel/coap/CoAPUnconvertibleBodyTest.java | 128 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 12 ++
15 files changed, 186 insertions(+), 26 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
index 9a6265a5f139..bcdcea36d561 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
index 88c4c89416d9..50d97d226bf3 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
index f69848818b02..50d31693853d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
index be9883c933d2..d980f140175d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
index 25449f89826d..1362c3f1beee 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
@@ -66,11 +66,16 @@ The following rules determine which request method the CoAP
producer will use to
3. `POST` if the message exchange body is not null.
4. `GET` otherwise.
+The payload of a `POST` or a `PUT` is the message body converted to `byte[]`,
and a message without a body sends an
+empty payload. A body that cannot be converted, such as a `Map`, a POJO or a
number, fails the exchange with an
+`InvalidPayloadException`: marshal it first (for example to JSON) or convert
it to a `String`.
+
=== Error responses from the CoAP consumer
When the exchange fails, the CoAP consumer answers the request with `5.00
Internal Server Error`. The same applies
when the response cannot be built once the route has completed, for example
when the body cannot be converted to
-the response payload.
+the response payload, such as a `Map`, a POJO or a number. A route without a
reply body answers `2.05 Content` with an
+empty payload.
By default the error response has no payload, so the exception's message is
not sent to the client. Set
`muteException=false` on the endpoint to send the exception's message as the
payload instead.
diff --git
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
index 9a6265a5f139..bcdcea36d561 100644
---
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
+++
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
index 88c4c89416d9..50d97d226bf3 100644
---
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
+++
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
index f69848818b02..50d31693853d 100644
---
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
+++
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
index be9883c933d2..d980f140175d 100644
---
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
+++
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
@@ -41,8 +41,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]",
"description": "The request payload as a byte array, empty for a request
without a payload, such as a GET; with observe, the payload of each
notification of the observed resource" },
- "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal
it first" },
- "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number is sent as an empty payload: marshal it first.
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+ "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file"
], "description": "The response payload: text, bytes, a stream, a file or no
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error:
convert or marshal it first" },
+ "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ],
"description": "The payload of a POST or a PUT: text, bytes, a stream or a
file. A Map, a POJO or a number fails the exchange: marshal it first. Without
CamelCoapMethod, no body sends a GET and a body a POST" },
"producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ {
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ],
"description": "The response payload as a byte array, empty for a response
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response
code is in the CamelCoapResponseCode header" }
},
"properties": {
diff --git a/components/camel-coap/src/main/docs/coap-component.adoc
b/components/camel-coap/src/main/docs/coap-component.adoc
index 25449f89826d..1362c3f1beee 100644
--- a/components/camel-coap/src/main/docs/coap-component.adoc
+++ b/components/camel-coap/src/main/docs/coap-component.adoc
@@ -66,11 +66,16 @@ The following rules determine which request method the CoAP
producer will use to
3. `POST` if the message exchange body is not null.
4. `GET` otherwise.
+The payload of a `POST` or a `PUT` is the message body converted to `byte[]`,
and a message without a body sends an
+empty payload. A body that cannot be converted, such as a `Map`, a POJO or a
number, fails the exchange with an
+`InvalidPayloadException`: marshal it first (for example to JSON) or convert
it to a `String`.
+
=== Error responses from the CoAP consumer
When the exchange fails, the CoAP consumer answers the request with `5.00
Internal Server Error`. The same applies
when the response cannot be built once the route has completed, for example
when the body cannot be converted to
-the response payload.
+the response payload, such as a `Map`, a POJO or a number. A route without a
reply body answers `2.05 Content` with an
+empty payload.
By default the error response has no payload, so the exception's message is
not sent to the client. Set
`muteException=false` on the endpoint to send the exception's message as the
payload instead.
diff --git
a/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
b/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
index 7366f4c7e257..af7390d37250 100644
---
a/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
+++
b/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
@@ -165,7 +165,9 @@ final class CamelCoapResource extends CoapResource {
}
int format =
MediaTypeRegistry.parse(target.getHeader(CoAPConstants.CONTENT_TYPE,
String.class));
- cexchange.respond(ResponseCode.CONTENT,
target.getBody(byte[].class), format);
+ // a body that cannot be converted fails the response with 5.00,
instead of being sent as an empty payload
+ byte[] payload = target.getBody() != null ?
target.getMandatoryBody(byte[].class) : null;
+ cexchange.respond(ResponseCode.CONTENT, payload, format);
} catch (Exception e) {
if (consumer != null) {
diff --git
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
index e94645a93ace..7a2bf2e1c3db 100644
---
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
+++
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
@@ -89,11 +89,11 @@ import static
org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDE
+ " notification of the
observed resource"),
consumerReply = @BodyType(value = { Carrier.NULL, Carrier.TEXT,
Carrier.BYTES, Carrier.STREAM, Carrier.FILE },
description = "The response payload:
text, bytes, a stream, a file or no body. A Map,"
- + " a POJO or a number is
sent as an empty payload: convert or marshal"
- + " it first"),
+ + " a POJO or a number is
answered with 5.00 Internal Server Error:"
+ + " convert or marshal it
first"),
producerIn = @BodyType(value = { Carrier.NULL, Carrier.TEXT,
Carrier.BYTES, Carrier.STREAM, Carrier.FILE },
description = "The payload of a POST or a
PUT: text, bytes, a stream or a file. A Map,"
- + " a POJO or a number is sent
as an empty payload: marshal it first."
+ + " a POJO or a number fails
the exchange: marshal it first."
+ " Without CamelCoapMethod,
no body sends a GET and a body a POST"),
producerOut = @BodyType(value = { Carrier.BYTES, Carrier.VALUE },
byOption = @BodyType.When(option =
"notify", value = "true",
diff --git
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
index e539607eaf44..adc57e04e092 100644
---
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
+++
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
@@ -20,6 +20,7 @@ import java.util.Locale;
import org.apache.camel.CamelExchangeException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.support.DefaultProducer;
import org.apache.camel.util.URISupport;
@@ -82,12 +83,10 @@ public class CoAPProducer extends DefaultProducer {
response = coapClient.delete();
break;
case CoAPConstants.METHOD_POST:
- byte[] bodyPost = exchange.getIn().getBody(byte[].class);
- response = coapClient.post(bodyPost, mediaType);
+ response = coapClient.post(getPayload(exchange), mediaType);
break;
case CoAPConstants.METHOD_PUT:
- byte[] bodyPut = exchange.getIn().getBody(byte[].class);
- response = coapClient.put(bodyPut, mediaType);
+ response = coapClient.put(getPayload(exchange), mediaType);
break;
case CoAPConstants.METHOD_PING:
pingResponse = coapClient.ping();
@@ -111,6 +110,15 @@ public class CoAPProducer extends DefaultProducer {
}
}
+ private static byte[] getPayload(Exchange exchange) throws
InvalidPayloadException {
+ Message in = exchange.getIn();
+ if (in.getBody() == null) {
+ return null;
+ }
+ // a body that cannot be converted fails the exchange, instead of
being sent as an empty payload
+ return in.getMandatoryBody(byte[].class);
+ }
+
protected void initClient() throws Exception {
lock.lock();
try {
diff --git
a/components/camel-coap/src/test/java/org/apache/camel/coap/CoAPUnconvertibleBodyTest.java
b/components/camel-coap/src/test/java/org/apache/camel/coap/CoAPUnconvertibleBodyTest.java
new file mode 100644
index 000000000000..6fe3e6846570
--- /dev/null
+++
b/components/camel-coap/src/test/java/org/apache/camel/coap/CoAPUnconvertibleBodyTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.coap;
+
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.eclipse.californium.core.CoapResponse;
+import org.eclipse.californium.core.coap.CoAP.ResponseCode;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * A body that cannot be converted to the CoAP payload must fail, instead of
being sent as an empty payload.
+ */
+public class CoAPUnconvertibleBodyTest extends CoAPTestSupport {
+
+ @Test
+ void producerFailsPostOfABodyThatCannotBeConverted() throws Exception {
+ MockEndpoint sink = getMockEndpoint("mock:sink");
+ sink.expectedMessageCount(0);
+
+ Exchange out = template.request(sinkUri(), e -> {
+ e.getIn().setHeader(CoAPConstants.COAP_METHOD,
CoAPConstants.METHOD_POST);
+ e.getIn().setBody(Map.of("temperature", 21));
+ });
+
+ assertInstanceOf(InvalidPayloadException.class, out.getException());
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ void producerFailsPutOfABodyThatCannotBeConverted() throws Exception {
+ MockEndpoint sink = getMockEndpoint("mock:sink");
+ sink.expectedMessageCount(0);
+
+ Exchange out = template.request(sinkUri(), e -> {
+ e.getIn().setHeader(CoAPConstants.COAP_METHOD,
CoAPConstants.METHOD_PUT);
+ e.getIn().setBody(new Reading(21));
+ });
+
+ assertInstanceOf(InvalidPayloadException.class, out.getException());
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ void producerSendsAnEmptyPayloadForAPostWithoutBody() throws Exception {
+ MockEndpoint sink = getMockEndpoint("mock:sink");
+ sink.expectedMessageCount(1);
+
+ Exchange out = template.request(sinkUri(), e ->
e.getIn().setHeader(CoAPConstants.COAP_METHOD,
+ CoAPConstants.METHOD_POST));
+
+ assertNull(out.getException());
+ MockEndpoint.assertIsSatisfied(context);
+ assertEquals(0,
sink.getReceivedExchanges().get(0).getIn().getBody(byte[].class).length);
+ }
+
+ @Test
+ void consumerAnswersAReplyBodyThatCannotBeConvertedWithServerError()
throws Exception {
+ CoapResponse response = createClient("/mapReply").get();
+
+ assertEquals(ResponseCode.INTERNAL_SERVER_ERROR, response.getCode());
+ assertEquals(0, response.getPayload().length, "the response must not
carry the exception's message");
+ }
+
+ @Test
+ void consumerAnswersAPojoReplyBodyWithServerError() throws Exception {
+ CoapResponse response = createClient("/pojoReply").get();
+
+ assertEquals(ResponseCode.INTERNAL_SERVER_ERROR, response.getCode());
+ }
+
+ @Test
+ void consumerAnswersANullReplyBodyWithAnEmptyContent() throws Exception {
+ CoapResponse response = createClient("/nullReply").get();
+
+ assertEquals(ResponseCode.CONTENT, response.getCode());
+ assertEquals(0, response.getPayload().length);
+ }
+
+ private String sinkUri() {
+ return "coap://localhost:" + PORT.getPort() + "/sink";
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ fromF("coap://localhost:%d/sink", PORT.getPort())
+ .to("mock:sink");
+
+ fromF("coap://localhost:%d/mapReply", PORT.getPort())
+ .process(e ->
e.getMessage().setBody(Map.of("temperature", 21)));
+
+ fromF("coap://localhost:%d/pojoReply", PORT.getPort())
+ .process(e -> e.getMessage().setBody(new Reading(21)));
+
+ fromF("coap://localhost:%d/nullReply", PORT.getPort())
+ .setBody(constant(null));
+ }
+ };
+ }
+
+ private record Reading(int temperature) {
+ }
+}
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 958cee6549cd..da68f957b38c 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,18 @@ 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-coap - a body that does not convert to a byte array fails
+
+The `coap` producer sent a `POST` or `PUT` body that has no conversion to
`byte[]`, such as a `Map`, a POJO or a
+number, as an empty payload, without an error, so the data was lost. It now
fails the exchange with an
+`InvalidPayloadException`. A message without a body is still sent with an
empty payload.
+
+The `coap` consumer answered such a reply body with `2.05 Content` and an
empty payload. It now answers
+`5.00 Internal Server Error`, as the CoAP component page documents. A route
without a reply body still answers
+`2.05 Content` with an empty payload.
+
+Marshal such a body first (for example to JSON) or convert it to a `String`.
+
=== 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