This is an automated email from the ASF dual-hosted git repository. nferraro pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit cfe4ea248b1024f90050e975730ee3880a7ac907 Author: nicolaferraro <ni.ferr...@gmail.com> AuthorDate: Fri May 14 00:16:51 2021 +0200 Add avro-jackson and protobuf-jackson dataformats --- components/camel-jackson-avro/pom.xml | 74 +++ .../avro/JacksonAvroDataFormatConfigurer.java | 26 + .../camel/configurer/avro-jackson-dataformat | 2 + .../org/apache/camel/dataformat.properties | 7 + .../org/apache/camel/dataformat/avro-jackson | 2 + .../camel/component/jackson/avro/avro-jackson.json | 22 + .../src/main/docs/jackson-avro-dataformat.adoc | 370 ++++++++++++++ .../jackson/avro/JacksonAvroDataFormat.java | 100 ++++ .../JacksonAvroMarshalUnmarshalJsonNodeTest.java | 169 +++++++ .../JacksonAvroMarshalUnmarshalPojoListTest.java | 124 +++++ .../avro/JacksonAvroMarshalUnmarshalPojoTest.java | 110 ++++ .../src/test/resources/log4j2.properties | 28 + components/camel-jackson-protobuf/pom.xml | 74 +++ .../JacksonProtobufDataFormatConfigurer.java | 26 + .../camel/configurer/protobuf-jackson-dataformat | 2 + .../org/apache/camel/dataformat.properties | 7 + .../org/apache/camel/dataformat/protobuf-jackson | 2 + .../jackson/protobuf/protobuf-jackson.json | 22 + .../src/main/docs/jackson-protobuf-dataformat.adoc | 370 ++++++++++++++ .../protobuf/JacksonProtobufDataFormat.java | 98 ++++ ...acksonProtobufMarshalUnmarshalJsonNodeTest.java | 107 ++++ .../JacksonProtobufMarshalUnmarshalPojoTest.java | 106 ++++ .../src/test/resources/log4j2.properties | 28 + ...aFormat.java => AbstractJacksonDataFormat.java} | 107 ++-- .../camel/component/jackson/JacksonDataFormat.java | 563 +-------------------- .../camel/component/jackson/SchemaResolver.java | 20 + components/pom.xml | 2 + core/camel-allcomponents/pom.xml | 8 + parent/pom.xml | 10 + 29 files changed, 2004 insertions(+), 582 deletions(-) diff --git a/components/camel-jackson-avro/pom.xml b/components/camel-jackson-avro/pom.xml new file mode 100644 index 0000000..dfe499c --- /dev/null +++ b/components/camel-jackson-avro/pom.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>3.10.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-jackson-avro</artifactId> + <packaging>jar</packaging> + <name>Camel :: Jackson Avro</name> + <description>Camel Jackson Avro support</description> + + <properties> + <camel.osgi.import.before.defaults> + com.fasterxml.jackson.*;version="[2.11,3)" + </camel.osgi.import.before.defaults> + </properties> + + <dependencies> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-avro</artifactId> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/components/camel-jackson-avro/src/generated/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormatConfigurer.java b/components/camel-jackson-avro/src/generated/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormatConfigurer.java new file mode 100644 index 0000000..2e5b5e3 --- /dev/null +++ b/components/camel-jackson-avro/src/generated/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormatConfigurer.java @@ -0,0 +1,26 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.jackson.avro; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class JacksonAvroDataFormatConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer { + + @Override + public boolean configure(CamelContext camelContext, Object target, String name, Object value, boolean ignoreCase) { + JacksonAvroDataFormat dataformat = (JacksonAvroDataFormat) target; + switch (ignoreCase ? name.toLowerCase() : name) { + default: return false; + } + } + +} + diff --git a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/configurer/avro-jackson-dataformat b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/configurer/avro-jackson-dataformat new file mode 100644 index 0000000..5719665 --- /dev/null +++ b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/configurer/avro-jackson-dataformat @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jackson.avro.JacksonAvroDataFormatConfigurer diff --git a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/dataformat.properties b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/dataformat.properties new file mode 100644 index 0000000..32c08ea --- /dev/null +++ b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/dataformat.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +dataFormats=avro-jackson +groupId=org.apache.camel +artifactId=camel-jackson-avro +version=3.10.0-SNAPSHOT +projectName=Camel :: Jackson Avro +projectDescription=Camel Jackson Avro support diff --git a/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/dataformat/avro-jackson b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/dataformat/avro-jackson new file mode 100644 index 0000000..8470044 --- /dev/null +++ b/components/camel-jackson-avro/src/generated/resources/META-INF/services/org/apache/camel/dataformat/avro-jackson @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jackson.avro.JacksonAvroDataFormat diff --git a/components/camel-jackson-avro/src/generated/resources/org/apache/camel/component/jackson/avro/avro-jackson.json b/components/camel-jackson-avro/src/generated/resources/org/apache/camel/component/jackson/avro/avro-jackson.json new file mode 100644 index 0000000..4826f7f --- /dev/null +++ b/components/camel-jackson-avro/src/generated/resources/org/apache/camel/component/jackson/avro/avro-jackson.json @@ -0,0 +1,22 @@ +{ + "dataformat": { + "kind": "dataformat", + "name": "avro-jackson", + "title": "Avro", + "description": "Serialize and deserialize messages using Apache Avro binary data format.", + "deprecated": false, + "firstVersion": "2.14.0", + "label": "dataformat,transformation", + "javaType": "org.apache.camel.component.jackson.avro.JacksonAvroDataFormat", + "supportLevel": "Stable", + "groupId": "org.apache.camel", + "artifactId": "camel-jackson-avro", + "version": "3.10.0-SNAPSHOT", + "modelName": "avro", + "modelJavaType": "org.apache.camel.model.dataformat.AvroDataFormat" + }, + "properties": { + "instanceClassName": { "kind": "attribute", "displayName": "Instance Class Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Class name to use for marshal and unmarshalling" }, + "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" } + } +} diff --git a/components/camel-jackson-avro/src/main/docs/jackson-avro-dataformat.adoc b/components/camel-jackson-avro/src/main/docs/jackson-avro-dataformat.adoc new file mode 100644 index 0000000..dfb41f2 --- /dev/null +++ b/components/camel-jackson-avro/src/main/docs/jackson-avro-dataformat.adoc @@ -0,0 +1,370 @@ +[[jackson-avro-dataformat]] += Jackson Avro DataFormat +:docTitle: Jackson Avro +:artifactId: camel-jackson-avro +:description: Unmarshal Avro payloads to POJOs and back using Jackson. +:since: 3.10 +:supportLevel: Stable + +*Since Camel {since}* + +Jackson XML is a Data Format which uses the +http://wiki.fasterxml.com/JacksonHome/[Jackson library] with the +https://github.com/FasterXML/jackson-dataformat-xml[XMLMapper extension] +to unmarshal an XML payload into Java objects or to marshal Java objects +into an XML payload. + +[TIP] +==== +If you are familiar with Jackson, this XML data format behaves in the +same way as its JSON counterpart, and thus can be used with classes +annotated for JSON serialization/deserialization. +==== + +This extension also mimics +https://github.com/FasterXML/jackson-dataformat-xml/blob/master/README.md[JAXB's +"Code first" approach]. + +This data format relies on +http://wiki.fasterxml.com/WoodstoxHome[Woodstox] (especially for +features like pretty printing), a fast and efficient XML processor. + +[source,java] +------------------------------- +from("activemq:My.Queue"). + unmarshal().jacksonxml(). + to("mqseries:Another.Queue"); +------------------------------- + +== JacksonXML Options + + + +// dataformat options: START +The JacksonXML dataformat supports 15 options, which are listed below. + + + +[width="100%",cols="2s,1m,1m,6",options="header"] +|=== +| Name | Default | Java Type | Description +| xmlMapper | | String | Lookup and use the existing XmlMapper with the given id. +| prettyPrint | false | Boolean | To enable pretty printing output nicely formatted. Is by default false. +| unmarshalTypeName | | String | Class name of the java type to use when unmarshalling +| jsonViewTypeName | | String | When marshalling a POJO to JSON you might want to exclude certain fields from the JSON output. With Jackson you can use JSON views to accomplish this. This option is to refer to the class which has JsonView annotations +| include | | String | If you want to marshal a pojo to JSON, and the pojo has some fields with null values. And you want to skip these null values, you can set this option to NON_NULL +| allowJmsType | false | Boolean | Used for JMS users to allow the JMSType header from the JMS spec to specify a FQN classname to use to unmarshal to. +| collectionTypeName | | String | Refers to a custom collection type to lookup in the registry to use. This option should rarely be used, but allows to use different collection types than java.util.Collection based as default. +| useList | false | Boolean | To unmarshal to a List of Map or a List of Pojo. +| enableJaxbAnnotationModule | false | Boolean | Whether to enable the JAXB annotations module when using jackson. When enabled then JAXB annotations can be used by Jackson. +| moduleClassNames | | String | To use custom Jackson modules com.fasterxml.jackson.databind.Module specified as a String with FQN class names. Multiple classes can be separated by comma. +| moduleRefs | | String | To use custom Jackson modules referred from the Camel registry. Multiple modules can be separated by comma. +| enableFeatures | | String | Set of features to enable on the Jackson com.fasterxml.jackson.databind.ObjectMapper. The features should be a name that matches a enum from com.fasterxml.jackson.databind.SerializationFeature, com.fasterxml.jackson.databind.DeserializationFeature, or com.fasterxml.jackson.databind.MapperFeature Multiple features can be separated by comma +| disableFeatures | | String | Set of features to disable on the Jackson com.fasterxml.jackson.databind.ObjectMapper. The features should be a name that matches a enum from com.fasterxml.jackson.databind.SerializationFeature, com.fasterxml.jackson.databind.DeserializationFeature, or com.fasterxml.jackson.databind.MapperFeature Multiple features can be separated by comma +| allowUnmarshallType | false | Boolean | If enabled then Jackson is allowed to attempt to use the CamelJacksonUnmarshalType header during the unmarshalling. This should only be enabled when desired to be used. +| contentTypeHeader | true | Boolean | Whether the data format should set the Content-Type header with the type from the data format. For example application/xml for data formats marshalling to XML, or application/json for data formats marshalling to JSON +|=== +// dataformat options: END + + +=== Using Jackson XML in Spring DSL + +When using Data Format in Spring DSL you need to +declare the data formats first. This is done in the *DataFormats* XML +tag. + +[source,xml] +----------------------------------------------------------------------------------------------------------------------------- + <dataFormats> + <!-- here we define a Xml data format with the id jack and that it should use the TestPojo as the class type when + doing unmarshal. The unmarshalTypeName is optional, if not provided Camel will use a Map as the type --> + <jacksonxml id="jack" unmarshalTypeName="org.apache.camel.component.jacksonxml.TestPojo"/> + </dataFormats> +----------------------------------------------------------------------------------------------------------------------------- + +And then you can refer to this id in the route: + +[source,xml] +------------------------------------- + <route> + <from uri="direct:back"/> + <unmarshal><custom ref="jack"/></unmarshal> + <to uri="mock:reverse"/> + </route> +------------------------------------- + +== Excluding POJO fields from marshalling + +When marshalling a POJO to XML you might want to exclude certain fields +from the XML output. With Jackson you can +use http://wiki.fasterxml.com/JacksonJsonViews[JSON views] to accomplish +this. First create one or more marker classes. + +Use the marker classes with the `@JsonView` annotation to +include/exclude certain fields. The annotation also works on getters. + +Finally use the Camel `JacksonXMLDataFormat` to marshall the above POJO +to XML. + +Note that the weight field is missing in the resulting XML: + +[source,java] +---------------------------- +<pojo age="30" weight="70"/> +---------------------------- + +== Include/Exclude fields using the `jsonView` attribute with `JacksonXML`DataFormat + +As an example of using this attribute you can instead of: + +[source,java] +--------------------------------------------------------------------------------------------------- +JacksonXMLDataFormat ageViewFormat = new JacksonXMLDataFormat(TestPojoView.class, Views.Age.class); +from("direct:inPojoAgeView"). + marshal(ageViewFormat); +--------------------------------------------------------------------------------------------------- + +Directly specify your http://wiki.fasterxml.com/JacksonJsonViews[JSON +view] inside the Java DSL as: + +[source,java] +------------------------------------------------------------ +from("direct:inPojoAgeView"). + marshal().jacksonxml(TestPojoView.class, Views.Age.class); +------------------------------------------------------------ + +And the same in XML DSL: + +[source,xml] +--------------------------------------------------------------------------------------------------------------------------------------------------- +<from uri="direct:inPojoAgeView"/> + <marshal> + <jacksonxml unmarshalTypeName="org.apache.camel.component.jacksonxml.TestPojoView" jsonView="org.apache.camel.component.jacksonxml.Views$Age"/> + </marshal> +--------------------------------------------------------------------------------------------------------------------------------------------------- + +== Setting serialization include option + +If you want to marshal a pojo to XML, and the pojo has some fields with +null values. And you want to skip these null values, then you need to +set either an annotation on the pojo, + +[source,java] +------------------------------ +@JsonInclude(Include.NON_NULL) +public class MyPojo { + ... +} +------------------------------ + +But this requires you to include that annotation in your pojo source +code. You can also configure the Camel JacksonXMLDataFormat to set the +include option, as shown below: + +[source,java] +--------------------------------------------------------- +JacksonXMLDataFormat format = new JacksonXMLDataFormat(); +format.setInclude("NON_NULL"); +--------------------------------------------------------- + +Or from XML DSL you configure this as + +[source,java] +------------------------------------------------------ + <dataFormats> + <jacksonxml id="jacksonxml" include="NON_NULL"/> + </dataFormats> +------------------------------------------------------ + +== Unmarshalling from XML to POJO with dynamic class name + +If you use jackson to unmarshal XML to POJO, then you can now specify a +header in the message that indicate which class name to unmarshal to. + +The header has key `CamelJacksonUnmarshalType` if that header is present +in the message, then Jackson will use that as FQN for the POJO class to +unmarshal the XML payload as. + + For JMS end users there is the JMSType header from the JMS spec that +indicates that also. To enable support for JMSType you would need to +turn that on, on the jackson data format as shown: + +[source,java] +--------------------------------------------------- +JacksonDataFormat format = new JacksonDataFormat(); +format.setAllowJmsType(true); +--------------------------------------------------- + +Or from XML DSL you configure this as + +[source,java] +------------------------------------------------------- + <dataFormats> + <jacksonxml id="jacksonxml" allowJmsType="true"/> + </dataFormats> +------------------------------------------------------- + +== Unmarshalling from XML to List<Map> or List<pojo> + +If you are using Jackson to unmarshal XML to a list of map/pojo, you can +now specify this by setting `useList="true"` or use +the `org.apache.camel.component.jacksonxml.ListJacksonXMLDataFormat`. +For example with Java you can do as shown below: + +[source,java] +------------------------------------------------------------- +JacksonXMLDataFormat format = new ListJacksonXMLDataFormat(); +// or +JacksonXMLDataFormat format = new JacksonXMLDataFormat(); +format.useList(); +// and you can specify the pojo class type also +format.setUnmarshalType(MyPojo.class); +------------------------------------------------------------- + +And if you use XML DSL then you configure to use list +using `useList` attribute as shown below: + +[source,java] +-------------------------------------------- + <dataFormats> + <jacksonxml id="jack" useList="true"/> + </dataFormats> +-------------------------------------------- + +And you can specify the pojo type also + +[source,java] +------------------------------------------------------------------------------- + <dataFormats> + <jacksonxml id="jack" useList="true" unmarshalTypeName="com.foo.MyPojo"/> + </dataFormats> +------------------------------------------------------------------------------- + +== Using custom Jackson modules + +You can use custom Jackson modules by specifying the class names of +those using the moduleClassNames option as shown below. + +[source,java] +----------------------------------------------------------------------------------------------------------------------------------------- + <dataFormats> + <jacksonxml id="jack" useList="true" unmarshalTypeName="com.foo.MyPojo" moduleClassNames="com.foo.MyModule,com.foo.MyOtherModule"/> + </dataFormats> +----------------------------------------------------------------------------------------------------------------------------------------- + +When using moduleClassNames then the custom jackson modules are not +configured, by created using default constructor and used as-is. If a +custom module needs any custom configuration, then an instance of the +module can be created and configured, and then use modulesRefs to refer +to the module as shown below: + +[source,java] +------------------------------------------------------------------------------------------------------------------ + <bean id="myJacksonModule" class="com.foo.MyModule"> + ... // configure the module as you want + </bean> + + <dataFormats> + <jacksonxml id="jacksonxml" useList="true" unmarshalTypeName="com.foo.MyPojo" moduleRefs="myJacksonModule"/> + </dataFormats> +------------------------------------------------------------------------------------------------------------------ + + Multiple modules can be specified separated by comma, such as +moduleRefs="myJacksonModule,myOtherModule" + +== Enabling or disable features using Jackson + +Jackson has a number of features you can enable or disable, which its +ObjectMapper uses. For example to disable failing on unknown properties +when marshalling, you can configure this using the disableFeatures: + +[source,java] +------------------------------------------------------------------------------------------------------------------- + <dataFormats> + <jacksonxml id="jacksonxml" unmarshalTypeName="com.foo.MyPojo" disableFeatures="FAIL_ON_UNKNOWN_PROPERTIES"/> + </dataFormats> +------------------------------------------------------------------------------------------------------------------- + +You can disable multiple features by separating the values using comma. +The values for the features must be the name of the enums from Jackson +from the following enum classes + +* com.fasterxml.jackson.databind.SerializationFeature +* com.fasterxml.jackson.databind.DeserializationFeature +* com.fasterxml.jackson.databind.MapperFeature + +To enable a feature use the enableFeatures options instead. + +From Java code you can use the type safe methods from camel-jackson +module: + +[source,java] +---------------------------------------------------------------------- +JacksonDataFormat df = new JacksonDataFormat(MyPojo.class); +df.disableFeature(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); +df.disableFeature(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); +---------------------------------------------------------------------- + +== Converting Maps to POJO using Jackson + +Jackson `ObjectMapper` can be used to convert maps to POJO objects. +Jackson component comes with the data converter that can be used to +convert `java.util.Map` instance to non-String, non-primitive and +non-Number objects. + +[source,java] +---------------------------------------------------------------- +Map<String, Object> invoiceData = new HashMap<String, Object>(); +invoiceData.put("netValue", 500); +producerTemplate.sendBody("direct:mapToInvoice", invoiceData); +... +// Later in the processor +Invoice invoice = exchange.getIn().getBody(Invoice.class); +---------------------------------------------------------------- + +If there is a single `ObjectMapper` instance available in the Camel +registry, it will used by the converter to perform the conversion. +Otherwise the default mapper will be used. + +== Formatted XML marshalling (pretty-printing) + +Using the `prettyPrint` option one can output a well formatted XML while +marshalling: + +[source,java] +------------------------------------------------ + <dataFormats> + <jacksonxml id="jack" prettyPrint="true"/> + </dataFormats> +------------------------------------------------ + +And in Java DSL: + +[source,java] +--------------------------------------------------- +from("direct:inPretty").marshal().jacksonxml(true); +--------------------------------------------------- + +Please note that there are 5 different overloaded `jacksonxml()` DSL +methods which support the `prettyPrint` option in combination with other +settings for `unmarshalType`, `jsonView` etc. + +== Dependencies + +To use Jackson XML in your camel routes you need to add the dependency +on *camel-jacksonxml* which implements this data format. + +If you use maven you could just add the following to your pom.xml, +substituting the version number for the latest & greatest release (see +the download page for the latest versions). + +[source,xml] +---------------------------------------------------------- +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jacksonxml</artifactId> + <version>x.x.x</version> + <!-- use the same version as your Camel core version --> +</dependency> +---------------------------------------------------------- + +include::{page-component-version}@camel-spring-boot::page$jacksonxml-starter.adoc[] diff --git a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java new file mode 100644 index 0000000..3ee8bb1 --- /dev/null +++ b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.avro; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import org.apache.camel.component.jackson.AbstractJacksonDataFormat; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.annotations.Dataformat; + +/** + * Marshal POJOs to Avro and back using <a href="http://jackson.codehaus.org/">Jackson</a> + */ +@Dataformat("avro-jackson") +@Metadata(excludeProperties = "library,permissions,dropRootNode") +public class JacksonAvroDataFormat extends AbstractJacksonDataFormat { + + /** + * Use the default Jackson {@link AvroMapper} and {@link Object} + */ + public JacksonAvroDataFormat() { + } + + /** + * Use the default Jackson {@link AvroMapper} and with a custom unmarshal type + * + * @param unmarshalType the custom unmarshal type + */ + public JacksonAvroDataFormat(Class<?> unmarshalType) { + super(unmarshalType); + } + + /** + * Use the default Jackson {@link AvroMapper} and with a custom unmarshal type and JSON view + * + * @param unmarshalType the custom unmarshal type + * @param jsonView marker class to specify properties to be included during marshalling. See also + */ + public JacksonAvroDataFormat(Class<?> unmarshalType, Class<?> jsonView) { + super(unmarshalType, jsonView); + } + + /** + * Use a custom Jackson {@link AvroMapper} and and unmarshal type + * + * @param mapper the custom mapper + * @param unmarshalType the custom unmarshal type + */ + public JacksonAvroDataFormat(AvroMapper mapper, Class<?> unmarshalType) { + super(mapper, unmarshalType); + } + + /** + * Use a custom Jackson {@link AvroMapper}, unmarshal type and JSON view + * + * @param mapper the custom mapper + * @param unmarshalType the custom unmarshal type + * @param jsonView marker class to specify properties to be included during marshalling. See also + */ + public JacksonAvroDataFormat(AvroMapper mapper, Class<?> unmarshalType, Class<?> jsonView) { + super(mapper, unmarshalType, jsonView); + } + + + + @Override + public String getDataFormatName() { + return "avro-jackson"; + } + + @Override + protected String getDefaultContentType() { + return "application/avro"; + } + + @Override + protected AvroMapper createNewObjectMapper() { + return new AvroMapper(); + } + + @Override + protected Class<? extends ObjectMapper> getObjectMapperClass() { + return AvroMapper.class; + } + +} diff --git a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalJsonNodeTest.java b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalJsonNodeTest.java new file mode 100644 index 0000000..e863a8e --- /dev/null +++ b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalJsonNodeTest.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.avro; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import org.apache.avro.Schema; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.SchemaResolver; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JacksonAvroMarshalUnmarshalJsonNodeTest extends CamelTestSupport { + + @Test + public void testMarshalUnmarshalJsonNode() throws Exception { + MockEndpoint mock1 = getMockEndpoint("mock:serialized"); + mock1.expectedMessageCount(1); + mock1.message(0).body().isInstanceOf(byte[].class); + + Pojo pojo = new Pojo("Hello"); + template.sendBody("direct:pojo", pojo); + + mock1.assertIsSatisfied(); + + byte[] serialized = mock1.getReceivedExchanges().get(0).getIn().getBody(byte[].class); + assertNotNull(serialized); + assertEquals(6, serialized.length); + + MockEndpoint mock2 = getMockEndpoint("mock:pojo"); + mock2.expectedMessageCount(1); + mock2.message(0).body().isInstanceOf(JsonNode.class); + + template.sendBody("direct:serialized", serialized); + mock2.assertIsSatisfied(); + + JsonNode back = mock2.getReceivedExchanges().get(0).getIn().getBody(JsonNode.class); + + assertEquals(pojo.getText(), back.at("/text").asText()); + } + + @Test + public void testMarshalUnmarshalJsonNodeList() throws Exception { + MockEndpoint mock1 = getMockEndpoint("mock:serialized"); + mock1.expectedMessageCount(1); + mock1.message(0).body().isInstanceOf(byte[].class); + + List<JacksonAvroMarshalUnmarshalPojoListTest.Pojo> pojos = new ArrayList<>(); + pojos.add(new JacksonAvroMarshalUnmarshalPojoListTest.Pojo("Hello")); + pojos.add(new JacksonAvroMarshalUnmarshalPojoListTest.Pojo("World")); + + template.sendBodyAndHeader("direct:pojo", pojos, "list", true); + + mock1.assertIsSatisfied(); + + byte[] serialized = mock1.getReceivedExchanges().get(0).getIn().getBody(byte[].class); + assertNotNull(serialized); + assertEquals(14, serialized.length); + + MockEndpoint mock2 = getMockEndpoint("mock:pojo"); + mock2.expectedMessageCount(1); + mock2.message(0).body().isInstanceOf(JsonNode.class); + + template.sendBodyAndHeader("direct:serialized", serialized, "list", true); + mock2.assertIsSatisfied(); + + @SuppressWarnings("unchecked") + JsonNode back = mock2.getReceivedExchanges().get(0).getIn().getBody(JsonNode.class); + assertTrue(back.isArray()); + assertEquals(2, back.size()); + assertEquals("Hello", back.get(0).at("/text").asText()); + assertEquals("World", back.get(1).at("/text").asText()); + } + + @Override + protected void bindToRegistry(Registry registry) throws Exception { + String SCHEMA_JSON = "{\n" + + "\"type\": \"record\",\n" + + "\"name\": \"Pojo\",\n" + + "\"fields\": [\n" + + " {\"name\": \"text\", \"type\": \"string\"}\n" + + "]}"; + String LIST_SCHEMA_JSON = "{\n" + + " \"type\": \"array\", \n" + + " \"items\":{\n" + + " \"name\":\"Pojo\",\n" + + " \"type\":\"record\",\n" + + " \"fields\":[\n" + + " {\"name\":\"text\", \"type\":\"string\"}\n" + + " ]\n" + + " }\n" + + "}"; + + Schema raw = new Schema.Parser().setValidate(true).parse(SCHEMA_JSON); + AvroSchema schema = new AvroSchema(raw); + + Schema rawList = new Schema.Parser().setValidate(true).parse(LIST_SCHEMA_JSON); + AvroSchema schemaList = new AvroSchema(rawList); + + SchemaResolver resolver = ex -> { + Boolean isList = ex.getMessage().getHeader("list", Boolean.class); + if (isList != null && isList) { + return schemaList; + } + return schema; + }; + registry.bind("schema-resolver", SchemaResolver.class, resolver); + + JacksonAvroDataFormat df = new JacksonAvroDataFormat(); + df.setUnmarshalType(JsonNode.class); + registry.bind("custom-df", DataFormat.class, df); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:serialized").unmarshal().custom("custom-df").to("mock:pojo"); + from("direct:pojo").marshal().custom("custom-df").to("mock:serialized"); + } + }; + } + + public static class Pojo { + + public Pojo() { + } + + public Pojo(String text) { + this.text = text; + } + + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + +} diff --git a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalPojoListTest.java b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalPojoListTest.java new file mode 100644 index 0000000..c643cca --- /dev/null +++ b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalPojoListTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.avro; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import org.apache.avro.Schema; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.SchemaResolver; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class JacksonAvroMarshalUnmarshalPojoListTest extends CamelTestSupport { + + @Test + public void testMarshalUnmarshalPojoList() throws Exception { + MockEndpoint mock1 = getMockEndpoint("mock:serialized"); + mock1.expectedMessageCount(1); + mock1.message(0).body().isInstanceOf(byte[].class); + + List<Pojo> pojos = new ArrayList<>(); + pojos.add(new Pojo("Hello")); + pojos.add(new Pojo("World")); + + template.sendBody("direct:pojo", pojos); + + mock1.assertIsSatisfied(); + + byte[] serialized = mock1.getReceivedExchanges().get(0).getIn().getBody(byte[].class); + assertNotNull(serialized); + assertEquals(14, serialized.length); + + MockEndpoint mock2 = getMockEndpoint("mock:pojo"); + mock2.expectedMessageCount(1); + mock2.message(0).body().isInstanceOf(List.class); + + template.sendBody("direct:serialized", serialized); + mock2.assertIsSatisfied(); + + @SuppressWarnings("unchecked") + List<Pojo> back = mock2.getReceivedExchanges().get(0).getIn().getBody(List.class); + + assertEquals(2, back.size()); + assertEquals("Hello", back.get(0).getText()); + assertEquals("World", back.get(1).getText()); + } + + @Override + protected void bindToRegistry(Registry registry) throws Exception { + String SCHEMA_JSON = "{\n" + + " \"type\": \"array\", \n" + + " \"items\":{\n" + + " \"name\":\"Pojo\",\n" + + " \"type\":\"record\",\n" + + " \"fields\":[\n" + + " {\"name\":\"text\", \"type\":\"string\"}\n" + + " ]\n" + + " }\n" + + "}"; + Schema raw = new Schema.Parser().setValidate(true).parse(SCHEMA_JSON); + AvroSchema schema = new AvroSchema(raw); + SchemaResolver resolver = ex -> schema; + registry.bind("schema-resolver", SchemaResolver.class, resolver); + + JacksonAvroDataFormat df = new JacksonAvroDataFormat(); + df.setUnmarshalType(Pojo.class); + df.setUseList(true); + registry.bind("custom-df", DataFormat.class, df); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:serialized").unmarshal().custom("custom-df").to("mock:pojo"); + from("direct:pojo").marshal().custom("custom-df").to("mock:serialized"); + } + }; + } + + public static class Pojo { + + public Pojo() { + } + + public Pojo(String text) { + this.text = text; + } + + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + +} diff --git a/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalPojoTest.java b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalPojoTest.java new file mode 100644 index 0000000..53326bc --- /dev/null +++ b/components/camel-jackson-avro/src/test/java/org/apache/camel/component/jackson/avro/JacksonAvroMarshalUnmarshalPojoTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.avro; + +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import org.apache.avro.Schema; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.SchemaResolver; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class JacksonAvroMarshalUnmarshalPojoTest extends CamelTestSupport { + + @Test + public void testMarshalUnmarshalPojo() throws Exception { + MockEndpoint mock1 = getMockEndpoint("mock:serialized"); + mock1.expectedMessageCount(1); + mock1.message(0).body().isInstanceOf(byte[].class); + + Pojo pojo = new Pojo("Hello"); + template.sendBody("direct:pojo", pojo); + + mock1.assertIsSatisfied(); + + byte[] serialized = mock1.getReceivedExchanges().get(0).getIn().getBody(byte[].class); + assertNotNull(serialized); + assertEquals(6, serialized.length); + + MockEndpoint mock2 = getMockEndpoint("mock:pojo"); + mock2.expectedMessageCount(1); + mock2.message(0).body().isInstanceOf(Pojo.class); + + template.sendBody("direct:serialized", serialized); + mock2.assertIsSatisfied(); + + Pojo back = mock2.getReceivedExchanges().get(0).getIn().getBody(Pojo.class); + + assertEquals(pojo.getText(), back.getText()); + } + + @Override + protected void bindToRegistry(Registry registry) throws Exception { + String SCHEMA_JSON = "{\n" + + "\"type\": \"record\",\n" + + "\"name\": \"Pojo\",\n" + + "\"fields\": [\n" + + " {\"name\": \"text\", \"type\": \"string\"}\n" + + "]}"; + Schema raw = new Schema.Parser().setValidate(true).parse(SCHEMA_JSON); + AvroSchema schema = new AvroSchema(raw); + SchemaResolver resolver = ex -> schema; + registry.bind("schema-resolver", SchemaResolver.class, resolver); + + JacksonAvroDataFormat df = new JacksonAvroDataFormat(); + df.setUnmarshalType(Pojo.class); + registry.bind("custom-df", DataFormat.class, df); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:serialized").unmarshal().custom("custom-df").to("mock:pojo"); + from("direct:pojo").marshal().custom("custom-df").to("mock:serialized"); + } + }; + } + + public static class Pojo { + + public Pojo() { + } + + public Pojo(String text) { + this.text = text; + } + + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + +} diff --git a/components/camel-jackson-avro/src/test/resources/log4j2.properties b/components/camel-jackson-avro/src/test/resources/log4j2.properties new file mode 100644 index 0000000..6bf5a0b --- /dev/null +++ b/components/camel-jackson-avro/src/test/resources/log4j2.properties @@ -0,0 +1,28 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-jackson-avro-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file diff --git a/components/camel-jackson-protobuf/pom.xml b/components/camel-jackson-protobuf/pom.xml new file mode 100644 index 0000000..a261eff --- /dev/null +++ b/components/camel-jackson-protobuf/pom.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>3.10.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-jackson-protobuf</artifactId> + <packaging>jar</packaging> + <name>Camel :: Jackson Protobuf</name> + <description>Camel Jackson Protobuf support</description> + + <properties> + <camel.osgi.import.before.defaults> + com.fasterxml.jackson.*;version="[2.11,3)" + </camel.osgi.import.before.defaults> + </properties> + + <dependencies> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-protobuf</artifactId> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/components/camel-jackson-protobuf/src/generated/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufDataFormatConfigurer.java b/components/camel-jackson-protobuf/src/generated/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufDataFormatConfigurer.java new file mode 100644 index 0000000..0d1123d --- /dev/null +++ b/components/camel-jackson-protobuf/src/generated/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufDataFormatConfigurer.java @@ -0,0 +1,26 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.jackson.protobuf; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class JacksonProtobufDataFormatConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer { + + @Override + public boolean configure(CamelContext camelContext, Object target, String name, Object value, boolean ignoreCase) { + JacksonProtobufDataFormat dataformat = (JacksonProtobufDataFormat) target; + switch (ignoreCase ? name.toLowerCase() : name) { + default: return false; + } + } + +} + diff --git a/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/configurer/protobuf-jackson-dataformat b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/configurer/protobuf-jackson-dataformat new file mode 100644 index 0000000..aac9d1a --- /dev/null +++ b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/configurer/protobuf-jackson-dataformat @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jackson.protobuf.JacksonProtobufDataFormatConfigurer diff --git a/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/dataformat.properties b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/dataformat.properties new file mode 100644 index 0000000..717a80a --- /dev/null +++ b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/dataformat.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +dataFormats=protobuf-jackson +groupId=org.apache.camel +artifactId=camel-jackson-protobuf +version=3.10.0-SNAPSHOT +projectName=Camel :: Jackson Protobuf +projectDescription=Camel Jackson Protobuf support diff --git a/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/dataformat/protobuf-jackson b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/dataformat/protobuf-jackson new file mode 100644 index 0000000..26c2c39 --- /dev/null +++ b/components/camel-jackson-protobuf/src/generated/resources/META-INF/services/org/apache/camel/dataformat/protobuf-jackson @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jackson.protobuf.JacksonProtobufDataFormat diff --git a/components/camel-jackson-protobuf/src/generated/resources/org/apache/camel/component/jackson/protobuf/protobuf-jackson.json b/components/camel-jackson-protobuf/src/generated/resources/org/apache/camel/component/jackson/protobuf/protobuf-jackson.json new file mode 100644 index 0000000..7da3c24 --- /dev/null +++ b/components/camel-jackson-protobuf/src/generated/resources/org/apache/camel/component/jackson/protobuf/protobuf-jackson.json @@ -0,0 +1,22 @@ +{ + "dataformat": { + "kind": "dataformat", + "name": "protobuf-jackson", + "title": "Avro", + "description": "Serialize and deserialize messages using Apache Avro binary data format.", + "deprecated": false, + "firstVersion": "2.14.0", + "label": "dataformat,transformation", + "javaType": "org.apache.camel.component.jackson.protobuf.JacksonProtobufDataFormat", + "supportLevel": "Stable", + "groupId": "org.apache.camel", + "artifactId": "camel-jackson-protobuf", + "version": "3.10.0-SNAPSHOT", + "modelName": "avro", + "modelJavaType": "org.apache.camel.model.dataformat.AvroDataFormat" + }, + "properties": { + "instanceClassName": { "kind": "attribute", "displayName": "Instance Class Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Class name to use for marshal and unmarshalling" }, + "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" } + } +} diff --git a/components/camel-jackson-protobuf/src/main/docs/jackson-protobuf-dataformat.adoc b/components/camel-jackson-protobuf/src/main/docs/jackson-protobuf-dataformat.adoc new file mode 100644 index 0000000..6b79de6 --- /dev/null +++ b/components/camel-jackson-protobuf/src/main/docs/jackson-protobuf-dataformat.adoc @@ -0,0 +1,370 @@ +[[jackson-protobuf-dataformat]] += Jackson Protobuf DataFormat +:docTitle: Jackson Protobuf +:artifactId: camel-jackson-protobuf +:description: Unmarshal Protobuf payloads to POJOs and back using Jackson. +:since: 3.10 +:supportLevel: Stable + +*Since Camel {since}* + +Jackson XML is a Data Format which uses the +http://wiki.fasterxml.com/JacksonHome/[Jackson library] with the +https://github.com/FasterXML/jackson-dataformat-xml[XMLMapper extension] +to unmarshal an XML payload into Java objects or to marshal Java objects +into an XML payload. + +[TIP] +==== +If you are familiar with Jackson, this XML data format behaves in the +same way as its JSON counterpart, and thus can be used with classes +annotated for JSON serialization/deserialization. +==== + +This extension also mimics +https://github.com/FasterXML/jackson-dataformat-xml/blob/master/README.md[JAXB's +"Code first" approach]. + +This data format relies on +http://wiki.fasterxml.com/WoodstoxHome[Woodstox] (especially for +features like pretty printing), a fast and efficient XML processor. + +[source,java] +------------------------------- +from("activemq:My.Queue"). + unmarshal().jacksonxml(). + to("mqseries:Another.Queue"); +------------------------------- + +== JacksonXML Options + + + +// dataformat options: START +The JacksonXML dataformat supports 15 options, which are listed below. + + + +[width="100%",cols="2s,1m,1m,6",options="header"] +|=== +| Name | Default | Java Type | Description +| xmlMapper | | String | Lookup and use the existing XmlMapper with the given id. +| prettyPrint | false | Boolean | To enable pretty printing output nicely formatted. Is by default false. +| unmarshalTypeName | | String | Class name of the java type to use when unmarshalling +| jsonViewTypeName | | String | When marshalling a POJO to JSON you might want to exclude certain fields from the JSON output. With Jackson you can use JSON views to accomplish this. This option is to refer to the class which has JsonView annotations +| include | | String | If you want to marshal a pojo to JSON, and the pojo has some fields with null values. And you want to skip these null values, you can set this option to NON_NULL +| allowJmsType | false | Boolean | Used for JMS users to allow the JMSType header from the JMS spec to specify a FQN classname to use to unmarshal to. +| collectionTypeName | | String | Refers to a custom collection type to lookup in the registry to use. This option should rarely be used, but allows to use different collection types than java.util.Collection based as default. +| useList | false | Boolean | To unmarshal to a List of Map or a List of Pojo. +| enableJaxbAnnotationModule | false | Boolean | Whether to enable the JAXB annotations module when using jackson. When enabled then JAXB annotations can be used by Jackson. +| moduleClassNames | | String | To use custom Jackson modules com.fasterxml.jackson.databind.Module specified as a String with FQN class names. Multiple classes can be separated by comma. +| moduleRefs | | String | To use custom Jackson modules referred from the Camel registry. Multiple modules can be separated by comma. +| enableFeatures | | String | Set of features to enable on the Jackson com.fasterxml.jackson.databind.ObjectMapper. The features should be a name that matches a enum from com.fasterxml.jackson.databind.SerializationFeature, com.fasterxml.jackson.databind.DeserializationFeature, or com.fasterxml.jackson.databind.MapperFeature Multiple features can be separated by comma +| disableFeatures | | String | Set of features to disable on the Jackson com.fasterxml.jackson.databind.ObjectMapper. The features should be a name that matches a enum from com.fasterxml.jackson.databind.SerializationFeature, com.fasterxml.jackson.databind.DeserializationFeature, or com.fasterxml.jackson.databind.MapperFeature Multiple features can be separated by comma +| allowUnmarshallType | false | Boolean | If enabled then Jackson is allowed to attempt to use the CamelJacksonUnmarshalType header during the unmarshalling. This should only be enabled when desired to be used. +| contentTypeHeader | true | Boolean | Whether the data format should set the Content-Type header with the type from the data format. For example application/xml for data formats marshalling to XML, or application/json for data formats marshalling to JSON +|=== +// dataformat options: END + + +=== Using Jackson XML in Spring DSL + +When using Data Format in Spring DSL you need to +declare the data formats first. This is done in the *DataFormats* XML +tag. + +[source,xml] +----------------------------------------------------------------------------------------------------------------------------- + <dataFormats> + <!-- here we define a Xml data format with the id jack and that it should use the TestPojo as the class type when + doing unmarshal. The unmarshalTypeName is optional, if not provided Camel will use a Map as the type --> + <jacksonxml id="jack" unmarshalTypeName="org.apache.camel.component.jacksonxml.TestPojo"/> + </dataFormats> +----------------------------------------------------------------------------------------------------------------------------- + +And then you can refer to this id in the route: + +[source,xml] +------------------------------------- + <route> + <from uri="direct:back"/> + <unmarshal><custom ref="jack"/></unmarshal> + <to uri="mock:reverse"/> + </route> +------------------------------------- + +== Excluding POJO fields from marshalling + +When marshalling a POJO to XML you might want to exclude certain fields +from the XML output. With Jackson you can +use http://wiki.fasterxml.com/JacksonJsonViews[JSON views] to accomplish +this. First create one or more marker classes. + +Use the marker classes with the `@JsonView` annotation to +include/exclude certain fields. The annotation also works on getters. + +Finally use the Camel `JacksonXMLDataFormat` to marshall the above POJO +to XML. + +Note that the weight field is missing in the resulting XML: + +[source,java] +---------------------------- +<pojo age="30" weight="70"/> +---------------------------- + +== Include/Exclude fields using the `jsonView` attribute with `JacksonXML`DataFormat + +As an example of using this attribute you can instead of: + +[source,java] +--------------------------------------------------------------------------------------------------- +JacksonXMLDataFormat ageViewFormat = new JacksonXMLDataFormat(TestPojoView.class, Views.Age.class); +from("direct:inPojoAgeView"). + marshal(ageViewFormat); +--------------------------------------------------------------------------------------------------- + +Directly specify your http://wiki.fasterxml.com/JacksonJsonViews[JSON +view] inside the Java DSL as: + +[source,java] +------------------------------------------------------------ +from("direct:inPojoAgeView"). + marshal().jacksonxml(TestPojoView.class, Views.Age.class); +------------------------------------------------------------ + +And the same in XML DSL: + +[source,xml] +--------------------------------------------------------------------------------------------------------------------------------------------------- +<from uri="direct:inPojoAgeView"/> + <marshal> + <jacksonxml unmarshalTypeName="org.apache.camel.component.jacksonxml.TestPojoView" jsonView="org.apache.camel.component.jacksonxml.Views$Age"/> + </marshal> +--------------------------------------------------------------------------------------------------------------------------------------------------- + +== Setting serialization include option + +If you want to marshal a pojo to XML, and the pojo has some fields with +null values. And you want to skip these null values, then you need to +set either an annotation on the pojo, + +[source,java] +------------------------------ +@JsonInclude(Include.NON_NULL) +public class MyPojo { + ... +} +------------------------------ + +But this requires you to include that annotation in your pojo source +code. You can also configure the Camel JacksonXMLDataFormat to set the +include option, as shown below: + +[source,java] +--------------------------------------------------------- +JacksonXMLDataFormat format = new JacksonXMLDataFormat(); +format.setInclude("NON_NULL"); +--------------------------------------------------------- + +Or from XML DSL you configure this as + +[source,java] +------------------------------------------------------ + <dataFormats> + <jacksonxml id="jacksonxml" include="NON_NULL"/> + </dataFormats> +------------------------------------------------------ + +== Unmarshalling from XML to POJO with dynamic class name + +If you use jackson to unmarshal XML to POJO, then you can now specify a +header in the message that indicate which class name to unmarshal to. + +The header has key `CamelJacksonUnmarshalType` if that header is present +in the message, then Jackson will use that as FQN for the POJO class to +unmarshal the XML payload as. + + For JMS end users there is the JMSType header from the JMS spec that +indicates that also. To enable support for JMSType you would need to +turn that on, on the jackson data format as shown: + +[source,java] +--------------------------------------------------- +JacksonDataFormat format = new JacksonDataFormat(); +format.setAllowJmsType(true); +--------------------------------------------------- + +Or from XML DSL you configure this as + +[source,java] +------------------------------------------------------- + <dataFormats> + <jacksonxml id="jacksonxml" allowJmsType="true"/> + </dataFormats> +------------------------------------------------------- + +== Unmarshalling from XML to List<Map> or List<pojo> + +If you are using Jackson to unmarshal XML to a list of map/pojo, you can +now specify this by setting `useList="true"` or use +the `org.apache.camel.component.jacksonxml.ListJacksonXMLDataFormat`. +For example with Java you can do as shown below: + +[source,java] +------------------------------------------------------------- +JacksonXMLDataFormat format = new ListJacksonXMLDataFormat(); +// or +JacksonXMLDataFormat format = new JacksonXMLDataFormat(); +format.useList(); +// and you can specify the pojo class type also +format.setUnmarshalType(MyPojo.class); +------------------------------------------------------------- + +And if you use XML DSL then you configure to use list +using `useList` attribute as shown below: + +[source,java] +-------------------------------------------- + <dataFormats> + <jacksonxml id="jack" useList="true"/> + </dataFormats> +-------------------------------------------- + +And you can specify the pojo type also + +[source,java] +------------------------------------------------------------------------------- + <dataFormats> + <jacksonxml id="jack" useList="true" unmarshalTypeName="com.foo.MyPojo"/> + </dataFormats> +------------------------------------------------------------------------------- + +== Using custom Jackson modules + +You can use custom Jackson modules by specifying the class names of +those using the moduleClassNames option as shown below. + +[source,java] +----------------------------------------------------------------------------------------------------------------------------------------- + <dataFormats> + <jacksonxml id="jack" useList="true" unmarshalTypeName="com.foo.MyPojo" moduleClassNames="com.foo.MyModule,com.foo.MyOtherModule"/> + </dataFormats> +----------------------------------------------------------------------------------------------------------------------------------------- + +When using moduleClassNames then the custom jackson modules are not +configured, by created using default constructor and used as-is. If a +custom module needs any custom configuration, then an instance of the +module can be created and configured, and then use modulesRefs to refer +to the module as shown below: + +[source,java] +------------------------------------------------------------------------------------------------------------------ + <bean id="myJacksonModule" class="com.foo.MyModule"> + ... // configure the module as you want + </bean> + + <dataFormats> + <jacksonxml id="jacksonxml" useList="true" unmarshalTypeName="com.foo.MyPojo" moduleRefs="myJacksonModule"/> + </dataFormats> +------------------------------------------------------------------------------------------------------------------ + + Multiple modules can be specified separated by comma, such as +moduleRefs="myJacksonModule,myOtherModule" + +== Enabling or disable features using Jackson + +Jackson has a number of features you can enable or disable, which its +ObjectMapper uses. For example to disable failing on unknown properties +when marshalling, you can configure this using the disableFeatures: + +[source,java] +------------------------------------------------------------------------------------------------------------------- + <dataFormats> + <jacksonxml id="jacksonxml" unmarshalTypeName="com.foo.MyPojo" disableFeatures="FAIL_ON_UNKNOWN_PROPERTIES"/> + </dataFormats> +------------------------------------------------------------------------------------------------------------------- + +You can disable multiple features by separating the values using comma. +The values for the features must be the name of the enums from Jackson +from the following enum classes + +* com.fasterxml.jackson.databind.SerializationFeature +* com.fasterxml.jackson.databind.DeserializationFeature +* com.fasterxml.jackson.databind.MapperFeature + +To enable a feature use the enableFeatures options instead. + +From Java code you can use the type safe methods from camel-jackson +module: + +[source,java] +---------------------------------------------------------------------- +JacksonDataFormat df = new JacksonDataFormat(MyPojo.class); +df.disableFeature(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); +df.disableFeature(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); +---------------------------------------------------------------------- + +== Converting Maps to POJO using Jackson + +Jackson `ObjectMapper` can be used to convert maps to POJO objects. +Jackson component comes with the data converter that can be used to +convert `java.util.Map` instance to non-String, non-primitive and +non-Number objects. + +[source,java] +---------------------------------------------------------------- +Map<String, Object> invoiceData = new HashMap<String, Object>(); +invoiceData.put("netValue", 500); +producerTemplate.sendBody("direct:mapToInvoice", invoiceData); +... +// Later in the processor +Invoice invoice = exchange.getIn().getBody(Invoice.class); +---------------------------------------------------------------- + +If there is a single `ObjectMapper` instance available in the Camel +registry, it will used by the converter to perform the conversion. +Otherwise the default mapper will be used. + +== Formatted XML marshalling (pretty-printing) + +Using the `prettyPrint` option one can output a well formatted XML while +marshalling: + +[source,java] +------------------------------------------------ + <dataFormats> + <jacksonxml id="jack" prettyPrint="true"/> + </dataFormats> +------------------------------------------------ + +And in Java DSL: + +[source,java] +--------------------------------------------------- +from("direct:inPretty").marshal().jacksonxml(true); +--------------------------------------------------- + +Please note that there are 5 different overloaded `jacksonxml()` DSL +methods which support the `prettyPrint` option in combination with other +settings for `unmarshalType`, `jsonView` etc. + +== Dependencies + +To use Jackson XML in your camel routes you need to add the dependency +on *camel-jacksonxml* which implements this data format. + +If you use maven you could just add the following to your pom.xml, +substituting the version number for the latest & greatest release (see +the download page for the latest versions). + +[source,xml] +---------------------------------------------------------- +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jacksonxml</artifactId> + <version>x.x.x</version> + <!-- use the same version as your Camel core version --> +</dependency> +---------------------------------------------------------- + +include::{page-component-version}@camel-spring-boot::page$jacksonxml-starter.adoc[] diff --git a/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufDataFormat.java b/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufDataFormat.java new file mode 100644 index 0000000..8900ca7 --- /dev/null +++ b/components/camel-jackson-protobuf/src/main/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufDataFormat.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.protobuf; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.protobuf.ProtobufMapper; +import org.apache.camel.component.jackson.AbstractJacksonDataFormat; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.annotations.Dataformat; + +/** + * Marshal POJOs to Protobuf and back using <a href="http://jackson.codehaus.org/">Jackson</a> + */ +@Dataformat("protobuf-jackson") +@Metadata(excludeProperties = "library,permissions,dropRootNode") +public class JacksonProtobufDataFormat extends AbstractJacksonDataFormat { + + /** + * Use the default Jackson {@link ProtobufMapper} and {@link Object} + */ + public JacksonProtobufDataFormat() { + } + + /** + * Use the default Jackson {@link ProtobufMapper} and with a custom unmarshal type + * + * @param unmarshalType the custom unmarshal type + */ + public JacksonProtobufDataFormat(Class<?> unmarshalType) { + super(unmarshalType); + } + + /** + * Use the default Jackson {@link ProtobufMapper} and with a custom unmarshal type and JSON view + * + * @param unmarshalType the custom unmarshal type + * @param jsonView marker class to specify properties to be included during marshalling. See also + */ + public JacksonProtobufDataFormat(Class<?> unmarshalType, Class<?> jsonView) { + super(unmarshalType, jsonView); + } + + /** + * Use a custom Jackson {@link ProtobufMapper} and and unmarshal type + * + * @param mapper the custom mapper + * @param unmarshalType the custom unmarshal type + */ + public JacksonProtobufDataFormat(ProtobufMapper mapper, Class<?> unmarshalType) { + super(mapper, unmarshalType); + } + + /** + * Use a custom Jackson {@link ProtobufMapper}, unmarshal type and JSON view + * + * @param mapper the custom mapper + * @param unmarshalType the custom unmarshal type + * @param jsonView marker class to specify properties to be included during marshalling. See also + */ + public JacksonProtobufDataFormat(ProtobufMapper mapper, Class<?> unmarshalType, Class<?> jsonView) { + super(mapper, unmarshalType, jsonView); + } + + @Override + public String getDataFormatName() { + return "protobuf-jackson"; + } + + @Override + protected String getDefaultContentType() { + return "application/protobuf"; + } + + @Override + protected ProtobufMapper createNewObjectMapper() { + return new ProtobufMapper(); + } + + @Override + protected Class<? extends ObjectMapper> getObjectMapperClass() { + return ProtobufMapper.class; + } + +} diff --git a/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufMarshalUnmarshalJsonNodeTest.java b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufMarshalUnmarshalJsonNodeTest.java new file mode 100644 index 0000000..b896f7a --- /dev/null +++ b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufMarshalUnmarshalJsonNodeTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.protobuf; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; +import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.SchemaResolver; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class JacksonProtobufMarshalUnmarshalJsonNodeTest extends CamelTestSupport { + + @Test + public void testMarshalUnmarshalJsonNode() throws Exception { + MockEndpoint mock1 = getMockEndpoint("mock:serialized"); + mock1.expectedMessageCount(1); + mock1.message(0).body().isInstanceOf(byte[].class); + + Pojo pojo = new Pojo("Hello"); + template.sendBody("direct:pojo", pojo); + + mock1.assertIsSatisfied(); + + byte[] serialized = mock1.getReceivedExchanges().get(0).getIn().getBody(byte[].class); + assertNotNull(serialized); + assertEquals(7, serialized.length); + + MockEndpoint mock2 = getMockEndpoint("mock:pojo"); + mock2.expectedMessageCount(1); + mock2.message(0).body().isInstanceOf(JsonNode.class); + + template.sendBody("direct:serialized", serialized); + mock2.assertIsSatisfied(); + + JsonNode back = mock2.getReceivedExchanges().get(0).getIn().getBody(JsonNode.class); + + assertEquals(pojo.getText(), back.at("/text").asText()); + } + + @Override + protected void bindToRegistry(Registry registry) throws Exception { + String protobuf_str = "message Pojo {\n" + + " required string text = 1;\n" + + "}\n"; + ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protobuf_str); + SchemaResolver resolver = ex -> schema; + registry.bind("schema-resolver", SchemaResolver.class, resolver); + + JacksonProtobufDataFormat df = new JacksonProtobufDataFormat(); + df.setUnmarshalType(JsonNode.class); + registry.bind("custom-df", DataFormat.class, df); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:serialized").unmarshal().custom("custom-df").to("mock:pojo"); + from("direct:pojo").marshal().custom("custom-df").to("mock:serialized"); + } + }; + } + + public static class Pojo { + + public Pojo() { + } + + public Pojo(String text) { + this.text = text; + } + + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + +} diff --git a/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufMarshalUnmarshalPojoTest.java b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufMarshalUnmarshalPojoTest.java new file mode 100644 index 0000000..5213989 --- /dev/null +++ b/components/camel-jackson-protobuf/src/test/java/org/apache/camel/component/jackson/protobuf/JacksonProtobufMarshalUnmarshalPojoTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jackson.protobuf; + +import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; +import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.SchemaResolver; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class JacksonProtobufMarshalUnmarshalPojoTest extends CamelTestSupport { + + @Test + public void testMarshalUnmarshalPojo() throws Exception { + MockEndpoint mock1 = getMockEndpoint("mock:serialized"); + mock1.expectedMessageCount(1); + mock1.message(0).body().isInstanceOf(byte[].class); + + Pojo pojo = new Pojo("Hello"); + template.sendBody("direct:pojo", pojo); + + mock1.assertIsSatisfied(); + + byte[] serialized = mock1.getReceivedExchanges().get(0).getIn().getBody(byte[].class); + assertNotNull(serialized); + assertEquals(7, serialized.length); + + MockEndpoint mock2 = getMockEndpoint("mock:pojo"); + mock2.expectedMessageCount(1); + mock2.message(0).body().isInstanceOf(Pojo.class); + + template.sendBody("direct:serialized", serialized); + mock2.assertIsSatisfied(); + + Pojo back = mock2.getReceivedExchanges().get(0).getIn().getBody(Pojo.class); + + assertEquals(pojo.getText(), back.getText()); + } + + @Override + protected void bindToRegistry(Registry registry) throws Exception { + String protobuf_str = "message Pojo {\n" + + " required string text = 1;\n" + + "}\n"; + ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protobuf_str); + SchemaResolver resolver = ex -> schema; + registry.bind("schema-resolver", SchemaResolver.class, resolver); + + JacksonProtobufDataFormat df = new JacksonProtobufDataFormat(); + df.setUnmarshalType(Pojo.class); + registry.bind("custom-df", DataFormat.class, df); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:serialized").unmarshal().custom("custom-df").to("mock:pojo"); + from("direct:pojo").marshal().custom("custom-df").to("mock:serialized"); + } + }; + } + + public static class Pojo { + + public Pojo() { + } + + public Pojo(String text) { + this.text = text; + } + + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + +} diff --git a/components/camel-jackson-protobuf/src/test/resources/log4j2.properties b/components/camel-jackson-protobuf/src/test/resources/log4j2.properties new file mode 100644 index 0000000..dd227bc --- /dev/null +++ b/components/camel-jackson-protobuf/src/test/resources/log4j2.properties @@ -0,0 +1,28 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-jackson-protobuf-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file diff --git a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/AbstractJacksonDataFormat.java similarity index 85% copy from components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java copy to components/camel-jackson/src/main/java/org/apache/camel/component/jackson/AbstractJacksonDataFormat.java index bcb8b9b..ffc2233 100644 --- a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java +++ b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/AbstractJacksonDataFormat.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.FormatSchema; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.Module; @@ -40,8 +41,6 @@ import org.apache.camel.Exchange; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.DataFormatContentTypeHeader; import org.apache.camel.spi.DataFormatName; -import org.apache.camel.spi.Metadata; -import org.apache.camel.spi.annotations.Dataformat; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.ObjectHelper; import org.apache.camel.support.service.ServiceSupport; @@ -50,13 +49,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Marshal POJOs to JSON and back using <a href="http://jackson.codehaus.org/">Jackson</a> + * Abstract superclass of <a href="http://jackson.codehaus.org/">Jackson</a> based data formats. */ -@Dataformat("json-jackson") -@Metadata(excludeProperties = "library,permissions,dropRootNode") -public class JacksonDataFormat extends ServiceSupport +public abstract class AbstractJacksonDataFormat extends ServiceSupport implements DataFormat, DataFormatName, DataFormatContentTypeHeader, CamelContextAware { - private static final Logger LOG = LoggerFactory.getLogger(JacksonDataFormat.class); + private static final Logger LOG = LoggerFactory.getLogger(AbstractJacksonDataFormat.class); private CamelContext camelContext; private ObjectMapper objectMapper; @@ -81,11 +78,13 @@ public class JacksonDataFormat extends ServiceSupport private boolean contentTypeHeader = true; private TimeZone timezone; private boolean autoDiscoverObjectMapper; + private SchemaResolver schemaResolver; + private boolean autoDiscoverSchemaResolver = true; /** * Use the default Jackson {@link ObjectMapper} and {@link Object} */ - public JacksonDataFormat() { + public AbstractJacksonDataFormat() { this(Object.class); } @@ -94,7 +93,7 @@ public class JacksonDataFormat extends ServiceSupport * * @param unmarshalType the custom unmarshal type */ - public JacksonDataFormat(Class<?> unmarshalType) { + public AbstractJacksonDataFormat(Class<?> unmarshalType) { this(unmarshalType, null); } @@ -105,7 +104,7 @@ public class JacksonDataFormat extends ServiceSupport * @param jsonView marker class to specify properties to be included during marshalling. See also * http://wiki.fasterxml.com/JacksonJsonViews */ - public JacksonDataFormat(Class<?> unmarshalType, Class<?> jsonView) { + public AbstractJacksonDataFormat(Class<?> unmarshalType, Class<?> jsonView) { this.unmarshalType = unmarshalType; this.jsonView = jsonView; } @@ -116,7 +115,7 @@ public class JacksonDataFormat extends ServiceSupport * @param mapper the custom mapper * @param unmarshalType the custom unmarshal type */ - public JacksonDataFormat(ObjectMapper mapper, Class<?> unmarshalType) { + public AbstractJacksonDataFormat(ObjectMapper mapper, Class<?> unmarshalType) { this(mapper, unmarshalType, null); } @@ -128,18 +127,13 @@ public class JacksonDataFormat extends ServiceSupport * @param jsonView marker class to specify properties to be included during marshalling. See also * http://wiki.fasterxml.com/JacksonJsonViews */ - public JacksonDataFormat(ObjectMapper mapper, Class<?> unmarshalType, Class<?> jsonView) { + public AbstractJacksonDataFormat(ObjectMapper mapper, Class<?> unmarshalType, Class<?> jsonView) { this.objectMapper = mapper; this.unmarshalType = unmarshalType; this.jsonView = jsonView; } @Override - public String getDataFormatName() { - return "json-jackson"; - } - - @Override public CamelContext getCamelContext() { return camelContext; } @@ -151,20 +145,27 @@ public class JacksonDataFormat extends ServiceSupport @Override public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception { - this.objectMapper.writerWithView(jsonView).writeValue(stream, graph); + FormatSchema schema = null; + if (this.schemaResolver != null) { + schema = this.schemaResolver.resolve(exchange); + } + this.objectMapper.writerWithView(jsonView).with(schema).writeValue(stream, graph); if (contentTypeHeader) { if (exchange.hasOut()) { - exchange.getOut().setHeader(Exchange.CONTENT_TYPE, "application/json"); + exchange.getOut().setHeader(Exchange.CONTENT_TYPE, getDefaultContentType()); } else { - exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/json"); + exchange.getIn().setHeader(Exchange.CONTENT_TYPE, getDefaultContentType()); } } } @Override public Object unmarshal(Exchange exchange, InputStream stream) throws Exception { - + FormatSchema schema = null; + if (this.schemaResolver != null) { + schema = this.schemaResolver.resolve(exchange); + } // is there a header with the unmarshal type? Class<?> clazz = unmarshalType; String type = null; @@ -179,9 +180,9 @@ public class JacksonDataFormat extends ServiceSupport } if (collectionType != null) { CollectionType collType = objectMapper.getTypeFactory().constructCollectionType(collectionType, clazz); - return this.objectMapper.readValue(stream, collType); + return this.objectMapper.readerFor(collType).with(schema).readValue(stream); } else { - return this.objectMapper.readValue(stream, clazz); + return this.objectMapper.reader(schema).readValue(stream, clazz); } } @@ -325,14 +326,14 @@ public class JacksonDataFormat extends ServiceSupport } /** - * Uses {@link java.util.ArrayList} when unmarshalling. + * Uses {@link ArrayList} when unmarshalling. */ public void useList() { setCollectionType(ArrayList.class); } /** - * Uses {@link java.util.HashMap} when unmarshalling. + * Uses {@link HashMap} when unmarshalling. */ public void useMap() { setCollectionType(null); @@ -340,8 +341,8 @@ public class JacksonDataFormat extends ServiceSupport } /** - * Allows jackson to use the <tt>JMSType</tt> header as an indicator what the classname is for unmarshaling json - * content to POJO + * Allows jackson to use the <tt>JMSType</tt> header as an indicator what the classname is for unmarshaling content + * to POJO * <p/> * By default this option is <tt>false</tt>. */ @@ -383,7 +384,7 @@ public class JacksonDataFormat extends ServiceSupport } /** - * If enabled then Jackson will set the Content-Type header to <tt>application/json</tt> when marshalling. + * If enabled then Jackson will set the Content-Type header to the correct mime type when marshalling. */ public void setContentTypeHeader(boolean contentTypeHeader) { this.contentTypeHeader = contentTypeHeader; @@ -411,6 +412,28 @@ public class JacksonDataFormat extends ServiceSupport this.autoDiscoverObjectMapper = autoDiscoverObjectMapper; } + public SchemaResolver getSchemaResolver() { + return schemaResolver; + } + + /** + * Optional schema resolver used to lookup schemas for the data in transit. + */ + public void setSchemaResolver(SchemaResolver schemaResolver) { + this.schemaResolver = schemaResolver; + } + + public boolean isAutoDiscoverSchemaResolver() { + return autoDiscoverSchemaResolver; + } + + /** + * When not disabled, the SchemaResolver will be looked up into the registry + */ + public void setAutoDiscoverSchemaResolver(boolean autoDiscoverSchemaResolver) { + this.autoDiscoverSchemaResolver = autoDiscoverSchemaResolver; + } + public String getEnableFeatures() { return enableFeatures; } @@ -504,7 +527,7 @@ public class JacksonDataFormat extends ServiceSupport // lookup if there is a single default mapper we can use if (useDefaultObjectMapper && camelContext != null) { if (isAutoDiscoverObjectMapper()) { - Set<ObjectMapper> set = camelContext.getRegistry().findByType(ObjectMapper.class); + Set<? extends ObjectMapper> set = camelContext.getRegistry().findByType(getObjectMapperClass()); if (set.size() == 1) { objectMapper = set.iterator().next(); LOG.info("Found single ObjectMapper in Registry to use: {}", objectMapper); @@ -519,7 +542,7 @@ public class JacksonDataFormat extends ServiceSupport } } if (objectMapper == null) { - objectMapper = new ObjectMapper(); + objectMapper = createNewObjectMapper(); LOG.debug("Creating new ObjectMapper to use: {}", objectMapper); } } @@ -627,6 +650,22 @@ public class JacksonDataFormat extends ServiceSupport } else { LOG.info("The objectMapper was already found in the registry, no customizations will be applied"); } + + if (schemaResolver == null && isAutoDiscoverSchemaResolver()) { + if (camelContext != null) { + Set<SchemaResolver> set = camelContext.getRegistry().findByType(SchemaResolver.class); + if (set.size() == 1) { + schemaResolver = set.iterator().next(); + LOG.info("Found single SchemaResolver in Registry to use: {}", schemaResolver); + } else if (set.size() > 1) { + LOG.debug( + "Found {} SchemaResolver in Registry cannot use as default as there are more than one instance.", + set.size()); + } + } + } else { + LOG.info("The option autoDiscoverSchemaResolver is set to false, Camel won't search in the registry"); + } } @Override @@ -634,4 +673,12 @@ public class JacksonDataFormat extends ServiceSupport // noop } + public abstract String getDataFormatName(); + + protected abstract ObjectMapper createNewObjectMapper(); + + protected abstract Class<? extends ObjectMapper> getObjectMapperClass(); + + protected abstract String getDefaultContentType(); + } diff --git a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java index bcb8b9b..cb515cb 100644 --- a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java +++ b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java @@ -16,77 +16,21 @@ */ package org.apache.camel.component.jackson; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.type.CollectionType; -import org.apache.camel.CamelContext; -import org.apache.camel.CamelContextAware; -import org.apache.camel.Exchange; -import org.apache.camel.spi.DataFormat; -import org.apache.camel.spi.DataFormatContentTypeHeader; -import org.apache.camel.spi.DataFormatName; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.annotations.Dataformat; -import org.apache.camel.support.CamelContextHelper; -import org.apache.camel.support.ObjectHelper; -import org.apache.camel.support.service.ServiceSupport; -import org.apache.camel.util.CastUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Marshal POJOs to JSON and back using <a href="http://jackson.codehaus.org/">Jackson</a> */ @Dataformat("json-jackson") @Metadata(excludeProperties = "library,permissions,dropRootNode") -public class JacksonDataFormat extends ServiceSupport - implements DataFormat, DataFormatName, DataFormatContentTypeHeader, CamelContextAware { - private static final Logger LOG = LoggerFactory.getLogger(JacksonDataFormat.class); - - private CamelContext camelContext; - private ObjectMapper objectMapper; - private boolean useDefaultObjectMapper = true; - private String collectionTypeName; - private Class<? extends Collection> collectionType; - private List<Module> modules; - private String moduleClassNames; - private String moduleRefs; - private String unmarshalTypeName; - private Class<?> unmarshalType; - private String jsonViewTypeName; - private Class<?> jsonView; - private String include; - private boolean prettyPrint; - private boolean allowJmsType; - private boolean useList; - private String enableFeatures; - private String disableFeatures; - private boolean enableJacksonTypeConverter; - private boolean allowUnmarshallType; - private boolean contentTypeHeader = true; - private TimeZone timezone; - private boolean autoDiscoverObjectMapper; +public class JacksonDataFormat extends AbstractJacksonDataFormat { /** * Use the default Jackson {@link ObjectMapper} and {@link Object} */ public JacksonDataFormat() { - this(Object.class); } /** @@ -95,7 +39,7 @@ public class JacksonDataFormat extends ServiceSupport * @param unmarshalType the custom unmarshal type */ public JacksonDataFormat(Class<?> unmarshalType) { - this(unmarshalType, null); + super(unmarshalType); } /** @@ -103,11 +47,9 @@ public class JacksonDataFormat extends ServiceSupport * * @param unmarshalType the custom unmarshal type * @param jsonView marker class to specify properties to be included during marshalling. See also - * http://wiki.fasterxml.com/JacksonJsonViews */ public JacksonDataFormat(Class<?> unmarshalType, Class<?> jsonView) { - this.unmarshalType = unmarshalType; - this.jsonView = jsonView; + super(unmarshalType, jsonView); } /** @@ -117,7 +59,7 @@ public class JacksonDataFormat extends ServiceSupport * @param unmarshalType the custom unmarshal type */ public JacksonDataFormat(ObjectMapper mapper, Class<?> unmarshalType) { - this(mapper, unmarshalType, null); + super(mapper, unmarshalType); } /** @@ -126,12 +68,9 @@ public class JacksonDataFormat extends ServiceSupport * @param mapper the custom mapper * @param unmarshalType the custom unmarshal type * @param jsonView marker class to specify properties to be included during marshalling. See also - * http://wiki.fasterxml.com/JacksonJsonViews */ public JacksonDataFormat(ObjectMapper mapper, Class<?> unmarshalType, Class<?> jsonView) { - this.objectMapper = mapper; - this.unmarshalType = unmarshalType; - this.jsonView = jsonView; + super(mapper, unmarshalType, jsonView); } @Override @@ -140,498 +79,18 @@ public class JacksonDataFormat extends ServiceSupport } @Override - public CamelContext getCamelContext() { - return camelContext; - } - - @Override - public void setCamelContext(CamelContext camelContext) { - this.camelContext = camelContext; - } - - @Override - public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception { - this.objectMapper.writerWithView(jsonView).writeValue(stream, graph); - - if (contentTypeHeader) { - if (exchange.hasOut()) { - exchange.getOut().setHeader(Exchange.CONTENT_TYPE, "application/json"); - } else { - exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/json"); - } - } - } - - @Override - public Object unmarshal(Exchange exchange, InputStream stream) throws Exception { - - // is there a header with the unmarshal type? - Class<?> clazz = unmarshalType; - String type = null; - if (allowUnmarshallType) { - type = exchange.getIn().getHeader(JacksonConstants.UNMARSHAL_TYPE, String.class); - } - if (type == null && isAllowJmsType()) { - type = exchange.getIn().getHeader("JMSType", String.class); - } - if (type != null) { - clazz = exchange.getContext().getClassResolver().resolveMandatoryClass(type); - } - if (collectionType != null) { - CollectionType collType = objectMapper.getTypeFactory().constructCollectionType(collectionType, clazz); - return this.objectMapper.readValue(stream, collType); - } else { - return this.objectMapper.readValue(stream, clazz); - } - } - - // Properties - // ------------------------------------------------------------------------- - - public ObjectMapper getObjectMapper() { - return this.objectMapper; - } - - public void setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } - - public boolean isUseDefaultObjectMapper() { - return useDefaultObjectMapper; - } - - public void setUseDefaultObjectMapper(boolean useDefaultObjectMapper) { - this.useDefaultObjectMapper = useDefaultObjectMapper; - } - - public Class<?> getUnmarshalType() { - return this.unmarshalType; - } - - public void setUnmarshalType(Class<?> unmarshalType) { - this.unmarshalType = unmarshalType; - } - - public String getUnmarshalTypeName() { - return unmarshalTypeName; - } - - public void setUnmarshalTypeName(String unmarshalTypeName) { - this.unmarshalTypeName = unmarshalTypeName; - } - - public Class<? extends Collection> getCollectionType() { - return collectionType; - } - - public void setCollectionType(Class<? extends Collection> collectionType) { - this.collectionType = collectionType; - } - - public String getCollectionTypeName() { - return collectionTypeName; - } - - public void setCollectionTypeName(String collectionTypeName) { - this.collectionTypeName = collectionTypeName; - } - - public Class<?> getJsonView() { - return jsonView; - } - - public void setJsonView(Class<?> jsonView) { - this.jsonView = jsonView; - } - - public String getJsonViewTypeName() { - return jsonViewTypeName; - } - - public void setJsonViewTypeName(String jsonViewTypeName) { - this.jsonViewTypeName = jsonViewTypeName; - } - - public String getInclude() { - return include; - } - - public void setInclude(String include) { - this.include = include; - } - - public boolean isAllowJmsType() { - return allowJmsType; - } - - public boolean isPrettyPrint() { - return prettyPrint; - } - - public void setPrettyPrint(boolean prettyPrint) { - this.prettyPrint = prettyPrint; - } - - public boolean isUseList() { - return useList; - } - - public void setUseList(boolean useList) { - this.useList = useList; - } - - public List<Module> getModules() { - return modules; - } - - /** - * To use custom Jackson {@link Module}s - */ - public void setModules(List<Module> modules) { - this.modules = modules; - } - - public String getModuleClassNames() { - return moduleClassNames; - } - - /** - * To use the custom Jackson module - */ - public void addModule(Module module) { - if (this.modules == null) { - this.modules = new ArrayList<>(); - } - this.modules.add(module); - } - - /** - * To use custom Jackson {@link Module}s specified as a String with FQN class names. Multiple classes can be - * separated by comma. - */ - public void setModuleClassNames(String moduleClassNames) { - this.moduleClassNames = moduleClassNames; - } - - public String getModuleRefs() { - return moduleRefs; - } - - /** - * To use custom Jackson modules referred from the Camel registry. Multiple modules can be separated by comma. - */ - public void setModuleRefs(String moduleRefs) { - this.moduleRefs = moduleRefs; - } - - /** - * Uses {@link java.util.ArrayList} when unmarshalling. - */ - public void useList() { - setCollectionType(ArrayList.class); - } - - /** - * Uses {@link java.util.HashMap} when unmarshalling. - */ - public void useMap() { - setCollectionType(null); - setUnmarshalType(HashMap.class); - } - - /** - * Allows jackson to use the <tt>JMSType</tt> header as an indicator what the classname is for unmarshaling json - * content to POJO - * <p/> - * By default this option is <tt>false</tt>. - */ - public void setAllowJmsType(boolean allowJmsType) { - this.allowJmsType = allowJmsType; - } - - public boolean isEnableJacksonTypeConverter() { - return enableJacksonTypeConverter; - } - - /** - * If enabled then Jackson is allowed to attempt to be used during Camels - * <a href="https://camel.apache.org/type-converter.html">type converter</a> as a - * {@link org.apache.camel.FallbackConverter} that attempts to convert POJOs to/from {@link Map}/{@link List} types. - * <p/> - * This should only be enabled when desired to be used. - */ - public void setEnableJacksonTypeConverter(boolean enableJacksonTypeConverter) { - this.enableJacksonTypeConverter = enableJacksonTypeConverter; - } - - public boolean isAllowUnmarshallType() { - return allowUnmarshallType; - } - - /** - * If enabled then Jackson is allowed to attempt to use the CamelJacksonUnmarshalType header during the - * unmarshalling. - * <p/> - * This should only be enabled when desired to be used. - */ - public void setAllowUnmarshallType(boolean allowJacksonUnmarshallType) { - this.allowUnmarshallType = allowJacksonUnmarshallType; - } - - public boolean isContentTypeHeader() { - return contentTypeHeader; - } - - /** - * If enabled then Jackson will set the Content-Type header to <tt>application/json</tt> when marshalling. - */ - public void setContentTypeHeader(boolean contentTypeHeader) { - this.contentTypeHeader = contentTypeHeader; - } - - public TimeZone getTimezone() { - return timezone; - } - - /** - * If set then Jackson will use the Timezone when marshalling/unmarshalling. - */ - public void setTimezone(TimeZone timezone) { - this.timezone = timezone; - } - - public boolean isAutoDiscoverObjectMapper() { - return autoDiscoverObjectMapper; - } - - /** - * If set to true then Jackson will lookup for an objectMapper into the registry - */ - public void setAutoDiscoverObjectMapper(boolean autoDiscoverObjectMapper) { - this.autoDiscoverObjectMapper = autoDiscoverObjectMapper; - } - - public String getEnableFeatures() { - return enableFeatures; - } - - /** - * Set of features to enable on the Jackson {@link ObjectMapper}. The features should be a name that matches a enum - * from {@link SerializationFeature}, {@link DeserializationFeature}, or {@link MapperFeature}. - */ - public void setEnableFeatures(String enableFeatures) { - this.enableFeatures = enableFeatures; - } - - public String getDisableFeatures() { - return disableFeatures; - } - - /** - * Set of features to disable on the Jackson {@link ObjectMapper}. The features should be a name that matches a enum - * from {@link SerializationFeature}, {@link DeserializationFeature}, or {@link MapperFeature}. - */ - public void setDisableFeatures(String disableFeatures) { - this.disableFeatures = disableFeatures; - } - - public void enableFeature(SerializationFeature feature) { - if (enableFeatures == null) { - enableFeatures = feature.name(); - } else { - enableFeatures += "," + feature.name(); - } - } - - public void enableFeature(DeserializationFeature feature) { - if (enableFeatures == null) { - enableFeatures = feature.name(); - } else { - enableFeatures += "," + feature.name(); - } - } - - public void enableFeature(MapperFeature feature) { - if (enableFeatures == null) { - enableFeatures = feature.name(); - } else { - enableFeatures += "," + feature.name(); - } - } - - public void disableFeature(SerializationFeature feature) { - if (disableFeatures == null) { - disableFeatures = feature.name(); - } else { - disableFeatures += "," + feature.name(); - } - } - - public void disableFeature(DeserializationFeature feature) { - if (disableFeatures == null) { - disableFeatures = feature.name(); - } else { - disableFeatures += "," + feature.name(); - } - } - - public void disableFeature(MapperFeature feature) { - if (disableFeatures == null) { - disableFeatures = feature.name(); - } else { - disableFeatures += "," + feature.name(); - } - } - - @Override - protected void doInit() throws Exception { - if (unmarshalTypeName != null && (unmarshalType == null || unmarshalType == Object.class)) { - unmarshalType = camelContext.getClassResolver().resolveClass(unmarshalTypeName); - } - if (jsonViewTypeName != null && jsonView == null) { - jsonView = camelContext.getClassResolver().resolveClass(jsonViewTypeName); - } - if (collectionTypeName != null && collectionType == null) { - Class<?> clazz = camelContext.getClassResolver().resolveClass(collectionTypeName); - collectionType = CastUtils.cast(clazz); - } + protected ObjectMapper createNewObjectMapper() { + return new ObjectMapper(); } @Override - protected void doStart() throws Exception { - boolean objectMapperFoundRegistry = false; - if (objectMapper == null) { - // lookup if there is a single default mapper we can use - if (useDefaultObjectMapper && camelContext != null) { - if (isAutoDiscoverObjectMapper()) { - Set<ObjectMapper> set = camelContext.getRegistry().findByType(ObjectMapper.class); - if (set.size() == 1) { - objectMapper = set.iterator().next(); - LOG.info("Found single ObjectMapper in Registry to use: {}", objectMapper); - objectMapperFoundRegistry = true; - } else if (set.size() > 1) { - LOG.debug( - "Found {} ObjectMapper in Registry cannot use as default as there are more than one instance.", - set.size()); - } - } else { - LOG.info("The option autoDiscoverObjectMapper is set to false, Camel won't search in the registry"); - } - } - if (objectMapper == null) { - objectMapper = new ObjectMapper(); - LOG.debug("Creating new ObjectMapper to use: {}", objectMapper); - } - } - - if (!objectMapperFoundRegistry) { - if (useList) { - setCollectionType(ArrayList.class); - } - if (include != null) { - JsonInclude.Include inc - = getCamelContext().getTypeConverter().mandatoryConvertTo(JsonInclude.Include.class, include); - objectMapper.setSerializationInclusion(inc); - } - if (prettyPrint) { - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - } - - if (enableFeatures != null) { - Iterator<?> it = ObjectHelper.createIterator(enableFeatures); - while (it.hasNext()) { - String enable = it.next().toString(); - // it can be different kind - SerializationFeature sf - = getCamelContext().getTypeConverter().tryConvertTo(SerializationFeature.class, enable); - if (sf != null) { - objectMapper.enable(sf); - continue; - } - DeserializationFeature df - = getCamelContext().getTypeConverter().tryConvertTo(DeserializationFeature.class, enable); - if (df != null) { - objectMapper.enable(df); - continue; - } - MapperFeature mf = getCamelContext().getTypeConverter().tryConvertTo(MapperFeature.class, enable); - if (mf != null) { - objectMapper.enable(mf); - continue; - } - throw new IllegalArgumentException( - "Enable feature: " + enable - + " cannot be converted to an accepted enum of types [SerializationFeature,DeserializationFeature,MapperFeature]"); - } - } - if (disableFeatures != null) { - Iterator<?> it = ObjectHelper.createIterator(disableFeatures); - while (it.hasNext()) { - String disable = it.next().toString(); - // it can be different kind - SerializationFeature sf - = getCamelContext().getTypeConverter().tryConvertTo(SerializationFeature.class, disable); - if (sf != null) { - objectMapper.disable(sf); - continue; - } - DeserializationFeature df - = getCamelContext().getTypeConverter().tryConvertTo(DeserializationFeature.class, disable); - if (df != null) { - objectMapper.disable(df); - continue; - } - MapperFeature mf = getCamelContext().getTypeConverter().tryConvertTo(MapperFeature.class, disable); - if (mf != null) { - objectMapper.disable(mf); - continue; - } - throw new IllegalArgumentException( - "Disable feature: " + disable - + " cannot be converted to an accepted enum of types [SerializationFeature,DeserializationFeature,MapperFeature]"); - } - } - - if (modules != null) { - for (Module module : modules) { - LOG.debug("Registering module: {}", module); - objectMapper.registerModules(module); - } - } - if (moduleClassNames != null) { - Iterable<?> it = ObjectHelper.createIterable(moduleClassNames); - for (Object o : it) { - String name = o.toString(); - Class<Module> clazz = camelContext.getClassResolver().resolveMandatoryClass(name, Module.class); - Module module = camelContext.getInjector().newInstance(clazz); - LOG.debug("Registering module: {} -> {}", name, module); - objectMapper.registerModule(module); - } - } - if (moduleRefs != null) { - Iterable<?> it = ObjectHelper.createIterable(moduleRefs); - for (Object o : it) { - String name = o.toString(); - if (name.startsWith("#")) { - name = name.substring(1); - } - Module module = CamelContextHelper.mandatoryLookup(camelContext, name, Module.class); - LOG.debug("Registering module: {} -> {}", name, module); - objectMapper.registerModule(module); - } - } - if (org.apache.camel.util.ObjectHelper.isNotEmpty(timezone)) { - LOG.debug("Setting timezone to Object Mapper: {}", timezone); - objectMapper.setTimeZone(timezone); - } - } else { - LOG.info("The objectMapper was already found in the registry, no customizations will be applied"); - } + protected Class<? extends ObjectMapper> getObjectMapperClass() { + return ObjectMapper.class; } @Override - protected void doStop() throws Exception { - // noop + protected String getDefaultContentType() { + return "application/json"; } } diff --git a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/SchemaResolver.java b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/SchemaResolver.java new file mode 100644 index 0000000..2829515 --- /dev/null +++ b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/SchemaResolver.java @@ -0,0 +1,20 @@ +package org.apache.camel.component.jackson; + +import com.fasterxml.jackson.core.FormatSchema; +import org.apache.camel.Exchange; + +/** + * Interface for resolving schemas using pluggable strategies. + */ +@FunctionalInterface +public interface SchemaResolver { + + /** + * Resolves a schema for the given exchange. + * + * @param exchange the exchange for which the schema should be resolved + * @return the resolved format or null if no format is found + */ + FormatSchema resolve(Exchange exchange); + +} diff --git a/components/pom.xml b/components/pom.xml index 607c800..41bf284 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -181,6 +181,8 @@ <module>camel-irc</module> <module>camel-ironmq</module> <module>camel-jackson</module> + <module>camel-jackson-avro</module> + <module>camel-jackson-protobuf</module> <module>camel-jacksonxml</module> <module>camel-jasypt</module> <module>camel-jaxb</module> diff --git a/core/camel-allcomponents/pom.xml b/core/camel-allcomponents/pom.xml index e0b8b93..2179122 100644 --- a/core/camel-allcomponents/pom.xml +++ b/core/camel-allcomponents/pom.xml @@ -659,6 +659,14 @@ </dependency> <dependency> <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson-avro</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson-protobuf</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> <artifactId>camel-jackson</artifactId> </dependency> <dependency> diff --git a/parent/pom.xml b/parent/pom.xml index a965866..53abc1a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -1600,6 +1600,16 @@ </dependency> <dependency> <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson-avro</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson-protobuf</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> <artifactId>camel-jackson</artifactId> <version>${project.version}</version> </dependency>