Dr. Stefan Winkler
freier Softwareentwickler und IT-Berater

NOTE! This site uses cookies to track your user session.

If you not change browser settings to prevent accepting cookies, you agree to it.

I understand

I am currently working for a customer on an existing Eclipse RCP (based on Luna) which consists of 99% Eclipse 3.x API. The customer wants to migrate to E4 gradually, but there is no budget to migrate existing code all at once. Instead, the plan is to start using e4 with new features and migrate the other code step by step.

So, when I was given the task of creating a new view, I wanted to use the "new" (in Luna, anyways) e4view element for the org.eclipse.ui.views extension point. The good thing about this is that you can easily write JUnit tests for the new class because it is a POJO and does not have many dependencies. 

My problem is that part of the customer's RCP uses Xtext and several components or "services" (not actual services in an OSGi sense) are available via Guice.

So I was confronted with the requirement to get a dependency available via Guice injected in an E4-style view implementation:

public class MyViewPart
{
    @Inject // <- should be injected via Guice
    ISomeCustomComponent component;

    @PostConstruct // <- should be called and injected via E4 DI
    public void createView(Composite parent)
    {
        // ...
    }
}

 

The usual way to get classes contributed via extension point injected by Guice is to use an implementation of AbstractGuiceAwareExecutableExtensionFactory like this:

<plugin>
   <extension
         point="org.eclipse.ui.views">
      <e4view
            class="my.app.MyExecutableExtensionFactory:my.app.MyViewPart"
            id="my.app.view"
            name="my view"
            restorable="true">
      </e4view>
   </extension>
</plugin>

The colon in the class attribute is usually interpreted by the framework in a way that the class identified before the colon is instantiated as an IExecutableExtensionFactory and the actual object is identified by the parameter (given after the colon) and created by that factory.

But I did not expect this to work, because I thought it would bypass the E4 class creation mechanism; and actually, it seems to be the other way round and the e4view.class element seems to ignore the extension factory create the my.app.MyViewPart to inject it with E4DI. The MyExecutableExtensionFactory is never getting called.

As I said, I didn't expect both DI frameworks to coexist without conflict, so I thought the solution to my problem would be to put those objects which I need injected into the E4 context. After googling a bit, I have found multiple approaches, and I didn't know which one is the "correct" or "nice" one.

Among the approaches I have found, there were:

  1. providing context functions which delegate to the guice injector
  2. retrieving the objects from Guice and configure them as injector bindings
  3. retrieving the objects from Guice, obtain a context and put them in the context

(The first two approaches are mentioned in the "Configure Bindings" section of  https://wiki.eclipse.org/Eclipse4/RCP/Dependency_Injection )

I ended up trying all three, but could only get the third alternative to work.

This is what I tried:

Context Functions

I tried to register the context functions as services in the Bundle Activator with this utility method:

private void registerGuiceDelegatingInjection(final BundleContext context, final Class<?> clazz)
{
IContextFunction func = new ContextFunction()
{
@Override
public Object compute(final IEclipseContext context, final String contextKey)
{
return guiceInjector.getInstance(clazz);
}
};

ServiceRegistration<IContextFunction> registration =
context.registerService(IContextFunction.class, func,
new Hashtable<>(Collections.singletonMap(
IContextFunction.SERVICE_CONTEXT_KEY, clazz.getName()
)));
}

and called registerGuiceDelegatingInjection() in the BundleActivator's start() method for each class I needed to be retrieved via Guice.

For some reason, however, this did not work. The service itself was registered as expected (I checked via the OSGi console) but the context function was never called. Instead I got injection errors that the objects could not be found during injection.

Injector Bindings

I quickly found out that this solution does not work for me, because you can only specify an interface-class to implementation-class mapping in the form

InjectorFactory.getDefault().addBinding(IMyComponent.class).implementedBy(MyComponent.class)

You obviously cannot configure instances or factories this way, so this is not an option, because I need to delegate to Guice and get Guice-injected instances of the target classes...

Putting the objects in the context

Finally, the solution that worked for me was getting the IEclipseContext and put the required classes there myself during the bundle activator's start() method.

private void registerGuiceDelegatingInjection(final BundleContext context, final Class<?> clazz)
{
  IServiceLocator s = PlatformUI.getWorkbench();
  IEclipseContext ctx = (IEclipseContext) s.getService(IEclipseContext.class);
  ctx.set(clazz.getName(), guiceInjector.getInstance(clazz));
}

This works at least for now. I am not sure how it works out in the future if more bundles would directly put instances in the context; maybe in the long-term named instances would be needed. Also, for me this works, because the injected objects are singletons, so it does not do any harm to put single instances in the context.

I would have liked the context function approach better, but I could not get it to work so far.

Maybe one of you, the readers, can see my mistake. If so, please feel free to comment or to add an answer to my initial StackOverflow question.