X

JAX-RS Custom Entity Providers (TOTD #193)

Guest Author


JAX-RS defines Entity Providers that supply mapping services
between on-the-wire representations and their associated Java types.
The entities, also known as "message payload" or "payload" represent
the main part of an HTTP message. These are specified as method
parameters and return types of resource methods. Several standard
Java types such as String, byte[], javax.xml.bind.JAXBElement,
java.io.InputStream, java.io.File, and
others have a pre-defined mapping and is required by the
specification. Applications may provide their own mapping to custom
types using MessageBodyReader and MessageBodyWriter
interfaces. This allows to extend the JAX-RS runtime easily to
support your own custom entity providers.



The MessageBodyReader interface defines the contract
for a provider that supports the conversion of a stream to a Java
type. The MessageBodyWriter interface defines the
contract for a provider that supports the conversion of a Java type
to a stream. This Tip Of The Day
(TOTD) will explain how to write these custom entity providers.



Note, this functionality was defined in JAX-RS 1.0 as opposed to
Client API, Client- and Server-side async, Hypermedia, and many
other features. The complete source code for this sample application
can be href="//cdn.app.compendium.com/uploads/user/e7c690e8-6ff9-102a-ac6d-e4aebca50425/f4a5b21d-66fa-4885-92bf-c4e81c06d916/File/242574754936f77319e43da6ede89759/totd193_readerwriter.zip">downloaded
here and will work on href="http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/glassfish-4.0-b70.zip">GlassFish
4.0 b70.



Here is a simple resource that returns name of the fruit based upon
the index passed as parameter:


@Path("fruits")
public class MyResource {
private String[] response = { "apple", "banana", "mango" };

@POST
public String getFruit(int index) {
return response[index % 3];
}
}

This resource can be invoked using the newly introduced Client API
as:


Client client = ClientFactory.newClient();
WebTarget target = client.target("http://"
+ request.getServerName()
+ ":"
+ request.getServerPort()
+ request.getContextPath()
+ "/webresources/fruits");
String fruit = target
.request()
.post(Entity.text("1"), String.class);



If we update the resource such that the index
parameter is passed as the following object:


public class MyObject implements Serializable {
public static final String MIME_TYPE = "application/myType";

private int index;

public MyObject() {
}

public MyObject(int index) {
this.index = index;
}

public int getIndex() {
return index;
}

public void setIndex(int index) {
this.index = index;
}
}


Then the resource method would look like:


@POST
@Consumes(value=MyObject.MIME_TYPE)
public String getFruit(MyObject mo) {
return response[Integer.valueOf(mo.getIndex()) % 3];
}



The additional @Consumes annotation defines the media
type that the method can accept. The following custom entity
providers are defined to support this custom type. The first one, MyReader,
defines the conversion of on-the-wire representation to MyObject
Java type.


@Provider
@Consumes(MyObject.MIME_TYPE)
public class MyReader implements MessageBodyReader<MyObject> {

@Override
public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
return MyObject.class.isAssignableFrom(type);
}

@Override
public MyObject readFrom(Class<MyObject> type,
Type type1,
Annotation[] antns,
MediaType mt, MultivaluedMap<String, String> mm,
InputStream in) throws IOException, WebApplicationException {
try {
ObjectInputStream ois = new ObjectInputStream(in);
return (MyObject)ois.readObject();
} catch (ClassNotFoundException ex) {
Logger.getLogger(MyReader.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}


The interface require two methods, isReadable and readFrom,
to be implemented. The implementation has @Consumes
annotation restricting the media type supported by this entity
provider.



The second one, MyWriter, defines the conversion from
MyObject Java type to on-the-wire representation.


@Provider
@Produces(MyObject.MIME_TYPE)
public class MyWriter implements MessageBodyWriter<MyObject> {

@Override
public boolean isWriteable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
return MyObject.class.isAssignableFrom(type);
}

@Override
public long getSize(MyObject t, Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
// As of JAX-RS 2.0, the method has been deprecated and the
// value returned by the method is ignored by a JAX-RS runtime.
// All MessageBodyWriter implementations are advised to return -1 from
// the method.

return -1;
}

@Override
public void writeTo(MyObject t,
Class<?> type,
Type type1,
Annotation[] antns,
MediaType mt,
MultivaluedMap<String, Object> mm,
OutputStream out) throws IOException, WebApplicationException {
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(t);
}
}

The interface require three methods isWriteable, getSize,
writeTo to be implemented. The implementation of
getSize method is recommended to return -1 as JAX-RS runtime is
required to compute the actual Content-Length header
value. The implementation has @Produces annotation
restricting the media type supported by this entity provider.



The reader and writer entity providers are marked with @Provider
and are thus automatically discovered at runtime on the server-side.
But until JERSEY-1634
is fixed, they need to be explicitly specified in the Application
class as:


@ApplicationPath("webresources")
public class MyApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
resources.add(MyResource.class);
resources.add(MyReader.class);
resources.add(MyWriter.class);
return resources;
}
}



On the client-side, the providers need to be explicitly registered
as shown:


Client client = ClientFactory.newClient();
client
.configuration()
.register(MyReader.class)
.register(MyWriter.class);
String fruit = target
.request()
.post(Entity.entity(new MyObject(1), MyObject.MIME_TYPE), String.class);


Notice, how invoking the endpoint requires to specify the media type
as part of post method invocation.



The complete source code for this sample application can be href="//cdn.app.compendium.com/uploads/user/e7c690e8-6ff9-102a-ac6d-e4aebca50425/f4a5b21d-66fa-4885-92bf-c4e81c06d916/File/242574754936f77319e43da6ede89759/totd193_readerwriter.zip">downloaded
here and will work on href="http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/glassfish-4.0-b70.zip">GlassFish
4.0 b70.

Here are some more pointers to follow

  • href="http://jcp.org/aboutJava/communityprocess/pr/jsr339/index.html">JAX-RS
    2
    Specification Public Review
  • Latest status on
    specification
    (jax-rs-spec.java.net)

  • href="http://jax-rs-spec.java.net/nonav/2.0-SNAPSHOT/apidocs/index.html">Latest
    JAX-RS
    2.0 Javadocs
  • Latest status on Jersey
    (Reference Implementation of JAX-RS 2 - jersey.java.net)
  • href="http://jersey.java.net/nonav/apidocs/snapshot/jersey/index.html">Latest
    Jersey
    API Javadocs
  • href="http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/latest-glassfish.zip">Latest
    GlassFish
    4.0 Promoted Build

  • Follow @gf_jersey

Provide feedback on Jersey 2 to href="http://java.net/projects/jersey/lists/users/archive">users@jersey.java.net
and JAX-RS specification to href="http://java.net/projects/jax-rs-spec/lists/users/archive">users@jax-rs-spec.java.net.



Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha