Movatterモバイル変換


[0]ホーム

URL:


Chapter 8. JAX-RS Entity Providers
Prev   Next

Links: Table of Contents | Single HTML

Chapter 8. JAX-RS Entity Providers

Table of Contents

8.1. Introduction
8.2. How to Write Custom Entity Providers
8.2.1. MessageBodyWriter
8.2.2. MessageBodyReader
8.3. Entity Provider Selection
8.4. JerseyMessageBodyWorkers API
8.5. Default Jersey Entity Providers

8.1. Introduction

Entity payload, if present in an received HTTP message, is passed to Jersey from an I/O container as an input stream. The stream may, for example, contain data represented as a plain text, XML or JSON document. However, in many JAX-RS components that process these inbound data, such as resource methods or client responses, the JAX-RS API user can access the inbound entity as an arbitrary Java object that is created from the content of the input stream based on the representation type information. For example, an entity created from an input stream that contains data represented as a XML document, can be converted to a custom JAXB bean. Similar concept is supported for the outbound entities. An entity returned from the resource method in the form of an arbitrary Java object can be serialized by Jersey into a container output stream as a specified representation. Of course, while JAX-RS implementations do provide default support for most common combinations of Java type and it's respective on-the-wire representation formats, JAX-RS implementations do not support the conversion described above for any arbitrary Java type and any arbitrary representation format by default. Instead, a generic extension concept is exposed in JAX-RS API to allow application-level customizations of this JAX-RS runtime to support for entity conversions. The JAX-RS extension API components that provide the user-level extensibility are typically referred to by several terms with the same meaning, such asentity providers,message body providers,message body workers ormessage body readers and writers. You may find all these terms used interchangeably throughout the user guide and they all refer to the same concept.

In JAX-RS extension API (or SPI - service provider interface, if you like) the concept is captured in 2 interfaces. One for handling inbound entity representation-to-Java de-serialization -MessageBodyReader<T> and the other one for handling the outbound entity Java-to-representation serialization -MessageBodyWriter<T>. AMessageBodyReader<T>, as the name suggests, is an extension that supports reading the message body representation from an input stream and converting the data into an instance of a specific Java type. AMessageBodyWriter<T> is then responsible for converting a message payload from an instance of a specific Java type into a specific representation format that is sent over the wire to the other party as part of an HTTP message exchange. Both of these providers can be used to provide message payload serialization and de-serialization support on the server as well as the client side. A message body reader or writer is always used whenever a HTTP request or response contains an entity and the entity is either requested by the application code (e.g. injected as a parameter of JAX-RS resource method or a response entity read on the client from aResponse) or has to be serialized and sent to the other party (e.g. an instance returned from a JAX-RS resource method or a request entity sent by a JAX-RS client).

8.2. How to Write Custom Entity Providers

A best way how to learn about entity providers is to walk through an example of writing one. Therefore we will describe here the process of implementing a customMessageBodyWriter<T> andMessageBodyReader<T> using a practical example. Let's first setup the stage by defining a JAX-RS resource class for the server side story of our application.

Example 8.1. Example resource class

@Path("resource")public class MyResource {    @GET    @Produces("application/xml")    public MyBean getMyBean() {        return new MyBean("Hello World!", 42);    }    @POST    @Consumes("application/xml")    public String postMyBean(MyBean myBean) {        return myBean.anyString;    }}


The resource class definesGET andPOST resource methods. Both methods work with an entity that is an instance ofMyBean.

TheMyBean class is defined in the next example:

Example 8.2. MyBean entity class

