e4 on RAP – How does it work

In the post in which we introduced e4 on RAP we promised to give you some technical background information how we managed to get e4 applications running on RAP – if you are interested in the background, read on.

[ Editor’s note: See also Tom Schindl’s post on update for Luna M7. ]

Preface

There are 2 important concepts in e4 which we make use of:

  • Renderers are plugable so if you are uncomfortable with a default implementation you can replace it with your own
  • The DI system can be enhanced with OSGi-Services contributing 2 different things:
    • IContextFunctions which act as a factory creating DI-Data on demand
    • ExtendedObjectSuppliers which teach the DI-System new annotations like @UIEventTopic, @Preference, … which are extremely powerful and open doors to many advanced things

Unsupported SWT stuff

The problem

RAP does not implement all SWT-APIs and one of those omissions is the support for MouseMoveListener which are used in e4 to implement a custom layout to render MPartSashContainer hierarchies.

The solution

Because we forked the renderers.swt bundle we could have replaced the code in the SashRenderer and use a SashForm directly. We went another route in this case and left the original code untouched but specialized the WorkbenchRenderFactory to return a RAP specific implementation built on top of SashForm. In the next release timeframe we should try to have a native RAP implementation of the layout (maybe by moving the layout from e4 to SWT).

Singletons

Singletons you ask? Didn’t e4 define that there are no singletons? You are right, e4 has no singletons & statics in the code base but it implicitly uses 2 types:

  • OSGi-EventAdmin is used as the IEventBroker
  • OSGi-Services (most importantly) ExtendedObjectSuppliers to enhance the DI-Framework

EventBroker

The problem

e4 uses a publish & subscribe pattern to deliver events and while the IEventBroker itself is not a singleton (it is created through an IContextFunction) the underlying OSGi-EventAdmin used to deliver and subscribe is. In an RCP-Environment it doesn’t matter, in a multi-userenv like RAP it does.

The solution

The solution is twofold:

    1. We contribute a ContextFunction with a service.ranking=1 replacing the default implementation coming with e4 to the OSGi-Service-Registry
    2. We implemented our own IEventBroker implementation which simply prefixes the topics with a session prefix so if you execute code like this
      
      @PostConstruct
      void init(IEventBroker broker) {
        broker.subscribe("mytopic/test", ...);
        broker.send("mytopic/test","Hello e4");
        broker.post("mytopic/test","Hello e4");
      }
      

      You are not subscribing / delivering to the event-topic "mytopic/test" but "7a474aba-61ac-472c-80ae-9206ddd3a0a3/mytopic/test"

so our IEventBroker implementation is looking like this:


public class RAPEventBroker implements IEventBroker {
  @Inject
  @Named(E4Application.INSTANCEID)
  String instanceId;

  // ....

  private Event constructEvent(String topic, Object data) {
    topic = rapifyTopic(instanceId, topic);
    // ...
  }

  public static String rapifyTopic(String instanceId, String topic) {
    return instanceId + "/" + topic;
  }
}

In 4.5 we aim to contribute that back to upstream so that we don’t have to roll our own one.

ExtendedObjectSuppliers

The problem

ExtendedObjectSuppliers a OSGi-Services consulted by the DI-System when it detects the use of an annotation like @UIEventTopic, @Preference, … and because the object-supplier needs information from the workbench context (e.g. the UISynchronize) the DI-System initializes the OSGi-Services when looked up using

IInjector injector = InjectorFactory.getDefault();
injector.inject(supplier, objectSupplier);

Now in a multi-instance environment like RAP this would mean that e.g. the initial workbench would win! Not really what we want!

The solution

The solution to this problem is more complex than the replacing of IContextFunction. Let’s make up an example.

Let’s suppose our ExtendedObjectSupplier looks like this:


public class UIEventObjectSupplier extends ExtendedObjectSupplier {
  @Inject
  protected UISynchronize uiSync;

  @Override
  public Object get(IObjectDescriptor descriptor, 
    IRequestor requestor, 
    boolean track, boolean group) {
    // ...
  }
}

Now instead of having one supplier / DI-Engine we need to have one supplier / Workbench context and we can reach that with an ExtendedObjectSupplier which creates the real supplier on demand for each workbench context it detects:


public class Dispatch_UIEventObjectSupplier extends ExtendedObjectSupplier {
  private Map supplierCache = 
     Collections.synchronizedMap(new HashMap());
  private Map rootContextMap = 
     Collections.synchronizedMap(new HashMap());

  public Object get(IObjectDescriptor descriptor, 
                        IRequestor requestor,
			boolean track, boolean group) {
    IEclipseContext c = getContext(requestor);
    IEclipseContext appContext = rootContextMap.get(c);

    if( appContext == null ) {
      // never seen this context need to find workbench-context
    }

    ExtendedObjectSupplier supplier = 
       supplierCache.get(appContext);

    if( supplier == null ) {
      // never seen this supplier for this context 
      // create on correct context scope
      supplier = ContextInjectionFactory.make(
        UIEventObjectSupplier.class, appContext);

      supplierCache.put(appContext, supplier);
    }

    return supplier.get(descriptor, requestor, track, group);
  }

  private static IEclipseContext getContext(IRequestor _r) {
    Requestor r = (Requestor) _r; 
    return ((ContextObjectSupplier)r.getPrimarySupplier())
      .getContext();
  }
}

The nice side effect of this is that the original implementation is UNCHANGED!

You notice that there’s slightly bigger overhead than the original implementation because we have to calculate the root-context for each IEclipseContext once so pushing this strategy back upstream is not going to work out.

Conclusion

Reassessing the solutions we used to get RAP working on e4, this once more shows the extreme power of the Eclipse 4 Application Platform which provides default implementation for things but allows you to replace exactly those parts not working as you want in your context leaving 90% of the other parts untouched and free lunch.

I hope you followed along to the end and have learned something new about RAP, e4 or both of them – happy hacking!

No Comments

Sorry, the comment form is closed at this time.