From 7c0a3678c6444cbdab5a9bbc0d3b46e986a8465e Mon Sep 17 00:00:00 2001 From: perilbrain <> Date: Sat, 17 Aug 2019 22:36:38 +0530 Subject: [PATCH 1/2] Fix for #336, multiple class adapted for the feature. - Accepts new annotation for collection type. --- .gitignore | 1 + .../xml/JacksonXmlAnnotationIntrospector.java | 13 +++++- .../xml/XmlAnnotationIntrospector.java | 18 ++++++++ .../xml/annotation/JacksonXmlRootElement.java | 3 ++ .../jaxb/XmlJaxbAnnotationIntrospector.java | 6 +++ .../xml/ser/XmlSerializerProvider.java | 38 +++++++++++----- .../xml/util/XmlRootNameLookup.java | 39 ++++++++++++++++ .../xml/ser/TestSerializationCollection.java | 45 +++++++++++++++++++ 8 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestSerializationCollection.java diff --git a/.gitignore b/.gitignore index 84914ecb6..c3bde8034 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ target *.iml *.ipr *.iws +.idea diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/JacksonXmlAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/JacksonXmlAnnotationIntrospector.java index 7831671ec..1f682c94d 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/JacksonXmlAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/JacksonXmlAnnotationIntrospector.java @@ -1,7 +1,9 @@ package com.fasterxml.jackson.dataformat.xml; import com.fasterxml.jackson.databind.PropertyName; -import com.fasterxml.jackson.databind.introspect.*; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.dataformat.xml.annotation.*; /** @@ -80,6 +82,15 @@ public PropertyName findRootName(AnnotatedClass ac) } return super.findRootName(ac); } + + @Override + public String getWrapperForIndexedType(AnnotatedClass ac){ + JacksonXmlRootElement root = ac.getAnnotation(JacksonXmlRootElement.class); + if (root != null) { + return root.wrapperForIndexedType(); + } + return JacksonXmlRootElement.DEFAULT_WRAPPER_NAME; + } /* /********************************************************************** diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlAnnotationIntrospector.java index 8db508288..275b79d60 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlAnnotationIntrospector.java @@ -2,7 +2,9 @@ import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; /** @@ -43,6 +45,8 @@ public interface XmlAnnotationIntrospector public Boolean isOutputAsCData(Annotated ann); public void setDefaultUseWrapper(boolean b); + + public String getWrapperForIndexedType(AnnotatedClass ac); /* /********************************************************************** @@ -135,6 +139,15 @@ public void setDefaultUseWrapper(boolean b) { _xmlSecondary.setDefaultUseWrapper(b); } } + + @Override + public String getWrapperForIndexedType(AnnotatedClass ac) { + String value = (_xmlPrimary == null) ? null : _xmlPrimary.getWrapperForIndexedType(ac); + if ((value == null) && (_xmlSecondary != null)) { + value = _xmlSecondary.getWrapperForIndexedType(ac); + } + return value; + } } /* @@ -183,5 +196,10 @@ public Boolean isOutputAsCData(Annotated ann) { public void setDefaultUseWrapper(boolean b) { // not used with JAXB } + + @Override + public String getWrapperForIndexedType(AnnotatedClass ac) { + return JacksonXmlRootElement.DEFAULT_WRAPPER_NAME; + } } } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/annotation/JacksonXmlRootElement.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/annotation/JacksonXmlRootElement.java index 9d2e94d70..b207801da 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/annotation/JacksonXmlRootElement.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/annotation/JacksonXmlRootElement.java @@ -14,6 +14,9 @@ @Retention(RetentionPolicy.RUNTIME) public @interface JacksonXmlRootElement { + public static String DEFAULT_WRAPPER_NAME="item"; + String namespace() default ""; String localName() default ""; + String wrapperForIndexedType() default DEFAULT_WRAPPER_NAME; } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/jaxb/XmlJaxbAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/jaxb/XmlJaxbAnnotationIntrospector.java index 3569bef22..94539ec4a 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/jaxb/XmlJaxbAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/jaxb/XmlJaxbAnnotationIntrospector.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.dataformat.xml.XmlAnnotationIntrospector; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; /** @@ -57,4 +58,9 @@ public Boolean isOutputAsCData(Annotated ann) { public void setDefaultUseWrapper(boolean b) { // nothing to do with JAXB } + + @Override + public String getWrapperForIndexedType(AnnotatedClass ac) { + return JacksonXmlRootElement.DEFAULT_WRAPPER_NAME; + } } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java index 555442784..a0ec2b40a 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java @@ -1,22 +1,23 @@ package com.fasterxml.jackson.dataformat.xml.ser; -import java.io.IOException; - -import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; - -import com.fasterxml.jackson.core.*; - +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.TokenStreamFactory; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.GeneratorSettings; -import com.fasterxml.jackson.databind.ser.SerializerFactory; import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; import com.fasterxml.jackson.databind.ser.SerializerCache; +import com.fasterxml.jackson.databind.ser.SerializerFactory; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.fasterxml.jackson.dataformat.xml.util.StaxUtil; import com.fasterxml.jackson.dataformat.xml.util.TypeUtil; import com.fasterxml.jackson.dataformat.xml.util.XmlRootNameLookup; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; + /** * We need to override some parts of * {@link com.fasterxml.jackson.databind.SerializerProvider} @@ -68,7 +69,8 @@ public void serializeValue(JsonGenerator gen, Object value) throws IOException _initWithRootName(xgen, rootName); asArray = TypeUtil.isIndexedType(cls); if (asArray) { - _startRootArray(xgen, rootName); + String indexedRootName = _rootNameLookup.findWrapperForIndexedType(getTypeOfCollection(value), _config); + _startRootArray(xgen, indexedRootName); } } @@ -107,7 +109,8 @@ public void serializeValue(JsonGenerator gen, Object value, JavaType rootType, _initWithRootName(xgen, rootName); asArray = TypeUtil.isIndexedType(rootType); if (asArray) { - _startRootArray(xgen, rootName); + String indexedRootName = _rootNameLookup.findWrapperForIndexedType(getTypeOfCollection(rootType), _config); + _startRootArray(xgen, indexedRootName); } } if (ser == null) { @@ -125,6 +128,17 @@ public void serializeValue(JsonGenerator gen, Object value, JavaType rootType, } } + protected Class getTypeOfCollection(Object value){ + Class eleClass = value.getClass(); + if(Collection.class.isAssignableFrom(eleClass)) { + Collection collection = (Collection) value; + Iterator iterator = collection.iterator(); + if (iterator.hasNext()) + eleClass = iterator.next().getClass(); + } + return eleClass; + } + protected void _serializeXmlNull(JsonGenerator jgen) throws IOException { // 14-Nov-2016, tatu: As per [dataformat-xml#213], we may have explicitly @@ -139,11 +153,11 @@ protected void _serializeXmlNull(JsonGenerator jgen) throws IOException super.serializeValue(jgen, null); } - protected void _startRootArray(ToXmlGenerator xgen, QName rootName) throws IOException + protected void _startRootArray(ToXmlGenerator xgen, String rootName) throws IOException { xgen.writeStartObject(); // Could repeat root name, but what's the point? How to customize? - xgen.writeFieldName("item"); + xgen.writeFieldName(rootName); } protected void _initWithRootName(ToXmlGenerator xgen, QName rootName) throws IOException diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java index 8cb4d1699..3bd862d3d 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.type.ClassKey; import com.fasterxml.jackson.databind.util.SimpleLookupCache; import com.fasterxml.jackson.dataformat.xml.XmlAnnotationIntrospector; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; /** * Helper class used for efficiently finding root element name used with @@ -26,6 +27,7 @@ public class XmlRootNameLookup * state */ protected final transient SimpleLookupCache _rootNames = new SimpleLookupCache<>(40, 200); + protected final transient SimpleLookupCache _indexedWrapperNames = new SimpleLookupCache<>(10, 200); public XmlRootNameLookup() { } @@ -57,6 +59,27 @@ public QName findRootName(Class rootType, MapperConfig config) } return name; } + + public String findWrapperForIndexedType(JavaType rootType, MapperConfig config) { + return findWrapperForIndexedType(rootType.getRawClass(), config); + } + + public String findWrapperForIndexedType(Class rootType, MapperConfig config) + { + ClassKey key = new ClassKey(rootType); + String wrapperName; + synchronized (_indexedWrapperNames) { + wrapperName = _indexedWrapperNames.get(key); + } + if (wrapperName != null) { + return wrapperName; + } + wrapperName = findWrapperName(rootType, config); + synchronized (_indexedWrapperNames) { + _indexedWrapperNames.put(key, wrapperName); + } + return wrapperName; + } // NOTE: needed to be synchronized in 2.6.4, but 2.7.0 adds a proper fix // for annotation introspection hence not needed any more @@ -102,4 +125,20 @@ private String findNamespace(AnnotationIntrospector ai, AnnotatedClass ann) } return null; } + + private String findWrapperName(Class rootType, MapperConfig config) + { + BeanDescription beanDesc = config.introspectClassAnnotations(rootType); + AnnotationIntrospector annotationIntrospector = config.getAnnotationIntrospector(); + AnnotatedClass annotatedClass = beanDesc.getClassInfo(); + for (AnnotationIntrospector intr : annotationIntrospector.allIntrospectors()) { + if (intr instanceof XmlAnnotationIntrospector) { + String ns = ((XmlAnnotationIntrospector) intr).getWrapperForIndexedType(annotatedClass); + if (ns != null) { + return ns; + } + } + } + return JacksonXmlRootElement.DEFAULT_WRAPPER_NAME; + } } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestSerializationCollection.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestSerializationCollection.java new file mode 100644 index 000000000..0fa10cc6e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestSerializationCollection.java @@ -0,0 +1,45 @@ +package com.fasterxml.jackson.dataformat.xml.ser; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.XmlTestBase; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class TestSerializationCollection extends XmlTestBase { + + + @JacksonXmlRootElement(localName="person", namespace="http://example.org/person", wrapperForIndexedType = "Person") + static class Person { + + @JacksonXmlProperty(isAttribute = true) + public Integer id; + + public String n; + + public Person(Integer id, String name) { + this.id = id; + this.n = name; + } + } + + @JacksonXmlRootElement(localName = "persons") + static class PersonList extends ArrayList{} + + public void testList() throws Exception + { + List personNames = Arrays.asList("A", "B", "C"); + PersonList personList = IntStream.range(0, personNames.size()) + .mapToObj(count -> new Person(count, personNames.get(count))) + .collect(Collectors.toCollection(PersonList::new)); + XmlMapper xmlMapper = new XmlMapper(); + String xml = xmlMapper.writeValueAsString(personList); + assertEquals("ABC", + xml); + } +} From e291d1b6c6ef42a7bd8fe2c1ae422d2b570a48bd Mon Sep 17 00:00:00 2001 From: perilbrain <14003663+perilbrain@users.noreply.github.com> Date: Sat, 24 Aug 2019 15:36:37 +0530 Subject: [PATCH 2/2] Value is the right candidate for type resolution This functional usually called from the factories of various frameworks like spring needs to resolve the the type of `value` directly rather than trying to figure parameter of some generic. --- .../jackson/dataformat/xml/ser/XmlSerializerProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java index a0ec2b40a..cecf6845f 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/XmlSerializerProvider.java @@ -109,7 +109,7 @@ public void serializeValue(JsonGenerator gen, Object value, JavaType rootType, _initWithRootName(xgen, rootName); asArray = TypeUtil.isIndexedType(rootType); if (asArray) { - String indexedRootName = _rootNameLookup.findWrapperForIndexedType(getTypeOfCollection(rootType), _config); + String indexedRootName = _rootNameLookup.findWrapperForIndexedType(getTypeOfCollection(value), _config); _startRootArray(xgen, indexedRootName); } }