@XmlRootElementpublic class MyBean {    @XmlElement    public String anyString;    @XmlElement    public int anyNumber;    public MyBean(String anyString, int anyNumber) {        this.anyString = anyString;        this.anyNumber = anyNumber;    }    // empty constructor needed for deserialization by JAXB    public MyBean() {    }    @Override    public String toString() {        return "MyBean{" +            "anyString='" + anyString + '\'' +            ", anyNumber=" + anyNumber +            '}';    }}


8.2.1. MessageBodyWriter

TheMyBean is a JAXB-annotated POJO. InGET resource method we return the instance of MyBean and we would like Jersey runtime to serialize it into XML and write it as an entity body to the response output stream. We design a customMessageBodyWriter<T> that can serialize this POJO into XML. See the following code sample:

Note

Please note, that this is only a demonstration of how to write a custom entity provider. Jersey already contains default support for entity providers that can serialize JAXB beans into XML.

Example 8.3. MessageBodyWriter example

@Produces("application/xml")public class MyBeanMessageBodyWriter implements MessageBodyWriter<MyBean> {    @Override    public boolean isWriteable(Class<?> type, Type genericType,                               Annotation[] annotations, MediaType mediaType) {        return type == MyBean.class;    }    @Override    public long getSize(MyBean myBean, Class<?> type, Type genericType,                        Annotation[] annotations, MediaType mediaType) {        // deprecated by JAX-RS 2.0 and ignored by Jersey runtime        return -1;    }    @Override    public void writeTo(MyBean myBean,                        Class<?> type,                        Type genericType,                        Annotation[] annotations,                        MediaType mediaType,                        MultivaluedMap<String, Object> httpHeaders,                        OutputStream entityStream)                        throws IOException, WebApplicationException {        try {            JAXBContext jaxbContext = JAXBContext.newInstance(MyBean.class);            // serialize the entity myBean to the entity output stream            jaxbContext.createMarshaller().marshal(myBean, entityStream);        } catch (JAXBException jaxbException) {            throw new ProcessingException(                "Error serializing a MyBean to the output stream", jaxbException);        }    }}


TheMyBeanMessageBodyWriter implements theMessageBodyWriter<T> interface that contains three methods. In the next sections we'll explore these methods more closely.

8.2.1.1. MessageBodyWriter.isWriteable

A methodisWriteable should return true if theMessageBodyWriter<T> is able to write the given type. Method does not decide only based on the Java type of the entity but also on annotations attached to the entity and the requested representation media type.

Parameterstype andgenericType both define the entity, wheretype is a raw Java type (for example, ajava.util.List class) andgenericType is aParameterizedType including generic information (for exampleList<String>).

Parameterannotations contains annotations that are either attached to the resource method and/or annotations that are attached to the entity by building response like in the following piece of code:

Example 8.4. Example of assignment of annotations to a response entity

@Path("resource")public static class AnnotatedResource {    @GET    public Response get() {        Annotation annotation = AnnotatedResource.class                            .getAnnotation(Path.class);        return Response.ok()                .entity("Entity", new Annotation[] {annotation}).build();    }}


In the example above, theMessageBodyWriter<T> would getannotations parameter containing a JAX-RS@GET annotation as it annotates the resource method and also a@Path annotation as it is passed in the response (but not because it annotates the resource; only resource method annotations are included). In the case ofMyResource and methodgetMyBean the annotations would contain the@GET and the@Produces annotation.

The last parameter of theisWriteable method is themediaType which is the media type attached to the response entity by annotating the resource method with a@Produces annotation or the request media type specified in the JAX-RS Client API. In our example, the media type passed to providers for the resourceMyResource and methodgetMyBean would be"application/xml".

In our implementation of theisWriteable method, we just check that the type isMyBean. Please note, that this method might be executed multiple times by Jersey runtime as Jersey needs to check whether this provider can be used for a particular combination of entity Java type, media type, and attached annotations, which may be potentially a performance hog. You can limit the number of execution by properly defining the@Produces annotation on theMessageBodyWriter<T>. In our case thanks to@Produces annotation, the provider will be considered as writeable (and the methodisWriteable might be executed) only if the media type of the outbound message is"application/xml". Additionally, the provider will only be considered as possible candidate and itsisWriteable method will be executed, if the generic type of the provider is either a sub class or super class oftype parameter.

8.2.1.2. MessageBodyWriter.writeTo

Once a message body writer is selected as the most appropriate (see theSection 8.3, “Entity Provider Selection” for more details on entity provider selection), itswriteTo method is invoked. This method receives parameters with the same meaning as inisWriteable as well as a few additional ones.

In addition to the parameters already introduced, thewriteTo method defies alsohttpHeaders parameter, that contains HTTP headers associated with the outbound message.

Note

When aMessageBodyWriter<T> is invoked, the headers still can be modified in this point and any modification will be reflected in the outbound HTTP message being sent. The modification of headers must however happen before a first byte is written to the supplied output stream.

Another new parameter,myBean, contains the entity instance to be serialized (the type of entity corresponds to generic type ofMessageBodyWriter<T>). Related parameterentityStream contains the entity output stream to which the method should serialize the entity. In our case we use JAXB to marshall the entity into theentityStream. Note, that theentityStream is not closed at the end of method; the stream will be closed by Jersey.

Important

Do not close the entity output stream in thewriteTo method of yourMessageBodyWriter<T> implementation.

8.2.1.3. MessageBodyWriter.getSize

The method is deprecated since JAX-RS 2.0 and Jersey 2 ignores the return value. In JAX-RS 1.0 the method could return the size of the entity that would be then used for "Content-Length" response header. In Jersey 2.0 the "Content-Length" parameter is computed automatically using an internal outbound entity buffering. For details about configuration options of outbound entity buffering see the javadoc ofMessageProperties, propertyOUTBOUND_CONTENT_LENGTH_BUFFER which configures the size of the buffer.

Note

You can disable the Jersey outbound entity buffering by setting the buffer size to 0.

8.2.1.4. Testing aMessageBodyWriter<T>

Before testing theMyBeanMessageBodyWriter, the writer must be registered as a custom JAX-RS extension provider. It should either be added to your applicationResourceConfig, or returned from your customApplication sub-class, or annotated with@Provider annotation to leverage JAX-RS provider auto-discovery feature.

After registering theMyBeanMessageBodyWriter andMyResource class in our application, the request can be initiated (in this example from Client API).

Example 8.5. Client code testing MyBeanMessageBodyWriter

WebTarget webTarget = // initialize web target to the context root            // of example applicationResponse response = webTarget.path("resource")                        .request(MediaType.APPLICATION_XML).get();System.out.println(response.getStatus());String myBeanXml = response.readEntity(String.class);System.out.println(myBeanXml);


The client code initiates theGET which will be matched to the resource methodMyResource.getMyBean(). The response entity is de-serialized as aString.

The result of console output is:

Example 8.6. Result of MyBeanMessageBodyWriter test

200<?xml version="1.0" encoding="UTF-8" standalone="yes"?><myBean><anyString>Hello World!</anyString><anyNumber>42</anyNumber></myBean>


The returned status is 200 and the entity is stored in the response in aXML format. Next, we will look at how the Jersey de-serializes this XML document into aMyBean consumed by ourPOST resource method.

8.2.2. MessageBodyReader

In order to de-serialize the entity ofMyBean on the server or the client, we need to implement a customMessageBodyReader<T>.

Note

Please note, that this is only a demonstration of how to write a custom entity provider. Jersey already contains default support for entity providers that can serialize JAXB beans into XML.

OurMessageBodyReader<T> implementation is listed inExample 8.7, “MessageBodyReader example”.

Example 8.7. MessageBodyReader example

public static class MyBeanMessageBodyReader        implements MessageBodyReader<MyBean> {@Overridepublic boolean isReadable(Class<?> type, Type genericType,    Annotation[] annotations, MediaType mediaType) {    return type == MyBean.class;}@Overridepublic MyBean readFrom(Class<MyBean> type,    Type genericType,    Annotation[] annotations, MediaType mediaType,    MultivaluedMap<String, String> httpHeaders,    InputStream entityStream)        throws IOException, WebApplicationException {    try {        JAXBContext jaxbContext = JAXBContext.newInstance(MyBean.class);        MyBean myBean = (MyBean) jaxbContext.createUnmarshaller()            .unmarshal(entityStream);        return myBean;    } catch (JAXBException jaxbException) {        throw new ProcessingException("Error deserializing a MyBean.",            jaxbException);    }}}


It is obvious that theMessageBodyReader<T> interface is similar toMessageBodyWriter<T>. In the next couple of sections we will explore it's API methods.

8.2.2.1. MessageBodyReader.isReadable

It defines the methodisReadable() which has a very similar meaning as methodisWriteable() inMessageBodyWriter<T>. The method returnstrue if it is able to de-serialize the given type. Theannotations parameter contains annotations that are attached to the entity parameter in the resource method. In ourPOST resource methodpostMyBean the entity parametermyBean is not annotated, therefore no annotation will be passed to the isReadable. ThemediaType parameter contains the entity media type. The media type, in our case, must be consumable by thePOST resource method, which is specified by placing a JAX-RS@Consumes annotation to the method. The resource methodpostMyBean() is annotated with@Consumes("application/xml"), therefore for purpose of de-serialization of entity for thepostMyBean() method, only requests with entities represented as"application/xml" media type will match the method. However, this method might be executed for entity types that are sub classes or super classes of the declared generic type on theMessageBodyReader<T> will be also considered. It is a responsibility of theisReadable method to decide whether it is able to de-serialize the entity and type comparison is one of the basic decision steps.

Tip

In order to reduce number ofisReadable executions, always define correctly the consumable media type(s) with the@Consumes annotation on your customMessageBodyReader<T>.

8.2.2.2. MessageBodyReader.readFrom

ThereadForm() method gets the parameters with the same meaning as inisReadable(). The additionalentityStream parameter provides a handle to the entity input stream from which the entity bytes should be read and de-serialized into a Java entity which is then returned from the method. OurMyBeanMessageBodyReader de-serializes the incoming XML data into an instance ofMyBean using JAXB.

Important

Do not close the entity input stream in yourMessageBodyReader<T> implementation. The stream will be automatically closed by Jersey runtime.

8.2.2.3. Testing aMessageBodyWriter<T>

Now let's send a test request using the JAX-RS Client API.

Example 8.8. Testing MyBeanMessageBodyReader

final MyBean myBean = new MyBean("posted MyBean", 11);Response response = webTarget.path("resource").request("application/xml")        .post(Entity.entity(myBean, "application/xml"));System.out.println(response.getStatus());final String responseEntity = response.readEntity(String.class);System.out.println(responseEntity);


The console output is:

Example 8.9. Result of testing MyBeanMessageBodyReader

200posted MyBean


8.2.2.4. Using Entity Providers with JAX-RS Client API

Both,MessageBodyReader<T> andMessageBodyWriter<T> can be registered in a configuration of JAX-RS Client API components typically without any need to change their code. The exampleExample 8.10, “MessageBodyReader registered on a JAX-RS client” is a variation on theExample 8.5, “Client code testing MyBeanMessageBodyWriter” listed in one of the previous sections.

Example 8.10. MessageBodyReader registered on a JAX-RS client

Client client = ClientBuilder.newBuilder()    .register(MyBeanMessageBodyReader.class).build();Response response = client.target("http://example/comm/resource")    .request(MediaType.APPLICATION_XML).get();System.out.println(response.getStatus());MyBean myBean = response.readEntity(MyBean.class);System.out.println(myBean);


The code above registersMyBeanMessageBodyReader to theClient configuration using aClientBuilder which means that the provider will be used for anyWebTarget produced by theclient instance.

Note

You could also register the JAX-RS entity (and any other) providers to individualWebTarget instances produced by the client.

Then, using the fluent chain of method invocations, a resource target pointing to ourMyResource is defined, a HTTPGET request is invoked. The response entity is then read as an instance of aMyBean type by invoking theresponse.readEntity method, that internally locates the registeredMyBeanMessageBodyReader and uses it for entity de-serialization.

The console output for the example is:

Example 8.11. Result of client code execution

200MyBean{anyString='Hello World!', anyNumber=42}


8.3. Entity Provider Selection

Usually there are many entity providers registered on the server or client side (be default there must be at least providers mandated by the JAX-RS specification, such as providers for primitive types, byte array, JAXB beans, etc.). JAX-RS defines an algorithm for selecting the most suitable provider for entity processing. This algorithm works with information such as entity Java type and on-the-wire media type representation of entity, and searches for the most suitable entity provider from the list of available providers based on the supported media type declared on each provider (defined by@Produces or@Consumes on the provider class) as well as based on the generic type declaration of the available providers. When a list of suitable candidate entity providers is selected and sorted based on the rules defined in JAX-RS specification, a JAX-RS runtime then it invokesisReadable orisWriteable method respectively on each provider in the list until a first provider is found that returnstrue. This provider is then used to process the entity.

The following steps describe the algorithm for selecting aMessageBodyWriter<T> (extracted from JAX-RS with little modifications). The steps refer to the previously discussed example application. TheMessageBodyWriter<T> is searched for purpose of deserialization ofMyBean entity returned from the methodgetMyBean. So,type isMyBean and media type"application/xml". Let's assume the runtime contains also registered providers, namely:

A:@Produces("application/*") with generic type<Object>
B:@Produces("*/*") with generic type<MyBean>
C:@Produces("text/plain") with generic type<MyBean>
D:@Produces("application/xml") with generic type<Object>
MyBeanMessageBodyWriter:@Produces("application/xml") with generic type<MyBean>

The algorithm executed by a JAX-RS runtime to select a properMessageBodyWriter<T> implementation is illustrated inProcedure 8.1, “MessageBodyWriter<T> Selection Algorithm”.

Procedure 8.1. MessageBodyWriter<T> Selection Algorithm

  1. Obtain the object that will be mapped to the message entity body. For a return type of Response or subclasses, the object is the value of the entity property, for other return types it is the returned object.

    So in our case, for the resource methodgetMyBean the type will beMyBean.

  2. Determine the media type of the response.

    In our case, for resource methodgetMyBean annotated with@Produces("application/xml"), the media type will be"application/xml".

  3. Select the set of MessageBodyWriter providers that support the object and media type of the message entity body.

    In our case, for entity media type"application/xml" and typeMyBean, the appropriateMessageBodyWriter<T> will be theA,B,D andMyBeanMessageBodyWriter. The providerC does not define the appropriate media type.A andB are fine as their type is more generic and compatible with"application/xml".

  4. Sort the selected MessageBodyWriter providers with a primary key of generic type where providers whose generic type is the nearest superclass of the object class are sorted first and a secondary key of media type. Additionally, JAX-RS specification mandates that custom, user registered providers have to be sorted ahead of default providers provided by JAX-RS implementation. This is used as a tertiary comparison key. User providers are places prior to Jersey internal providers in to the final ordered list.

    The sorted providers will be:MyBeanMessageBodyWriter,B.D,A.

  5. Iterate through the sortedMessageBodyWriter<T> providers and, utilizing theisWriteable method of each until you find aMessageBodyWriter<T> that returnstrue.

    The first provider in the list - ourMyBeanMessageBodyWriter returnstrue as it compares types and the types matches. If it would returnfalse, the next providerB would by check by invoking itsisWriteable method.

  6. If step 5 locates a suitableMessageBodyWriter<T> then use its writeTo method to map the object to the entity body.

    MyBeanMessageBodyWriter.writeTo will be executed and it will serialize the entity.

    • Otherwise, the server runtime MUST generate anInternalServerErrorException, a subclass ofWebApplicationException with its status set to 500, and no entity and the client runtime MUST generate aProcessingException.

      We have successfully found a provider, thus no exception is generated.

Note

JAX-RS 3.x/2.x is incompatible with JAX-RS 1.x in one step of the entity provider selection algorithm. JAX-RS 1.x defines sorting keys priorities in theStep 4 in exactly opposite order. So, in JAX-RS 1.x the keys are defined in the order: primary media type, secondary type declaration distance where custom providers have always precedence to internal providers. If you want to force Jersey to use the algorithm compatible with JAX-RS 1.x, setup the property (toResourceConfig or return fromApplication from itsgetProperties method):

jersey.config.workers.legacyOrdering=true

Documentation of this property can be found in the javadoc ofMessageProperties.

The algorithm for selection ofMessageBodyReader<T> is similar, including the incompatibility between JAX-RS 3.x/2.x and JAX-RS 1.x and the property to workaround it. The algorithm is defined as follows:

Procedure 8.2. MessageBodyReader<T> Selection Algorithm

  1. Obtain the media type of the request. If the request does not contain aContent-Type header then useapplication/octet-stream media type.

  2. Identify the Java type of the parameter whose value will be mapped from the entity body. The Java type on the server is the type of the entity parameter of the resource method. On the client it is theClass passed toreadFrom method.

  3. Select the set of availableMessageBodyReader<T> providers that support the media type of the request.

  4. Iterate through the selectedMessageBodyReader<T> classes and, utilizing theirisReadable method, choose the firstMessageBodyReader<T> provider that supports the desired combination of Java type/media type/annotations parameters.

  5. IfStep 4 locates a suitableMessageBodyReader<T>, then use itsreadFrom method to map the entity body to the desired Java type.

    • Otherwise, the server runtime MUST generate aNotSupportedException (HTTP 415 status code) and no entity and the client runtime MUST generate an instance ofProcessingException.

8.4. JerseyMessageBodyWorkers API

In case you need to directly work with JAX-RS entity providers, for example to serialize an entity in your resource method, filter or in a composite entity provider, you would need to perform quite a lot of steps. You would need to choose the appropriateMessageBodyWriter<T> based on the type, media type and other parameters. Then you would need to instantiate it, check it byisWriteable method and basically perform all the steps that are normally performed by Jersey (seeProcedure 8.2, “MessageBodyReader<T> Selection Algorithm”).

To remove this burden from developers, Jersey exposes a proprietary public API that simplifies the manipulation of entity providers. The API is defined byMessageBodyWorkers interface and Jersey provides an implementation that can be injected using the@Context injection annotation. The interface declares methods for selection of most appropriateMessageBodyReader<T> andMessageBodyWriter<T> based on the rules defined in JAX-RS spec, methods for writing and reading entity that ensure proper and timely invocation of interceptors and other useful methods.

See the following example of usage ofMessageBodyWorkers.

Example 8.12. Usage of MessageBodyWorkers interface

@Path("workers")public static class WorkersResource {    @Context    private MessageBodyWorkers workers;    @GET    @Produces("application/xml")    public String getMyBeanAsString() {        final MyBean myBean = new MyBean("Hello World!", 42);        // buffer into which myBean will be serialized        ByteArrayOutputStream baos = new ByteArrayOutputStream();        // get most appropriate MBW        final MessageBodyWriter<MyBean> messageBodyWriter =                workers.getMessageBodyWriter(MyBean.class, MyBean.class,                        new Annotation[]{}, MediaType.APPLICATION_XML_TYPE);        try {            // use the MBW to serialize myBean into baos            messageBodyWriter.writeTo(myBean,                MyBean.class, MyBean.class, new Annotation[] {},                MediaType.APPLICATION_XML_TYPE, new MultivaluedHashMap<String, Object>(),                baos);        } catch (IOException e) {            throw new RuntimeException(                "Error while serializing MyBean.", e);        }        final String stringXmlOutput = baos.toString();        // stringXmlOutput now contains XML representation:        // "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>        // <myBean><anyString>Hello World!</anyString>        // <anyNumber>42</anyNumber></myBean>"        return stringXmlOutput;    }}


In the example a resource injectsMessageBodyWorkers and uses it for selection of the most appropriateMessageBodyWriter<T>. Then the writer is utilized to serialize the entity into the buffer as XML document. TheString content of the buffer is then returned. This will cause that Jersey will not useMyBeanMessageBodyWriter to serialize the entity as it is already in theString type (MyBeanMessageBodyWriter does not supportString). Instead, a simpleString-basedMessageBodyWriter<T> will be chosen and it will only serialize theString with XML to the output entity stream by writing out the bytes of theString.

Of course, the code in the example does not bring any benefit as the entity could have been serialized byMyBeanMessageBodyWriter by Jersey as in previous examples; the purpose of the example was to show how to useMessageBodyWorkers in a resource method.

8.5. Default Jersey Entity Providers

Jersey internally contains entity providers for these types with combination of media types (in brackets):

byte[] (*/*)
String (*/*)
InputStream (*/*)
Reader (*/*)
File (*/*)
DataSource (*/*)
Source (text/xml,application/xml and media types of the formapplication/*+xml)
JAXBElement (text/xml,application/xml and media types of the formapplication/*+xml)
MultivaluedMap<K,V> (application/x-www-form-urlencoded)
Form (application/x-www-form-urlencoded)
StreamingOutput ((*/*)) - this class can be used as an lightweightMessageBodyWriter<T> that can be returned from a resource method
Boolean,Character andNumber (text/plain) - corresponding primitive types supported via boxing/unboxing conversion

For other media type supported in jersey please see theChapter 9,Support for Common Media Type Representations which describes additional Jersey entity provider extensions for serialization to JSON, XML, serialization of collections,Multi Part and others.


Prev   Next
Chapter 7. Representations and Responses Home Chapter 9. Support for Common Media Type Representations

[8]ページ先頭

©2009-2025 Movatter.jp