Red Hat Training

A Red Hat training course is available for Red Hat Fuse

Chapter 49. Entity Support

Abstract

The Apache CXF runtime supports a limited number of mappings between MIME types and Java objects out of the box. Developers can extend the mappings by implementing custom readers and writers. The custom readers and writers are registered with the runtime at start-up.

Overview

The runtime relies on JAX-RS MessageBodyReader and MessageBodyWriter implementations to serialize and de-serialize data between the HTTP messages and their Java representations. The readers and writers can restrict the MIME types they are capable of processing.
The runtime provides readers and writers for a number of common mappings. If an application requires more advanced mappings, a developer can provide custom implementations of the MessageBodyReader interface and/or the MessageBodyWriter interface. Custom readers and writers are registered with the runtime when the application is started.

Natively supported types

Table 49.1, “Natively supported entity mappings” lists the entity mappings provided by Apache CXF out of the box.

Table 49.1. Natively supported entity mappings

Java TypeMIME Type
primitive typestext/plain
java.lang.Numbertext/plain
byte[]*/*
java.lang.String*/*
java.io.InputStream*/*
java.io.Reader*/*
java.io.File*/*
javax.activation.DataSource*/*
javax.xml.transform.Sourcetext/xml, application/xml, application/*+xml
javax.xml.bind.JAXBElementtext/xml, application/xml, application/*+xml
JAXB annotated objectstext/xml, application/xml, application/*+xml
javax.ws.rs.core.MultivaluedMap<String, String>application/x-www-form-urlencoded [a]
javax.ws.rs.core.StreamingOutput*/* [b]
[a] This mapping is used for handling HTML form data.
[b] This mapping is only supported for returning data to a consumer.

Custom readers

Custom entity readers are responsible for mapping incoming HTTP requests into a Java type that a service's implementation can manipulate. They implement the javax.ws.rs.ext.MessageBodyReader interface.
The interface, shown in Example 49.1, “Message reader interface”, has two methods that need implementing:

Example 49.1. Message reader interface

package javax.ws.rs.ext;

public interface MessageBodyReader<T>
{
  public boolean isReadable(java.lang.Class<?> type, 
                            java.lang.reflect.Type genericType, 
                            java.lang.annotation.Annotation[] annotations, 
                            javax.ws.rs.core.MediaType mediaType);

  public T readFrom(java.lang.Class<T> type, 
                    java.lang.reflect.Type genericType, 
                    java.lang.annotation.Annotation[] annotations, 
                    javax.ws.rs.core.MediaType mediaType, 
                    javax.ws.rs.core.MultivaluedMap<String, String> httpHeaders, 
                    java.io.InputStream entityStream)
  throws java.io.IOException, WebApplicationException;
}
isReadable()
The isReadable() method determines if the reader is capable of reading the data stream and creating the proper type of entity representation. If the reader can create the proper type of entity the method returns true.

Table 49.2. Parameters used to determine if a reader can produce an entity

ParameterTypeDescription
typeClass<T>Specifies the actual Java class of the object used to store the entity.
genericTypeTypeSpecifies the Java type of the object used to store the entity. For example, if the message body is to be converted into a method parameter, the value will be the type of the method parameter as returned by the Method.getGenericParameterTypes() method.
annotationsAnnotation[]Specifies the list of annotations on the declaration of the object created to store the entity. For example if the message body is to be converted into a method parameter, this will be the annotations on that parameter returned by the Method.getParameterAnnotations() method.
mediaTypeMediatTypeSpecifies the MIME type of the HTTP entity.
readFrom()
The readFrom() method reads the HTTP entity and coverts it into the desired Java object. If the reading is successful the method returns the created Java object containing the entity. If an error occurs when reading the input stream the method should throw an IOException exception. If an error occurs that requires an HTTP error response, an WebApplicationException with the HTTP response should be thrown.
Table 49.3, “Parameters used to read an entity” describes the readFrom() method's parameters.

Table 49.3. Parameters used to read an entity

