Showing posts with label REST. Show all posts
Showing posts with label REST. Show all posts

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);

 ...
}

Friday, October 7, 2011

Draw masked images with Java 2D API and Jersey servlet

The task I had for one Struts web project was the dynamic image painting. You maybe know a small flag icon in MS Outlook which indicates message states. It can be red, green, etc. We needed the similar flag icon with configurable colors. The color is variable and thus unknown a priori - it can be set dynamically in backend and passed to front-end. I develop Struts web applications with JSF in mind. We can't use custom JSF resource handler in Struts, but we can use servlets. A HTTP servlet is able to catch GET requests causing by Struts or JSF image tags and render an image. All parameters should be passed in URL - they should be parts of URL. We need following parameters:
  • image format like "png" or "jpg"
  • file name of base image
  • file name of mask image
  • color (in HEX without leading "#" or "0x" signs)
An URL-example is
 
http://host:port/webappcontext/masked/png/flag/flag-mask/FF0000/mfile.imgdrawer
 
To save me pain for manually parsing of URL string and extraction of all parameters I have took Jersey - reference implementation for building RESTful Web services. Jersey's RESTful Web services are implemented as servlet which takes requests and does some actions according to URL structure. We just need to implement such actions. My action (and task) is image drawing / painting. That occurs by means of Java 2D API - a set of classes for advanced 2D graphics and imaging. Let me show the end result at first. Base image looks as follows


Mask image looks as follows

And now the magic is comming. I'm sending a GET request to draw a red flag (color FF0000) dynamically:


I'm sending a GET request to draw a green flag (color 00FF00):


Do you want to see a blue image? No problem, I can type as color 00ff.

You need four things to achieve that:

1. Dependencies to Jersey and Java 2D Graphics. Maven users can add these as follows:
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-server</artifactId>
    <version>... last version ...</version>
</dependency>
<dependency>
    <groupId>com.sun.media</groupId>
    <artifactId>jai_codec</artifactId>
    <version>... last version ...</version>
</dependency>
<dependency>
    <groupId>com.sun.media</groupId>
    <artifactId>jai_imageio</artifactId>
    <version>... last version ...</version>
</dependency>
2. Two image files in the web application. I have placed them under webapp/resources/themes/app/images/ and they are called in my case flag.png and flag-mask.png.

3. Configuration for Jersey servlet in web.xml. My configuration is
<servlet>
    <servlet-name>imagedrawer</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>ip.client.commons.web.servlet</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>imagedrawer</servlet-name>
    <url-pattern>*.imgdrawer</url-pattern>
</servlet-mapping>
Image-URL should be ended with imgdrawer to be able to handled by Jersey servlet. By the way, my FacesServlet is mapped to *.jsf and Struts ActionServlet to *.do. Init parameter com.sun.jersey.config.property.packages points to the Java package where the handler class is placed. Writing of handler class is the step 4.

4. Handler class which treats GET requests and draws / paints a new masked image by means of Java 2D API. The new image is a composition of two predefined images from the step 2. The mask gets painted and overlapped with the base image. The code is more or less documented, so that I omit any comments :-)
/**
 * Jersey annotated class for image drawing. Drawed images don't need to be cached server side because they are cached
 * proper on the client side. Set "expires" and "max-age" ensure client side caching.
 */
@Path("/")
@Produces("image/*")
@Singleton
public class ImageDrawer {
    @Context ServletContext context;

    /** directory for image files (can be done configurable if needed) */
    private String imageDir = "/resources/themes/app/images/";

