CoreXMLDeserializers.java
package tools.jackson.databind.ext;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.datatype.*;
import javax.xml.namespace.QName;
import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.std.FromStringDeserializer;
/**
* Container deserializers that handle "core" XML types: ones included in standard
* JDK 1.5. Types are directly needed by JAXB, but may be unavailable on some
* limited platforms; hence separate out from basic deserializer factory.
*/
public class CoreXMLDeserializers
{
protected final static QName EMPTY_QNAME = QName.valueOf("");
/**
* Data type factories are thread-safe after instantiation (and
* configuration, if any); and since instantiation (esp. implementation
* introspection) can be expensive we better reuse the instance.
*/
final static DatatypeFactory _dataTypeFactory;
static {
try {
_dataTypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
throw new RuntimeException(e);
}
}
public static ValueDeserializer<?> findBeanDeserializer(DeserializationConfig config,
JavaType type)
{
Class<?> raw = type.getRawClass();
if (raw == QName.class) {
return new Std(raw, TYPE_QNAME);
}
if (raw == XMLGregorianCalendar.class) {
return new Std(raw, TYPE_G_CALENDAR);
}
if (raw == Duration.class) {
return new Std(raw, TYPE_DURATION);
}
return null;
}
public static boolean hasDeserializerFor(Class<?> valueType) {
return (valueType == QName.class)
|| (valueType == XMLGregorianCalendar.class)
|| (valueType == Duration.class)
;
}
/*
/**********************************************************************
/* Concrete deserializers
/**********************************************************************
*/
protected final static int TYPE_DURATION = 1;
protected final static int TYPE_G_CALENDAR = 2;
protected final static int TYPE_QNAME = 3;
/**
* Combo-deserializer that supports deserialization of somewhat optional
* javax.xml types {@link QName}, {@link Duration} and {@link XMLGregorianCalendar}.
* Combined into a single class to eliminate bunch of one-off implementation
* classes, to reduce resulting jar size (mostly).
*/
public static class Std extends FromStringDeserializer<Object>
{
protected final int _kind;
public Std(Class<?> raw, int kind) {
super(raw);
_kind = kind;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws JacksonException
{
// GregorianCalendar also allows integer value (timestamp),
// which needs separate handling
if (_kind == TYPE_G_CALENDAR) {
if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
return _gregorianFromDate(ctxt, _parseDate(p, ctxt));
}
}
// QName also allows object value, which needs separate handling
if (_kind == TYPE_QNAME) {
if (p.hasToken(JsonToken.START_OBJECT)) {
return _parseQNameObject(p, ctxt);
}
}
return super.deserialize(p, ctxt);
}
private QName _parseQNameObject(JsonParser p, DeserializationContext ctxt)
throws JacksonException
{
JsonNode tree = ctxt.readTree(p);
JsonNode localPart = tree.get("localPart");
if (localPart == null) {
ctxt.reportInputMismatch(this,
"Object value for `QName` is missing required property 'localPart'");
}
if (!localPart.isString()) {
ctxt.reportInputMismatch(this,
"Object value property 'localPart' for `QName` must be of type STRING, not %s",
localPart.getNodeType());
}
// Both optional properties validated regardless of which ones are defined
JsonNode namespaceURI = _optionalStringProperty(ctxt, tree, "namespaceURI");
JsonNode prefix = _optionalStringProperty(ctxt, tree, "prefix");
// Missing 'namespaceURI' same as empty one ("no namespace")
final String ns = (namespaceURI == null)
? XMLConstants.NULL_NS_URI : namespaceURI.asString();
if (prefix != null) {
return new QName(ns, localPart.asString(), prefix.asString());
}
return new QName(ns, localPart.asString());
}
/**
* Helper method for accessing one of optional {@code QName} properties, verifying
* that its value (if any) is of type STRING: explicit JSON {@code null} is taken
* to mean "not defined", but other non-STRING values are rejected same as for
* required 'localPart' (since {@code asString()} would coerce numbers and
* booleans into their textual form).
*
* @return Node for the property if it has usable value; {@code null} if not defined
*/
private JsonNode _optionalStringProperty(DeserializationContext ctxt,
JsonNode tree, String propName)
throws JacksonException
{
JsonNode n = tree.get(propName);
if ((n == null) || n.isNull()) {
return null;
}
if (!n.isString()) {
ctxt.reportInputMismatch(this,
"Object value property '%s' for `QName` must be of type STRING, not %s",
propName, n.getNodeType());
}
return n;
}
@Override
protected Object _deserialize(String value, DeserializationContext ctxt)
throws JacksonException
{
switch (_kind) {
case TYPE_DURATION:
// [databind#6127] DatatypeFactory.newDuration() parses date/time components
// into BigIntegers (and fractional seconds into BigDecimal), using the
// O(n^2) BigInteger(String)/BigDecimal(String) constructors. Rather than
// isolating individual components we check length of the whole value as a
// conservative upper bound: it is never shorter than any single component,
// and no valid Duration comes anywhere near the (default 1000) limit.
_validateTimestampLength(ctxt, value);
return _dataTypeFactory.newDuration(value);
case TYPE_QNAME:
return QName.valueOf(value);
case TYPE_G_CALENDAR:
// [databind#6127] Similar to TYPE_DURATION above: length of the whole value
// is checked as a conservative upper bound for numeric components, before
// handing value to DatatypeFactory.newXMLGregorianCalendar(), which parses
// fractional seconds into a BigDecimal via O(n^2) BigDecimal(String).
_validateTimestampLength(ctxt, value);
Date d;
try {
d = _parseDate(value, ctxt);
} catch (DatabindException e) {
// try to parse from native XML Schema 1.0 lexical representation String,
// which includes time-only formats not handled by parseXMLGregorianCalendarFromJacksonFormat(...)
return _dataTypeFactory.newXMLGregorianCalendar(value);
}
return _gregorianFromDate(ctxt, d);
}
throw new IllegalStateException();
}
@Override
protected Object _deserializeFromEmptyString(DeserializationContext ctxt) {
if (_kind == TYPE_QNAME) {
return EMPTY_QNAME;
}
return super._deserializeFromEmptyString(ctxt);
}
protected XMLGregorianCalendar _gregorianFromDate(DeserializationContext ctxt,
Date d)
{
if (d == null) {
return null;
}
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(d);
TimeZone tz = ctxt.getTimeZone();
if (tz != null) {
calendar.setTimeZone(tz);
}
return _dataTypeFactory.newXMLGregorianCalendar(calendar);
}
}
}