ParameterTypeDescription
typeClass<T>Specifies the actual Java class of the object used to store the entity.
genericTypeTypeSpecifies the Java type of the object used to store the entity. For example, if the message body is to be converted into a method parameter, the value will be the type of the method parameter as returned by the Method.getGenericParameterTypes() method.
annotationsAnnotation[]Specifies the list of annotations on the declaration of the object created to store the entity. For example if the message body is to be converted into a method parameter, this will be the annotations on that parameter returned by the Method.getParameterAnnotations() method.
mediaTypeMediatTypeSpecifies the MIME type of the HTTP entity.
httpHeadersMultivaluedMap<String, String>Specifies the HTTP message headers associated with the entity.
entityStreamInputStreamSpecifies the input stream containing the HTTP entity.
Important
This method should not close the input stream.
Before an MessageBodyReader implementation can be used as an entity reader, it must be decorated with the javax.ws.rs.ext.Provider annotation. The @Provider annotation alerts the runtime that the supplied implementation provides additional functionality. The implementation must also be registered with the runtime as described in the section called “Registering readers and writers”.
By default a custom entity provider handles all MIME types. You can limit the MIME types that a custom entity reader will handle using the javax.ws.rs.Consumes annotation. The @Consumes annotation specifies a comma separated list of MIME types that the custom entity provider reads. If an entity is not of a specified MIME type, the entity provider will not be selected as a possible reader.
Example 49.2, “XML source entity reader” shows an entity reader the consumes XML entities and stores them in a Source object.

Example 49.2. XML source entity reader

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Document;
import org.apache.cxf.jaxrs.ext.xml.XMLSource;

@Provider
@Consumes({"application/xml", "application/*+xml", "text/xml", "text/html" })
public class SourceProvider implements MessageBodyReader<Object>
{
  public boolean isReadable(Class<?> type,
                            Type genericType,
                            Annotation[] annotations,
                            MediaType mt)
  {
    return Source.class.isAssignableFrom(type) || XMLSource.class.isAssignableFrom(type);
  }

  public Object readFrom(Class<Object> source,
                         Type genericType,
                         Annotation[] annotations,
                         MediaType mediaType, 
                         MultivaluedMap<String, String> httpHeaders,
                         InputStream is)
  throws IOException
  {
    if (DOMSource.class.isAssignableFrom(source))
    {
      Document doc = null;
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder;
      try
      {
        builder = factory.newDocumentBuilder();
        doc = builder.parse(is);
      }
      catch (Exception e)
      {
        IOException ioex = new IOException("Problem creating a Source object");
        ioex.setStackTrace(e.getStackTrace());
        throw ioex;
       }

       return new DOMSource(doc);
    }
    else if (StreamSource.class.isAssignableFrom(source) || Source.class.isAssignableFrom(source))
    {
      return new StreamSource(is);
    }
    else if (XMLSource.class.isAssignableFrom(source))
    {
      return new XMLSource(is);
    }

    throw new IOException("Unrecognized source");
  }
}

Custom writers

Custom entity writers are responsible for mapping Java types into HTTP entities. They implement the javax.ws.rs.ext.MessageBodyWriter interface.
The interface, shown in Example 49.3, “Message writer interface”, has three methods that need implementing:

Example 49.3. Message writer interface

package javax.ws.rs.ext;

public interface MessageBodyWriter<T>
{
  public boolean isWriteable(java.lang.Class<?> type, 
                             java.lang.reflect.Type genericType, 
                             java.lang.annotation.Annotation[] annotations, 
                             javax.ws.rs.core.MediaType mediaType);

  public long getSize(T t,
                      java.lang.Class<?> type,
                      java.lang.reflect.Type genericType,
                      java.lang.annotation.Annotation[] annotations,
                      javax.ws.rs.core.MediaType mediaType);

  public void writeTo(T t,
                      java.lang.Class<?> type,
                      java.lang.reflect.Type genericType,
                      java.lang.annotation.Annotation[] annotations,
                      javax.ws.rs.core.MediaType mediaType,
                      javax.ws.rs.core.MultivaluedMap<String, Object> httpHeaders,
                      java.io.OutputStream entityStream)
  throws java.io.IOException, WebApplicationException;
}
isWriteable()
The isWriteable() method determines if the entity writer can map the Java type to the proper entity type. If the writer can do the mapping, the method returns true.
Table 49.4, “Parameters used to read an entity” describes the isWritable() method's parameters.

Table 49.4. Parameters used to read an entity

ParameterTypeDescription
typeClass<T>Specifies the Java class of the object being written.
genericTypeTypeSpecifies the Java type of object to be written, obtained either by reflection of a resource method return type or via inspection of the returned instance. The GenericEntity class, described in Section 47.4, “Returning entities with generic type information”, provides support for controlling this value.
annotationsAnnotation[]Specifies the list of annotations on the method returning the entity.
mediaTypeMediatTypeSpecifies the MIME type of the HTTP entity.
getSize()
The getSize() method is called before the writeTo(). It returns the length, in bytes, of the entity being written. If a positive value is returned the value is written into the HTTP message's Content-Length header.
Table 49.5, “Parameters used to read an entity” describes the getSize() method's parameters.

Table 49.5. Parameters used to read an entity

