Monday Mar 04, 2013

Consuming and Producing JSON using JAX-RS Entity Providers and JSR 353 Streaming API (TOTD# 210)


TOTD #193 explained how JAX-RS Entity Providers can be used to provide mapping between on-the-wire representations and their associated Java types. This blog shows how you can use Java API for JSON Processing (JSR 353), already integrated in GlassFish 4 promoted builds, to produce and consume JSON payload using Entity Providers.

The source code in this blog can be downloaded here and runs on GlassFish b76.

Lets say your domain object is defined as:

public class MyObject {

  private String name;
  private int age;

  //. . .
}

And your resource endpoint is defined as:

@Path("endpoint")
public class MyResource {
  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  public MyObject echoObject(MyObject mo) {
    return mo;
  }
}

This is just echoing the domain object but I suspect your domain logic would be more complex than that ;-)

Using JAX-RS Client API, this endpoint can be invoked as:

WebTarget target = client.target(".../endpoint");
MyObject mo = target
               .request()
               .post(
                 Entity.entity(new MyObject("Duke", 18), MediaType.APPLICATION_JSON),
                 MyObject.class);
System.out.println("Received response: " + mo.getName() + ", " + mo.getAge() + "<br><br>");

The MessageBodyWriter.writeTo method, that writes MyObject to the underlying OutputStream, uses Streaming API from JSR 353 and looks like:

@Override
public void writeTo(MyObject t,
                    Class<?> type,
                    Type type1,
                    Annotation[] antns,
                    MediaType mt,
                    MultivaluedMap<String, Object> mm,
                    OutputStream out)
            throws IOException, WebApplicationException {
  JsonGeneratorFactory factory = Json.createGeneratorFactory();
  JsonGenerator gen = factory.createGenerator(out);
  gen.writeStartObject()
     .write("name", t.getName())
     .write("age", t.getAge())
     .writeEnd();
  gen.flush();
}

Similarly MessageBodyReader.readFrom method, that reads MyObject from the underlying InputStream, uses Streaming API from JSR 353 and looks like:

@Override
public MyObject readFrom(Class<MyObject> type,
                         Type type1,
                         Annotation[] antns,
                         MediaType mt,
                         MultivaluedMap<String, String> mm,
                         InputStream in)
                throws IOException, WebApplicationException {
  MyObject mo = new MyObject();
  JsonParser parser = Json.createParser(in);
  while (parser.hasNext()) {
    switch (parser.next()) {
      case KEY_NAME:
        String key = parser.getString();
        parser.next();
        switch (key) {
          case "name":
            mo.setName(parser.getString());
            break;
          case "age":
            mo.setAge(parser.getIntValue());
            break;
          default:
            break;
        }
        break;
      default:
        break;
    }
  }
  return mo;
}

The code is pretty straight forward and refer to Java API for JSON Processing javadocs if you need help in understanding the code.

Download the source code and enjoy!

Friday Jan 11, 2013

JAX-RS Client API and GlassFish 4 - Logging HTTP messages (TOTD #194)


One of the main features added in JAX-RS 2 is Client API. This API is used to access Web resources and provides integration with JAX-RS Providers. Without this API, the users need to use a low-level HttpUrlConnection to access the REST endpoint.

If the resource looks like:

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

@GET
public String get() {

then, before this API, the endpoint can be accessed as:

URL url = new URL("http://. . ./webresources/fruit");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(false);
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
    //. . .
}
With this newly introduced API, the endpoint can be accessed as:

Client client = ClientFactory.newClient();
WebTarget target = client.target("http://. . ./webresources/fruit");
String response = target.request().get(String.class);
Client is the entry point to the Client API and is used to build and execute client requests and consume responses returned. The default instance of Client can be obtained by calling ClientFactory.newClient. Using this we can create a WebTarget by specifying URI of the web resource. These targets are then used to prepare client request invocation by specifying additional query or matrix parameters and resolving the URI template for different names. Finally, one of the HTTP methods is invoked on the prepared client.

The fluency of the API hides the complexity but a better understanding of the flow allows to write better code.

All the classes introduced in the specification are available in javax.ws.rs.client package.

Lets take a look at a complete sample. The complete source code can be downloaded here.

For a resource defined as:

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

@GET
public String get() {
return Database.getAll();
}

@GET
@Path("{name}")
public String get(@PathParam("name")String payload) {
return Database.get(payload);
}

@POST
public void post(String payload) {
Database.add(payload);
}

@PUT
public void put(String payload) {
Database.add(payload);
}

@DELETE
public void delete(String payload) {
Database.delete(payload);
}
}

