Wednesday, March 14, 2012

JSF and Swing Part 3 - JSF push solution

After we saw how easy it was to push information to a Swing client, we might be tempted to believe it is similarly easy to push data to our JSF client.  But, in reality, that process is quite complicated.  There are several ways to accomplish the task, but many of the technologies are still in transition.  Richfaces, Primefaces, and Icefaces all have mechanisms to push data to a JSF client, but each has its downside and I don't think any are ready for "primetime".  Depending on the version you use you may be implementing push with a version of polling instead of an actual push technology.  The versions that do use push technology still seem to have compatibility issues with either the container, browser, or other libraries you are using.  Part of the reason for this, is the WebSocket API is still being defined and the different projects are implementing the spec without a complete definition.  That said, we are going to implement our own component using WebSockets.  We will not use any of the predefined push components from Icefaces, Richfaces, or Primefaces.  We will also demonstrate the usage of CDI events.

First, what is a WebSocket.  WebSockets are new to HTML5 and as previously said, are still being defined.  But the essence is that the client is going to establish a connection with the server using HTTP, then the connection will be kept open and used by the server to send data back.  The client will create a listener to listen for any data being sent by the server.  We will implement a WebSocket using Glassfish and grizzly.

To begin, we are going to need a WebSocket "server" that the clients are going to connect to, and receive broadcasts from.  To do this, we are going to implement a servlet running in our container.  We modify our web.xml by adding our servlet:

    <servlet>
        <servlet-name>WebSocketServletProcessMonitor</servlet-name>
        <servlet-class>com.sample.sockets.WebSocketServletProcessMonitor</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebSocketServletProcessMonitor</servlet-name>
        <url-pattern>/monitor</url-pattern>
    </servlet-mapping>

Then, we have our servlet WebSocketServletProcessMonitor.java:

