com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector Maven / Gradle / Ivy
Show all versions of swagger-all Show documentation
package com.fasterxml.jackson.module.jaxb;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.BeanUtil;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
import com.fasterxml.jackson.module.jaxb.deser.DataHandlerJsonDeserializer;
import com.fasterxml.jackson.module.jaxb.ser.DataHandlerJsonSerializer;
/**
* Annotation introspector that leverages JAXB annotations where applicable to JSON mapping.
* As of Jackson 2.0, most JAXB annotations are supported at least to some degree.
* Ones that are NOT yet supported are:
*
* - {@link XmlAnyAttribute} not yet used (as of 1.5) but may be in future (as an alias for @JsonAnySetter?)
*
- {@link XmlAnyElement} not yet used, may be as per [JACKSON-253]
*
- {@link javax.xml.bind.annotation.XmlAttachmentRef}: JSON does not support external attachments
*
- {@link XmlElementDecl}
*
- {@link XmlElementRefs} because Jackson doesn't have any support for 'named' collection items -- however,
* this may become partially supported as per [JACKSON-253].
*
- {@link javax.xml.bind.annotation.XmlInlineBinaryData} since the underlying concepts
* (like XOP) do not exist in JSON -- Jackson will always use inline base64 encoding as the method
*
- {@link javax.xml.bind.annotation.XmlList} because JSON does have (or necessarily need)
* method of serializing list of values as space-separated Strings
*
- {@link javax.xml.bind.annotation.XmlMimeType}
*
- {@link javax.xml.bind.annotation.XmlMixed} since JSON has no concept of mixed content
*
- {@link XmlRegistry}
*
- {@link XmlSchema} not used, unlikely to be used
*
- {@link XmlSchemaType} not used, unlikely to be used
*
- {@link XmlSchemaTypes} not used, unlikely to be used
*
- {@link XmlSeeAlso} not yet supported, but [ISSUE-1] filed to use it, so may be supported.
*
*
* Note also the following limitations:
*
*
* - Any property annotated with {@link XmlValue} will have implicit property named 'value' on
* its JSON object; although (as of 2.4) it should be possible to override this name
*
*
*
* A note on compatibility with Jackson XML module: since this module does not depend
* on Jackson XML module, it is bit difficult to make sure we will properly expose
* all information. But effort is made (as of version 2.3.3) to expose this information,
* even without using a specific sub-class from that project.
*
* @author Ryan Heaton
* @author Tatu Saloranta
*/
public class JaxbAnnotationIntrospector
extends AnnotationIntrospector
implements Versioned
{
private static final long serialVersionUID = 2406885758759038380L;
protected final static String DEFAULT_NAME_FOR_XML_VALUE = "value";
protected final static boolean DEFAULT_IGNORE_XMLIDREF = false;
protected final static String MARKER_FOR_DEFAULT = "##default";
// @since 2.5
protected final static JsonFormat.Value FORMAT_STRING = new JsonFormat.Value().withShape(JsonFormat.Shape.STRING);
// @since 2.5
protected final static JsonFormat.Value FORMAT_INT = new JsonFormat.Value().withShape(JsonFormat.Shape.NUMBER_INT);
protected final String _jaxbPackageName;
protected final JsonSerializer> _dataHandlerSerializer;
protected final JsonDeserializer> _dataHandlerDeserializer;
protected final TypeFactory _typeFactory;
protected final boolean _ignoreXmlIDREF;
/**
* When using {@link @XmlValue} annotation, a placeholder name is assigned
* to property (unless overridden by explicit name); this configuration
* value specified what that name is.
*/
protected String _xmlValueName = DEFAULT_NAME_FOR_XML_VALUE;
/**
* @deprecated Since 2.1, use the ctor that takes TypeFactory
*/
@Deprecated
public JaxbAnnotationIntrospector() {
this(TypeFactory.defaultInstance());
}
public JaxbAnnotationIntrospector(MapperConfig> config) {
this(config.getTypeFactory());
}
public JaxbAnnotationIntrospector(TypeFactory typeFactory) {
this(typeFactory, DEFAULT_IGNORE_XMLIDREF);
}
/**
* @param typeFactory Type factory used for resolving type information
* @param ignoreXmlIDREF Whether {@link XmlIDREF} annotation should be processed
* JAXB style (meaning that references are always serialized using id), or
* not (first reference as full POJO, others as ids)
*/
public JaxbAnnotationIntrospector(TypeFactory typeFactory, boolean ignoreXmlIDREF)
{
_typeFactory = (typeFactory == null)? TypeFactory.defaultInstance() : typeFactory;
_ignoreXmlIDREF = ignoreXmlIDREF;
_jaxbPackageName = XmlElement.class.getPackage().getName();
JsonSerializer> dataHandlerSerializer = null;
JsonDeserializer> dataHandlerDeserializer = null;
/* Data handlers included dynamically, to try to prevent issues on platforms
* with less than complete support for JAXB API
*/
try {
dataHandlerSerializer = (JsonSerializer>) DataHandlerJsonSerializer.class.newInstance();
dataHandlerDeserializer = (JsonDeserializer>) DataHandlerJsonDeserializer.class.newInstance();
} catch (Throwable e) {
//dataHandlers not supported...
}
_dataHandlerSerializer = dataHandlerSerializer;
_dataHandlerDeserializer = dataHandlerDeserializer;
}
/**
* Method that will return version information stored in and read from jar
* that contains this class.
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* Configuration method that can be used to change default name
* ("value") used for properties annotated with {@link XmlValue};
* note that setting it to null
will actually avoid
* name override, and name will instead be derived from underlying
* method name using standard bean name introspection.
*
* @since 2.5
*/
public void setNameUsedForXmlValue(String name) {
_xmlValueName = name;
}
/**
* Accessor for getting currently configured placeholder named
* used for property annotated with {@link XmlValue}.
*/
public String getNameUsedForXmlValue() {
return _xmlValueName;
}
/*
/**********************************************************
/* Extended API (XmlAnnotationIntrospector)
/**********************************************************
*/
// From XmlAnnotationIntrospector
// @Override
public String findNamespace(Annotated ann) {
String ns = null;
if (ann instanceof AnnotatedClass) {
// For classes, it must be @XmlRootElement. Also, we do
// want to use defaults from package, base class
XmlRootElement elem = findRootElementAnnotation((AnnotatedClass) ann);
if (elem != null) {
ns = elem.namespace();
}
} else {
// For others, XmlElement or XmlAttribute work (anything else?)
XmlElement elem = findAnnotation(XmlElement.class, ann, false, false, false);
if (elem != null) {
ns = elem.namespace();
}
if (ns == null || MARKER_FOR_DEFAULT.equals(ns)) {
XmlAttribute attr = findAnnotation(XmlAttribute.class, ann, false, false, false);
if (attr != null) {
ns = attr.namespace();
}
}
}
// JAXB uses marker for "not defined"
if (MARKER_FOR_DEFAULT.equals(ns)) {
ns = null;
}
return ns;
}
// From XmlAnnotationIntrospector
// @Override
/**
* Here we assume fairly simple logic; if there is XmlAttribute
to be found,
* we consider it an attribute; if XmlElement
, not-an-attribute; and otherwise
* we will consider there to be no information.
* Caller is likely to default to considering things as elements.
*/
public Boolean isOutputAsAttribute(Annotated ann) {
XmlAttribute attr = findAnnotation(XmlAttribute.class, ann, false, false, false);
if (attr != null) {
return Boolean.TRUE;
}
XmlElement elem = findAnnotation(XmlElement.class, ann, false, false, false);
if (elem != null) {
return Boolean.FALSE;
}
return null;
}
// From XmlAnnotationIntrospector
// @Override
public Boolean isOutputAsText(Annotated ann) {
XmlValue attr = findAnnotation(XmlValue.class, ann, false, false, false);
if (attr != null) {
return Boolean.TRUE;
}
return null;
}
/*
/**********************************************************
/* General annotations (for classes, properties)
/**********************************************************
*/
@Override
public ObjectIdInfo findObjectIdInfo(Annotated ann)
{
/* To work in the way that works with JAXB and Jackson,
* we need to do things in bit of round-about way, starting
* with AnnotatedClass, locating @XmlID property, if any.
*/
if (!(ann instanceof AnnotatedClass)) {
return null;
}
AnnotatedClass ac = (AnnotatedClass) ann;
/* Ideally, should not have to infer settings for class from
* individual fields and/or methods; but for now this
* has to do ...
*/
PropertyName idPropName = null;
method_loop:
for (AnnotatedMethod m : ac.memberMethods()) {
XmlID idProp = m.getAnnotation(XmlID.class);
if (idProp == null) {
continue;
}
switch (m.getParameterCount()) {
case 0: // getter
idPropName = findJaxbPropertyName(m, m.getRawType(), BeanUtil.okNameForGetter(m));
break method_loop;
case 1: // setter
idPropName = findJaxbPropertyName(m, m.getRawType(), BeanUtil.okNameForSetter(m));
break method_loop;
}
}
if (idPropName == null) {
for (AnnotatedField f : ac.fields()) {
XmlID idProp = f.getAnnotation(XmlID.class);
if (idProp != null) {
idPropName = findJaxbPropertyName(f, f.getRawType(), f.getName());
break;
}
}
}
if (idPropName != null) {
/* Scoping... hmmh. Could XML requires somewhat global scope, n'est pas?
* The alternative would be to use declared type of this class.
*/
Class> scope = Object.class; // alternatively would use 'ac.getRawType()'
// and we will assume that there exists property thus named...
return new ObjectIdInfo(idPropName,
scope, ObjectIdGenerators.PropertyGenerator.class,
// should we customize Object Id resolver somehow?
SimpleObjectIdResolver.class);
}
return null;
}
@Override
public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo base)
{
if (!_ignoreXmlIDREF) {
XmlIDREF idref = ann.getAnnotation(XmlIDREF.class);
/* JAXB makes XmlIDREF mean "always as id", as far as I know.
* May need to make it configurable in future, but for not that
* is fine...
*/
if (idref != null) {
base = base.withAlwaysAsId(true);
}
}
return base;
}
/*
/**********************************************************
/* General class annotations
/**********************************************************
*/
@Override
public PropertyName findRootName(AnnotatedClass ac)
{
XmlRootElement elem = findRootElementAnnotation(ac);
if (elem != null) {
return _combineNames(elem.name(), elem.namespace(), "");
}
return null;
}
/*
@Override
public String[] findPropertiesToIgnore(Annotated a) {
// nothing in JAXB for this?
return null;
}
*/
@Override
public Boolean findIgnoreUnknownProperties(AnnotatedClass ac) {
/* 08-Nov-2009, tatus: This is bit trickier: by default JAXB
* does actually ignore all unknown properties.
* But since there is no annotation to
* specify or change this, it seems wrong to claim such setting
* is in effect. May need to revisit this issue in future
*/
return null;
}
@Override
public Boolean isIgnorableType(AnnotatedClass ac) {
// Does JAXB have any such indicators? No?
return null;
}
/*
/**********************************************************
/* General member (field, method/constructor) annotations
/**********************************************************
*/
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m.getAnnotation(XmlTransient.class) != null;
}
@Override
public PropertyName findWrapperName(Annotated ann)
{
XmlElementWrapper w = findAnnotation(XmlElementWrapper.class, ann, false, false, false);
if (w != null) {
/* 18-Sep-2013, tatu: As per #24, need to take special care with empty
* String, as that should indicate here "use underlying unmodified
* property name" (that is, one NOT overridden by @JsonProperty)
*/
PropertyName name = _combineNames(w.name(), w.namespace(), "");
// clumsy, yes, but has to do:
if (!name.hasSimpleName()) {
if (ann instanceof AnnotatedMethod) {
AnnotatedMethod am = (AnnotatedMethod) ann;
String str;
if (am.getParameterCount() == 0) {
str = BeanUtil.okNameForGetter(am);
} else {
str = BeanUtil.okNameForSetter(am);
}
if (str != null) {
return name.withSimpleName(str);
}
}
return name.withSimpleName(ann.getName());
}
return name;
}
return null;
}
// since 2.4
@Override
public String findImplicitPropertyName(AnnotatedMember m) {
XmlValue valueInfo = m.getAnnotation(XmlValue.class);
if (valueInfo != null) {
return _xmlValueName;
}
return null;
}
@Override
public JsonFormat.Value findFormat(Annotated m) {
/* [Issue#33]: Use @XmlEnum value (Class) to indicate format,
* iff it makes sense
*/
if (m instanceof AnnotatedClass) {
XmlEnum ann = m.getAnnotation(XmlEnum.class);
if (ann != null) {
Class> type = ann.value();
if (type == String.class || type.isEnum()) {
return FORMAT_STRING;
}
if (Number.class.isAssignableFrom(type)) {
return FORMAT_INT;
}
}
}
return null;
}
/*
/**********************************************************
/* Property auto-detection
/**********************************************************
*/
@Override
public VisibilityChecker> findAutoDetectVisibility(AnnotatedClass ac,
VisibilityChecker> checker)
{
XmlAccessType at = findAccessType(ac);
if (at == null) {
/* JAXB default is "PUBLIC_MEMBER"; however, here we should not
* override settings if there is no annotation -- that would mess
* up global baseline. Fortunately Jackson defaults are very close
* to JAXB 'PUBLIC_MEMBER' settings (considering that setters and
* getters must come in pairs)
*/
return checker;
}
// Note: JAXB does not do creator auto-detection, can (and should) ignore
switch (at) {
case FIELD: // all fields, independent of visibility; no methods
return checker.withFieldVisibility(Visibility.ANY)
.withSetterVisibility(Visibility.NONE)
.withGetterVisibility(Visibility.NONE)
.withIsGetterVisibility(Visibility.NONE)
;
case NONE: // no auto-detection
return checker.withFieldVisibility(Visibility.NONE)
.withSetterVisibility(Visibility.NONE)
.withGetterVisibility(Visibility.NONE)
.withIsGetterVisibility(Visibility.NONE)
;
case PROPERTY:
return checker.withFieldVisibility(Visibility.NONE)
.withSetterVisibility(Visibility.PUBLIC_ONLY)
.withGetterVisibility(Visibility.PUBLIC_ONLY)
.withIsGetterVisibility(Visibility.PUBLIC_ONLY)
;
case PUBLIC_MEMBER:
return checker.withFieldVisibility(Visibility.PUBLIC_ONLY)
.withSetterVisibility(Visibility.PUBLIC_ONLY)
.withGetterVisibility(Visibility.PUBLIC_ONLY)
.withIsGetterVisibility(Visibility.PUBLIC_ONLY)
;
}
return checker;
}
/**
* Method for locating JAXB {@link XmlAccessType} annotation value
* for given annotated entity, if it has one, or inherits one from
* its ancestors (in JAXB sense, package etc). Returns null if
* nothing has been explicitly defined.
*/
protected XmlAccessType findAccessType(Annotated ac)
{
XmlAccessorType at = findAnnotation(XmlAccessorType.class, ac, true, true, true);
return (at == null) ? null : at.value();
}
/*
/**********************************************************
/* Class annotations for PM type handling (1.5+)
/**********************************************************
*/
@Override
public TypeResolverBuilder> findTypeResolver(MapperConfig> config,
AnnotatedClass ac, JavaType baseType)
{
// no per-class type resolvers, right?
return null;
}
@Override
public TypeResolverBuilder> findPropertyTypeResolver(MapperConfig> config,
AnnotatedMember am, JavaType baseType)
{
/* First: @XmlElements and @XmlElementRefs only applies type for immediate property, if it
* is NOT a structured type.
*/
if (baseType.isContainerType()) return null;
return _typeResolverFromXmlElements(am);
}
@Override
public TypeResolverBuilder> findPropertyContentTypeResolver(MapperConfig> config,
AnnotatedMember am, JavaType containerType)
{
/* First: let's ensure property is a container type: caller should have
* verified but just to be sure
*/
if (!containerType.isContainerType()) {
throw new IllegalArgumentException("Must call method with a container type (got "+containerType+")");
}
return _typeResolverFromXmlElements(am);
}
protected TypeResolverBuilder> _typeResolverFromXmlElements(AnnotatedMember am)
{
/* If simple type, @XmlElements and @XmlElementRefs are applicable.
* Note: @XmlElement and @XmlElementRef are NOT handled here, since they
* are handled specifically as non-polymorphic indication
* of the actual type
*/
XmlElements elems = findAnnotation(XmlElements.class, am, false, false, false);
XmlElementRefs elemRefs = findAnnotation(XmlElementRefs.class, am, false, false, false);
if (elems == null && elemRefs == null) {
return null;
}
TypeResolverBuilder> b = new StdTypeResolverBuilder();
// JAXB always uses type name as id
b = b.init(JsonTypeInfo.Id.NAME, null);
// and let's consider WRAPPER_OBJECT to be canonical inclusion method
b = b.inclusion(JsonTypeInfo.As.WRAPPER_OBJECT);
return b;
}
@Override
public List findSubtypes(Annotated a)
{
// No package/superclass defaulting (only used with fields, methods)
XmlElements elems = findAnnotation(XmlElements.class, a, false, false, false);
ArrayList result = null;
if (elems != null) {
result = new ArrayList();
for (XmlElement elem : elems.value()) {
String name = elem.name();
if (MARKER_FOR_DEFAULT.equals(name)) name = null;
result.add(new NamedType(elem.type(), name));
}
} else {
XmlElementRefs elemRefs = findAnnotation(XmlElementRefs.class, a, false, false, false);
if (elemRefs != null) {
result = new ArrayList();
for (XmlElementRef elemRef : elemRefs.value()) {
Class> refType = elemRef.type();
// only good for types other than JAXBElement (which is XML based)
if (!JAXBElement.class.isAssignableFrom(refType)) {
// [JACKSON-253] first consider explicit name declaration
String name = elemRef.name();
if (name == null || MARKER_FOR_DEFAULT.equals(name)) {
XmlRootElement rootElement = (XmlRootElement) refType.getAnnotation(XmlRootElement.class);
if (rootElement != null) {
name = rootElement.name();
}
}
if (name == null || MARKER_FOR_DEFAULT.equals(name)) {
name = Introspector.decapitalize(refType.getSimpleName());
}
result.add(new NamedType(refType, name));
}
}
}
}
// [Issue#1] check @XmlSeeAlso as well.
/* 17-Aug-2012, tatu: But wait! For structured type, what we really is
* value (content) type!
* If code below does not make full (or any) sense, do not despair -- it
* is wrong. Yet it works. The call sequence before we get here is mangled,
* its logic twisted... but as Dire Straits put it: "That ain't working --
* that's The Way You Do It!"
*/
XmlSeeAlso ann = a.getAnnotation(XmlSeeAlso.class);
if (ann != null) {
if (result == null) {
result = new ArrayList();
}
for (Class> cls : ann.value()) {
result.add(new NamedType(cls));
}
}
return result;
}
@Override
public String findTypeName(AnnotatedClass ac) {
XmlType type = findAnnotation(XmlType.class, ac, false, false, false);
if (type != null) {
String name = type.name();
if (!MARKER_FOR_DEFAULT.equals(name)) return name;
}
return null;
}
/*
/**********************************************************
/* Serialization: general annotations
/**********************************************************
*/
@Override
public JsonSerializer> findSerializer(Annotated am)
{
final Class> type = _rawSerializationType(am);
/*
// As per [JACKSON-722], more checks for structured types
XmlAdapter