JBoss.orgCommunity Documentation

Chapter 22. Jakarta XML Binding providers

22.1. Jakarta XML Binding Decorators
22.2. Pluggable JAXBContext's with ContextResolvers
22.3. Jakarta XML Binding + XML provider
22.3.1. @XmlHeader and @Stylesheet
22.4. Jakarta XML Binding + JSON provider
22.5. Jakarta XML Binding + FastinfoSet provider
22.6. Arrays and Collections of Jakarta XML Binding Objects
22.6.1. Retrieving Collections on the client side
22.6.2. JSON and Jakarta XML Binding Collections/arrays
22.7. Maps of XML Objects
22.7.1. Retrieving Maps on the client side
22.7.2. JSON and XML maps
22.8. Interfaces, Abstract Classes, and Jakarta XML Binding
22.9. Configuring Jakarta XML Binding Marshalling

As required by the specification, RESTEasy includes support for (un)marshalling Jakarta XML Binding annotated classes. RESTEasy provides multiple providers to address some subtle differences between classes generated by XJC and classes which are simply annotated with @XmlRootElement, or working with JAXBElement classes directly.

For the most part, developers using the Jakarta RESTful Web Services API, the selection of which provider is invoked will be completely transparent. For developers wishing to access the providers directly (which most folks won't need to do), this document describes which provider is best suited for different configurations.

A Jakarta XML Binding Provider is selected by RESTEasy when a parameter or return type is an object that is annotated with Jakarta XML Binding annotations (such as @XmlRootEntity or @XmlType) or if the type is a JAXBElement. Additionally, the resource class or resource method will be annotated with either a @Consumes or @Produces annotation and contain one or more of the following values:

RESTEasy will select a different provider based on the return type or parameter type used in the resource. This section describes how the selection process works.

@XmlRootEntity When a class is annotated with a @XmlRootElement annotation, RESTEasy will select the JAXBXmlRootElementProvider. This provider handles basic marshaling and unmarshalling of custom Jakarta XML Binding entities.

@XmlType Classes which have been generated by XJC will most likely not contain an @XmlRootEntity annotation. In order for these classes to marshalled, they must be wrapped within a JAXBElement instance. This is typically accomplished by invoking a method on the class which serves as the XmlRegistry and is named ObjectFactory.

The JAXBXmlTypeProvider provider is selected when the class is annotated with an XmlType annotation and not an XmlRootElement annotation.

This provider simplifies this task by attempting to locate the XmlRegistry for the target class. By default, a Jakarta XML Binding implementation will create a class called ObjectFactory and is located in the same package as the target class. When this class is located, it will contain a "create" method that takes the object instance as a parameter. For example, if the target type is called "Contact", then the ObjectFactory class will have a method:

public JAXBElement createContact(Contact value) {..

JAXBElement<?> If your resource works with the JAXBElement class directly, the RESTEasy runtime will select the JAXBElementProvider. This provider examines the ParameterizedType value of the JAXBElement in order to select the appropriate JAXBContext.

Resteasy's Jakarta XML Binding providers have a pluggable way to decorate Marshaller and Unmarshaller instances. The way it works is that you can write an annotation that can trigger the decoration of a Marshaller or Unmarshaller. Your decorators can do things like set Marshaller or Unmarshaller properties, set up validation, stuff like that. Here's an example. Let's say we want to have an annotation that will trigger pretty-printing, nice formatting, of an XML document. If we were doing raw Jakarta XML Binding, we would set a property on the Marshaller of Marshaller.JAXB_FORMATTED_OUTPUT. Let's write a Marshaller decorator.

First we define a annotation:

import org.jboss.resteasy.annotations.Decorator;

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Decorator(processor = PrettyProcessor.class, target = Marshaller.class)
public @interface Pretty {}

To get this to work, we must annotate our @Pretty annotation with a meta-annotation called @Decorator. The target() attribute must be the Jakarta XML Binding Marshaller class. The processor() attribute is a class we will write next.

import org.jboss.resteasy.core.interception.DecoratorProcessor;
import org.jboss.resteasy.annotations.DecorateTypes;

import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.Produces;
import java.lang.annotation.Annotation;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
@DecorateTypes({"text/*+xml", "application/*+xml"})
public class PrettyProcessor implements DecoratorProcessor<Marshaller, Pretty>
{
    public Marshaller decorate(Marshaller target, Pretty annotation,
                  Class type, Annotation[] annotations, MediaType mediaType)
    {
       target.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    }
}

The processor implementation must implement the DecoratorProcessor interface and should also be annotated with @DecorateTypes. This annotation specifies what media types the processor can be used with. Now that we've defined our annotation and our Processor, we can use it on our Jakarta RESTful Web Services resource methods or Jakarta XML Binding types as follows:

@GET
@Pretty
@Produces("application/xml")
public SomeJAXBObject get() {...}

If you are confused, check the RESTEasy source code for the implementation of @XmlHeader

You should not use this feature unless you know what you're doing.

Based on the class you are marshalling/unmarshalling, RESTEasy will, by default create and cache JAXBContext instances per class type. If you do not want RESTEasy to create JAXBContexts, you can plug-in your own by implementing an instance of javax.ws.rs.ext.ContextResolver

public interface ContextResolver<T>
{
    T getContext(Class<?> type);
}

@Provider
@Produces("application/xml")
public class MyJAXBContextResolver implements ContextResolver<JAXBContext>
{
    JAXBContext getContext(Class<?> type)
    {
        if (type.equals(WhateverClassIsOverridedFor.class)) return JAXBContext.newInstance()...;
    }
}

You must provide a @Produces annotation to specify the media type the context is meant for. You must also make sure to implement ContextResolver<JAXBContext>. This helps the runtime match to the correct context resolver. You must also annotate the ContextResolver class with @Provider.

There are multiple ways to make this ContextResolver available.

  1. Return it as a class or instance from a javax.ws.rs.core.Application implementation
  2. List it as a provider with resteasy.providers
  3. Let RESTEasy automatically scan for it within your WAR file. See Configuration Guide
  4. Manually add it via ResteasyProviderFactory.getInstance().registerProvider(Class) or registerProviderInstance(Object)

RESTEasy is required to provide Jakarta XML Binding provider support for XML. It has a few extra annotations that can help code your app.

RESTEasy allows you to marshall Jakarta XML Binding annotated POJOs to and from JSON. This provider wraps the Jackson2 library to accomplish this.

To use this integration with Jackson you need to import the resteasy-jackson2-provider Maven module.

For example, consider this Jakarta XML Binding class:

@XmlRootElement(name = "book")
public class Book
{
    private String author;
    private String ISBN;
    private String title;

    public Book()
    {
    }

    public Book(String author, String ISBN, String title)
    {
        this.author = author;
        this.ISBN = ISBN;
        this.title = title;
    }

    @XmlElement
    public String getAuthor()
    {
        return author;
    }

    public void setAuthor(String author)
    {
    this.author = author;
    }

    @XmlElement
    public String getISBN()
    {
        return ISBN;
    }

    public void setISBN(String ISBN)
    {
        this.ISBN = ISBN;
    }

    @XmlAttribute
    public String getTitle()
    {
        return title;
    }

    public void setTitle(String title)
    {
        this.title = title;
    }
}

And we can write a method to use the above entity:

@Path("/test_json")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Book test_json() {
    Book book = new Book();
    book.setTitle("EJB 3.0");
    book.setAuthor("Bill Burke");
    book.setISBN("596529260");
    return book;
}

Requesting from the above method, and we can see the default Jackson2 marshaller would return JSON that looked like this:

$ http localhost:8080/dummy/test_json
HTTP/1.1 200
...
Content-Type: application/json

{
"ISBN": "596529260",
"author": "Bill Burke",
"title": "EJB 3.0"
}

RESTEasy supports the FastinfoSet mime type with Jakarta XML Binding annotated classes. Fast infoset documents are faster to serialize and parse, and smaller in size, than logically equivalent XML documents. Thus, fast infoset documents may be used whenever the size and processing time of XML documents is an issue. It is configured the same way the provider is so really no other documentation is needed here.

To use this integration with Fastinfoset you need to import the resteasy-fastinfoset-provider Maven module. Older versions of RESTEasy used to include this within the resteasy-jaxb-provider but we decided to modularize it more.

RESTEasy will automatically marshal arrays, java.util.Set's, and java.util.List's of Jakarta XML Binding objects to and from XML, JSON, Fastinfoset (or any other new Jakarta XML Binding mapper Restasy comes up with).

@XmlRootElement(name = "customer")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer
{
    @XmlElement
    private String name;

    public Customer()
    {
    }

    public Customer(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }
}

@Path("/")
public class MyResource
{
    @PUT
    @Path("array")
    @Consumes("application/xml")
    public void putCustomers(Customer[] customers)
    {
        Assert.assertEquals("bill", customers[0].getName());
        Assert.assertEquals("monica", customers[1].getName());
    }

    @GET
    @Path("set")
    @Produces("application/xml")
    public Set<Customer> getCustomerSet()
    {
        HashSet<Customer> set = new HashSet<Customer>();
        set.add(new Customer("bill"));
        set.add(new Customer("monica"));

        return set;
    }

    @PUT
    @Path("list")
    @Consumes("application/xml")
    public void putCustomers(List<Customer> customers)
    {
        Assert.assertEquals("bill", customers.get(0).getName());
        Assert.assertEquals("monica", customers.get(1).getName());
    }
}

The above resource can publish and receive Jakarta XML Binding objects. It is assumed that are wrapped in a collection element

<collection>
    <customer><name>bill</name></customer>
    <customer><name>monica</name></customer>
<collection>

You can change the namespace URI, namespace tag, and collection element name by using the @org.jboss.resteasy.annotations.providers.jaxb.Wrapped annotation on a parameter or method

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Wrapped
{
    String element() default "collection";

    String namespace() default "http://jboss.org/resteasy";

    String prefix() default "resteasy";
}

So, if we wanted to output this XML

<foo:list xmlns:foo="http://foo.org">
    <customer><name>bill</name></customer>
    <customer><name>monica</name></customer>
</foo:list>

We would use the @Wrapped annotation as follows:

@GET
@Path("list")
@Produces("application/xml")
@Wrapped(element="list", namespace="http://foo.org", prefix="foo")
public List<Customer> getCustomerSet()
{
    List<Customer> list = new ArrayList<Customer>();
    list.add(new Customer("bill"));
    list.add(new Customer("monica"));

    return list;
}

RESTEasy will automatically marshal maps of &XML-BIND-API; objects to and from XML, JSON, Fastinfoset (or any other new &XML-BIND-API; mapper RESTEasy comes up with). Your parameter or method return type must be a generic with a String as the key and the &XML-BIND-API; object's type.

@XmlRootElement(namespace = "http://foo.com")
public static class Foo
{
    @XmlAttribute
    private String name;

    public Foo()
    {
    }

    public Foo(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }
}

@Path("/map")
public static class MyResource
{
    @POST
    @Produces("application/xml")
    @Consumes("application/xml")
    public Map<String, Foo> post(Map<String, Foo> map)
    {
        Assert.assertEquals(2, map.size());
        Assert.assertNotNull(map.get("bill"));
        Assert.assertNotNull(map.get("monica"));
        Assert.assertEquals(map.get("bill").getName(), "bill");
        Assert.assertEquals(map.get("monica").getName(), "monica");
        return map;
    }
}

The above resource can publish and receive XML objects within a map. By default, they are wrapped in a "map" element in the default namespace. Also, each "map" element has zero or more "entry" elements with a "key" attribute.

<map>
    <entry key="bill" xmlns="http://foo.com">
        <foo name="bill"/>
    </entry>
    <entry key="monica" xmlns="http://foo.com">
        <foo name="monica"/>
    </entry>
</map>

You can change the namespace URI, namespace prefix and map, entry, and key element and attribute names by using the @org.jboss.resteasy.annotations.providers.jaxb.WrappedMap annotation on a parameter or method

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WrappedMap
{
    /**
     * map element name
     */
    String map() default "map";

    /**
     * entry element name *
     */
    String entry() default "entry";

    /**
     * entry's key attribute name
     */
    String key() default "key";

    String namespace() default "";

    String prefix() default "";
}

So, if we wanted to output this XML

<hashmap>
    <hashentry hashkey="bill" xmlns:foo="http://foo.com">
        <foo:foo name="bill"/>
    </hashentry>
</map>

We would use the @WrappedMap annotation as follows:

@Path("/map")
public static class MyResource
{
    @GET
    @Produces("application/xml")
    @WrappedMap(map="hashmap", entry="hashentry", key="hashkey")
    public Map<String, Foo> get()
    {
        ...
        return map;
    }
}

Some objects models use abstract classes and interfaces heavily. Unfortunately, Jakarta XML Binding doesn't work with interfaces that are root elements and RESTEasy can't unmarshal parameters that are interfaces or raw abstract classes because it doesn't have enough information to create a JAXBContext. For example:

public interface IFoo {}

@XmlRootElement
public class RealFoo implements IFoo {}

@Path("/xml")
public class MyResource {

    @PUT
    @Consumes("application/xml")
    public void put(IFoo foo) {...}
}

In this example, you would get an error from RESTEasy of something like "Cannot find a MessageBodyReader for...". This is because RESTEasy does not know that implementations of IFoo are Jakarta XML Binding classes and doesn't know how to create a JAXBContext for it. As a workaround, RESTEasy allows you to use the Jakarta XML Binding annotation @XmlSeeAlso on the interface to correct the problem. (NOTE, this will not work with manual, hand-coded Jakarta XML Binding).

@XmlSeeAlso(RealFoo.class)
public interface IFoo {}

The extra @XmlSeeAlso on IFoo allows RESTEasy to create a JAXBContext that knows how to unmarshal RealFoo instances.

As a consumer of XML datasets, Jakarta XML Binding is subject to a form of attack known as the XXE (Xml eXternal Entity) Attack (https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing), in which expanding an external entity causes an unsafe file to be loaded. Preventing the expansion of external entities is discussed in Section 21.4, “Configuring Document Marshalling”. The same parameter,

resteasy.document.expand.entity.references

applies to Jakarta XML Binding unmarshallers as well.

Section 21.4, “Configuring Document Marshalling” also discusses the prohibition of DTDs and the imposition of limits on entity expansion and the number of attributes per element. The parameters

resteasy.document.secure.disableDTDs

and

resteasy.document.secure.processing.feature

discussed there, and their default values, also apply to the representation of Jakarta XML Binding objects.