Thursday, December 27, 2012

GET / POST with RESTful Client API

There are many stuff in the internet how to work with RESTful Client API. These are basics. But even though the subject seems to be trivial, there are hurdles, especially for beginners. In this post I will try to summurize my know-how how I did this in real projects. I usually use Jersey (reference implementation for building RESTful services). See e.g. my other post. In this post, I will call a real remote service from JSF beans. Let's write a session scoped bean RestClient.
package com.cc.metadata.jsf.controller.common;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;

/**
 * This class encapsulates some basic REST client API.
 */
@ManagedBean
@SessionScoped
public class RestClient implements Serializable {

    private transient Client client;

    public String SERVICE_BASE_URI;

    @PostConstruct
    protected void initialize() {
        FacesContext fc = FacesContext.getCurrentInstance();
        SERVICE_BASE_URI = fc.getExternalContext().getInitParameter("metadata.serviceBaseURI");

        client = Client.create();
    }

    public WebResource getWebResource(String relativeUrl) {
        if (client == null) {
            initialize();
        }

        return client.resource(SERVICE_BASE_URI + relativeUrl);
    }

    public ClientResponse clientGetResponse(String relativeUrl) {
        WebResource webResource = client.resource(SERVICE_BASE_URI + relativeUrl);
        return webResource.accept("application/json").get(ClientResponse.class);
    }
}
In this class we got the service base URI which is specified (configured) in the web.xml.
<context-param>
   <param-name>metadata.serviceBaseURI</param-name>
   <param-value>http://somehost/metadata/</param-value>
</context-param>
Furthermore, we wrote two methods to receive remote resources. We intend to receive resources in JSON format and convert them to Java objects. The next bean demonstrates how to do this task for GET requests. The bean HistoryBean converts received JSON to a Document object by using GsonConverter. The last two classes will not be shown here (they don't matter). Document is a simple POJO and GsonConverter is a singleton instance which wraps Gson.
package com.cc.metadata.jsf.controller.history;

import com.cc.metadata.jsf.controller.common.RestClient;
import com.cc.metadata.jsf.util.GsonConverter;
import com.cc.metadata.model.Document;

import com.sun.jersey.api.client.ClientResponse;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

/**
 * Bean getting history of the last extracted documents.
 */
@ManagedBean
@ViewScoped
public class HistoryBean implements Serializable {

    @ManagedProperty(value = "#{restClient}")
    private RestClient restClient;

    private List<Document> documents;
    private String jsonHistory;

    public List<Document> getDocuments() {
        if (documents != null) {
            return documents;
        }

        ClientResponse response = restClient.clientGetResponse("history");

        if (response.getStatus() != 200) {
            throw new RuntimeException("Failed service call: HTTP error code : " + response.getStatus());
        }

        // get history as JSON
        jsonHistory = response.getEntity(String.class);

        // convert to Java array / list of Document instances
        Document[] docs = GsonConverter.getGson().fromJson(jsonHistory, Document[].class);
        documents = Arrays.asList(docs);

        return documents;
    }

    // getter / setter
 ...
}
The next bean demonstrates how to communicate with the remote service via POST. We intent to send the content of uploaded file. I use the PrimeFaces' FileUpload component, so that the content can be extracted as InputStream from the listener's parameter FileUploadEvent. This is not important here, you can also use any other web frameworks to get the file content (also as byte array). More important is to see how to deal with RESTful Client classes FormDataMultiPart and FormDataBodyPart.
package com.cc.metadata.jsf.controller.extract;

import com.cc.metadata.jsf.controller.common.RestClient;
import com.cc.metadata.jsf.util.GsonConverter;
import com.cc.metadata.model.Document;

import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataMultiPart;

import org.primefaces.event.FileUploadEvent;

import java.io.IOException;
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;

import javax.ws.rs.core.MediaType;

/**
 * Bean for extracting document properties (metadata).
 */
@ManagedBean
@ViewScoped
public class ExtractBean implements Serializable {

    @ManagedProperty(value = "#{restClient}")
    private RestClient restClient;

    private String path;

    public void handleFileUpload(FileUploadEvent event) throws IOException {
        String fileName = event.getFile().getFileName();

        FormDataMultiPart fdmp = new FormDataMultiPart();
        FormDataBodyPart fdbp = new FormDataBodyPart(FormDataContentDisposition.name("file").fileName(fileName).build(),
                event.getFile().getInputstream(), MediaType.APPLICATION_OCTET_STREAM_TYPE);
        fdmp.bodyPart(fdbp);

        WebResource resource = restClient.getWebResource("extract");
        ClientResponse response = resource.accept("application/json").type(MediaType.MULTIPART_FORM_DATA).post(
                ClientResponse.class, fdmp);

        if (response.getStatus() != 200) {
            throw new RuntimeException("Failed service call: HTTP error code : " + response.getStatus());
        }

        // get extracted document as JSON
        String jsonExtract = response.getEntity(String.class);

        // convert to Document instance
        Document doc = GsonConverter.getGson().fromJson(jsonExtract, Document.class);

        ...
    }

    // getter / setter
 ...
}
Last but not least, I would like to demonstrate how to send a GET request with any query string (URL parameters). The next method asks the remote service by URL which looks as http://somehost/metadata/extract?file=<some file path>
public void extractFile() {
 WebResource resource = restClient.getWebResource("extract");
 ClientResponse response = resource.queryParam("file", path).accept("application/json").get(
   ClientResponse.class);

 if (response.getStatus() != 200) {
  throw new RuntimeException("Failed service call: HTTP error code : " + response.getStatus());
 }

 // get extracted document as JSON
 String jsonExtract = response.getEntity(String.class);

 // convert to Document instance
 Document doc = GsonConverter.getGson().fromJson(jsonExtract, Document.class);

 ...
}

2 comments:

  1. Thanks for your useful informations, this blogs will be really help for software development

    ReplyDelete
  2. It's incredible that anyone comment about your article. Actually I'm trying create a Primefaces "GUI" for a Restful client and your post is very useful. Thanks for share in internet your "tests", overall when you ilustrate something that in other websites say that is not possible: JSF + JAX-RS/REST.

    I hope write some good article about my project when improve my JSF's knowledge.

    (Sorry for my bad English, I need improve it almost as my Java JSF abilities)

    ReplyDelete