Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Eclipse RAP's Push Session Mechanism

DZone's Guide to

Using Eclipse RAP's Push Session Mechanism

A detailed tutorial on how to use Eclipse RAP's push session mechanisms for a GPS-based app.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

There are probably few enthusuastic Eclipse developers who cannot appreciate the diligent efforts of Ralf Sternberg and his team to improve the UI of Eclipse RCP applications. Even with the upcoming E4 Rich Client Tooling project, the gradual replacement of all the Eclipse 3 SWT widget toolkit is still far off, and the insight of RAP to create an HTML/Javascript replacement for these was a stroke of genius!

However, there are a few fundamental problems when you want to make a browser-based variant of a rich client platform, and one of the most notable ones is that a browser based query always starts with an event spawned in the browser itself, such as clicking a link, or pushing a button. As long as the application respects this logic, there is little to worry about; the application knows that something is going to change (e.g. fill a table with values, or changing the settings in a combo box) and it can anticipate on this. Most RCP actions that affect the UI actually work this way, and the handling  of a UI event can be performed by handling a selection event or equivalent.

Problems start to occur when you do not want to wait for the results, but pass the handling of the request to a separate thread. When the results are returned, the UI needs to be notified that it can refresh the widgets, as the actual updating has to be carried out in the main UI thread. For this, the Display.asynexec() usually works fine in normal RCP applications, but in RAP this is not a guarantee, and you may end up with results that do not show on the screen. or UIThread exceptions. This problem is exacerbated when results have to presented to the UI which were never spawned by a UI action in the first place!

Let's take a real-life example that we ran into for the Aquabots programme that we are running at Research Centre Sustainable PortCities of the Rotterdam University of Applied Sciences. In this programme, students build small autonomous vessels for inspection and monitoring. Currently we are able to send a trajectory of waypoints to these vessels, after which they will follow the desired course with GPS coordinates. The client we developed was based on Eclipse RAP:

Image title

For the map, we use OpenLayer 3, a javascript library like Google Maps, that  contains most of the functionality to plot a trajectory, zoom in and out, and display relevant data on the chart. In RAP, the library can easily be integrated in the RCP through the Browser widget, and setting:

browser.setUrl( INDEX_HTML );

INDEX_HTML points to a resource that has been added to the extension registry (plugins.xml file), and contains the code needed to create the OpenLayers 3 map.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="chrome=1">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
    <style>
      .map {
        width: 100%;
      }
    </style>
    <script src="js/jquery-2.1.3.min.js" type="text/javascript"></script>
    <script src="http://openlayers.org/en/v3.8.2/build/ol.js" type="text/javascript"></script>
    <script src="js/openlayer.js" type="text/javascript"></script>
    <script src="js/general.js" type="text/javascript"></script>
    <link rel="stylesheet" href="http://ol3js.org/en/master/css/ol.css" type="text/css">
    <link rel="stylesheet" href="theme/maps.css" type="text/css">
    <title>Aquabots Dashboard</title>
  </head>
  <body onload='initInteraction()'>
    <div id="map"></div>
  </body>
</html>

Snippet 1: Index.html

All the events that are spawned from the UI are passed to OpenLayer 3 through the

BrowserUtil.evaluate(browser, <my_js_string> <my_callback_function> );

This way the functionality provided by OpenLayer 3  can be called upon by RAP. So far so good!

However, this does not give us an opening to respond to the results that are returned by OpenLayer 3,  and the vessels that are bobbing on the water! This data is offered to a few servlets which are also registered in the extension registry, but now we need to display this data in the widgets, and that is where the PushSession mechanism comes into play.

The Push Session

To recapitulate, we are now dealing with events spawned by javascript and from servlets, to which the UI has no knowledge how and when they occur. Sure, there is a button that was pushed at some point, a link that was clicked, but there is no way in telling when the underlying model and the widgets are updated, and the UI can be refreshed. Oh yes, and there is a possibility that more events can be returned, which may be started from the UI, but keep coming afterwards, for instance when monitoring data is being collected.

The guys from RAP came up with a pretty nifty solution to this problem, namely the push session. The trick is to emulate the standard browser-based approach by starting a push session from the UI, which is handled once the view's model is updated. Immediately afterward, the next push session is activated, in anticipation of another possible event. A push session therefore does not respond to events that should occur, like a regular UI event, instead it anticipates a possible event and handles this if it occurs.

Image title

Figure 2: Communication diagram

The mechanism is sketched in figure 2.In a way, it follows the regular pattern of updating the UI after a model has changed, but with the addition of starting another push session for another event. This keeps on, until the UI is disposed.

Another additional challenge is that updating the UI still has to be carried out in the main UI thread, so at some point we have to incorporate the well-known Display.asyncexec() in the mechanism. For this, I made a abstract class that handles most of threading issues, which you can see in snippet 2:

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.eclipse.rap.rwt.service.ServerPushSession;
import org.eclipse.swt.widgets.Display;

