Monday, February 08, 2016

A Look at the Upcoming JSF 2.3 Push Support

As mentioned in previous posts, there are a number of enhancements being added to the next release of JavaServer Faces (Mojarra).  JSF 2.3 is slated to be released with Java EE 8 in 2017, but you can get your hands on some of the enhancements and updates to JSF for testing purposes now by building from source or running a milestone release.

One such enhancement to the API is the addition of one-way (server-to-client) websocket based push communication via the f:websocket tag and Push API.  The team at OmniFaces has developed the JSF solution based on the o:socket, which is part of the OmniFaces utility library.  Specifically, JSR 372 Expert Group members Bauke Scholtz and Arjan Tijms have contributed this and many other enhancements and fixes to the Mojarra codebase.  

The patch which enables f:websocket support has not yet been applied to the Mojarra 2.3 branch, but you can obtain the patch from issue JAVASERVERFACES_SPEC_PUBLIC-1396.  Prior to applying the patch to your local Mojarra clone, you should be sure to update your sources from the central 2.3 branch to ensure that you have the latest updates applied.  The usage is simple, very similar to the well documented o:socket feature on the OmniFaces site, perform the following steps to make use of f:websocket.

First, add the
javax.faces.ENABLE_WEBSOCKET_ENDPOINT 
context parameter to the web.xml of your application, and set the value to true.

<context-param>
    <param-name>javax.faces.ENABLE_WEBSOCKET_ENDPOINT</param-name>
    <param-value>true</param-value>
 </context-param>

Client-Side Code

On your client (JSF view), add the f:websocket tag, and specify the channel to which you wish to connect.  You must also specify an onmessage listener that will execute a specified JavaScript function once the message is received.  The optional attribute onclose can also be specified, allowing a specified JavaScript function to execute on close of connection.  In the following example, we specify that the socket will connect to a channel named "duke", along with an onmessage listener named dukeMessageListener:

<f:websocket channel="duke" onmessage="dukeMessageListener"/>

The onmessage listener can be invoked with three parameters (push message JSON object, channel name, message event).  If you simply wish to pass a message, it may look similar to the following:

function dukeMessageListener(message) {
        PF('broadcastGrowl').show(message);
}

If the optional onclose listener is specified, the corresponding function could accept three parameters (close reason code - integer , channel name, message event), but only the first is required.

In most cases, the intention is to blast out a message from the server to notify all client views having the same websocket channel specification.  There is an optional scope attribute on f:websocket that can be set to "session", and this will limit the messages to all client views with the same websocket channel in the current session only.

Lastly, the optional port attribute can be set to specify a TCP port number other than the HTTP port, if needed.

Server-side Code

Since we are planning to push a message from the server to all connected clients, let's take a look at the server side code.  The new PushContext can be injected into any CDI artifact by including an @Push annotation, and the name of the context can either correspond to the channel name, or an optional channel attribute can be specified on the @Push annotation to indicate the channel to which the message should be broadcast.

@Inject @Push
    private PushContext duke;
...
public void sendMessage(Object message){
    duke.send(message);
}

The message will be encoded as JSON and delivered to the message argument of the JavaScript function on the client that is specified for the onmessage attribute of f:websocket.  It is possible to send any type of container, be it a plain String, JavaBean, Map, Collection, etc., as the message.

Example Usage

Suppose that we have an administrative console for our web application, and we want to provide the administrators a means of alerting the clients of something.  The administrative console may have a text area for the message input, along with a commandButton to invoke the sending of the message, as such.

<h:inputText id="pushMessage" value="#{testBean.pushMessage}"/>
<h:commandButton action="#{testBean.sendAdminMessage}" value="Send Message"/>

The JSF controller class testBean would then have a method sendAdminMessage, which takes the message that is stored in the pushMessage String, and sends it to our sendMessage method.

@Inject @Push
    private PushContext duke;

...

public void sendAdminMessage(){
    sendMessage(pushMessage);
    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Message has been broadcasted"));
}

...

public void sendMessage(Object message){
    duke.send(message);
}

Any client that will receive the message should contain the f:websocket tag, pointing to the duke channel.  The client should also include minimally a JavaScript function to be invoked when the message is received.

<f:websocket channel="duke" onmessage="dukeMessageListener"/>

<p:growl id="messages"/>

function dukeMessageListener(message) {
        facesmessage.severity = 'info';
        PF('broadcastGrowl').show(message);
}

In this particular example, a PrimeFaces growl message component will be updated when the message is received.

JSF 2.3 is shaping up well thanks to all of the excellent contributions from the JSR 372 Expert Group members.





1 comment:

Please leave a comment...