A Client invoking all the HTTP methods look like:

Client client = ClientFactory.newClient();
WebTarget target = client.target("http://"
+ request.getServerName()
+ ":"
+ request.getServerPort()
+ request.getContextPath()
+ "/webresources/fruit");

// POST
target.request().post(Entity.text("apple"));

// PUT
target.request().put(Entity.text("banana"));

// GET (all)
String r = target.request().get(String.class);

// GET (one)
r = target.path("apple").request().get(String.class);

// DELETE
target.path("banana").request().delete();

// GET (all)
r = target.request().get(String.class);

And here are the message dumps:

INFO: 1 * LoggingFilter - Request received on thread http-listener-1(5)
1 > POST http://localhost:8080/endpoint/webresources/fruit
1 > Content-Type: text/plain
apple

INFO: 2 * LoggingFilter - Response received on thread http-listener-1(5)
2 < 204
2 < Date: Fri, 11 Jan 2013 22:21:23 GMT
2 < Server: GlassFish Server Open Source Edition 4.0
2 < X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.7)

INFO: 3 * LoggingFilter - Request received on thread http-listener-1(5)
3 > PUT http://localhost:8080/endpoint/webresources/fruit
3 > Content-Type: text/plain
banana

INFO: 4 * LoggingFilter - Response received on thread http-listener-1(5)
4 < 204
4 < Date: Fri, 11 Jan 2013 22:21:23 GMT
4 < Server: GlassFish Server Open Source Edition 4.0
4 < X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.7)

INFO: 5 * LoggingFilter - Request received on thread http-listener-1(5)
5 > GET http://localhost:8080/endpoint/webresources/fruit

INFO: 6 * LoggingFilter - Response received on thread http-listener-1(5)
6 < 200
6 < Date: Fri, 11 Jan 2013 22:21:23 GMT
6 < Content-Length: 15
6 < Content-Type: text/html
6 < Server: GlassFish Server Open Source Edition 4.0
6 < X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.7)
[apple, banana]

INFO: 7 * LoggingFilter - Request received on thread http-listener-1(5)
7 > GET http://localhost:8080/endpoint/webresources/fruit/apple

INFO: 8 * LoggingFilter - Response received on thread http-listener-1(5)
8 < 200
8 < Date: Fri, 11 Jan 2013 22:21:23 GMT
8 < Content-Length: 5
8 < Content-Type: text/html
8 < Server: GlassFish Server Open Source Edition 4.0
8 < X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.7)
apple

INFO: 9 * LoggingFilter - Request received on thread http-listener-1(5)
9 > DELETE http://localhost:8080/endpoint/webresources/fruit/banana

INFO: 10 * LoggingFilter - Response received on thread http-listener-1(5)
10 < 204
10 < Date: Fri, 11 Jan 2013 22:21:23 GMT
10 < Server: GlassFish Server Open Source Edition 4.0
10 < X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.7)

INFO: 11 * LoggingFilter - Request received on thread http-listener-1(5)
11 > GET http://localhost:8080/endpoint/webresources/fruit

INFO: 12 * LoggingFilter - Response received on thread http-listener-1(5)
12 < 200
12 < Date: Fri, 11 Jan 2013 22:21:23 GMT
12 < Content-Length: 7
12 < Content-Type: text/html
12 < Server: GlassFish Server Open Source Edition 4.0
12 < X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.7)
[apple]

This has been tested on GlassFish 4 build 70 with the following dependencies:

<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0-m10</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
<version>2.0-m10</version>
<type>jar</type>
</dependency>

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.


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
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today