-
-
Notifications
You must be signed in to change notification settings - Fork 220
/
XmlSerializerProvider.java
244 lines (222 loc) · 8.23 KB
/
XmlSerializerProvider.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
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;
/**
* We need to override some parts of
* {@link com.fasterxml.jackson.databind.SerializerProvider}
* implementation to handle oddities of XML output, like "extra" root element.
*/
public class XmlSerializerProvider extends DefaultSerializerProvider
{
// As of 2.7
private static final long serialVersionUID = 1L;
/**
* If all we get to serialize is a null, there's no way to figure out
* expected root name; so let's just default to literal {@code "null"}.
*/
protected final static QName ROOT_NAME_FOR_NULL = new QName("null");
protected final XmlRootNameLookup _rootNameLookup;
public XmlSerializerProvider(XmlRootNameLookup rootNames)
{
super();
_rootNameLookup = rootNames;
}
public XmlSerializerProvider(XmlSerializerProvider src,
SerializationConfig config, SerializerFactory f)
{
super(src, config, f);
_rootNameLookup = src._rootNameLookup;
}
/**
* @since 2.8.9
*/
protected XmlSerializerProvider(XmlSerializerProvider src) {
super(src);
// 21-May-2018, tatu: As per [dataformat-xml#282], should NOT really copy
// root name lookup as that may link back to diff version, configuration
_rootNameLookup = new XmlRootNameLookup();
}
/*
/**********************************************************************
/* Overridden methods
/**********************************************************************
*/
@Override
public DefaultSerializerProvider copy() {
return new XmlSerializerProvider(this);
}
@Override
public DefaultSerializerProvider createInstance(SerializationConfig config,
SerializerFactory jsf) {
return new XmlSerializerProvider(this, config, jsf);
}
@SuppressWarnings("resource")
@Override
public void serializeValue(JsonGenerator gen, Object value) throws IOException
{
if (value == null) {
_serializeXmlNull(gen);
return;
}
final Class<?> cls = value.getClass();
final boolean asArray;
final ToXmlGenerator xgen = _asXmlGenerator(gen);
if (xgen == null) { // called by convertValue()
asArray = false;
} else {
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(cls, _config);
}
_initWithRootName(xgen, rootName);
asArray = TypeUtil.isIndexedType(cls);
if (asArray) {
_startRootArray(xgen, rootName);
}
}
// From super-class implementation
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
try {
ser.serialize(value, gen, this);
} catch (Exception e) { // but wrap RuntimeExceptions, to get path information
throw _wrapAsIOE(gen, e);
}
// end of super-class implementation
if (asArray) {
gen.writeEndObject();
}
}
// @since 2.1
@SuppressWarnings("resource")
@Override
public void serializeValue(JsonGenerator gen, Object value, JavaType rootType,
JsonSerializer<Object> ser) throws IOException
{
if (value == null) {
_serializeXmlNull(gen);
return;
}
final boolean asArray;
final ToXmlGenerator xgen = _asXmlGenerator(gen);
if (xgen == null) { // called by convertValue()
asArray = false;
} else {
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = _rootNameLookup.findRootName(rootType, _config);
}
_initWithRootName(xgen, rootName);
asArray = TypeUtil.isIndexedType(rootType);
if (asArray) {
_startRootArray(xgen, rootName);
}
}
if (ser == null) {
ser = findTypedValueSerializer(rootType, true, null);
}
// From super-class implementation
try {
ser.serialize(value, gen, this);
} catch (Exception e) { // but others do need to be, to get path etc
throw _wrapAsIOE(gen, e);
}
// end of super-class implementation
if (asArray) {
gen.writeEndObject();
}
}
protected void _serializeXmlNull(JsonGenerator jgen) throws IOException
{
// 14-Nov-2016, tatu: As per [dataformat-xml#213], we may have explicitly
// configured root name...
QName rootName = _rootNameFromConfig();
if (rootName == null) {
rootName = ROOT_NAME_FOR_NULL;
}
if (jgen instanceof ToXmlGenerator) {
_initWithRootName((ToXmlGenerator) jgen, rootName);
}
super.serializeValue(jgen, null);
}
protected void _startRootArray(ToXmlGenerator xgen, QName rootName) throws IOException
{
xgen.writeStartObject();
// Could repeat root name, but what's the point? How to customize?
xgen.writeFieldName("item");
}
protected void _initWithRootName(ToXmlGenerator xgen, QName rootName) throws IOException
{
/* 28-Nov-2012, tatu: We should only initialize the root
* name if no name has been set, as per [dataformat-xml#42],
* to allow for custom serializers to work.
*/
if (!xgen.setNextNameIfMissing(rootName)) {
// however, if we are root, we... insist
if (xgen.inRoot()) {
xgen.setNextName(rootName);
}
}
xgen.initGenerator();
String ns = rootName.getNamespaceURI();
/* [dataformat-xml#26] If we just try writing root element with namespace,
* we will get an explicit prefix. But we'd rather use the default
* namespace, so let's try to force that.
*/
if (ns != null && ns.length() > 0) {
try {
xgen.getStaxWriter().setDefaultNamespace(ns);
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, xgen);
}
}
}
protected QName _rootNameFromConfig()
{
PropertyName name = _config.getFullRootName();
if (name == null) {
return null;
}
String ns = name.getNamespace();
if (ns == null || ns.isEmpty()) {
return new QName(name.getSimpleName());
}
return new QName(ns, name.getSimpleName());
}
protected ToXmlGenerator _asXmlGenerator(JsonGenerator gen)
throws JsonMappingException
{
// [Issue#71]: When converting, we actually get TokenBuffer, which is fine
if (!(gen instanceof ToXmlGenerator)) {
// but verify
if (!(gen instanceof TokenBuffer)) {
throw JsonMappingException.from(gen,
"XmlMapper does not with generators of type other than ToXmlGenerator; got: "+gen.getClass().getName());
}
return null;
}
return (ToXmlGenerator) gen;
}
protected IOException _wrapAsIOE(JsonGenerator g, Exception e) {
if (e instanceof IOException) {
return (IOException) e;
}
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
return new JsonMappingException(g, msg, e);
}
}