ParameterTypeDescription
tgenericSpecifies the instance being written.
typeClass<T>Specifies the Java class of the object being written.
genericTypeTypeSpecifies the Java type of object to be written, obtained either by reflection of a resource method return type or via inspection of the returned instance. The GenericEntity class, described in Section 47.4, “Returning entities with generic type information”, provides support for controlling this value.
annotationsAnnotation[]Specifies the list of annotations on the method returning the entity.
mediaTypeMediatTypeSpecifies the MIME type of the HTTP entity.
writeTo()
The writeTo() method converts a Java object into the desired entity type and writes the entity to the output stream. If an error occurs when writing the entity to the output stream the method should throw an IOException exception. If an error occurs that requires an HTTP error response, an WebApplicationException with the HTTP response should be thrown.
Table 49.6, “Parameters used to read an entity” describes the writeTo() method's parameters.

Table 49.6. Parameters used to read an entity

ParameterTypeDescription
tgenericSpecifies the instance being written.
typeClass<T>Specifies the Java class of the object being written.
genericTypeTypeSpecifies the Java type of object to be written, obtained either by reflection of a resource method return type or via inspection of the returned instance. The GenericEntity class, described in Section 47.4, “Returning entities with generic type information”, provides support for controlling this value.
annotationsAnnotation[]Specifies the list of annotations on the method returning the entity.
mediaTypeMediatTypeSpecifies the MIME type of the HTTP entity.
httpHeadersMultivaluedMap<String, Object>Specifies the HTTP response headers associated with the entity.
entityStreamOutputStreamSpecifies the output stream into which the entity is written.
Before a MessageBodyWriter implementation can be used as an entity writer, it must be decorated with the javax.ws.rs.ext.Provider annotation. The @Provider annotation alerts the runtime that the supplied implementation provides additional functionality. The implementation must also be registered with the runtime as described in the section called “Registering readers and writers”.
By default a custom entity provider handles all MIME types. You can limit the MIME types that a custom entity writer will handle using the javax.ws.rs.Produces annotation. The @Produces annotation specifies a comma separated list of MIME types that the custom entity provider generates. If an entity is not of a specified MIME type, the entity provider will not be selected as a possible writer.
Example 49.4, “XML source entity writer” shows an entity writer that takes Source objects and produces XML entities.

Example 49.4. XML source entity writer

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import org.apache.cxf.jaxrs.ext.xml.XMLSource;

@Provider
@Produces({"application/xml", "application/*+xml", "text/xml" })
public class SourceProvider implements MessageBodyWriter<Source>
{

  public boolean isWriteable(Class<?> type,
                             Type genericType,
                             Annotation[] annotations,
                             MediaType mt)
  {
    return Source.class.isAssignableFrom(type);
  }

  public void writeTo(Source source,
                      Class<?> clazz,
                      Type genericType,
                      Annotation[] annotations,
                      MediaType mediatype,
                      MultivaluedMap<String, Object> httpHeaders,
                      OutputStream os)
  throws IOException
  {
    StreamResult result = new StreamResult(os);
    TransformerFactory tf = TransformerFactory.newInstance();
    try
    {
      Transformer t = tf.newTransformer();
      t.transform(source, result);
    }
    catch (TransformerException te)
    {
      te.printStackTrace();
      throw new WebApplicationException(te);
    }
  }

  public long getSize(Source source,
                      Class<?> type, 
                      Type genericType, 
                      Annotation[] annotations,
                      MediaType mt)
  {
    return -1;
  }
}

Registering readers and writers

Before a JAX-RS application can use any custom entity providers, the custom providers must be registered with the runtime. Providers are registered with the runtime using either the jaxrs:providers element in the application's configuration file or using the JAXRSServerFactoryBean class.
The jaxrs:providers element is a child of the jaxrs:server element and contains a list of bean elements. Each bean element defines one entity provider.
Example 49.5, “Registering entity providers with the runtime” show a JAX-RS server configured to use a set of custom entity providers.

Example 49.5. Registering entity providers with the runtime

<beans ...>
  <jaxrs:server id="customerService" address="/">
    ...
    <jaxrs:providers>
      <bean id="isProvider" class="com.bar.providers.InputStreamProvider"/>
      <bean id="longProvider" class="com.bar.providers.LongProvider"/>
    </jaxrs:providers>
  </jaxrs:server>
</beans>
The JAXRSServerFactoryBean class is a Apache CXF extension that provides access to the configuration APIs. It has a setProvider() method that allows you to add instantiated entity providers to an application. Example 49.6, “Programmatically registering an entity provider” shows code for registering an entity provider programmatically.

Example 49.6. Programmatically registering an entity provider

import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
...
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
...
SourceProvider provider = new SourceProvider();
sf.setProvider(provider);
...