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

 ...
}

Tuesday, December 18, 2012

Elegant truncated text with ellipses in web

Sometimes text is big than allowed space. Assume you have a table column with a very long text and don't want to have line breaks or make the table bigger in order to show the text completely. Having a fluid truncated (clipped) text with three dots at the end would be a good option. It would be nice to show a truncated text with ... and put a tooltip which displays the whole text on mouseover. Furthemore, the text should display more content or less content dynamically when resizing browser and the available space gets bigger or shrunk. How to deal with fluid truncated text? Well, there are some JavaScript solutions with advanced configuration. One of them is e.g. dotdotdot and the another one is trunk8

But in simple cases we don't need JavaScript at all. There is a CSS property text-overflow: ellipsis which works fine on container elements (like div) with white-space: nowrap and overflow: hidden. I defined a style class truncate which can be applied to any container element.
.truncate {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    -o-text-overflow: ellipsis;
    -ms-text-overflow: ellipsis;
    display: block;
    position: absolute;
}
This works smoothly. Example:
<div class="truncate">
    My very very very very very very very very very long text
</div>
The text will fit the space of the div element now.

As I develop JSF applications I need it for text in PrimeFaces p:column (rendered as td element). For that we need to set a max. width on p:column. For my project I decided to restrict the max. width to 160px. Style classes applied to p:column and the inner div (containing the text) look as follows:
.truncate {
    max-width: 160px;
    width: 160 px\9;
}

.truncate > div {
    width: 160 px\9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    -o-text-overflow: ellipsis;
    -ms-text-overflow: ellipsis;
    display: block;
    position: absolute;
}
\9 is a hack for IE7 / IE8. Unfortunately, I could not do text in table's columns fluid for IE7 / E8, so that I set a fix width 160px. The code in XHTML is like this one:
<p:column headerText="Description" sortBy="#{user.description}" styleClass="truncate">
    <h:outputText id="desc" value="#{user.description}"/>
    <pe:tooltip for="desc" value="#{user.description}"/>
</p:column>
And a final picture to complete this blog post.


The visible text's content (in the column "Description") is adjusted automatically while the table changes its size. There are no line breaks in columns when some text doesn't fit the available column's space. The table looks compacted.

Sunday, December 16, 2012

PrimeFaces Extensions 0.6.2 released

We are glad to announce the new release 0.6.2 of PrimeFaces Extensions. This is a maintenance release. It contains fixes and additional features for CKEditor, CodeMirror, Timeline, Tooltip and Layout. Layout options as tag attributes are back! Now you can build layout either by model (LayoutOptions class) or direct by attributes of pe:layoutPane. There are also new EL functions (check the showcase) and a nice extension for the JsonConverter (can handle Generic types). We have also released Resource Optimizer Maven plugin with updated dependencies to Google Closure Compiler and Plexus Utils.

Some quick links to dive into PrimeFaces Extensions:

Getting Started
Showcase Mojarra
Showcase MyFaces

All releases are available in the Maven central repo as usally. The next release will be again a maintenance release 0.6.3. The main focus is a migration to GitHub. After that we will add new components in the 0.7.0 release.

Have fun!

Thursday, December 6, 2012

Register hotkeys for PrimeFaces dialogs

In this blog post I would like to share a jQuery based (low level), fast working solution to register hotkeys for PrimeFaces dialogs. Why do we need this low level solution? Primary because the PrimeFaces component p:hotkey doesn't work as expected in dialogs. We were only interested in two hotkeys ESC and ENTER, so that I will only show a solution for these two hotkeys.

Requirements: When user presses the ESC key, a dialog should be closed. When user presses the ENTER key, the dialog action should be submitted (imagine e.g. a "Are you sure you want to delete?" dialog). Dialog can look like as follows. In the real world it is better to please the dialog into a composite component in order to reuse it.
<p:confirmDialog header="Confirmation" widgetVar="confirmDlgWidget" appendToBody="true">
  <f:facet name="message">
     ...
  </f:facet>
  <h:form>
    <p:commandButton value="OK" process="@this" update="..." action="..." oncomplete="confirmDlgWidget.hide();"/>
    <p:commandButton value="Cancel" onclick="confirmDlgWidget.hide();" type="button"/>
  </h:form>
</p:confirmDialog>
After the dialog tag, the following short JS script should be placed.
<script type="text/javascript">
 /* <![CDATA[ */
 $(function () {
    registerConfirmDialogHotkeys(confirmDlgWidget);
 });
 /* ]]> */
</script>
The JS function itself is located in a file. Here how it was implemented:
function registerConfirmDialogHotkeys(dlgWidget) {

    var key = 'keydown.pfdialog_' + dlgWidget.id;

    $(document).off(key).on(key, function (e) {
        var keyCode = $.ui.keyCode;
        var active = parseInt(dlgWidget.jq.css('z-index')) === PrimeFaces.zindex;

        if (dlgWidget.jq.hasClass('ui-overlay-visible') && active) {
            if (e.which === keyCode.ESCAPE) {
                dlgWidget.hide();
            } else if (e.which === keyCode.ENTER || e.which == keyCode.NUMPAD_ENTER) {
                dlgWidget.jq.find('button:submit:first').click();
                e.preventDefault();
            }
        }
    });
}
The function registers a namespaced keydown JS callback on document. The callback checks if the dialog is active currently. This is normally a last opened dialog. If it is active and the user has pressed ESC, then hide the dialog (widget variable is passed into the function). If it is active and the user has pressed ENTER, then find the first submit button within this dialog and click it to cause an action execution. That's all.