Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why check for root element is required in Jaxb2Marshaller?

I am using Jaxb2Marshaller to marshal Java beans through spring @ResponseBody annotation. For JSON marshaling was working fine. But for xml I was continuously getting HTTP 406 response. Little bit digging in Jaxb2Marshaller class reveals it checks for @XmlRootElement for bounded classes (see below snippet).

While generating java code from xsd my pojo does not contain @XmlRootElement and proper message converter was not identified by AnnotationMethodHandlerAdapter and finally result in 406.

Instead of auto generating java code from xsd I have created my own pojo class and used @XmlRootElement. Then marshaling works fine.

I want to understand why it is important of having @XmlRootElement check for bounded classes. Or any way to specify element as @XmlRootElement in xsd.

Code snippet from Jaxb2Marshaller:

public boolean supports(Class clazz) {
    return supportsInternal(clazz, true);
}

private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
    if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
        return false;
    }
    if (clazz.getAnnotation(XmlType.class) == null) {
        return false;
    }
    if (StringUtils.hasLength(getContextPath())) {
        String className = ClassUtils.getQualifiedName(clazz);
        int lastDotIndex = className.lastIndexOf('.');
        if (lastDotIndex == -1) {
            return false;
        }
        String packageName = className.substring(0, lastDotIndex);
        String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
        for (String contextPath : contextPaths) {
            if (contextPath.equals(packageName)) {
                return true;
            }
        }
        return false;
    }
    else if (!ObjectUtils.isEmpty(classesToBeBound)) {
        return Arrays.asList(classesToBeBound).contains(clazz);
    }
    return false;
}

Edit: Blaise answer helped me solve @XmlRootElement problem. But still if someone has any information about why check for XmlRootElement is required, will be a good info.

like image 227
Jaydeep Patel Avatar asked Jan 26 '11 10:01

Jaydeep Patel


3 Answers

Why the @XmlRootElement Annotation is Checked For

Spring requires a root element when marshalling the object to XML. JAXB provides two mechanisms to do this:

  1. The @XmlRootElement annotation
  2. Wrapping the root object in an instance of JAXBElement.

Since the object is not wrapped in a JAXBElement Spring is ensuring that the other condition is met.

How Generate an @XmlRootElement

JAXB will generate an @XmlRootElement annotation for all global elements in an XML schema. The following will cause an @XmlElement:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <xsd:element name="foo">
       <xsd:complexType>
           ...
       </xsd:complextType>
    </xsd:element>
</xsd:schema>

When @XmlRootElement is not Generated

An @XmlRootElement annotation will not be generated for global types.

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <xsd:element name="foo" type="foo"/>
   <xsd:complexType name="foo">
       ...
   </xsd:complexType>
</xsd:schema>

Instead the global element(s) associated with the global types are captured in the ObjectFactory class (annotated with @XmlRegistry) in the form of @XmlElementDecl annotations. These annotations

package generated;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;


@XmlRegistry
public class ObjectFactory {

    private final static QName _Foo_QNAME = new QName("", "foo");

    public Foo createFoo() {
        return new Foo();
    }

    @XmlElementDecl(namespace = "", name = "foo")
    public JAXBElement<Foo> createFoo(Foo value) {
        return new JAXBElement<Foo>(_Foo_QNAME, Foo.class, null, value);
    }

}

The @XmlElementDecl annotation provides similar information as @XmlRootElement and could be used for unmarshal operations. JAX-RS implementations probably do not leverage @XmlElementDecl however since marshal operations would require the object to be wrapped in a JAXBElement object to provide the root element name/namespace.

like image 148
bdoughan Avatar answered Oct 09 '22 03:10

bdoughan


it's an known issue: https://jira.springsource.org/browse/SPR-7931

"Checking for @XmlRootElement annotation should be made optional in Jaxb2Marshaller"

like image 36
Matthias M. Avatar answered Oct 09 '22 05:10

Matthias M.


You can use JaxbElement for classes that does not have @XmlRootElement annotation. @XmlRootElement annotation is placed only to non referenced top level objects if you are generating your code from xsd

Edit

See @Blaise Doughan answer.

@XmlRootElement will be placed only if there is no reference to that type in another type.
like image 1
fmucar Avatar answered Oct 09 '22 04:10

fmucar