public abstract class AbstractPushSession {

    public static final int DEFAULT_TIMEOUT = 5000;

    private Display display;

    private ServerPushSession session;
    private ExecutorService es;
    private boolean refresh = false;
    private int timeout;

    private Collection<ISessionListener> listeners;

    protected AbstractPushSession() {
        this( DEFAULT_TIMEOUT );
    }

    protected AbstractPushSession( int timeout ) {
        this.timeout = timeout;
        listeners = new ArrayList<ISessionListener>();
        es = Executors.newCachedThreadPool();
    }

    private Runnable runnable = new Runnable() {
        public void run() {
            while(!refresh && !runSession()){
                try{
                    Thread.sleep( timeout );
                }
                catch( InterruptedException ex ){
                    ex.printStackTrace();
                }
            }
            if( display.isDisposed())
                return;
            display.asyncExec( new Runnable() {
                public void run() {
                    for(ISessionListener listener: listeners)
                        listener.notifySessionChanged( new SessionEvent( this ));
                    session.stop();
                    start();
                }
            });
        };
    };

    public void addSessionListener( ISessionListener listener ){
        this.listeners.add( listener );
    }

    public void removeSessionListener( ISessionListener listener ){
        this.listeners.remove( listener );
    }

    public void init( Display display ){
            this.display = display;
    }

    protected boolean hasRefreshed() {
        return refresh;
    }

    protected void setRefresh(boolean refresh) {
        this.refresh = refresh;
    }

    public synchronized void start(){
        session = new ServerPushSession();
        session.start();
        this.refresh = false;
        es.execute(runnable);
    }

    /**
     * Run the session. returns true if the session completed succesfully.
     * @return
     */
    protected abstract boolean runSession();

    public synchronized void stop(){
        this.listeners.clear();
        this.refresh = false;
        es.shutdown();
    }
}

Snippet 2: Core Push Session Mechanism

The abstract class introduces a session listener which will take care of the UI after the model is updated. The user interface creates an instance of the push session, provides it with a display, starts it for the first time, and stops it when the UI is disposed.

public class MapSession extends AbstractPushSession {

    private Model model;
    private IModelListener listener =
      new IModelListener() {

      @Override
      public void notifyStatusChanged( ModelEvent event) {
           setRefresh( true );
           Thread.interrupted();
    }
  };

  private static MapSession session =
    new MapSession();

    public static MapSession getInstance(){
        return session;
    }   

    public void setModel( Model model) {
        this.model = model;
        this.model.addListener(listener);
    }

    @Override
    protected boolean runSession() {
        return ( this.model != null );
    }

    @Override
    public void stop() {
        if( this.model != null )
            this.model.removeListener(listener);
        super.stop();
    }
}

Snippet 3: Implementation of the Push Session Mechanism.

The implementation depicted above shows that the session is actually started once the model is first added to the push session; only then a

start()

 will have effect. In this case, the

MapSession

 listens for changes in the model, after which a

refresh

 command is given. This activates the

Runnable

 thread in the abstract class, which sees to it that all the session listeners can update their widgets in the main UI thread.

An example of its usage in a widget is depicted below:

public class MyComposite extends Composite {

      private MapSession session =
              MapSession.getInstance();
      private ISessionListener sl =
        new ISessionListener(){

        @Override
        public void notifySessionChanged(SessionEvent
            event){
          refresh();       
        }   
    };

    public MyComposite(Composite parent, int style) {
        super(parent, style);
        ...
        this.session.addSessionListener(sl);
        this.session.init( Display.getDefault());
        this.session.start();
   }

    /**
     * Refresh the UI
     */
    public void refresh(){
       ...
    }

   @Override
   public void dispose() {
       session.stop();
       super.dispose();
   }
}

Snippet 4: Activating the push session in a Composite

the composite sees to it that the correct display is loaded, and the push session is started. The handling of changes in the model is left to the MapSession object. Note that this can also be delayed until a first UI event is triggered that starts the communication.

Last we need an object that runs in a separate thread, like a servlet.

public class ModelServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private Model model;
    private MapSession session = MapSession.getInstance();

    @Override
    public void init() throws ServletException {
        session.setModel(model);
        super.init();
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        ...
        updateModel();
        ...
    }
}

Snippet 5: The Separate Thread.

Conclusion

The Push Session option is quite stable and useful once you've gotten the hang of how it works. With a little bit of additional coding, you can implement a class that takes care of most of the boilerplate code needed to mediate between the model and the view, when dealing with asynchronous communication, and the former is running in a separate thread. Hopefully this contribution makes it a bit easier.

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
eclipse rcp ,eclipse ,eclipse rap

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}