1 package com.fasterxml.jackson.dataformat.xml.ser;
2
3 import java.io.IOException;
4
5 import javax.xml.namespace.QName;
6 import javax.xml.stream.XMLStreamException;
7
8 import com.fasterxml.jackson.core.*;
9 import com.fasterxml.jackson.databind.JavaType;
10 import com.fasterxml.jackson.databind.JsonMappingException;
11 import com.fasterxml.jackson.databind.JsonSerializer;
12 import com.fasterxml.jackson.databind.PropertyName;
13 import com.fasterxml.jackson.databind.SerializationConfig;
14 import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
15 import com.fasterxml.jackson.databind.ser.SerializerFactory;
16 import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
17 import com.fasterxml.jackson.databind.util.TokenBuffer;
18 import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
19 import com.fasterxml.jackson.dataformat.xml.util.TypeUtil;
20 import com.fasterxml.jackson.dataformat.xml.util.XmlRootNameLookup;
21
22 /**
23  * We need to override some parts of
24  * {@link com.fasterxml.jackson.databind.SerializerProvider}
25  * implementation to handle oddities of XML output, like "extra" root element.
26  */

27 public class XmlSerializerProvider extends DefaultSerializerProvider
28 {
29     // As of 2.7
30     private static final long serialVersionUID = 1L;
31
32     /**
33      * If all we get to serialize is a null, there's no way to figure out
34      * expected root name; so let's just default to literal {@code "null"}.
35      */

36     protected final static QName ROOT_NAME_FOR_NULL = new QName("null");
37
38     protected final XmlRootNameLookup _rootNameLookup;
39
40     public XmlSerializerProvider(XmlRootNameLookup rootNames)
41     {
42         super();
43         _rootNameLookup = rootNames;
44     }
45
46     public XmlSerializerProvider(XmlSerializerProvider src,
47             SerializationConfig config, SerializerFactory f)
48     {
49         super(src, config, f);
50         _rootNameLookup  = src._rootNameLookup;
51     }
52
53     /**
54      * @since 2.8.9
55      */

56     protected XmlSerializerProvider(XmlSerializerProvider src) {
57         super(src);
58         // 21-May-2018, tatu: As per [dataformat-xml#282], should NOT really copy
59         //    root name lookup as that may link back to diff version, configuration
60         _rootNameLookup = new XmlRootNameLookup();
61     }
62
63     /*
64     /**********************************************************************
65     /* Overridden methods
66     /**********************************************************************
67      */

68
69     @Override
70     public DefaultSerializerProvider copy() {
71         return new XmlSerializerProvider(this);
72     }
73
74     @Override
75     public DefaultSerializerProvider createInstance(SerializationConfig config,
76             SerializerFactory jsf) {
77         return new XmlSerializerProvider(this, config, jsf);
78     }
79
80     @SuppressWarnings("resource")
81     @Override
82     public void serializeValue(JsonGenerator gen, Object value) throws IOException
83     {
84         _generator = gen;
85         if (value == null) {
86             _serializeXmlNull(gen);
87             return;
88         }
89         final Class<?> cls = value.getClass();
90         final boolean asArray;
91         final ToXmlGenerator xgen = _asXmlGenerator(gen);
92         if (xgen == null) { // called by convertValue()
93             asArray = false;
94         } else {
95             QName rootName = _rootNameFromConfig();
96             if (rootName == null) {
97                 rootName = _rootNameLookup.findRootName(cls, _config);
98             }
99             _initWithRootName(xgen, rootName);
100             asArray = TypeUtil.isIndexedType(cls);
101             if (asArray) {
102                 _startRootArray(xgen, rootName);
103             }
104         }
105         
106         // From super-class implementation
107         final JsonSerializer<Object> ser = findTypedValueSerializer(cls, truenull);
108         try {
109             ser.serialize(value, gen, this);
110         } catch (Exception e) { // but wrap RuntimeExceptions, to get path information
111             throw _wrapAsIOE(gen, e);
112         }
113         // end of super-class implementation
114
115         if (asArray) {
116             gen.writeEndObject();
117         }
118     }
119
120     @Override // since 2.11.1, was missing before
121     public void serializeValue(JsonGenerator gen, Object value, JavaType rootType) throws IOException
122     {
123         serializeValue(gen, value, rootType, null);
124     }
125     
126     // @since 2.1
127     @SuppressWarnings("resource")
128     @Override
129     public void serializeValue(JsonGenerator gen, Object value, JavaType rootType,
130             JsonSerializer<Object> ser) throws IOException
131     {
132         _generator = gen;
133         if (value == null) {
134             _serializeXmlNull(gen);
135             return;
136         }
137         // Let's ensure types are compatible at this point
138         if ((rootType != null) && !rootType.getRawClass().isAssignableFrom(value.getClass())) {
139             _reportIncompatibleRootType(value, rootType);
140         }
141         final boolean asArray;
142         final ToXmlGenerator xgen = _asXmlGenerator(gen);
143         if (xgen == null) { // called by convertValue()
144             asArray = false;
145         } else {
146             QName rootName = _rootNameFromConfig();
147             if (rootName == null) {
148                 rootName = _rootNameLookup.findRootName(rootType, _config);
149             }
150             _initWithRootName(xgen, rootName);
151             asArray = TypeUtil.isIndexedType(rootType);
152             if (asArray) {
153                 _startRootArray(xgen, rootName);
154             }
155         }
156         if (ser == null) {
157             ser = findTypedValueSerializer(rootType, truenull);
158         }
159         // From super-class implementation
160         try {
161             ser.serialize(value, gen, this);
162         } catch (Exception e) { // but others do need to be, to get path etc
163             throw _wrapAsIOE(gen, e);
164         }
165         // end of super-class implementation
166         if (asArray) {
167             gen.writeEndObject();
168         }
169     }
170
171     @SuppressWarnings("resource")
172     @Override // since 2.11.1, was missing before
173     public void serializePolymorphic(JsonGenerator gen, Object value, JavaType rootType,
174             JsonSerializer<Object> valueSer, TypeSerializer typeSer)
175         throws IOException
176     {
177         _generator = gen;
178         if (value == null) {
179             _serializeXmlNull(gen);
180             return;
181         }
182         // Let's ensure types are compatible at this point
183         if ((rootType != null) && !rootType.getRawClass().isAssignableFrom(value.getClass())) {
184             _reportIncompatibleRootType(value, rootType);
185         }
186         final boolean asArray;
187         final ToXmlGenerator xgen = _asXmlGenerator(gen);
188         if (xgen == null) { // called by convertValue()
189             asArray = false;
190         } else {
191             QName rootName = _rootNameFromConfig();
192             if (rootName == null) {
193                 rootName = _rootNameLookup.findRootName(rootType, _config);
194             }
195             _initWithRootName(xgen, rootName);
196             asArray = TypeUtil.isIndexedType(rootType);
197             if (asArray) {
198                 _startRootArray(xgen, rootName);
199             }
200         }
201         // 21-May-2020: See comments in `jackson-databind/DefaultSerializerProvider`
202         if (valueSer == null) {
203             if ((rootType != null) && rootType.isContainerType()) {
204                 valueSer = findValueSerializer(rootType, null);
205             } else {
206                 valueSer = findValueSerializer(value.getClass(), null);
207             }
208         }
209         // From super-class implementation
210         try {
211             valueSer.serializeWithType(value, gen, this, typeSer);
212         } catch (Exception e) { // but others do need to be, to get path etc
213             throw _wrapAsIOE(gen, e);
214         }
215         // end of super-class implementation
216         if (asArray) {
217             gen.writeEndObject();
218         }
219     }
220     
221     protected void _serializeXmlNull(JsonGenerator jgen) throws IOException
222     {
223         // 14-Nov-2016, tatu: As per [dataformat-xml#213], we may have explicitly
224         //    configured root name...
225         QName rootName = _rootNameFromConfig();
226         if (rootName == null) {
227             rootName = ROOT_NAME_FOR_NULL;
228         }
229         if (jgen instanceof ToXmlGenerator) {
230             _initWithRootName((ToXmlGenerator) jgen, rootName);
231         }
232         super.serializeValue(jgen, null);
233     }
234     
235     protected void _startRootArray(ToXmlGenerator xgen, QName rootName) throws IOException
236     {
237         xgen.writeStartObject();
238         // Could repeat root name, but what's the point? How to customize?
239         xgen.writeFieldName("item");
240     }    
241
242     protected void _initWithRootName(ToXmlGenerator xgen, QName rootName) throws IOException
243     {
244         /* 28-Nov-2012, tatu: We should only initialize the root
245          *  name if no name has been set, as per [dataformat-xml#42],
246          *  to allow for custom serializers to work.
247          */

248         if (!xgen.setNextNameIfMissing(rootName)) {
249             // however, if we are root, we... insist
250             if (xgen.inRoot()) {
251                 xgen.setNextName(rootName);
252             }
253         }
254         xgen.initGenerator();
255         String ns = rootName.getNamespaceURI();
256         /* [dataformat-xml#26] If we just try writing root element with namespace,
257          * we will get an explicit prefix. But we'd rather use the default
258          * namespace, so let's try to force that.
259          */

260         if (ns != null && ns.length() > 0) {
261             try {
262                 xgen.getStaxWriter().setDefaultNamespace(ns);
263             } catch (XMLStreamException e) {
264                 StaxUtil.throwAsGenerationException(e, xgen);
265             }
266         }
267     }
268
269     protected QName _rootNameFromConfig()
270     {
271         PropertyName name = _config.getFullRootName();
272         if (name == null) {
273             return null;
274         }
275         String ns = name.getNamespace();
276         if (ns == null || ns.isEmpty()) {
277             return new QName(name.getSimpleName());
278         }
279         return new QName(ns, name.getSimpleName());
280     }
281
282     protected ToXmlGenerator _asXmlGenerator(JsonGenerator gen)
283         throws JsonMappingException
284     {
285         // [Issue#71]: When converting, we actually get TokenBuffer, which is fine
286         if (!(gen instanceof ToXmlGenerator)) {
287             // but verify
288             if (!(gen instanceof TokenBuffer)) {
289                 throw JsonMappingException.from(gen,
290                         "XmlMapper does not with generators of type other than ToXmlGenerator; got: "+gen.getClass().getName());
291             }
292             return null;
293         }
294         return (ToXmlGenerator) gen;
295     }    
296
297     protected IOException _wrapAsIOE(JsonGenerator g, Exception e) {
298         if (e instanceof IOException) {
299             return (IOException) e;
300         }
301         String msg = e.getMessage();
302         if (msg == null) {
303             msg = "[no message for "+e.getClass().getName()+"]";
304         }
305         return new JsonMappingException(g, msg, e);
306     }
307 }
308