Monday Jan 07, 2013

JAX-RS Custom Entity Providers (TOTD #193)


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 downloaded here and will work on 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 downloaded here and will work on GlassFish 4.0 b70.

Here are some more pointers to follow

Provide feedback on Jersey 2 to users@jersey.java.net and JAX-RS specification to users@jax-rs-spec.java.net.


About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« January 2013 »
SunMonTueWedThuFriSat
  
1
3
4
5
6
8
9
10
12
13
14
15
16
17
19
20
21
23
24
25
27
31
  
       
Today