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.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
{
private static final long serialVersionUID = -141838337907252911L;
/**
* 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 something like "<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;
}
/*
/**********************************************************************
/* Overridden methods
/**********************************************************************
*/
@Override
public DefaultSerializerProvider createInstance(SerializationConfig config,
SerializerFactory jsf)
{
return new XmlSerializerProvider(this, config, jsf);
}
@SuppressWarnings("resource")
@Override
public void serializeValue(JsonGenerator jgen, Object value)
throws IOException, JsonProcessingException
{
if (value == null) {
_serializeXmlNull(jgen);
return;
}
final Class<?> cls = value.getClass();
final boolean asArray;
final ToXmlGenerator xgen = _asXmlGenerator(jgen);
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, jgen, this);
} catch (IOException ioe) { // As per [JACKSON-99], pass IOException and subtypes as-is
throw ioe;
} catch (Exception e) { // but wrap RuntimeExceptions, to get path information
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
// end of super-class implementation
if (asArray) {
jgen.writeEndObject();
}
}
@SuppressWarnings("resource")
@Override
public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType)
throws IOException, JsonProcessingException
{
if (value == null) {
_serializeXmlNull(jgen);
return;
}
final boolean asArray;
final ToXmlGenerator xgen = _asXmlGenerator(jgen);
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);
}
}
final JsonSerializer<Object> ser = findTypedValueSerializer(rootType, true, null);
// From super-class implementation
try {
ser.serialize(value, jgen, this);
} catch (IOException ioe) { // no wrapping for IO (and derived)
throw ioe;
} catch (Exception e) { // but others do need to be, to get path etc
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
// end of super-class implementation
if (asArray) {
jgen.writeEndObject();
}
}
// @since 2.1
@SuppressWarnings("resource")
@Override
public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType,
JsonSerializer<Object> ser)
throws IOException, JsonGenerationException
{
if (value == null) {
_serializeXmlNull(jgen);
return;
}
final boolean asArray;
final ToXmlGenerator xgen = _asXmlGenerator(jgen);
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, jgen, this);
} catch (IOException ioe) { // no wrapping for IO (and derived)
throw ioe;
} catch (Exception e) { // but others do need to be, to get path etc
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
// end of super-class implementation
if (asArray) {
jgen.writeEndObject();
}
}
protected void _serializeXmlNull(JsonGenerator jgen)
throws IOException, JsonProcessingException
{
if (jgen instanceof ToXmlGenerator)
_initWithRootName((ToXmlGenerator) jgen, ROOT_NAME_FOR_NULL);
super.serializeValue(jgen, null);
}
protected void _startRootArray(ToXmlGenerator xgen, QName rootName)
throws IOException, JsonProcessingException
{
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, JsonProcessingException
{
/* 28-Nov-2012, tatu: We should only initialize the root
* name if no name has been set, as per [Issue#42],
* to allow for custom serializers to work.
*/
if (!xgen.setNextNameIfMissing(rootName)) {
// however, if we are root, we... insist
if (xgen.getOutputContext().inRoot()) {
xgen.setNextName(rootName);
}
}
xgen.initGenerator();
String ns = rootName.getNamespaceURI();
/* [Issue#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.throwXmlAsIOException(e);
}
}
}
protected QName _rootNameFromConfig()
{
String name = _config.getRootName();
return (name == null) ? null : new QName(name);
}
protected ToXmlGenerator _asXmlGenerator(JsonGenerator jgen)
throws JsonMappingException
{
// [Issue#71]: When converting, we actually get TokenBuffer, which is fine
if (!(jgen instanceof ToXmlGenerator)) {
// but verify
if (!(jgen instanceof TokenBuffer)) {
throw new JsonMappingException("XmlMapper does not with generators of type other than ToXmlGenerator; got: "
+jgen.getClass().getName());
}
return null;
}
return (ToXmlGenerator) jgen;
}
}