This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 82f8b67 CAMEL-14679: Support DROP_ROOT_MODE in XStream JSON dataformat 82f8b67 is described below commit 82f8b67f23b34664995edd21e95ced86ffd58548 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Mar 19 12:59:22 2020 +0100 CAMEL-14679: Support DROP_ROOT_MODE in XStream JSON dataformat --- .../xstream/JsonDataFormatConfigurer.java | 2 + .../camel/dataformat/xstream/json-xstream.json | 1 + .../src/main/docs/json-xstream-dataformat.adoc | 3 +- .../dataformat/xstream/AbstractXStreamWrapper.java | 4 +- .../camel/dataformat/xstream/JsonDataFormat.java | 46 +++++++++---- .../MarshalDomainObjectJSONDropRootNodeTest.java | 79 ++++++++++++++++++++++ .../org/apache/camel/model/dataformat/json.json | 1 + .../camel/model/dataformat/JsonDataFormat.java | 25 +++++++ .../reifier/dataformat/JsonDataFormatReifier.java | 12 +++- .../java/org/apache/camel/xml/in/ModelParser.java | 1 + .../dataformats/pages/json-xstream-dataformat.adoc | 3 +- 11 files changed, 156 insertions(+), 21 deletions(-) diff --git a/components/camel-xstream/src/generated/java/org/apache/camel/dataformat/xstream/JsonDataFormatConfigurer.java b/components/camel-xstream/src/generated/java/org/apache/camel/dataformat/xstream/JsonDataFormatConfigurer.java index b5f8358..a29ed97 100644 --- a/components/camel-xstream/src/generated/java/org/apache/camel/dataformat/xstream/JsonDataFormatConfigurer.java +++ b/components/camel-xstream/src/generated/java/org/apache/camel/dataformat/xstream/JsonDataFormatConfigurer.java @@ -20,6 +20,8 @@ public class JsonDataFormatConfigurer extends PropertyConfigurerSupport implemen switch (ignoreCase ? name.toLowerCase() : name) { case "prettyprint": case "prettyPrint": dataformat.setPrettyPrint(property(camelContext, boolean.class, value)); return true; + case "droprootnode": + case "dropRootNode": dataformat.setDropRootNode(property(camelContext, boolean.class, value)); return true; case "permissions": dataformat.setPermissions(property(camelContext, java.lang.String.class, value)); return true; default: return false; } diff --git a/components/camel-xstream/src/generated/resources/org/apache/camel/dataformat/xstream/json-xstream.json b/components/camel-xstream/src/generated/resources/org/apache/camel/dataformat/xstream/json-xstream.json index 41dbaad..3b197e8 100644 --- a/components/camel-xstream/src/generated/resources/org/apache/camel/dataformat/xstream/json-xstream.json +++ b/components/camel-xstream/src/generated/resources/org/apache/camel/dataformat/xstream/json-xstream.json @@ -35,6 +35,7 @@ "allowUnmarshallType": { "kind": "attribute", "displayName": "Allow Unmarshall Type", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "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." }, "timezone": { "kind": "attribute", "displayName": "Timezone", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "If set then Jackson will use the Timezone when marshalling\/unmarshalling. This option will have no effect on the others Json DataFormat, like gson, fastjson and xstream." }, "autoDiscoverObjectMapper": { "kind": "attribute", "displayName": "Auto Discover Object Mapper", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "If set to true then Jackson will lookup for an objectMapper into the registry" }, + "dropRootNode": { "kind": "attribute", "displayName": "Drop Root Node", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether XStream will drop the root node in the generated JSon. You may want to enable this when using POJOs; as then the written object will include the class name as root node, which is often not intended to be written in the JSon output." }, "contentTypeHeader": { "kind": "attribute", "displayName": "Content Type Header", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the data format should set the Content-Type header with the type from the data format if the data format is capable of doing so. For example application\/xml for data formats marshalling to XML, or application\/json for data formats marshalling to JS [...] "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "The id of this node" } } diff --git a/components/camel-xstream/src/main/docs/json-xstream-dataformat.adoc b/components/camel-xstream/src/main/docs/json-xstream-dataformat.adoc index 6447c2c..916d414 100644 --- a/components/camel-xstream/src/main/docs/json-xstream-dataformat.adoc +++ b/components/camel-xstream/src/main/docs/json-xstream-dataformat.adoc @@ -28,7 +28,7 @@ Maven users will need to add the following dependency to their == Options // dataformat options: START -The JSon XStream dataformat supports 20 options, which are listed below. +The JSon XStream dataformat supports 21 options, which are listed below. @@ -54,6 +54,7 @@ The JSon XStream dataformat supports 20 options, which are listed below. | 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. | timezone | | String | If set then Jackson will use the Timezone when marshalling/unmarshalling. This option will have no effect on the others Json DataFormat, like gson, fastjson and xstream. | autoDiscoverObjectMapper | false | Boolean | If set to true then Jackson will lookup for an objectMapper into the registry +| dropRootNode | false | Boolean | Whether XStream will drop the root node in the generated JSon. You may want to enable this when using POJOs; as then the written object will include the class name as root node, which is often not intended to be written in the JSon output. | contentTypeHeader | false | Boolean | Whether the data format should set the Content-Type header with the type from the data format if the data format is capable of doing so. For example application/xml for data formats marshalling to XML, or application/json for data formats marshalling to JSon etc. |=== // dataformat options: END diff --git a/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/AbstractXStreamWrapper.java b/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/AbstractXStreamWrapper.java index ec2d4ec..7bf9b43 100644 --- a/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/AbstractXStreamWrapper.java +++ b/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/AbstractXStreamWrapper.java @@ -16,16 +16,14 @@ */ package org.apache.camel.dataformat.xstream; +import javax.xml.stream.XMLStreamException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.xml.stream.XMLStreamException; - import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.core.util.CompositeClassLoader; diff --git a/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/JsonDataFormat.java b/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/JsonDataFormat.java index bb5bebe..2697f5a 100644 --- a/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/JsonDataFormat.java +++ b/components/camel-xstream/src/main/java/org/apache/camel/dataformat/xstream/JsonDataFormat.java @@ -16,18 +16,16 @@ */ package org.apache.camel.dataformat.xstream; +import javax.xml.stream.XMLStreamException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.util.HashMap; -import java.util.Map; - -import javax.xml.stream.XMLStreamException; +import java.nio.charset.StandardCharsets; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.io.json.AbstractJsonWriter; import com.thoughtworks.xstream.io.json.JsonWriter; import com.thoughtworks.xstream.io.xml.QNameMap; import com.thoughtworks.xstream.io.xml.StaxReader; @@ -35,6 +33,7 @@ import com.thoughtworks.xstream.io.xml.StaxWriter; import org.apache.camel.Exchange; import org.apache.camel.spi.ClassResolver; import org.apache.camel.spi.annotations.Dataformat; +import org.codehaus.jettison.mapped.Configuration; import org.codehaus.jettison.mapped.MappedXMLInputFactory; import org.codehaus.jettison.mapped.MappedXMLOutputFactory; @@ -44,14 +43,12 @@ import org.codehaus.jettison.mapped.MappedXMLOutputFactory; */ @Dataformat("json-xstream") public class JsonDataFormat extends AbstractXStreamWrapper { - private final MappedXMLOutputFactory mof; - private final MappedXMLInputFactory mif; + private MappedXMLOutputFactory mof; + private MappedXMLInputFactory mif; private boolean prettyPrint; + private boolean dropRootNode; public JsonDataFormat() { - final Map<?, ?> nstjsons = new HashMap<>(); - mof = new MappedXMLOutputFactory(nstjsons); - mif = new MappedXMLInputFactory(nstjsons); } @Override @@ -67,6 +64,14 @@ public class JsonDataFormat extends AbstractXStreamWrapper { this.prettyPrint = prettyPrint; } + public boolean isDropRootNode() { + return dropRootNode; + } + + public void setDropRootNode(boolean dropRootNode) { + this.dropRootNode = dropRootNode; + } + @Override public void marshal(Exchange exchange, Object body, OutputStream stream) throws Exception { super.marshal(exchange, body, stream); @@ -94,11 +99,11 @@ public class JsonDataFormat extends AbstractXStreamWrapper { @Override protected HierarchicalStreamWriter createHierarchicalStreamWriter(Exchange exchange, Object body, OutputStream stream) throws XMLStreamException { if (isPrettyPrint()) { - try { - // the json spec. expects UTF-8 as the default encoding - return new JsonWriter(new OutputStreamWriter(stream, "UTF-8")); - } catch (UnsupportedEncodingException uee) { - throw new XMLStreamException(uee); + // the json spec. expects UTF-8 as the default encoding + if (isDropRootNode()) { + return new JsonWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8), AbstractJsonWriter.DROP_ROOT_MODE); + } else { + return new JsonWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8)); } } @@ -109,4 +114,15 @@ public class JsonDataFormat extends AbstractXStreamWrapper { protected HierarchicalStreamReader createHierarchicalStreamReader(Exchange exchange, InputStream stream) throws XMLStreamException { return new StaxReader(new QNameMap(), mif.createXMLStreamReader(stream)); } + + @Override + protected void doStart() throws Exception { + super.doStart(); + + Configuration config = new Configuration(); + config.setDropRootElement(dropRootNode); + + mof = new MappedXMLOutputFactory(config); + mif = new MappedXMLInputFactory(config); + } } diff --git a/components/camel-xstream/src/test/java/org/apache/camel/dataformat/xstream/MarshalDomainObjectJSONDropRootNodeTest.java b/components/camel-xstream/src/test/java/org/apache/camel/dataformat/xstream/MarshalDomainObjectJSONDropRootNodeTest.java new file mode 100644 index 0000000..a64b95e --- /dev/null +++ b/components/camel-xstream/src/test/java/org/apache/camel/dataformat/xstream/MarshalDomainObjectJSONDropRootNodeTest.java @@ -0,0 +1,79 @@ +/* + * 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.dataformat.xstream; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.model.dataformat.JsonDataFormat; +import org.apache.camel.model.dataformat.JsonLibrary; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MarshalDomainObjectJSONDropRootNodeTest extends CamelTestSupport { + + @Test + public void testMarshalAndUnmarshalWithPrettyPrint() throws Exception { + PurchaseOrder order = new PurchaseOrder(); + order.setName("pretty printed Camel"); + order.setAmount(1); + order.setPrice(7.91); + + MockEndpoint mock = getMockEndpoint("mock:reverse"); + mock.expectedMessageCount(1); + mock.message(0).body().isInstanceOf(PurchaseOrder.class); + mock.message(0).body().isEqualTo(order); + + Object marshalled = template.requestBody("direct:inPretty", order); + String marshalledAsString = context.getTypeConverter().convertTo(String.class, marshalled); + // the line-separator used by JsonWriter is "\n", even on windows + String expected = "{\n" + + " \"name\": \"pretty printed Camel\",\n" + + " \"price\": 7.91,\n" + + " \"amount\": 1.0\n" + + "}"; + assertEquals(expected, marshalledAsString); + + // must include class type when reversing + String back = "{\"org.apache.camel.dataformat.xstream.PurchaseOrder\": {\n" + + " \"name\": \"pretty printed Camel\",\n" + + " \"price\": 7.91,\n" + + " \"amount\": 1.0\n" + + "}}"; + + template.sendBody("direct:backPretty", back); + + mock.assertIsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("direct:reverse").unmarshal().json(JsonLibrary.XStream, PurchaseOrder.class).to("mock:reverse"); + + JsonDataFormat df = new JsonDataFormat().library(JsonLibrary.XStream).dropRootNode(true).prettyPrint(true); + from("direct:inPretty").marshal(df); + + from("direct:backPretty").unmarshal().json(JsonLibrary.XStream, PurchaseOrder.class, true).to("mock:reverse"); + } + }; + } + +} diff --git a/core/camel-core-engine/src/generated/resources/org/apache/camel/model/dataformat/json.json b/core/camel-core-engine/src/generated/resources/org/apache/camel/model/dataformat/json.json index 3a54d05..5b4e283 100644 --- a/core/camel-core-engine/src/generated/resources/org/apache/camel/model/dataformat/json.json +++ b/core/camel-core-engine/src/generated/resources/org/apache/camel/model/dataformat/json.json @@ -30,6 +30,7 @@ "allowUnmarshallType": { "kind": "attribute", "displayName": "Allow Unmarshall Type", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "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." }, "timezone": { "kind": "attribute", "displayName": "Timezone", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "If set then Jackson will use the Timezone when marshalling\/unmarshalling. This option will have no effect on the others Json DataFormat, like gson, fastjson and xstream." }, "autoDiscoverObjectMapper": { "kind": "attribute", "displayName": "Auto Discover Object Mapper", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "If set to true then Jackson will lookup for an objectMapper into the registry" }, + "dropRootNode": { "kind": "attribute", "displayName": "Drop Root Node", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether XStream will drop the root node in the generated JSon. You may want to enable this when using POJOs; as then the written object will include the class name as root node, which is often not intended to be written in the JSon output." }, "contentTypeHeader": { "kind": "attribute", "displayName": "Content Type Header", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the data format should set the Content-Type header with the type from the data format if the data format is capable of doing so. For example application\/xml for data formats marshalling to XML, or application\/json for data formats marshalling to JS [...] "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "The id of this node" } } diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/model/dataformat/JsonDataFormat.java b/core/camel-core-engine/src/main/java/org/apache/camel/model/dataformat/JsonDataFormat.java index b99ce64..fbda282 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/model/dataformat/JsonDataFormat.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/model/dataformat/JsonDataFormat.java @@ -84,6 +84,9 @@ public class JsonDataFormat extends DataFormatDefinition { @XmlAttribute @Metadata(javaType = "java.lang.Boolean", defaultValue = "false") private String autoDiscoverObjectMapper; + @XmlAttribute + @Metadata(javaType = "java.lang.Boolean", defaultValue = "false") + private String dropRootNode; public JsonDataFormat() { super("json"); @@ -386,6 +389,19 @@ public class JsonDataFormat extends DataFormatDefinition { this.autoDiscoverObjectMapper = autoDiscoverObjectMapper; } + public String getDropRootNode() { + return dropRootNode; + } + + /** + * Whether XStream will drop the root node in the generated JSon. + * You may want to enable this when using POJOs; as then the written object will include the class name + * as root node, which is often not intended to be written in the JSon output. + */ + public void setDropRootNode(String dropRootNode) { + this.dropRootNode = dropRootNode; + } + @Override public String getDataFormatName() { // json data format is special as the name can be from different bundles @@ -529,4 +545,13 @@ public class JsonDataFormat extends DataFormatDefinition { return this; } + public JsonDataFormat dropRootNode(boolean dropRootNode) { + return dropRootNode(Boolean.toString(dropRootNode)); + } + + public JsonDataFormat dropRootNode(String dropRootNode) { + this.dropRootNode = dropRootNode; + return this; + } + } diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java b/core/camel-core-engine/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java index b56c302..a934b03 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/reifier/dataformat/JsonDataFormatReifier.java @@ -32,7 +32,14 @@ public class JsonDataFormatReifier extends DataFormatReifier<JsonDataFormat> { @Override protected void prepareDataFormatConfig(Map<String, Object> properties) { properties.put("objectMapper", asRef(definition.getObjectMapper())); - properties.put("useDefaultObjectMapper", definition.getUseDefaultObjectMapper()); + if (definition.getLibrary() != JsonLibrary.XStream) { + if (definition.getUseDefaultObjectMapper() == null) { + // default true + properties.put("useDefaultObjectMapper", "true"); + } else { + properties.put("useDefaultObjectMapper", definition.getUseDefaultObjectMapper()); + } + } properties.put("unmarshalType", or(definition.getUnmarshalType(), definition.getUnmarshalTypeName())); properties.put("prettyPrint", definition.getPrettyPrint()); properties.put("jsonView", definition.getJsonView()); @@ -46,6 +53,9 @@ public class JsonDataFormatReifier extends DataFormatReifier<JsonDataFormat> { properties.put("enableFeatures", definition.getEnableFeatures()); properties.put("disableFeatures", definition.getDisableFeatures()); properties.put("allowUnmarshallType", definition.getAllowUnmarshallType()); + if (definition.getLibrary() == JsonLibrary.XStream) { + properties.put("dropRootNode", definition.getDropRootNode()); + } if (definition.getLibrary() == JsonLibrary.XStream && definition.getPermissions() == null) { // if we have the unmarshal type, but no permission set, then use it to be allowed String type = definition.getUnmarshalTypeName(); diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java index 0cf7eaa..33f3b43 100644 --- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java +++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java @@ -1941,6 +1941,7 @@ public class ModelParser extends BaseParser { case "autoDiscoverObjectMapper": def.setAutoDiscoverObjectMapper(val); break; case "collectionTypeName": def.setCollectionTypeName(val); break; case "disableFeatures": def.setDisableFeatures(val); break; + case "dropRootNode": def.setDropRootNode(val); break; case "enableFeatures": def.setEnableFeatures(val); break; case "enableJaxbAnnotationModule": def.setEnableJaxbAnnotationModule(val); break; case "include": def.setInclude(val); break; diff --git a/docs/components/modules/dataformats/pages/json-xstream-dataformat.adoc b/docs/components/modules/dataformats/pages/json-xstream-dataformat.adoc index 7068854..a90a7c3 100644 --- a/docs/components/modules/dataformats/pages/json-xstream-dataformat.adoc +++ b/docs/components/modules/dataformats/pages/json-xstream-dataformat.adoc @@ -29,7 +29,7 @@ Maven users will need to add the following dependency to their == Options // dataformat options: START -The JSon XStream dataformat supports 20 options, which are listed below. +The JSon XStream dataformat supports 21 options, which are listed below. @@ -55,6 +55,7 @@ The JSon XStream dataformat supports 20 options, which are listed below. | 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. | timezone | | String | If set then Jackson will use the Timezone when marshalling/unmarshalling. This option will have no effect on the others Json DataFormat, like gson, fastjson and xstream. | autoDiscoverObjectMapper | false | Boolean | If set to true then Jackson will lookup for an objectMapper into the registry +| dropRootNode | false | Boolean | Whether XStream will drop the root node in the generated JSon. You may want to enable this when using POJOs; as then the written object will include the class name as root node, which is often not intended to be written in the JSon output. | contentTypeHeader | false | Boolean | Whether the data format should set the Content-Type header with the type from the data format if the data format is capable of doing so. For example application/xml for data formats marshalling to XML, or application/json for data formats marshalling to JSon etc. |=== // dataformat options: END