package com.sample.sockets;
import com.sample.beans.ProcessEvent;
import com.sun.grizzly.websockets.WebSocketEngine;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.util.logging.Logger;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
public class WebSocketServletProcessMonitor extends HttpServlet {
    static final Logger logger = Logger.getLogger(WebSocketEngine.WEBSOCKET);
    @Inject
    private MonitorApplication monitorApplication;
    public WebSocketServletProcessMonitor() {
        WebSocketServletProcessMonitor.logger.info("Created Process Monitor Servlet");
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        WebSocketEngine.getEngine().register(monitorApplication);
        WebSocketServletProcessMonitor.logger.info("initializing servlet");
    }
    @Override
    public void destroy() {
        WebSocketEngine.getEngine().unregister(monitorApplication);
        WebSocketServletProcessMonitor.logger.info("destroying servlet");
    }
    public synchronized void processEventListener(@Observes ProcessEvent pushEvent) {

        WebSocketServletProcessMonitor.logger.info("processEventListener");
        if(pushEvent.getRun()) {
            monitorApplication.monitorProcess();
        } else {
            monitorApplication.stopProcess();
        }
}

And our MonitorApplication.java:

package com.sample.sockets;
import com.sample.beans.ProcessEvent;
import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.websockets.DefaultWebSocket;
import com.sun.grizzly.websockets.ProtocolHandler;
import com.sun.grizzly.websockets.WebSocket;
import com.sun.grizzly.websockets.WebSocketApplication;
import com.sun.grizzly.websockets.WebSocketException;
import com.sun.grizzly.websockets.WebSocketListener;
import javax.annotation.PreDestroy;
import javax.inject.Named;
import javax.ejb.Singleton;
@Named("monitorApplication")
@Singleton
public class MonitorApplication extends WebSocketApplication {
    @Override
    public WebSocket createWebSocket(ProtocolHandler protocolHandler, WebSocketListener... listeners) {
        WebSocketServletProcessMonitor.logger.info("createWebSocket");
        return new DefaultWebSocket(protocolHandler, listeners);
    }
    @Override
    public boolean isApplicationRequest(Request request) {
        WebSocketServletProcessMonitor.logger.info("isApplicationRequest");
        final String uri = request.requestURI().toString();
        return uri.endsWith("/monitor");
    }
    @Override
    public void onMessage(WebSocket socket, String text) {
        broadcast(text);
    }
    public void broadcast(String text) {
        for (WebSocket webSocket : getWebSockets()) {
            if (!webSocket.isConnected()) {
                continue;
            }
            try {
                WebSocketServletProcessMonitor.logger.info("Broadcasting : " + text + "-");
                webSocket.send(text);
            } catch (WebSocketException e) {
                remove(webSocket);
            }
        }
    }
    @PreDestroy
    public void destroy() {
        WebSocketServletProcessMonitor.logger.info("destroyMonitorApplication");
        done=true;
        if(thread != null && thread.isAlive()) try {
            thread.interrupt();
        } catch (Exception e) {
        }
    }
    private boolean done = false;
    private Thread thread = null;
    private class MonitorProcess extends Thread {
        @Override
        public void run() {
            int size = ObservedStatus.values().length;
            int status = 0;
            for (int i = 0; i < 100 && !done;) {
                try {
                    Thread.sleep(50);
                    String message = "Status: " + ObservedStatus.values()[status] + " " + i;
                    WebSocketServletProcessMonitor.logger.info("message");
                    broadcast(message);
                } catch (Exception e) {
                }
                if (i == 99) {
                    status = ++status % size;
                }
                i = (++i) % 100;
            }
            thread = null;
            done = false;
        }
    }
    public enum ObservedStatus {
        OK, WARNING, ERROR;
    }
    public synchronized void monitorProcess() {
        WebSocketServletProcessMonitor.logger.info("processEventListener");
        if (thread == null) {
            thread = new MonitorProcess();
            thread.start();
        }
    }
    public synchronized void stopProcess() {
        done = true;
    }
    public boolean isRunning() {
        return (thread != null && thread.isAlive() && !done);
    }
}

Our MonitorApplication is a server wide process that will broadcast to all the listeners the status of our process.  To do that, we made it an EJB singleton.  This will ensure when we create servlets on the fly for our incoming web connections, we have a consistent MonitorApplication, and that there will only be one thread running for monitoring our processes.

So what is our servlet doing?  Well, when a url comes in that ends in /monitor, a servlet will be created to process it.  It then registers our EJB as the WebSocket processor.  Then, when the client creates a WebSocket, the EJB will register it as a WebSocket in its list of WebSockets.  When the EJB receives a message, it will send that message to all its listeners.  We could at this time process the message, but we chose not to.  We could have listened to a message that said, "Start Monitor Process" or "End Monitor Process" to start and stop our thread that generates our messages, but I wanted to demonstrate doing that with CDI events, why?  Well, it isn't necessarily true that a client would start or stop the process, you might have some server event doing that and how would you communicate that to our MonitorApplication?  I.e. the client clicked some button that did a bunch of business logic which ended up starting or stopping the thread.  The business logic has not created a WebSocket, the client did.  So, how would you communicate that to the MonitorApplication?  Of course, you could inject the singleton ejb MonitorApplication into that class processing the business logic, but with an event listener, that class would be decoupled from the EJB and could notify any listeners to that event.  So, in the servlet we observed our cdi event ProcessEvent, and if it is fired, we pass the processEvent to our EJB so it can start or stop our thread.  Now, we aren't actually supposed to create a thread in an EJB, but because we are demonstrating the communication to a client via a WebSocket and not the monitoring of a process (the monitoring of the process is just to facilitate the discussion), we took a simplified course that works since we are using a Singleton EJB (well, it seemed simplified, but I decided to modify the code, see the bottom of this post for a proper implementation).

Our ProcessEvent.java:

package com.sample.beans;
/**
 * @author Thomas Dias
 */
public class ProcessEvent {
    private boolean run;
    public ProcessEvent(boolean run) {
        this.run = run;
    }
    public boolean getRun() {
        return run;
    }
    public void setRun(boolean run) {
        this.run = run;
    }
}

This just lets us pass a specific object that we can observe to tell us whether we should start or stop our thread.

Now we need our JSF page, index.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>My JSF Process Monitor</title>
        <script type="text/javascript" >
            var websocket = null;
            function startMonitor() {
                var Socket = "MozWebSocket" in window ? MozWebSocket : WebSocket;
                websocket = new Socket('ws://localhost:8080/socketSample/monitor');
                websocket.onopen = function() {
                    websocket.send('test message');
                };
                websocket.onmessage = function (evt) {
                    try {
                       document.getElementById('messages').innerHTML=evt.data;
                    } catch(exception) {
                    }
                };
                websocket.onclose = function() {
                };
                document.getElementById('startButton').disabled=true;
                document.getElementById('stopButton').disabled=false;
            }
            function stopMonitor() {
                document.getElementById('startButton').disabled=false;
                document.getElementById('stopButton').disabled=true;
                websocket.close();
                websocket = null;
            }
        </script>
    </h:head>
    <h:body>
        <h:form id="myform" prependId="false">
            <h:commandButton type="button" id="startButton" value="Start Monitor" onclick="startMonitor();" />
            <h:commandButton type="button" id="stopButton" value="End Monitor" disabled="true" onclick="stopMonitor();" />
            <h:commandButton id="startProcessButton" value="Start Process" actionListener="#{observerBean.startProcess}" >
                <f:ajax execute="startProcessButton" render="startProcessButton stopProcessButton"/>
            </h:commandButton>
            <h:commandButton id="stopProcessButton" value="Stop Process" actionListener="#{observerBean.stopProcess}" >
                <f:ajax execute="stopProcessButton" render="startProcessButton stopProcessButton"/>
            </h:commandButton>
            <br/>
            <h:outputText id="messages" value="Message Area"/>
        </h:form>
    </h:body>
</html>

Okay, I cheated and put it all in the one .xhtml file.  You really should separate out the javascript, but I left it in here for clarity.  When the start or stop monitor buttons are pressed, a web socket is established or closed.  The web socket is what will listen for any messages from the server.  When a message is received, the onMessage method will be invoked which will update the html in our messages component.  The start and stop process buttons will execute our backing bean's corresponding actionlisteners to fire the start and stop ProcessEvent. We disable and enable the monitor buttons on the client side with Javascript, we enable and disable the process buttons by asking the bean if the thread is alive.

Our JSF bean ObserverBean.java

package com.sample.beans;
import com.sample.sockets.MonitorApplication;
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Any;
import javax.faces.event.ActionEvent;
import javax.inject.Inject;
import javax.inject.Named;
/**
 * @author Thomas Dias
 */
@Named
@RequestScoped
public class ObserverBean implements Serializable {
    @Inject
    @Any
    Event<ProcessEvent> processEvent;
    @Inject
    MonitorApplication monitorApplication;
    public ObserverBean() {
    }
    public void startProcess(ActionEvent e) {
        processEvent.fire(new ProcessEvent(true));
    }
    public void stopProcess(ActionEvent e) {
        processEvent.fire(new ProcessEvent(false));
    }
}

And that, surprisingly, is it.  We linked our application to the libraries already contained in Glassfish and we have the ability for the server to monitor any process we want and send our clients status on an as needed basis.  Multiple clients will all get the same broadcast.  We look forward to the day when WebSockets are finalized and all the vendors have implemented compatible components, but for now, we still have a completely workable solution for implementing push technology.

Oh, and finally, the application looks like:




Where the 'Status: OK 72' is a constantly changing message.

Asynchronous EJB to run a background process.
Okay, I didn't feel like I could leave the tutorial with the EJB spawning a thread.  So, we'll take a moment to revise the code and use an asynchronous EJB instead of spawning the thread.

First the EJB, we'll move the MonitorProcess to its own class and make it a stateless EJB 3.1 bean.

package com.sample.sockets;
import com.sample.sockets.MonitorApplication.ObservedStatus;
import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.Stateless;
/**
 * @author Thomas Dias
 */
@Stateless
public class MonitorProcess {
    @Asynchronous
    public Future<String> monitor(MonitorApplication monitorApplication) {
        int status = 0;
        for (int i = 0; monitorApplication.isRun(); i++) {
            try {
                Thread.sleep(20);
                String message = "Status: " + ObservedStatus.values()[status] + " " + i;
                monitorApplication.broadcast(message);
            } catch (Exception e) {
            }
            if (i == 99) {
                status = ++status % ObservedStatus.values().length;
                i = 0;
            }
        }
        return new AsyncResult("done");
    }
}

Here we have an annotation @Asynchronous for the method, which will make the container spawn a thread instead of us doing it.  The return type of Future<String> will allow us to get the result, check if the process is done, tell it to stop, etc.  Although, I did pass in the MonitorApplication so it had access to the WebSockets and the process can check if it is time to stop running.

Next, we modify our MonitorApplication:

package com.sample.sockets;
import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.websockets.DefaultWebSocket;
import com.sun.grizzly.websockets.ProtocolHandler;
import com.sun.grizzly.websockets.WebSocket;
import com.sun.grizzly.websockets.WebSocketApplication;
import com.sun.grizzly.websockets.WebSocketException;
import com.sun.grizzly.websockets.WebSocketListener;
import java.util.concurrent.Future;
import javax.inject.Named;
import javax.ejb.Singleton;
import javax.inject.Inject;
@Named("monitorApplication")
@Singleton
public class MonitorApplication extends WebSocketApplication {
    @Inject
    MonitorProcess monitorProcess;
    @Override
    public WebSocket createWebSocket(ProtocolHandler protocolHandler, WebSocketListener... listeners) {
        WebSocketServletProcessMonitor.logger.info("createWebSocket");
        return new DefaultWebSocket(protocolHandler, listeners);
    }
    @Override
    public boolean isApplicationRequest(Request request) {
        WebSocketServletProcessMonitor.logger.info("isApplicationRequest");
        final String uri = request.requestURI().toString();
        return uri.endsWith("/monitor");
    }
    @Override
    public void onMessage(WebSocket socket, String text) {
        broadcast(text);
    }
    public void broadcast(String text) {
        for (WebSocket webSocket : getWebSockets()) {
            if (!webSocket.isConnected()) {
                continue;
            }
            try {
                webSocket.send(text);
            } catch (WebSocketException e) {
                remove(webSocket);
            }
        }
    }
    private boolean run = true;
    Future<String> result;
    public synchronized void monitorProcess() {
        run = true;
        if (result == null || result.isDone()) {
            result = monitorProcess.monitor(this);
        }
        System.out.println("Started Process");
    }
    public synchronized void stopProcess() {
        if (result == null || result.isDone()) {
            System.out.println("No process to stop");
        } else {
            if(result.cancel(true)) {
                System.out.println("Process stopped");
            }
        }
        run = false;
        result = null;
    }
    public boolean isRun() {
        return run;
    }
    public boolean isRunning() {
        boolean running = (result != null && !result.isDone());
        return running;
    }
    public enum ObservedStatus {
        OK, WARNING, ERROR;
    }
}

When we look at it, we realize it actually isn't any harder then the first approach, so there isn't really any reason to go outside the EJB parameters and spawn our own thread. You may ask why I didn't put the @Asynchronous method in the MonitorApplication class, but there was a conflict with it being a WebSocketApplication. I want to visit several more iterations of push including JMS (the approved way prior to EJB 3.1), Spring, etc. But I will spawn that off to a separate post at the end of this tutorial. For now, we'll continue the conversation regarding JSF and Swing.


No comments:

Post a Comment