    /**
     * Gets composed image consist of base and mask image.
     *
     * @param  format   format, e.g. "png", "jpg".
     * @param  basefile file name of base image without file extension
     * @param  maskfile file name of mask image without file extension
     * @param  hexcolor color in HEX without leading "#" or "0x" signs
     * @return Response new composed image
     * @throws WebApplicationException thrown exception, 404 or 500 status code.
    */
    @GET
    @Path("/masked/{format}/{basefile}/{maskfile}/{hexcolor}/{img}")
    public Response getImage(@PathParam("format") String format,
                             @PathParam("basefile") String basefile,
                             @PathParam("maskfile") String maskfile,
                             @PathParam("hexcolor") String hexcolor) {
        // check parameters
        if (format == null || basefile == null || maskfile == null || hexcolor == null) {
            throw new WebApplicationException(404);
        }

        // try to get images from web application
        InputStream is1 = context.getResourceAsStream(imageDir + basefile + "." + format);
        if (is1 == null) {
            throw new WebApplicationException(404);
        }

        InputStream is2 = context.getResourceAsStream(imageDir + maskfile + "." + format);
        if (is2 == null) {
            throw new WebApplicationException(404);
        }

        RenderedImage img1 = renderImage(is1);
        RenderedImage img2 = renderImage(is2);

        // convert HEX to RGB
        Color color = Color.decode("0x" + hexcolor);

        // draw the new image
        BufferedImage resImage = drawMaskedImage(img1, img2, color);

        byte[] resImageBytes = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            // use Apache IO
            ImageIO.write(resImage, format, baos);
            baos.flush();
            resImageBytes = baos.toByteArray();
        } catch (IOException e) {
            throw new WebApplicationException(500);
        } finally {
            try {
                baos.close();
            } catch (IOException e) {
                ;
            }
        }

        // cache in browser forever
        CacheControl cacheControl = new CacheControl();
        cacheControl.setMaxAge(Integer.MAX_VALUE);

        return Response.ok(resImageBytes, "image/" + format).cacheControl(cacheControl)
                       .expires(new Date(Long.MAX_VALUE)).build();
    }
 
    // Utilities (written not by me, copyright ©)

    private RenderedImage renderImage(InputStream in) {
        SeekableStream stream = createSeekableStream(in);
        boolean isImageIOAvailable = false;
        if (Thread.currentThread().getContextClassLoader().
            getResource("META-INF/services/javax.imageio.spi.ImageReaderSpi") != null) {
            isImageIOAvailable = true;
        }
        return JAI.create(isImageIOAvailable ? "ImageRead" : "stream", stream);
    }

    private BufferedImage drawMaskedImage(RenderedImage orig, RenderedImage mask, Color color) {
        BufferedImage overlay = manipulateImage(mask, null);
        return manipulateImage(orig, new ImageManipulator() {
            public void manipulate(final Graphics2D g2d) {
                float[] scaleFactors = new float[] {
                2f * (color.getRed() / 255f), 2f * (color.getGreen() / 255f),
                2f * (color.getBlue() / 255f), color.getAlpha() / 255f};
                float[] offsets = new float[] {0, 0, 0, 0};
                g2d.drawImage(overlay, new RescaleOp(scaleFactors, offsets, null), 0, 0);
            }
        });
    }

    private BufferedImage manipulateImage(RenderedImage orig, ImageManipulator manipulator) {
        BufferedImage image;
        boolean drawOriginal = false;
        ColorModel colorModel = orig.getColorModel();
        int colorSpaceType = colorModel.getColorSpace().getType();
        if (colorModel.getPixelSize() >= 4 && colorSpaceType != ColorSpace.TYPE_GRAY) {
            image = new RenderedImageAdapter(orig).getAsBufferedImage();
        } else if (colorSpaceType == ColorSpace.TYPE_GRAY) {
            image = new BufferedImage(orig.getWidth(), orig.getHeight(), BufferedImage.TYPE_INT_ARGB);
            drawOriginal = true;
        } else {
            image = new BufferedImage(orig.getWidth(), orig.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
            drawOriginal = true;
        }
   
        Graphics2D g2d = image.createGraphics();
        if (drawOriginal) {
            g2d.drawImage(new RenderedImageAdapter(orig).getAsBufferedImage(), 0, 0, null);
        }

        if (manipulator != null) {
            manipulator.manipulate(g2d);
        }
    
        return image;
    }

    private interface ImageManipulator {void manipulate(Graphics2D g2d);} 
}