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 8d987d91e21f CAMEL-26300: camel-jackson and camel-jackson3 -
application-json writes a Map, a List, text, bytes and a stream, avro-x-struct
reads Json text, the Java object transformers fail clearly on a null body and
read a JsonNode with Jackson 2 too, and the Jackson 3 Avro and Protobuf
transformers wrap their errors (#29215)
8d987d91e21f is described below
commit 8d987d91e21f7e1a0b658b1030c4dd965e71c9a3
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Nov 5 13:03:15 2026 +0100
CAMEL-26300: camel-jackson and camel-jackson3 - application-json writes a
Map, a List, text, bytes and a stream, avro-x-struct reads Json text, the Java
object transformers fail clearly on a null body and read a JsonNode with
Jackson 2 too, and the Jackson 3 Avro and Protobuf transformers wrap their
errors (#29215)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../catalog/transformers/application-json.json | 6 +-
.../catalog/transformers/avro-x-java-object.json | 2 +-
.../camel/catalog/transformers/avro-x-struct.json | 3 +-
.../transformers/protobuf-x-java-object.json | 2 +-
.../camel/transformer/avro-x-java-object.json | 2 +-
.../apache/camel/transformer/avro-x-struct.json | 3 +-
.../transform/AvroPojoDataTypeTransformer.java | 14 ++-
.../transform/AvroStructDataTypeTransformer.java | 10 +-
.../transform/AvroPojoDataTypeTransformerTest.java | 37 +++++-
.../AvroStructDataTypeTransformerTest.java | 46 ++++++--
.../camel/transformer/protobuf-x-java-object.json | 2 +-
.../transform/ProtobufPojoDataTypeTransformer.java | 9 +-
.../ProtobufStructDataTypeTransformer.java | 2 +-
.../ProtobufPojoDataTypeTransformerTest.java | 37 +++++-
.../ProtobufStructDataTypeTransformerTest.java | 16 +++
.../apache/camel/transformer/application-json.json | 6 +-
.../jackson/transform/JsonDataTypeTransformer.java | 44 ++++++--
.../transform/JsonPojoDataTypeTransformer.java | 2 +-
.../transform/JsonDataTypeTransformerTest.java | 125 ++++++++++++++++++++-
.../transform/JsonPojoDataTypeTransformerTest.java | 14 +++
.../camel/transformer/avro-x-java-object.json | 2 +-
.../apache/camel/transformer/avro-x-struct.json | 3 +-
.../transform/AvroBinaryDataTypeTransformer.java | 3 +-
.../transform/AvroPojoDataTypeTransformer.java | 9 +-
.../transform/AvroStructDataTypeTransformer.java | 13 ++-
.../AvroBinaryDataTypeTransformerTest.java | 12 ++
.../transform/AvroPojoDataTypeTransformerTest.java | 37 +++++-
.../AvroStructDataTypeTransformerTest.java | 53 ++++++---
.../camel/transformer/protobuf-x-java-object.json | 2 +-
.../ProtobufBinaryDataTypeTransformer.java | 3 +-
.../transform/ProtobufPojoDataTypeTransformer.java | 4 +-
.../ProtobufStructDataTypeTransformer.java | 5 +-
.../ProtobufBinaryDataTypeTransformerTest.java | 12 ++
.../ProtobufPojoDataTypeTransformerTest.java | 37 +++++-
.../ProtobufStructDataTypeTransformerTest.java | 28 +++++
.../apache/camel/transformer/application-json.json | 6 +-
.../transform/JsonDataTypeTransformer.java | 44 ++++++--
.../transform/JsonPojoDataTypeTransformer.java | 2 +-
.../transform/JsonDataTypeTransformerTest.java | 125 ++++++++++++++++++++-
.../transform/JsonPojoDataTypeTransformerTest.java | 14 +++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 33 ++++++
41 files changed, 722 insertions(+), 107 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/application-json.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/application-json.json
index 1cfae9b4ccfd..3e35142d8732 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/application-json.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/application-json.json
@@ -14,10 +14,14 @@
"transformerIn": {
"carriers": [
"text",
+ "bytes",
+ "stream",
+ "map",
+ "list",
"pojo",
"null"
],
- "description": "A Json object or array as text, a JsonNode or a POJO, of
the class of the X-Content-Class exchange property when set: other text, bytes,
a stream, a Map or a List fail, and a null body becomes null"
+ "description": "A Json object or array as text, bytes or a stream, a
Map, a List, a JsonNode or a POJO; other text, also a Json number, boolean,
null or quoted string, becomes a Json string; bytes and a stream are read as
text with the exchange charset, so a payload that is not text must be encoded
first, such as in Base64, or given a content class; with the X-Content-Class
exchange property only a Json object or array as text or a body of that class;
a null body becomes null"
},
"transformerOut": {
"carriers": [
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-java-object.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-java-object.json
index f03ee1231688..e679b539ff22 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-java-object.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-java-object.json
@@ -19,7 +19,7 @@
"file",
"pojo"
],
- "description": "Json text, or Avro binary with the Avro schema in the
X-Content-Schema exchange property, read into the class of the X-Content-Class
exchange property, which a body other than a POJO needs: a POJO of that class,
or any POJO without it, is kept, and a Map or a null body fail"
+ "description": "Json text or a JsonNode, or Avro binary with the Avro
schema in the X-Content-Schema exchange property, read into the class of the
X-Content-Class exchange property, which a body other than a POJO needs: a POJO
of that class, or any POJO without it, is kept, and a Map or a null body fail"
},
"transformerOut": {
"carriers": [
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-struct.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-struct.json
index 001eec1a900a..915f2f8418b4 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-struct.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/avro-x-struct.json
@@ -13,12 +13,13 @@
"bodyTypes": {
"transformerIn": {
"carriers": [
+ "text",
"bytes",
"stream",
"file",
"pojo"
],
- "description": "Needs the Avro schema in the X-Content-Schema exchange
property: Avro binary, a JsonNode or a POJO; Json text, a Map or a null body
fail"
+ "description": "Needs the Avro schema in the X-Content-Schema exchange
property: a Json object or array as text, Avro binary, a JsonNode or a POJO; a
Map or a null body fail"
},
"transformerOut": {
"carriers": [
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/protobuf-x-java-object.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/protobuf-x-java-object.json
index 4d9d4a164a20..eb133e1b7fa2 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/protobuf-x-java-object.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/protobuf-x-java-object.json
@@ -19,7 +19,7 @@
"file",
"pojo"
],
- "description": "Json text, or Protobuf binary with the Protobuf schema
in the X-Content-Schema exchange property, read into the class of the
X-Content-Class exchange property, which a body other than a POJO needs: a POJO
of that class, or any POJO without it, is kept, and a Map or a null body fail"
+ "description": "Json text or a JsonNode, or Protobuf binary with the
Protobuf schema in the X-Content-Schema exchange property, read into the class
of the X-Content-Class exchange property, which a body other than a POJO needs:
a POJO of that class, or any POJO without it, is kept, and a Map or a null body
fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
index f03ee1231688..e679b539ff22 100644
---
a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
+++
b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
@@ -19,7 +19,7 @@
"file",
"pojo"
],
- "description": "Json text, or Avro binary with the Avro schema in the
X-Content-Schema exchange property, read into the class of the X-Content-Class
exchange property, which a body other than a POJO needs: a POJO of that class,
or any POJO without it, is kept, and a Map or a null body fail"
+ "description": "Json text or a JsonNode, or Avro binary with the Avro
schema in the X-Content-Schema exchange property, read into the class of the
X-Content-Class exchange property, which a body other than a POJO needs: a POJO
of that class, or any POJO without it, is kept, and a Map or a null body fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
index 001eec1a900a..915f2f8418b4 100644
---
a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
+++
b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
@@ -13,12 +13,13 @@
"bodyTypes": {
"transformerIn": {
"carriers": [
+ "text",
"bytes",
"stream",
"file",
"pojo"
],
- "description": "Needs the Avro schema in the X-Content-Schema exchange
property: Avro binary, a JsonNode or a POJO; Json text, a Map or a null body
fail"
+ "description": "Needs the Avro schema in the X-Content-Schema exchange
property: a Json object or array as text, Avro binary, a JsonNode or a POJO; a
Map or a null body fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformer.java
b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformer.java
index 6daeddacee10..5e22ddf17e76 100644
---
a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformer.java
+++
b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformer.java
@@ -39,10 +39,11 @@ import org.apache.camel.spi.DataTypeTransformer;
@DataTypeTransformer(name = "avro-x-java-object",
description = "Transforms from JSon to Java object using
Jackson Avro (supports content schema)")
@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
- description = "Json text, or Avro binary
with the Avro schema in the X-Content-Schema"
- + " exchange property, read
into the class of the X-Content-Class exchange"
- + " property, which a body
other than a POJO needs: a POJO of that class,"
- + " or any POJO without it,
is kept, and a Map or a null body fail"),
+ description = "Json text or a JsonNode,
or Avro binary with the Avro schema in the"
+ + " X-Content-Schema
exchange property, read into the class of the"
+ + " X-Content-Class
exchange property, which a body other than a POJO"
+ + " needs: a POJO of that
class, or any POJO without it, is kept, and a"
+ + " Map or a null body
fail"),
transformerOut = @BodyType(value = Carrier.UNKNOWN,
description = "An object of the class of
the X-Content-Class exchange property"))
public class AvroPojoDataTypeTransformer extends JsonPojoDataTypeTransformer {
@@ -60,8 +61,9 @@ public class AvroPojoDataTypeTransformer extends
JsonPojoDataTypeTransformer {
"Missing proper Avro schema for Java object data type
processing", message.getExchange());
}
- if (message.getBody() instanceof JsonNode) {
- return
Avro.mapper().reader().forType(contentType).with(schema).readValue(message.getBody(JsonNode.class));
+ if (message.getBody() instanceof JsonNode jsonNode) {
+ // the parser of a JsonNode takes no Avro schema
+ return
Avro.mapper().reader().forType(contentType).readValue(jsonNode);
}
return
Avro.mapper().reader().forType(contentType).with(schema).readValue(getBodyAsStream(message));
diff --git
a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformer.java
b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformer.java
index 23bdafb33d17..e44586f249b8 100644
---
a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformer.java
+++
b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformer.java
@@ -27,6 +27,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.component.jackson.SchemaHelper;
+import org.apache.camel.component.jackson.transform.Json;
import org.apache.camel.spi.BodyType;
import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
@@ -41,9 +42,10 @@ import org.apache.camel.spi.Transformer;
*/
@DataTypeTransformer(name = "avro-x-struct",
description = "Transforms to generic JSonNode using
Jackson Avro (supports content schema)")
-@BodyTypes(transformerIn = @BodyType(value = { Carrier.BYTES, Carrier.STREAM,
Carrier.FILE, Carrier.POJO },
- description = "Needs the Avro schema in
the X-Content-Schema exchange property: Avro"
- + " binary, a JsonNode or a
POJO; Json text, a Map or a null body fail"),
+@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
+ description = "Needs the Avro schema in
the X-Content-Schema exchange property: a Json"
+ + " object or array as
text, Avro binary, a JsonNode or a POJO; a Map or a"
+ + " null body fail"),
transformerOut = @BodyType(value = Carrier.POJO, javaType =
"com.fasterxml.jackson.databind.JsonNode"))
public class AvroStructDataTypeTransformer extends Transformer {
@@ -63,6 +65,8 @@ public class AvroStructDataTypeTransformer extends
Transformer {
=
message.getExchange().getContext().getClassResolver().resolveMandatoryClass(contentClass);
unmarshalled =
Avro.mapper().reader().forType(JsonNode.class).with(schema)
.readValue(Avro.mapper().writerFor(contentType).with(schema).writeValueAsBytes(message.getBody()));
+ } else if (message.getBody() instanceof String jsonString &&
Json.isJson(jsonString)) {
+ unmarshalled = Json.mapper().readTree(jsonString);
} else {
unmarshalled =
Avro.mapper().reader().forType(JsonNode.class).with(schema).readValue(getBodyAsStream(message));
}
diff --git
a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformerTest.java
b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformerTest.java
index edcb75fab926..f2d92aca2a00 100644
---
a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformerTest.java
+++
b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroPojoDataTypeTransformerTest.java
@@ -31,6 +31,7 @@ import org.apache.avro.NameValidator;
import org.apache.avro.Schema;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.component.jackson.SchemaHelper;
import org.apache.camel.component.jackson.transform.Json;
import org.apache.camel.impl.DefaultCamelContext;
@@ -136,6 +137,35 @@ class AvroPojoDataTypeTransformerTest {
Assertions.assertEquals(19,
exchange.getMessage().getBody(Person.class).age());
}
+ @Test
+ void shouldHandleJsonNode() throws Exception {
+ // CAMEL-26300: a JsonNode is read into the class of the
X-Content-Class property, with the Avro schema set
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+
exchange.getMessage().setBody(Json.mapper().readerFor(JsonNode.class).readValue("""
+ { "name": "Goofy", "age": 25 }
+ """));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals(new Person("Goofy", 25),
exchange.getMessage().getBody());
+ }
+
+ @Test
+ void shouldFailOnNullBody() throws Exception {
+ // CAMEL-26300: a null body has no Avro binary to read, which fails as
other bodies without it do
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+ exchange.getMessage().setBody(null);
+
+ CamelExecutionException e =
Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ Assertions.assertInstanceOf(InvalidPayloadException.class,
e.getCause());
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
@@ -152,8 +182,8 @@ class AvroPojoDataTypeTransformerTest {
@Test
void theBodyTypesSayTheBody(@TempDir Path dir) throws Exception {
- // CAMEL-25928: Json text, or Avro binary as bytes, a stream or a
file, as transformerIn says, read into the
- // class of the X-Content-Class property, which transformerOut leaves
unknown
+ // CAMEL-25928: Json text or a JsonNode, or Avro binary as bytes, a
stream or a file, as transformerIn
+ // says, read into the class of the X-Content-Class property, which
transformerOut leaves unknown
BodyTypes bodyTypes =
AvroPojoDataTypeTransformer.class.getAnnotation(BodyTypes.class);
Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO),
List.of(bodyTypes.transformerIn().value()));
@@ -161,7 +191,8 @@ class AvroPojoDataTypeTransformerTest {
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
byte[] binary =
Avro.mapper().writer(getSchema()).writeValueAsBytes(new Person("Goofy", 25));
Path file = Files.write(dir.resolve("person.bin"), binary);
- for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile())) {
+ for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile(),
+ Json.mapper().readerFor(JsonNode.class).readValue(json))) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
diff --git
a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformerTest.java
b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformerTest.java
index 4e10a27cae7b..1e31f2c404a0 100644
---
a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformerTest.java
+++
b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/transform/AvroStructDataTypeTransformerTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.jackson.avro.transform;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@@ -64,6 +65,7 @@ class AvroStructDataTypeTransformerTest {
@Test
void shouldHandleJsonString() throws Exception {
+ // CAMEL-26300: Json text is read as Json, not as Avro binary
Exchange exchange = new DefaultExchange(camelContext);
AvroSchema avroSchema = getSchema();
@@ -74,6 +76,36 @@ class AvroStructDataTypeTransformerTest {
transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
Assertions.assertEquals(ObjectNode.class,
exchange.getMessage().getBody().getClass());
+ JsonNode body = exchange.getMessage().getBody(JsonNode.class);
+ Assertions.assertEquals("Christoph", body.get("name").asText());
+ Assertions.assertEquals(32, body.get("age").asInt());
+ }
+
+ @Test
+ void shouldWrapMalformedJsonInCamelExecutionException() throws Exception {
+ // CAMEL-26300: text that looks like Json is read as Json, and fails
as Json
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.getMessage().setBody("{ this is no json }");
+
+ Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ }
+
+ @Test
+ void shouldReadJsonStringWhateverTheExchangeCharset() throws Exception {
+ // CAMEL-26300: Json text is read as the String it is, so a non-ASCII
character works with any exchange charset
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+ exchange.getMessage().setBody("{ \"name\": \"Jos\u00e9\", \"age\": 32
}");
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JsonNode body = exchange.getMessage().getBody(JsonNode.class);
+ Assertions.assertEquals("Jos\u00e9", body.get("name").asText());
+ Assertions.assertEquals(32, body.get("age").asInt());
}
@Test
@@ -131,17 +163,17 @@ class AvroStructDataTypeTransformerTest {
@Test
void theBodyTypesSayTheBody(@TempDir Path dir) throws Exception {
- // CAMEL-25928: Avro binary as bytes, a stream or a file, a POJO or a
JsonNode, as transformerIn says, and
- // the JsonNode transformerOut says
+ // CAMEL-25928: Json text, Avro binary as bytes, a stream or a file, a
POJO or a JsonNode, as transformerIn
+ // says, and the JsonNode transformerOut says
BodyTypes bodyTypes =
AvroStructDataTypeTransformer.class.getAnnotation(BodyTypes.class);
- Assertions.assertEquals(List.of(Carrier.BYTES, Carrier.STREAM,
Carrier.FILE, Carrier.POJO),
+ Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO),
List.of(bodyTypes.transformerIn().value()));
Assertions.assertEquals(List.of(Carrier.POJO),
List.of(bodyTypes.transformerOut().value()));
Assertions.assertEquals(JsonNode.class.getName(),
bodyTypes.transformerOut().javaType());
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
byte[] binary =
Avro.mapper().writer(getSchema()).writeValueAsBytes(new Person("Goofy", 25));
Path file = Files.write(dir.resolve("person.bin"), binary);
- for (Object body : Arrays.asList(binary, new
ByteArrayInputStream(binary), file.toFile(), new Person("Goofy", 25),
+ for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile(), new Person("Goofy", 25),
Json.mapper().readerFor(JsonNode.class).readValue(json))) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
@@ -155,11 +187,5 @@ class AvroStructDataTypeTransformerTest {
other.getMessage().setBody(Map.of("name", "Goofy"));
Assertions.assertThrows(CamelExecutionException.class,
() -> transformer.transform(other.getMessage(), DataType.ANY,
DataType.ANY));
- // nor is Json text
- Exchange text = new DefaultExchange(camelContext);
- text.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
- text.getMessage().setBody(json);
- Assertions.assertThrows(RuntimeException.class,
- () -> transformer.transform(text.getMessage(), DataType.ANY,
DataType.ANY));
}
}
diff --git
a/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
index 4d9d4a164a20..eb133e1b7fa2 100644
---
a/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
+++
b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
@@ -19,7 +19,7 @@
"file",
"pojo"
],
- "description": "Json text, or Protobuf binary with the Protobuf schema
in the X-Content-Schema exchange property, read into the class of the
X-Content-Class exchange property, which a body other than a POJO needs: a POJO
of that class, or any POJO without it, is kept, and a Map or a null body fail"
+ "description": "Json text or a JsonNode, or Protobuf binary with the
Protobuf schema in the X-Content-Schema exchange property, read into the class
of the X-Content-Class exchange property, which a body other than a POJO needs:
a POJO of that class, or any POJO without it, is kept, and a Map or a null body
fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformer.java
b/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformer.java
index 051abeff6462..3bfa0f80a6d5 100644
---
a/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformer.java
+++
b/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformer.java
@@ -39,8 +39,8 @@ import org.apache.camel.spi.DataTypeTransformer;
@DataTypeTransformer(name = "protobuf-x-java-object",
description = "Transforms from JSon to Java object using
Jackson Protobuf (supports content schema)")
@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
- description = "Json text, or Protobuf
binary with the Protobuf schema in the"
- + " X-Content-Schema
exchange property, read into the class of the"
+ description = "Json text or a JsonNode,
or Protobuf binary with the Protobuf schema"
+ + " in the X-Content-Schema
exchange property, read into the class of the"
+ " X-Content-Class
exchange property, which a body other than a POJO"
+ " needs: a POJO of that
class, or any POJO without it, is kept, and a"
+ " Map or a null body
fail"),
@@ -61,8 +61,9 @@ public class ProtobufPojoDataTypeTransformer extends
JsonPojoDataTypeTransformer
"Missing proper Protobuf schema for Java object data
type processing", message.getExchange());
}
- if (message.getBody() instanceof JsonNode) {
- return
Protobuf.mapper().reader().forType(contentType).with(schema).readValue(message.getBody(JsonNode.class));
+ if (message.getBody() instanceof JsonNode jsonNode) {
+ // the parser of a JsonNode takes no Protobuf schema
+ return
Protobuf.mapper().reader().forType(contentType).readValue(jsonNode);
}
return
Protobuf.mapper().reader().forType(contentType).with(schema).readValue(getBodyAsStream(message));
diff --git
a/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformer.java
b/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformer.java
index b2997899d881..d17d3d9c8f2b 100644
---
a/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformer.java
+++
b/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformer.java
@@ -66,7 +66,7 @@ public class ProtobufStructDataTypeTransformer extends
Transformer {
unmarshalled =
Protobuf.mapper().reader().forType(JsonNode.class).with(schema)
.readValue(Protobuf.mapper().writerFor(contentType).with(schema).writeValueAsBytes(message.getBody()));
} else if (message.getBody() instanceof String jsonString &&
Json.isJson(jsonString)) {
- unmarshalled =
Json.mapper().readTree(getBodyAsStream(message));
+ unmarshalled = Json.mapper().readTree(jsonString);
} else {
unmarshalled
=
Protobuf.mapper().reader().forType(JsonNode.class).with(schema).readValue(getBodyAsStream(message));
diff --git
a/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
index 836446ad74c2..a238eade2c68 100644
---
a/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
+++
b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.component.jackson.SchemaHelper;
import org.apache.camel.component.jackson.transform.Json;
import org.apache.camel.impl.DefaultCamelContext;
@@ -134,6 +135,35 @@ class ProtobufPojoDataTypeTransformerTest {
Assertions.assertEquals(19,
exchange.getMessage().getBody(Person.class).age());
}
+ @Test
+ void shouldHandleJsonNode() throws Exception {
+ // CAMEL-26300: a JsonNode is read into the class of the
X-Content-Class property, with the Protobuf schema set
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+
exchange.getMessage().setBody(Json.mapper().readerFor(JsonNode.class).readValue("""
+ { "name": "Goofy", "age": 25 }
+ """));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals(new Person("Goofy", 25),
exchange.getMessage().getBody());
+ }
+
+ @Test
+ void shouldFailOnNullBody() throws Exception {
+ // CAMEL-26300: a null body has no Protobuf binary to read, which
fails as other bodies without it do
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+ exchange.getMessage().setBody(null);
+
+ CamelExecutionException e =
Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ Assertions.assertInstanceOf(InvalidPayloadException.class,
e.getCause());
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
@@ -149,8 +179,8 @@ class ProtobufPojoDataTypeTransformerTest {
@Test
void theBodyTypesSayTheBody(@TempDir Path dir) throws Exception {
- // CAMEL-25928: Json text, or Protobuf binary as bytes, a stream or a
file, as transformerIn says, read into the
- // class of the X-Content-Class property, which transformerOut leaves
unknown
+ // CAMEL-25928: Json text or a JsonNode, or Protobuf binary as bytes,
a stream or a file, as transformerIn
+ // says, read into the class of the X-Content-Class property, which
transformerOut leaves unknown
BodyTypes bodyTypes =
ProtobufPojoDataTypeTransformer.class.getAnnotation(BodyTypes.class);
Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO),
List.of(bodyTypes.transformerIn().value()));
@@ -158,7 +188,8 @@ class ProtobufPojoDataTypeTransformerTest {
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
byte[] binary =
Protobuf.mapper().writer(getSchema()).writeValueAsBytes(new Person("Goofy",
25));
Path file = Files.write(dir.resolve("person.bin"), binary);
- for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile())) {
+ for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile(),
+ Json.mapper().readerFor(JsonNode.class).readValue(json))) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
diff --git
a/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
index e3346a6bebae..8da716e59e4e 100644
---
a/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
+++
b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.jackson.protobuf.transform;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@@ -75,6 +76,21 @@ class ProtobufStructDataTypeTransformerTest {
Assertions.assertEquals(ObjectNode.class,
exchange.getMessage().getBody().getClass());
}
+ @Test
+ void shouldReadJsonStringWhateverTheExchangeCharset() throws Exception {
+ // CAMEL-26300: Json text is read as the String it is, so a non-ASCII
character works with any exchange charset
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+ exchange.getMessage().setBody("{ \"name\": \"Jos\u00e9\", \"age\": 32
}");
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JsonNode body = exchange.getMessage().getBody(JsonNode.class);
+ Assertions.assertEquals("Jos\u00e9", body.get("name").asText());
+ Assertions.assertEquals(32, body.get("age").asInt());
+ }
+
@Test
void shouldHandlePojo() throws Exception {
Exchange exchange = new DefaultExchange(camelContext);
diff --git
a/components/camel-jackson/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
b/components/camel-jackson/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
index 1cfae9b4ccfd..3e35142d8732 100644
---
a/components/camel-jackson/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
+++
b/components/camel-jackson/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
@@ -14,10 +14,14 @@
"transformerIn": {
"carriers": [
"text",
+ "bytes",
+ "stream",
+ "map",
+ "list",
"pojo",
"null"
],
- "description": "A Json object or array as text, a JsonNode or a POJO, of
the class of the X-Content-Class exchange property when set: other text, bytes,
a stream, a Map or a List fail, and a null body becomes null"
+ "description": "A Json object or array as text, bytes or a stream, a
Map, a List, a JsonNode or a POJO; other text, also a Json number, boolean,
null or quoted string, becomes a Json string; bytes and a stream are read as
text with the exchange charset, so a payload that is not text must be encoded
first, such as in Base64, or given a content class; with the X-Content-Class
exchange property only a Json object or array as text or a body of that class;
a null body becomes null"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformer.java
b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformer.java
index ee9fba6f6c92..d451c1559983 100644
---
a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformer.java
+++
b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformer.java
@@ -16,12 +16,17 @@
*/
package org.apache.camel.component.jackson.transform;
+import java.io.InputStream;
+import java.io.Reader;
import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.component.jackson.SchemaHelper;
import org.apache.camel.spi.BodyType;
@@ -34,14 +39,20 @@ import org.apache.camel.spi.Transformer;
/**
* Data type uses Jackson data format to marshal given Exchange payload to a
Json (binary byte array representation).
- * Requires Exchange payload as JsonNode representation.
+ * Json text is kept, other text becomes a Json string, and a Map, a List, a
JsonNode or a POJO is written as Json.
+ * Bytes and a stream are read as text with the charset of the exchange.
*/
@DataTypeTransformer(name = "application-json",
description = "Transforms from JSon to binary (byte
array) using Jackson")
-@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.POJO,
Carrier.NULL },
- description = "A Json object or array as
text, a JsonNode or a POJO, of the class of"
- + " the X-Content-Class
exchange property when set: other text, bytes, a"
- + " stream, a Map or a List
fail, and a null body becomes null"),
+@BodyTypes(transformerIn = @BodyType(value = {
+ Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.MAP,
Carrier.LIST, Carrier.POJO, Carrier.NULL },
+ description = "A Json object or array as
text, bytes or a stream, a Map, a List, a"
+ + " JsonNode or a POJO;
other text, also a Json number, boolean, null or"
+ + " quoted string, becomes
a Json string; bytes and a stream are read as"
+ + " text with the exchange
charset, so a payload that is not text must be"
+ + " encoded first, such as
in Base64, or given a content class; with the"
+ + " X-Content-Class
exchange property only a Json object or array as text"
+ + " or a body of that
class; a null body becomes null"),
transformerOut = @BodyType(value = Carrier.BYTES, javaType =
"byte[]", format = "json"))
public class JsonDataTypeTransformer extends Transformer {
@@ -49,14 +60,24 @@ public class JsonDataTypeTransformer extends Transformer {
public void transform(Message message, DataType fromType, DataType toType)
{
try {
byte[] marshalled;
- if (message.getBody() instanceof String jsonString &&
Json.isJson(jsonString)) {
+ Object body = message.getBody();
+ boolean hasContentClass =
message.getExchange().getProperty(SchemaHelper.CONTENT_CLASS, String.class) !=
null;
+ if (body instanceof String jsonString && Json.isJson(jsonString)) {
marshalled = jsonString.getBytes(StandardCharsets.UTF_8);
+ } else if (!hasContentClass && (body instanceof Map || body
instanceof Collection)) {
+ // a Map or a List has no class to write it as: it becomes a
Json object or array
+ marshalled = Json.mapper().writeValueAsBytes(body);
+ } else if (!hasContentClass && isText(body)) {
+ // read as text with the exchange charset: a Json object or
array is kept, and any other text, also a
+ // Json number, boolean, null or quoted string, becomes a Json
string
+ String text = message.getMandatoryBody(String.class);
+ marshalled = Json.isJson(text) ?
text.getBytes(StandardCharsets.UTF_8) : Json.mapper().writeValueAsBytes(text);
} else {
String contentClass =
SchemaHelper.resolveContentClass(message.getExchange(),
JsonNode.class.getName());
Class<?> contentType
=
message.getExchange().getContext().getClassResolver().resolveMandatoryClass(contentClass);
- marshalled =
Json.mapper().writer().forType(contentType).writeValueAsBytes(message.getBody());
+ marshalled =
Json.mapper().writer().forType(contentType).writeValueAsBytes(body);
}
message.setBody(marshalled);
@@ -66,8 +87,15 @@ public class JsonDataTypeTransformer extends Transformer {
if (contentSchema != null) {
message.setHeader(SchemaHelper.CONTENT_SCHEMA, contentSchema);
}
- } catch (JsonProcessingException | ClassNotFoundException e) {
+ } catch (InvalidPayloadException | JsonProcessingException |
ClassNotFoundException e) {
throw new CamelExecutionException("Failed to apply Json output
data type on exchange", message.getExchange(), e);
}
}
+
+ /**
+ * Text, bytes or a stream, which hold Json as text.
+ */
+ private static boolean isText(Object body) {
+ return body instanceof String || body instanceof byte[] || body
instanceof InputStream || body instanceof Reader;
+ }
}
diff --git
a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformer.java
b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformer.java
index f423cd395b61..2c23d1cc812f 100644
---
a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformer.java
+++
b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformer.java
@@ -75,7 +75,7 @@ public class JsonPojoDataTypeTransformer extends Transformer
implements CamelCon
message.setHeader(Exchange.CONTENT_TYPE,
MimeType.JAVA_OBJECT.type());
Class<?> contentType =
camelContext.getClassResolver().resolveMandatoryClass(contentClass);
- if (contentType.isAssignableFrom(message.getBody().getClass())) {
+ if (contentType.isInstance(message.getBody())) {
return;
}
diff --git
a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformerTest.java
b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformerTest.java
index fa0f4ee7d234..0acae6cfd382 100644
---
a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformerTest.java
+++
b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonDataTypeTransformerTest.java
@@ -17,7 +17,11 @@
package org.apache.camel.component.jackson.transform;
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -25,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
import org.apache.camel.component.jackson.SchemaHelper;
+import org.apache.camel.converter.stream.InputStreamCache;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
@@ -103,26 +108,134 @@ class JsonDataTypeTransformerTest {
Assertions.assertEquals(JsonDataTypeTransformer.class,
transformer.getClass());
}
+ @Test
+ void shouldHandleMapAndList() throws Exception {
+ // CAMEL-26300: a Map or a List has no class of its own, and is
written as a Json object or array
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("name", "Goofy");
+ map.put("age", 25);
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(map);
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JSONAssert.assertEquals("""
+ {"name":"Goofy","age":25}
+ """, exchange.getMessage().getBody(String.class), true);
+
+ exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(List.of(new Person("Goofy", 25),
Map.of("name", "Mickey")));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JSONAssert.assertEquals("""
+ [{"name":"Goofy","age":25},{"name":"Mickey"}]
+ """, exchange.getMessage().getBody(String.class), true);
+ }
+
+ @Test
+ void shouldHandleJsonAsBytesOrStream() throws Exception {
+ // CAMEL-26300: Json as bytes, a stream, a reader or the stream cache
is kept as it is
+ String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
+ byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
+ for (Object body : List.of(bytes, new ByteArrayInputStream(bytes), new
StringReader(json),
+ new InputStreamCache(bytes))) {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(body);
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertInstanceOf(byte[].class,
exchange.getMessage().getBody(), body.getClass().getName());
+ Assertions.assertEquals(json,
exchange.getMessage().getBody(String.class), body.getClass().getName());
+ }
+ }
+
+ @Test
+ void shouldWriteOtherTextAsJsonString() throws Exception {
+ // CAMEL-26300: text that is no Json object or array, as a String or
as bytes, becomes a Json string
+ String text = "Hello \"Goofy\"";
+ for (Object body : List.of(text,
text.getBytes(StandardCharsets.UTF_8))) {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(body);
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals("\"Hello \\\"Goofy\\\"\"",
exchange.getMessage().getBody(String.class),
+ body.getClass().getName());
+ Assertions.assertEquals(text,
Json.mapper().readTree(exchange.getMessage().getBody(byte[].class)).asText());
+ }
+ }
+
+ @Test
+ void shouldWriteJsonScalarTextAsJsonString() throws Exception {
+ // CAMEL-26300: only a Json object or array is kept as Json text, so a
Json number, boolean, null or quoted
+ // string given as text becomes a Json string
+ Map<String, String> expected = new LinkedHashMap<>();
+ expected.put("42", "\"42\"");
+ expected.put("true", "\"true\"");
+ expected.put("null", "\"null\"");
+ expected.put("\"Hello\"", "\"\\\"Hello\\\"\"");
+ for (Map.Entry<String, String> entry : expected.entrySet()) {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(entry.getKey());
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals(entry.getValue(),
exchange.getMessage().getBody(String.class), entry.getKey());
+ Assertions.assertEquals(entry.getKey(),
+
Json.mapper().readTree(exchange.getMessage().getBody(byte[].class)).asText());
+ }
+ }
+
+ @Test
+ void shouldReadBytesWithTheExchangeCharset() throws Exception {
+ // CAMEL-26300: bytes are read as text with the charset of the
exchange, and the Json is written in UTF-8
+ String json = "{\"name\":\"Jos\u00e9\"}";
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+
exchange.getMessage().setBody(json.getBytes(StandardCharsets.ISO_8859_1));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertArrayEquals(json.getBytes(StandardCharsets.UTF_8),
exchange.getMessage().getBody(byte[].class));
+
+ exchange = new DefaultExchange(camelContext);
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+
exchange.getMessage().setBody("caf\u00e9".getBytes(StandardCharsets.ISO_8859_1));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+
Assertions.assertArrayEquals("\"caf\u00e9\"".getBytes(StandardCharsets.UTF_8),
+ exchange.getMessage().getBody(byte[].class));
+ }
+
+ @Test
+ void shouldWriteBytesInBase64WithTheByteArrayContentClass() throws
Exception {
+ // CAMEL-26300: a payload that is not text can be given the content
class byte[], which Jackson writes in Base64
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS, "byte[]");
+ exchange.getMessage().setBody(new byte[] { 0, 1, 2, (byte) 0xFF });
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals("\"AAEC/w==\"",
exchange.getMessage().getBody(String.class));
+ }
+
@Test
void theBodyTypesSayTheBody() throws Exception {
- // CAMEL-25928: Json text, a JsonNode, a POJO or a null body, as
transformerIn says, and the Json bytes
- // transformerOut says
+ // CAMEL-25928: Json as text, bytes or a stream, other text, a Map, a
List, a JsonNode, a POJO or a null body,
+ // as transformerIn says, and the Json bytes transformerOut says
BodyTypes bodyTypes =
JsonDataTypeTransformer.class.getAnnotation(BodyTypes.class);
- Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.POJO,
Carrier.NULL),
+ Assertions.assertEquals(
+ List.of(Carrier.TEXT, Carrier.BYTES, Carrier.STREAM,
Carrier.MAP, Carrier.LIST, Carrier.POJO, Carrier.NULL),
List.of(bodyTypes.transformerIn().value()));
Assertions.assertEquals(List.of(Carrier.BYTES),
List.of(bodyTypes.transformerOut().value()));
Assertions.assertEquals(byte[].class.getCanonicalName(),
bodyTypes.transformerOut().javaType());
Assertions.assertEquals("json", bodyTypes.transformerOut().format());
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
- for (Object body : Arrays.asList(json,
Json.mapper().readerFor(JsonNode.class).readValue(json), new Person("Goofy",
25),
- null)) {
+ byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
+ for (Object body : Arrays.asList(json, bytes, new
ByteArrayInputStream(bytes), "Goofy", Map.of("name", "Goofy"),
+ List.of("Goofy"),
Json.mapper().readerFor(JsonNode.class).readValue(json), new Person("Goofy",
25), null)) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.getMessage().setBody(body);
transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
Assertions.assertInstanceOf(byte[].class,
exchange.getMessage().getBody(), String.valueOf(body));
}
- // a Map is no JsonNode, the class written without the X-Content-Class
property
+ // with the X-Content-Class property only a body of that class
Exchange other = new DefaultExchange(camelContext);
+ other.setProperty(SchemaHelper.CONTENT_CLASS, Person.class.getName());
other.getMessage().setBody(Map.of("name", "Goofy"));
Assertions.assertThrows(CamelExecutionException.class,
() -> transformer.transform(other.getMessage(), DataType.ANY,
DataType.ANY));
diff --git
a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformerTest.java
b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformerTest.java
index 3bd36d0c1de4..dc0db78e868f 100644
---
a/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformerTest.java
+++
b/components/camel-jackson/src/test/java/org/apache/camel/component/jackson/transform/JsonPojoDataTypeTransformerTest.java
@@ -28,6 +28,7 @@ import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.component.jackson.SchemaHelper;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.BodyType.Carrier;
@@ -124,6 +125,19 @@ class JsonPojoDataTypeTransformerTest {
Assertions.assertEquals(19,
exchange.getMessage().getBody(Person.class).age());
}
+ @Test
+ void shouldFailOnNullBody() {
+ // CAMEL-26300: a null body has no Json to read, which fails as other
bodies without Json do
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+ exchange.getMessage().setBody(null);
+
+ CamelExecutionException e =
Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ Assertions.assertInstanceOf(InvalidPayloadException.class,
e.getCause());
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
diff --git
a/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
b/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
index f91a8856e8d5..0d488b5eaa92 100644
---
a/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
+++
b/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-java-object.json
@@ -19,7 +19,7 @@
"file",
"pojo"
],
- "description": "Json text, or Avro binary with the Avro schema in the
X-Content-Schema exchange property, read into the class of the X-Content-Class
exchange property, which a body other than a POJO needs: a POJO of that class,
or any POJO without it, is kept, and a Map or a null body fail"
+ "description": "Json text or a JsonNode, or Avro binary with the Avro
schema in the X-Content-Schema exchange property, read into the class of the
X-Content-Class exchange property, which a body other than a POJO needs: a POJO
of that class, or any POJO without it, is kept, and a Map or a null body fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
b/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
index 6c31d85e72e3..c834924de3cf 100644
---
a/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
+++
b/components/camel-jackson3-avro/src/generated/resources/META-INF/services/org/apache/camel/transformer/avro-x-struct.json
@@ -13,12 +13,13 @@
"bodyTypes": {
"transformerIn": {
"carriers": [
+ "text",
"bytes",
"stream",
"file",
"pojo"
],
- "description": "Needs the Avro schema in the X-Content-Schema exchange
property: Avro binary, a JsonNode or a POJO; Json text, a Map or a null body
fail"
+ "description": "Needs the Avro schema in the X-Content-Schema exchange
property: a Json object or array as text, Avro binary, a JsonNode or a POJO; a
Map or a null body fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformer.java
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformer.java
index 69de1c8099fa..3c1875a1c814 100644
---
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformer.java
+++
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformer.java
@@ -34,6 +34,7 @@ import org.apache.camel.spi.DataType;
import org.apache.camel.spi.DataTypeTransformer;
import org.apache.camel.spi.MimeType;
import org.apache.camel.spi.Transformer;
+import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.dataformat.avro.AvroSchema;
@@ -77,7 +78,7 @@ public class AvroBinaryDataTypeTransformer extends
Transformer {
message.setHeader(Exchange.CONTENT_TYPE,
MimeType.AVRO_BINARY.type());
message.setHeader(SchemaHelper.CONTENT_SCHEMA,
schema.getAvroSchema().getFullName());
- } catch (InvalidPayloadException | IOException |
ClassNotFoundException e) {
+ } catch (InvalidPayloadException | IOException | JacksonException |
ClassNotFoundException e) {
throw new CamelExecutionException("Failed to apply Avro binary
data type on exchange", message.getExchange(), e);
}
}
diff --git
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformer.java
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformer.java
index 7d17434b2203..e754ed5fbfcd 100644
---
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformer.java
+++
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformer.java
@@ -39,10 +39,11 @@ import tools.jackson.databind.JsonNode;
@DataTypeTransformer(name = "avro-x-java-object",
description = "Transforms from JSon to Java object using
Jackson Avro (supports content schema)")
@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
- description = "Json text, or Avro binary
with the Avro schema in the X-Content-Schema"
- + " exchange property, read
into the class of the X-Content-Class exchange"
- + " property, which a body
other than a POJO needs: a POJO of that class,"
- + " or any POJO without it,
is kept, and a Map or a null body fail"),
+ description = "Json text or a JsonNode,
or Avro binary with the Avro schema in the"
+ + " X-Content-Schema
exchange property, read into the class of the"
+ + " X-Content-Class
exchange property, which a body other than a POJO"
+ + " needs: a POJO of that
class, or any POJO without it, is kept, and a"
+ + " Map or a null body
fail"),
transformerOut = @BodyType(value = Carrier.UNKNOWN,
description = "An object of the class of
the X-Content-Class exchange property"))
public class AvroPojoDataTypeTransformer extends JsonPojoDataTypeTransformer {
diff --git
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformer.java
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformer.java
index cf9b6403abc0..db3b15bf250f 100644
---
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformer.java
+++
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformer.java
@@ -24,6 +24,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.component.jackson3.SchemaHelper;
+import org.apache.camel.component.jackson3.transform.Json;
import org.apache.camel.spi.BodyType;
import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
@@ -31,6 +32,7 @@ import org.apache.camel.spi.DataType;
import org.apache.camel.spi.DataTypeTransformer;
import org.apache.camel.spi.MimeType;
import org.apache.camel.spi.Transformer;
+import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.dataformat.avro.AvroSchema;
@@ -40,9 +42,10 @@ import tools.jackson.dataformat.avro.AvroSchema;
*/
@DataTypeTransformer(name = "avro-x-struct",
description = "Transforms to generic JSonNode using
Jackson Avro (supports content schema)")
-@BodyTypes(transformerIn = @BodyType(value = { Carrier.BYTES, Carrier.STREAM,
Carrier.FILE, Carrier.POJO },
- description = "Needs the Avro schema in
the X-Content-Schema exchange property: Avro"
- + " binary, a JsonNode or a
POJO; Json text, a Map or a null body fail"),
+@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
+ description = "Needs the Avro schema in
the X-Content-Schema exchange property: a Json"
+ + " object or array as
text, Avro binary, a JsonNode or a POJO; a Map or a"
+ + " null body fail"),
transformerOut = @BodyType(value = Carrier.POJO, javaType =
"tools.jackson.databind.JsonNode"))
public class AvroStructDataTypeTransformer extends Transformer {
@@ -62,6 +65,8 @@ public class AvroStructDataTypeTransformer extends
Transformer {
=
message.getExchange().getContext().getClassResolver().resolveMandatoryClass(contentClass);
unmarshalled =
Avro.mapper().reader().forType(JsonNode.class).with(schema)
.readValue(Avro.mapper().writerFor(contentType).with(schema).writeValueAsBytes(message.getBody()));
+ } else if (message.getBody() instanceof String jsonString &&
Json.isJson(jsonString)) {
+ unmarshalled = Json.mapper().readTree(jsonString);
} else {
unmarshalled =
Avro.mapper().reader().forType(JsonNode.class).with(schema).readValue(getBodyAsStream(message));
}
@@ -69,7 +74,7 @@ public class AvroStructDataTypeTransformer extends
Transformer {
message.setBody(unmarshalled);
message.setHeader(Exchange.CONTENT_TYPE, MimeType.STRUCT.type());
- } catch (InvalidPayloadException | ClassNotFoundException e) {
+ } catch (InvalidPayloadException | JacksonException |
ClassNotFoundException e) {
throw new CamelExecutionException("Failed to apply Avro x-struct
data type on exchange", message.getExchange(), e);
}
}
diff --git
a/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformerTest.java
b/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformerTest.java
index db1d56326507..c4ce9568fdf5 100644
---
a/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformerTest.java
+++
b/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroBinaryDataTypeTransformerTest.java
@@ -118,6 +118,18 @@ class AvroBinaryDataTypeTransformerTest {
Avro.mapper().reader().with(avroSchema).readTree(exchange.getMessage().getBody(byte[].class))),
true);
}
+ @Test
+ void shouldWrapMalformedJsonInCamelExecutionException() throws Exception {
+ // CAMEL-26300: the JacksonException of Jackson 3 is wrapped, as the
errors of Jackson 2 are
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.getMessage().setBody("{ this is no json }");
+
+ Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
diff --git
a/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformerTest.java
b/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformerTest.java
index ef41b4042a8a..78e268f22c5b 100644
---
a/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformerTest.java
+++
b/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroPojoDataTypeTransformerTest.java
@@ -29,6 +29,7 @@ import org.apache.avro.NameValidator;
import org.apache.avro.Schema;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.component.jackson3.SchemaHelper;
import org.apache.camel.component.jackson3.transform.Json;
import org.apache.camel.impl.DefaultCamelContext;
@@ -136,6 +137,35 @@ class AvroPojoDataTypeTransformerTest {
Assertions.assertEquals(19,
exchange.getMessage().getBody(Person.class).age());
}
+ @Test
+ void shouldHandleJsonNode() throws Exception {
+ // CAMEL-26300: a JsonNode is read into the class of the
X-Content-Class property, with the Avro schema set
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+
exchange.getMessage().setBody(Json.mapper().readerFor(JsonNode.class).readValue("""
+ { "name": "Goofy", "age": 25 }
+ """));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals(new Person("Goofy", 25),
exchange.getMessage().getBody());
+ }
+
+ @Test
+ void shouldFailOnNullBody() throws Exception {
+ // CAMEL-26300: a null body has no Avro binary to read, which fails as
other bodies without it do
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+ exchange.getMessage().setBody(null);
+
+ CamelExecutionException e =
Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ Assertions.assertInstanceOf(InvalidPayloadException.class,
e.getCause());
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
@@ -152,8 +182,8 @@ class AvroPojoDataTypeTransformerTest {
@Test
void theBodyTypesSayTheBody(@TempDir Path dir) throws Exception {
- // CAMEL-25928: Json text, or Avro binary as bytes, a stream or a
file, as transformerIn says, read into the
- // class of the X-Content-Class property, which transformerOut leaves
unknown
+ // CAMEL-25928: Json text or a JsonNode, or Avro binary as bytes, a
stream or a file, as transformerIn
+ // says, read into the class of the X-Content-Class property, which
transformerOut leaves unknown
BodyTypes bodyTypes =
AvroPojoDataTypeTransformer.class.getAnnotation(BodyTypes.class);
Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO),
List.of(bodyTypes.transformerIn().value()));
@@ -161,7 +191,8 @@ class AvroPojoDataTypeTransformerTest {
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
byte[] binary =
Avro.mapper().writer(getSchema()).writeValueAsBytes(new Person("Goofy", 25));
Path file = Files.write(dir.resolve("person.bin"), binary);
- for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile())) {
+ for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile(),
+ Json.mapper().readerFor(JsonNode.class).readValue(json))) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
diff --git
a/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformerTest.java
b/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformerTest.java
index 5f01dec19e16..102fc907d5d1 100644
---
a/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformerTest.java
+++
b/components/camel-jackson3-avro/src/test/java/org/apache/camel/component/jackson3/avro/transform/AvroStructDataTypeTransformerTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.jackson3.avro.transform;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@@ -68,19 +69,47 @@ class AvroStructDataTypeTransformerTest {
@Test
void shouldHandleJsonString() throws Exception {
+ // CAMEL-26300: Json text is read as Json, not as Avro binary
Exchange exchange = new DefaultExchange(camelContext);
AvroSchema avroSchema = getSchema();
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, avroSchema);
- Person person = Json.mapper().readValue("""
+ exchange.getMessage().setBody("""
{ "name": "Christoph", "age": 32 }
- """, Person.class);
- exchange.getMessage().setBody(AvroMapper.builder()
-
.disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS).build()
- .writer(avroSchema).writeValueAsBytes(person));
+ """);
transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
Assertions.assertEquals(ObjectNode.class,
exchange.getMessage().getBody().getClass());
+ JsonNode body = exchange.getMessage().getBody(JsonNode.class);
+ Assertions.assertEquals("Christoph", body.get("name").asString());
+ Assertions.assertEquals(32, body.get("age").asInt());
+ }
+
+ @Test
+ void shouldWrapMalformedJsonInCamelExecutionException() throws Exception {
+ // CAMEL-26300: the JacksonException of Jackson 3 is wrapped, as the
errors of Jackson 2 are
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.getMessage().setBody("{ this is no json }");
+
+ Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ }
+
+ @Test
+ void shouldReadJsonStringWhateverTheExchangeCharset() throws Exception {
+ // CAMEL-26300: Json text is read as the String it is, so a non-ASCII
character works with any exchange charset
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+ exchange.getMessage().setBody("{ \"name\": \"Jos\u00e9\", \"age\": 32
}");
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JsonNode body = exchange.getMessage().getBody(JsonNode.class);
+ Assertions.assertEquals("Jos\u00e9", body.get("name").asString());
+ Assertions.assertEquals(32, body.get("age").asInt());
}
@Test
@@ -138,17 +167,17 @@ class AvroStructDataTypeTransformerTest {
@Test
void theBodyTypesSayTheBody(@TempDir Path dir) throws Exception {
- // CAMEL-25928: Avro binary as bytes, a stream or a file, a POJO or a
JsonNode, as transformerIn says, and
- // the JsonNode transformerOut says
+ // CAMEL-25928: Json text, Avro binary as bytes, a stream or a file, a
POJO or a JsonNode, as transformerIn
+ // says, and the JsonNode transformerOut says
BodyTypes bodyTypes =
AvroStructDataTypeTransformer.class.getAnnotation(BodyTypes.class);
- Assertions.assertEquals(List.of(Carrier.BYTES, Carrier.STREAM,
Carrier.FILE, Carrier.POJO),
+ Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO),
List.of(bodyTypes.transformerIn().value()));
Assertions.assertEquals(List.of(Carrier.POJO),
List.of(bodyTypes.transformerOut().value()));
Assertions.assertEquals(JsonNode.class.getName(),
bodyTypes.transformerOut().javaType());
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
byte[] binary =
Avro.mapper().writer(getSchema()).writeValueAsBytes(new Person("Goofy", 25));
Path file = Files.write(dir.resolve("person.bin"), binary);
- for (Object body : Arrays.asList(binary, new
ByteArrayInputStream(binary), file.toFile(), new Person("Goofy", 25),
+ for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile(), new Person("Goofy", 25),
Json.mapper().readerFor(JsonNode.class).readValue(json))) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
@@ -162,11 +191,5 @@ class AvroStructDataTypeTransformerTest {
other.getMessage().setBody(Map.of("name", "Goofy"));
Assertions.assertThrows(CamelExecutionException.class,
() -> transformer.transform(other.getMessage(), DataType.ANY,
DataType.ANY));
- // nor is Json text
- Exchange text = new DefaultExchange(camelContext);
- text.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
- text.getMessage().setBody(json);
- Assertions.assertThrows(RuntimeException.class,
- () -> transformer.transform(text.getMessage(), DataType.ANY,
DataType.ANY));
}
}
diff --git
a/components/camel-jackson3-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
b/components/camel-jackson3-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
index 96e39da0c2e0..ab1b1270e99f 100644
---
a/components/camel-jackson3-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
+++
b/components/camel-jackson3-protobuf/src/generated/resources/META-INF/services/org/apache/camel/transformer/protobuf-x-java-object.json
@@ -19,7 +19,7 @@
"file",
"pojo"
],
- "description": "Json text, or Protobuf binary with the Protobuf schema
in the X-Content-Schema exchange property, read into the class of the
X-Content-Class exchange property, which a body other than a POJO needs: a POJO
of that class, or any POJO without it, is kept, and a Map or a null body fail"
+ "description": "Json text or a JsonNode, or Protobuf binary with the
Protobuf schema in the X-Content-Schema exchange property, read into the class
of the X-Content-Class exchange property, which a body other than a POJO needs:
a POJO of that class, or any POJO without it, is kept, and a Map or a null body
fail"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformer.java
b/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformer.java
index 84c7ac299ee5..400deeba15c1 100644
---
a/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformer.java
+++
b/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformer.java
@@ -33,6 +33,7 @@ import org.apache.camel.spi.DataType;
import org.apache.camel.spi.DataTypeTransformer;
import org.apache.camel.spi.MimeType;
import org.apache.camel.spi.Transformer;
+import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.dataformat.protobuf.schema.ProtobufSchema;
@@ -76,7 +77,7 @@ public class ProtobufBinaryDataTypeTransformer extends
Transformer {
message.setHeader(Exchange.CONTENT_TYPE,
MimeType.PROTOBUF_BINARY.type());
message.setHeader(SchemaHelper.CONTENT_SCHEMA,
schema.getSource().toString());
- } catch (InvalidPayloadException | IOException |
ClassNotFoundException e) {
+ } catch (InvalidPayloadException | IOException | JacksonException |
ClassNotFoundException e) {
throw new CamelExecutionException(
"Failed to apply Protobuf binary data type on exchange",
message.getExchange(), e);
}
diff --git
a/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformer.java
b/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformer.java
index 78842648f837..6c1409955d48 100644
---
a/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformer.java
+++
b/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformer.java
@@ -39,8 +39,8 @@ import tools.jackson.databind.JsonNode;
@DataTypeTransformer(name = "protobuf-x-java-object",
description = "Transforms from JSon to Java object using
Jackson Protobuf (supports content schema)")
@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
- description = "Json text, or Protobuf
binary with the Protobuf schema in the"
- + " X-Content-Schema
exchange property, read into the class of the"
+ description = "Json text or a JsonNode,
or Protobuf binary with the Protobuf schema"
+ + " in the X-Content-Schema
exchange property, read into the class of the"
+ " X-Content-Class
exchange property, which a body other than a POJO"
+ " needs: a POJO of that
class, or any POJO without it, is kept, and a"
+ " Map or a null body
fail"),
diff --git
a/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformer.java
b/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformer.java
index 88cb18321a67..4d23fd053b81 100644
---
a/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformer.java
+++
b/components/camel-jackson3-protobuf/src/main/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformer.java
@@ -32,6 +32,7 @@ import org.apache.camel.spi.DataType;
import org.apache.camel.spi.DataTypeTransformer;
import org.apache.camel.spi.MimeType;
import org.apache.camel.spi.Transformer;
+import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.dataformat.protobuf.schema.ProtobufSchema;
@@ -65,7 +66,7 @@ public class ProtobufStructDataTypeTransformer extends
Transformer {
unmarshalled =
Protobuf.mapper().reader().forType(JsonNode.class).with(schema)
.readValue(Protobuf.mapper().writerFor(contentType).with(schema).writeValueAsBytes(message.getBody()));
} else if (message.getBody() instanceof String jsonString &&
Json.isJson(jsonString)) {
- unmarshalled =
Json.mapper().readTree(getBodyAsStream(message));
+ unmarshalled = Json.mapper().readTree(jsonString);
} else {
unmarshalled
=
Protobuf.mapper().reader().forType(JsonNode.class).with(schema).readValue(getBodyAsStream(message));
@@ -74,7 +75,7 @@ public class ProtobufStructDataTypeTransformer extends
Transformer {
message.setBody(unmarshalled);
message.setHeader(Exchange.CONTENT_TYPE, MimeType.STRUCT.type());
- } catch (InvalidPayloadException | ClassNotFoundException e) {
+ } catch (InvalidPayloadException | JacksonException |
ClassNotFoundException e) {
throw new CamelExecutionException(
"Failed to apply Protobuf x-struct data type on exchange",
message.getExchange(), e);
}
diff --git
a/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformerTest.java
b/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformerTest.java
index c9f26c9a4169..56f87be5718c 100644
---
a/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformerTest.java
+++
b/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufBinaryDataTypeTransformerTest.java
@@ -116,6 +116,18 @@ class ProtobufBinaryDataTypeTransformerTest {
Protobuf.mapper().reader().with(protobufSchema).readTree(exchange.getMessage().getBody(byte[].class))),
true);
}
+ @Test
+ void shouldWrapMalformedJsonInCamelExecutionException() throws Exception {
+ // CAMEL-26300: the JacksonException of Jackson 3 is wrapped, as the
errors of Jackson 2 are
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.getMessage().setBody("{ this is no json }");
+
+ Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
diff --git
a/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
b/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
index a8bb2721d733..ae0c3060ef4b 100644
---
a/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
+++
b/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufPojoDataTypeTransformerTest.java
@@ -27,6 +27,7 @@ import java.util.Map;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.component.jackson3.SchemaHelper;
import org.apache.camel.component.jackson3.transform.Json;
import org.apache.camel.impl.DefaultCamelContext;
@@ -134,6 +135,35 @@ class ProtobufPojoDataTypeTransformerTest {
Assertions.assertEquals(19,
exchange.getMessage().getBody(Person.class).age());
}
+ @Test
+ void shouldHandleJsonNode() throws Exception {
+ // CAMEL-26300: a JsonNode is read into the class of the
X-Content-Class property, with the Protobuf schema set
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+
exchange.getMessage().setBody(Json.mapper().readerFor(JsonNode.class).readValue("""
+ { "name": "Goofy", "age": 25 }
+ """));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals(new Person("Goofy", 25),
exchange.getMessage().getBody());
+ }
+
+ @Test
+ void shouldFailOnNullBody() throws Exception {
+ // CAMEL-26300: a null body has no Protobuf binary to read, which
fails as other bodies without it do
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+ exchange.getMessage().setBody(null);
+
+ CamelExecutionException e =
Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ Assertions.assertInstanceOf(InvalidPayloadException.class,
e.getCause());
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
@@ -149,8 +179,8 @@ class ProtobufPojoDataTypeTransformerTest {
@Test
void theBodyTypesSayTheBody(@TempDir Path dir) throws Exception {
- // CAMEL-25928: Json text, or Protobuf binary as bytes, a stream or a
file, as transformerIn says, read into the
- // class of the X-Content-Class property, which transformerOut leaves
unknown
+ // CAMEL-25928: Json text or a JsonNode, or Protobuf binary as bytes,
a stream or a file, as transformerIn
+ // says, read into the class of the X-Content-Class property, which
transformerOut leaves unknown
BodyTypes bodyTypes =
ProtobufPojoDataTypeTransformer.class.getAnnotation(BodyTypes.class);
Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.BYTES,
Carrier.STREAM, Carrier.FILE, Carrier.POJO),
List.of(bodyTypes.transformerIn().value()));
@@ -158,7 +188,8 @@ class ProtobufPojoDataTypeTransformerTest {
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
byte[] binary =
Protobuf.mapper().writer(getSchema()).writeValueAsBytes(new Person("Goofy",
25));
Path file = Files.write(dir.resolve("person.bin"), binary);
- for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile())) {
+ for (Object body : Arrays.asList(json, binary, new
ByteArrayInputStream(binary), file.toFile(),
+ Json.mapper().readerFor(JsonNode.class).readValue(json))) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
diff --git
a/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
b/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
index d1ae3b1b3e13..67c10feecae9 100644
---
a/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
+++
b/components/camel-jackson3-protobuf/src/test/java/org/apache/camel/component/jackson3/protobuf/transform/ProtobufStructDataTypeTransformerTest.java
@@ -19,6 +19,7 @@ package
org.apache.camel.component.jackson3.protobuf.transform;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@@ -75,6 +76,21 @@ class ProtobufStructDataTypeTransformerTest {
Assertions.assertEquals(ObjectNode.class,
exchange.getMessage().getBody().getClass());
}
+ @Test
+ void shouldReadJsonStringWhateverTheExchangeCharset() throws Exception {
+ // CAMEL-26300: Json text is read as the String it is, so a non-ASCII
character works with any exchange charset
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+ exchange.getMessage().setBody("{ \"name\": \"Jos\u00e9\", \"age\": 32
}");
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JsonNode body = exchange.getMessage().getBody(JsonNode.class);
+ Assertions.assertEquals("Jos\u00e9", body.get("name").asString());
+ Assertions.assertEquals(32, body.get("age").asInt());
+ }
+
@Test
void shouldHandlePojo() throws Exception {
Exchange exchange = new DefaultExchange(camelContext);
@@ -114,6 +130,18 @@ class ProtobufStructDataTypeTransformerTest {
Assertions.assertEquals(ObjectNode.class,
exchange.getMessage().getBody().getClass());
}
+ @Test
+ void shouldWrapMalformedJsonInCamelExecutionException() throws Exception {
+ // CAMEL-26300: the JacksonException of Jackson 3 is wrapped, as the
errors of Jackson 2 are
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_SCHEMA, getSchema());
+ exchange.getMessage().setBody("{ this is no json }");
+
+ Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
diff --git
a/components/camel-jackson3/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
b/components/camel-jackson3/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
index f1142ee097ce..35a29467a7fe 100644
---
a/components/camel-jackson3/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
+++
b/components/camel-jackson3/src/generated/resources/META-INF/services/org/apache/camel/transformer/application-json.json
@@ -14,10 +14,14 @@
"transformerIn": {
"carriers": [
"text",
+ "bytes",
+ "stream",
+ "map",
+ "list",
"pojo",
"null"
],
- "description": "A Json object or array as text, a JsonNode or a POJO, of
the class of the X-Content-Class exchange property when set: other text, bytes,
a stream, a Map or a List fail, and a null body becomes null"
+ "description": "A Json object or array as text, bytes or a stream, a
Map, a List, a JsonNode or a POJO; other text, also a Json number, boolean,
null or quoted string, becomes a Json string; bytes and a stream are read as
text with the exchange charset, so a payload that is not text must be encoded
first, such as in Base64, or given a content class; with the X-Content-Class
exchange property only a Json object or array as text or a body of that class;
a null body becomes null"
},
"transformerOut": {
"carriers": [
diff --git
a/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformer.java
b/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformer.java
index 62d17ccc2c5a..3518ce062ef1 100644
---
a/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformer.java
+++
b/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformer.java
@@ -16,10 +16,15 @@
*/
package org.apache.camel.component.jackson3.transform;
+import java.io.InputStream;
+import java.io.Reader;
import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.component.jackson3.SchemaHelper;
import org.apache.camel.spi.BodyType;
@@ -34,14 +39,20 @@ import tools.jackson.databind.JsonNode;
/**
* Data type uses Jackson data format to marshal given Exchange payload to a
Json (binary byte array representation).
- * Requires Exchange payload as JsonNode representation.
+ * Json text is kept, other text becomes a Json string, and a Map, a List, a
JsonNode or a POJO is written as Json.
+ * Bytes and a stream are read as text with the charset of the exchange.
*/
@DataTypeTransformer(name = "application-json",
description = "Transforms from JSon to binary (byte
array) using Jackson 3")
-@BodyTypes(transformerIn = @BodyType(value = { Carrier.TEXT, Carrier.POJO,
Carrier.NULL },
- description = "A Json object or array as
text, a JsonNode or a POJO, of the class of"
- + " the X-Content-Class
exchange property when set: other text, bytes, a"
- + " stream, a Map or a List
fail, and a null body becomes null"),
+@BodyTypes(transformerIn = @BodyType(value = {
+ Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.MAP,
Carrier.LIST, Carrier.POJO, Carrier.NULL },
+ description = "A Json object or array as
text, bytes or a stream, a Map, a List, a"
+ + " JsonNode or a POJO;
other text, also a Json number, boolean, null or"
+ + " quoted string, becomes
a Json string; bytes and a stream are read as"
+ + " text with the exchange
charset, so a payload that is not text must be"
+ + " encoded first, such as
in Base64, or given a content class; with the"
+ + " X-Content-Class
exchange property only a Json object or array as text"
+ + " or a body of that
class; a null body becomes null"),
transformerOut = @BodyType(value = Carrier.BYTES, javaType =
"byte[]", format = "json"))
public class JsonDataTypeTransformer extends Transformer {
@@ -49,14 +60,24 @@ public class JsonDataTypeTransformer extends Transformer {
public void transform(Message message, DataType fromType, DataType toType)
{
try {
byte[] marshalled;
- if (message.getBody() instanceof String jsonString &&
Json.isJson(jsonString)) {
+ Object body = message.getBody();
+ boolean hasContentClass =
message.getExchange().getProperty(SchemaHelper.CONTENT_CLASS, String.class) !=
null;
+ if (body instanceof String jsonString && Json.isJson(jsonString)) {
marshalled = jsonString.getBytes(StandardCharsets.UTF_8);
+ } else if (!hasContentClass && (body instanceof Map || body
instanceof Collection)) {
+ // a Map or a List has no class to write it as: it becomes a
Json object or array
+ marshalled = Json.mapper().writeValueAsBytes(body);
+ } else if (!hasContentClass && isText(body)) {
+ // read as text with the exchange charset: a Json object or
array is kept, and any other text, also a
+ // Json number, boolean, null or quoted string, becomes a Json
string
+ String text = message.getMandatoryBody(String.class);
+ marshalled = Json.isJson(text) ?
text.getBytes(StandardCharsets.UTF_8) : Json.mapper().writeValueAsBytes(text);
} else {
String contentClass =
SchemaHelper.resolveContentClass(message.getExchange(),
JsonNode.class.getName());
Class<?> contentType
=
message.getExchange().getContext().getClassResolver().resolveMandatoryClass(contentClass);
- marshalled =
Json.mapper().writer().forType(contentType).writeValueAsBytes(message.getBody());
+ marshalled =
Json.mapper().writer().forType(contentType).writeValueAsBytes(body);
}
message.setBody(marshalled);
@@ -66,8 +87,15 @@ public class JsonDataTypeTransformer extends Transformer {
if (contentSchema != null) {
message.setHeader(SchemaHelper.CONTENT_SCHEMA, contentSchema);
}
- } catch (JacksonException | ClassNotFoundException e) {
+ } catch (InvalidPayloadException | JacksonException |
ClassNotFoundException e) {
throw new CamelExecutionException("Failed to apply Json output
data type on exchange", message.getExchange(), e);
}
}
+
+ /**
+ * Text, bytes or a stream, which hold Json as text.
+ */
+ private static boolean isText(Object body) {
+ return body instanceof String || body instanceof byte[] || body
instanceof InputStream || body instanceof Reader;
+ }
}
diff --git
a/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformer.java
b/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformer.java
index 234364e9bbc7..5531aaa25d4e 100644
---
a/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformer.java
+++
b/components/camel-jackson3/src/main/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformer.java
@@ -76,7 +76,7 @@ public class JsonPojoDataTypeTransformer extends Transformer
implements CamelCon
message.setHeader(Exchange.CONTENT_TYPE,
MimeType.JAVA_OBJECT.type());
Class<?> contentType =
camelContext.getClassResolver().resolveMandatoryClass(contentClass);
- if (contentType.isAssignableFrom(message.getBody().getClass())) {
+ if (contentType.isInstance(message.getBody())) {
return;
}
diff --git
a/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformerTest.java
b/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformerTest.java
index eec04ffe331f..270627455b95 100644
---
a/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformerTest.java
+++
b/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonDataTypeTransformerTest.java
@@ -17,13 +17,18 @@
package org.apache.camel.component.jackson3.transform;
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
import org.apache.camel.component.jackson3.SchemaHelper;
+import org.apache.camel.converter.stream.InputStreamCache;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
@@ -103,26 +108,134 @@ class JsonDataTypeTransformerTest {
Assertions.assertEquals(JsonDataTypeTransformer.class,
transformer.getClass());
}
+ @Test
+ void shouldHandleMapAndList() throws Exception {
+ // CAMEL-26300: a Map or a List has no class of its own, and is
written as a Json object or array
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("name", "Goofy");
+ map.put("age", 25);
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(map);
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JSONAssert.assertEquals("""
+ {"name":"Goofy","age":25}
+ """, exchange.getMessage().getBody(String.class), true);
+
+ exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(List.of(new Person("Goofy", 25),
Map.of("name", "Mickey")));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ JSONAssert.assertEquals("""
+ [{"name":"Goofy","age":25},{"name":"Mickey"}]
+ """, exchange.getMessage().getBody(String.class), true);
+ }
+
+ @Test
+ void shouldHandleJsonAsBytesOrStream() throws Exception {
+ // CAMEL-26300: Json as bytes, a stream, a reader or the stream cache
is kept as it is
+ String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
+ byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
+ for (Object body : List.of(bytes, new ByteArrayInputStream(bytes), new
StringReader(json),
+ new InputStreamCache(bytes))) {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(body);
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertInstanceOf(byte[].class,
exchange.getMessage().getBody(), body.getClass().getName());
+ Assertions.assertEquals(json,
exchange.getMessage().getBody(String.class), body.getClass().getName());
+ }
+ }
+
+ @Test
+ void shouldWriteOtherTextAsJsonString() throws Exception {
+ // CAMEL-26300: text that is no Json object or array, as a String or
as bytes, becomes a Json string
+ String text = "Hello \"Goofy\"";
+ for (Object body : List.of(text,
text.getBytes(StandardCharsets.UTF_8))) {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(body);
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals("\"Hello \\\"Goofy\\\"\"",
exchange.getMessage().getBody(String.class),
+ body.getClass().getName());
+ Assertions.assertEquals(text,
Json.mapper().readTree(exchange.getMessage().getBody(byte[].class)).asString());
+ }
+ }
+
+ @Test
+ void shouldWriteJsonScalarTextAsJsonString() throws Exception {
+ // CAMEL-26300: only a Json object or array is kept as Json text, so a
Json number, boolean, null or quoted
+ // string given as text becomes a Json string
+ Map<String, String> expected = new LinkedHashMap<>();
+ expected.put("42", "\"42\"");
+ expected.put("true", "\"true\"");
+ expected.put("null", "\"null\"");
+ expected.put("\"Hello\"", "\"\\\"Hello\\\"\"");
+ for (Map.Entry<String, String> entry : expected.entrySet()) {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(entry.getKey());
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals(entry.getValue(),
exchange.getMessage().getBody(String.class), entry.getKey());
+ Assertions.assertEquals(entry.getKey(),
+
Json.mapper().readTree(exchange.getMessage().getBody(byte[].class)).asString());
+ }
+ }
+
+ @Test
+ void shouldReadBytesWithTheExchangeCharset() throws Exception {
+ // CAMEL-26300: bytes are read as text with the charset of the
exchange, and the Json is written in UTF-8
+ String json = "{\"name\":\"Jos\u00e9\"}";
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+
exchange.getMessage().setBody(json.getBytes(StandardCharsets.ISO_8859_1));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertArrayEquals(json.getBytes(StandardCharsets.UTF_8),
exchange.getMessage().getBody(byte[].class));
+
+ exchange = new DefaultExchange(camelContext);
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.ISO_8859_1.name());
+
exchange.getMessage().setBody("caf\u00e9".getBytes(StandardCharsets.ISO_8859_1));
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+
Assertions.assertArrayEquals("\"caf\u00e9\"".getBytes(StandardCharsets.UTF_8),
+ exchange.getMessage().getBody(byte[].class));
+ }
+
+ @Test
+ void shouldWriteBytesInBase64WithTheByteArrayContentClass() throws
Exception {
+ // CAMEL-26300: a payload that is not text can be given the content
class byte[], which Jackson writes in Base64
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS, "byte[]");
+ exchange.getMessage().setBody(new byte[] { 0, 1, 2, (byte) 0xFF });
+ transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
+
+ Assertions.assertEquals("\"AAEC/w==\"",
exchange.getMessage().getBody(String.class));
+ }
+
@Test
void theBodyTypesSayTheBody() throws Exception {
- // CAMEL-25928: Json text, a JsonNode, a POJO or a null body, as
transformerIn says, and the Json bytes
- // transformerOut says
+ // CAMEL-25928: Json as text, bytes or a stream, other text, a Map, a
List, a JsonNode, a POJO or a null body,
+ // as transformerIn says, and the Json bytes transformerOut says
BodyTypes bodyTypes =
JsonDataTypeTransformer.class.getAnnotation(BodyTypes.class);
- Assertions.assertEquals(List.of(Carrier.TEXT, Carrier.POJO,
Carrier.NULL),
+ Assertions.assertEquals(
+ List.of(Carrier.TEXT, Carrier.BYTES, Carrier.STREAM,
Carrier.MAP, Carrier.LIST, Carrier.POJO, Carrier.NULL),
List.of(bodyTypes.transformerIn().value()));
Assertions.assertEquals(List.of(Carrier.BYTES),
List.of(bodyTypes.transformerOut().value()));
Assertions.assertEquals(byte[].class.getCanonicalName(),
bodyTypes.transformerOut().javaType());
Assertions.assertEquals("json", bodyTypes.transformerOut().format());
String json = "{ \"name\": \"Goofy\", \"age\": 25 }";
- for (Object body : Arrays.asList(json,
Json.mapper().readerFor(JsonNode.class).readValue(json), new Person("Goofy",
25),
- null)) {
+ byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
+ for (Object body : Arrays.asList(json, bytes, new
ByteArrayInputStream(bytes), "Goofy", Map.of("name", "Goofy"),
+ List.of("Goofy"),
Json.mapper().readerFor(JsonNode.class).readValue(json), new Person("Goofy",
25), null)) {
Exchange exchange = new DefaultExchange(camelContext);
exchange.getMessage().setBody(body);
transformer.transform(exchange.getMessage(), DataType.ANY,
DataType.ANY);
Assertions.assertInstanceOf(byte[].class,
exchange.getMessage().getBody(), String.valueOf(body));
}
- // a Map is no JsonNode, the class written without the X-Content-Class
property
+ // with the X-Content-Class property only a body of that class
Exchange other = new DefaultExchange(camelContext);
+ other.setProperty(SchemaHelper.CONTENT_CLASS, Person.class.getName());
other.getMessage().setBody(Map.of("name", "Goofy"));
Assertions.assertThrows(CamelExecutionException.class,
() -> transformer.transform(other.getMessage(), DataType.ANY,
DataType.ANY));
diff --git
a/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformerTest.java
b/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformerTest.java
index a800930b55ac..170ad85c72c3 100644
---
a/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformerTest.java
+++
b/components/camel-jackson3/src/test/java/org/apache/camel/component/jackson3/transform/JsonPojoDataTypeTransformerTest.java
@@ -27,6 +27,7 @@ import java.util.Map;
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
import org.apache.camel.component.jackson3.SchemaHelper;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.BodyType.Carrier;
@@ -135,6 +136,19 @@ class JsonPojoDataTypeTransformerTest {
() -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
}
+ @Test
+ void shouldFailOnNullBody() {
+ // CAMEL-26300: a null body has no Json to read, which fails as other
bodies without Json do
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ exchange.setProperty(SchemaHelper.CONTENT_CLASS,
Person.class.getName());
+ exchange.getMessage().setBody(null);
+
+ CamelExecutionException e =
Assertions.assertThrows(CamelExecutionException.class,
+ () -> transformer.transform(exchange.getMessage(),
DataType.ANY, DataType.ANY));
+ Assertions.assertInstanceOf(InvalidPayloadException.class,
e.getCause());
+ }
+
@Test
public void shouldLookupDataTypeTransformer() throws Exception {
Transformer transformer = camelContext.getTransformerRegistry()
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 5b081f718d75..a0c7ff895e8c 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
@@ -4654,6 +4654,39 @@ the record that is being routed. The other records of
that poll (up to `maxResul
skipped when the route was resumed. They are now routed after the resume. A
consumer that is suspended directly
(`consumer.suspend()`) routes all the records of its poll and was not affected.
+=== camel-jackson and camel-jackson3 - the bodies of the data type transformers
+
+When the `X-Content-Class` exchange property is not set, the
`application-json` data type transformer now writes a
+`Map` body as a JSON object and a `List` (or another `Collection`) body as a
JSON array, and reads text, bytes or a
+stream (an `InputStream`, a `Reader` or the stream cache) as text: a JSON
object or array is kept as it is, and any
+other text becomes a JSON string, so `Hello` becomes `"Hello"`. This includes
text that is a JSON number, boolean, null
+or quoted string: `42` becomes `"42"`, `true` becomes `"true"`, and `"Hello"`
with its quotes becomes `"\"Hello\""`.
+These bodies failed before. With the `X-Content-Class` exchange property set,
the transformer still writes only a body
+of that class, or JSON object or array text. A `Map` entry with a null or
empty value is left out, as the transformer
+already did for the properties of a POJO.
+
+Bytes and a stream are read as text with the charset of the exchange (the
`CamelCharsetName` header or exchange
+property, else UTF-8), and the JSON is written in UTF-8. A payload that is not
text, such as Avro binary or an image,
+therefore becomes a JSON string with replacement characters. Encode such a
payload first, for example in Base64, or
+give it a content class: a `byte[]` body with the `X-Content-Class` exchange
property set to `byte[]` is written as a
+Base64 JSON string.
+
+The `avro-x-struct` data type transformer now reads a JSON object or array
given as text as JSON, as
+`protobuf-x-struct` does. Before, it read the text as Avro binary, which
failed or gave a wrong `JsonNode`.
+
+The `avro-x-java-object` and `protobuf-x-java-object` data type transformers
of camel-jackson-avro and
+camel-jackson-protobuf now read a `JsonNode` body into the class of the
`X-Content-Class` exchange property, as the
+camel-jackson3 ones do. Before, they failed with an
`UnsupportedOperationException`.
+
+The `application-x-java-object`, `avro-x-java-object` and
`protobuf-x-java-object` data type transformers now fail on a
+null body with a `CamelExecutionException`, whose cause is an
`InvalidPayloadException`, instead of a
+`NullPointerException`, when the `X-Content-Class` exchange property is set.
+
+The `avro-binary`, `avro-x-struct`, `protobuf-binary` and `protobuf-x-struct`
data type transformers of
+camel-jackson3-avro and camel-jackson3-protobuf now wrap a `JacksonException`
in a `CamelExecutionException`, as their
+camel-jackson-avro and camel-jackson-protobuf counterparts and the
camel-jackson3 JSON transformers do. The
+`JacksonException` is the cause of the `CamelExecutionException`.
+
=== camel-core - the route structure leaves out onException clauses that never
run
A route with `noErrorHandler` runs none of its `onException` clauses, and a
global `onException` is copied into every