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

Eclipse's RAP Push Session Revisited

DZone's Guide to

Eclipse's RAP Push Session Revisited

If you're writing code for a web application using Java, read on to learn about how to work with the newest updates to Eclipse.

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

A few years ago, I wrote an article on Eclipse's RAP Push Session mechanism. To review, the Remote Application Platform team faced the problem that a full-fledged Rich Client application comes with its conundrums when this needs to be implemented with HTTP and JavaScript. A regular rich client can respond to any event that it receives, while HTTP always starts with an event that is spawned in the client itself. Usually, this problem is tackled by polling the event source, and generating a UI event when the event source changes. The Push Session mechanism provided such a mechanism in Eclipse RAP but needed a bit of boilerplate code to make it work. My previous post addressed this issue.

I've been using the proposed mechanism for a few years, without too much ado, but there were a few developments which made me reconsider the solution I wrote about. The main ones are:

  1. The solution seemed less efficient for extremely fast events, which caused the Rich Client to freeze every now and then.
  2. I realized that the push session is always activated by event listeners, which allowed for a more efficient design.
  3. The proposed solution sometimes makes handling of Java to JavaScript calls problematic.

The latter issue may need some further explanation! One of the libraries I have developed is an OpenLayers plugin, which is used to create and handle maps. The calls are made in Java, which are transformed to JavaScript functions that are consequently handled by OpenLayers. A callback function notifies listeners about the success of these calls, and are sometimes used to generate new calls to the OpenLayers map.

The nature of this approach in RAP is such that a call has to be completely handled before the next call can be made. If not, a  "JavaScript function is already pending" exception is thrown, and the next call will not be processed. I had worked around this problem by first collecting all the JavaScript calls that are required as a response to an event, prior to calling a synchronize()  function that processes all the calls as a batch.

With the push session mechanism, this approach becomes problematic! An event may notify a number of listeners about a change, and all these may affect the OpenLayers map. As the handling of the event in the UI has to be performed in the UI thread, the result was that all the listeners were processed simultaneously, and the calls to OpenLayers were interfering with each other. As a result, the improved Push Session mechanism I present here will give a notification when all the listeners have been processed, after which the synchronise() call can be made.

An Improved Push Session Handler

As mentioned earlier, the basic principles of the previous post are still correct, so I will not dwell too much on the implementation details. The basic premise of the session handler is that a listener can provide a data object (usually a  java.util.EventObject ), after which a Display.asynexec() call will handle this at some point. This is a fairly standard approach for UI handling. The Session handler adds the Push Session mechanism and sees to it that a new push session is started when the handling of the UI has completed.

The improved session handler can collect multiple events, prior to the processing of these events by Display.asynexec(), and sees to it that this call only has to be made once, which greatly improves the UI performance. The resulting code is depicted below

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.rap.rwt.service.ServerPushSession;
import org.eclipse.swt.widgets.Display;

public class RefreshSession {

    private Display display;
    private ServerPushSession session;
    private Collection data;
    private boolean started;
    private boolean refresh;
    private Collection> listeners;
    private Lock lock; 

    public RefreshSession() {
        listeners = new ArrayList>();
        this.started = false;
        this.refresh = false;
        data = new ArrayList<>();
        lock = new ReentrantLock();
    }

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

    /**
     * Called to refresh the UI
     */
    public synchronized void addData( T data ){
        lock.lock();
        try {
            this.data.add( data );
        }
        finally {
            lock.unlock();
        } 
        refresh();
    }

    public void start(){
        session = new ServerPushSession();
        session.start();
        this.started = true;
    }

    public void stop(){
        this.started = false;
    }

    protected void notifyListeners( SessionEvent event ) {
        for( ISessionListener listener: listeners){
            try{
                if( listener != null )
                    listener.notifySessionChanged( event );
            } catch( Exception ex ){
               ex.printStackTrace();
            }
        }
    }

    public void dispose(){
        this.listeners.clear();
        this.display = null;
        this.stop();
    }

    protected void refresh() {
        if( !started || this.refresh || ( display == null ) || ( display.isDisposed()))
            return;
        this.refresh = true;
        display.asyncExec(
            new Runnable() {

                @Override public void run() {
                    lock.lock();
                    try {
                        SessionEvent event = null;
                        for( T dt: data ) {
                            event = new SessionEvent( this, dt );
                            notifyListeners(event);
                        }
                        data.clear();
                        refresh = false;
                        session.stop();
                        notifyListeners( new SessionEvent( this, ISessionListener.EventTypes.COMPLETED, null ));
                    }
                    finally {
                        lock.unlock();
                    }
                    start();
                }
            });
        }
    }
}

This bit of code is fairly similar to my previous post, but is better optimised to handle incoming events. All that remains to be done is to:

  • Add the RefreshSession to a widget, for instance a org.eclipse.swt.Composite class.
  • Call the addData() method in the event handlers.
  • Add a session listener to handle the code.
  • Dispose the RefreshSession when the widget is disposed.

Obviously, this bit of boilerplate code can also be abstracted, resulting in the AbstractSessionHandler depicted below:

import org.eclipse.swt.widgets.Display;
/**
 * Handle the session. This is usually implmenented as an inner class in
 * a composite or other widget
 * @author keesp
 *
 * @param
 */
 public abstract class AbstractSessionHandler {
    private RefreshSession session;
    private ISessionListener listener = new ISessionListener(){
        @Override public void notifySessionChanged(SessionEvent event) {
            onHandleSession( event );
        }
    };

    public void addData( D data ) {
        session.addData(data);
    }

    protected AbstractSessionHandler( Display display ) {
        this.session = new RefreshSession<>();
        this.session.init( display );
        this.session.addSessionListener( listener);
        this.session.start();
    }

    protected abstract void onHandleSession( SessionEvent sevent );
        
    public void dispose() {
        this.session.removeSessionListener(listener);
        this.session.stop();
    }
}

You can now easily add this handler as an inner class in your widget, as follows:

import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import java.util.EventObject;
import org.eclipse.swt.SWT;

....

public class MyComposite extends Composite {
    private static final long serialVersionUID = 1L;

   ....

    private SessionHandler handler;

    public MyComposite(Composite parent, int style) {
        super(parent, style);
        ....
        handler = new SessionHandler( super.getDisplay());
    }

    @Override
    public void dispose() {
        this.handler.dispose();
        ....
        super.dispose();
    }

    private class SessionHandler extends AbstractSessionHandler<MyEvent> implements IMyListener{
        protected SessionHandler(Display display) {
            super(display);
        }

        @Override protected void onHandleSession(SessionEvent<MyEvent> sevent) {
            try{
                /** HANDLE YOUR EVENT HERE **/
            }
            catch( Exception ex ){
                ex.printStackTrace();
            }
        }

        /**
         * This bit of code implements the event listener and is always the same!
        */
        @Override
        public void notifyChanged(MyEvent event) {
            if( getDisplay().isDisposed() )
                return;
            super.addData(event);
        }
    }
}

Conclusion

With the above code, much of the boilerplate code required to implement a push session in Eclipse RAP has been abstracted away and results in efficient handling of external events by the graphical user interface.  

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
eclipse ,gui ,web dev ,java web development

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}