Eclipse RCP
20.09.2010 - 24.09.2010, Hamburg
git
07.10.2010 - 08.10.2010, Essen
30.07.2009

Tutorial: Spring OSGi + Eclipse RCP

Spring is not only a helpful framework for the server side, its “Spring Dynamic Modules for OSGi” can be beneficial for Eclipse RCP applications on the client side as well:

Getting started

I assume that you know Eclipse RCP and Spring quite well and just want to know how to integrate both.

At first, you need to get the Spring Dynamic Modules plug-ins and add them to your target platform. I recommend keeping those separate in a folder and add a new directory entry to your target definition. You can download all the bundles from the Spring Bundle Repository. I added them to my example target platform as well, so you can look up a list of all required plug-ins or download them as one package: eclipse_rcp_de/spring_osgi.

You should also install the Spring IDE plug-ins in your Eclipse IDE. These help a lot with editing Spring configuration files. The update site is here:

http://dist.springframework.org/release/IDE

Adding Spring application contexts to your bundles

With Spring Dynamic Modules, every bundle gets its own application context. The XML configuration files are added to the folder META-INF/spring/ of your bundle. The naming convention for these files is [module]-context.xml:

de.ralfebert.someplugin
|-- META-INF
|   |-- MANIFEST.MF
|   `-- spring
|       `-- services-context.xml
|-- build.properties
`-- src

If you’ve installed the Spring IDE, you can add the Spring nature to your plug-in (Plug-in project context menu > Spring Tools > Add Spring Project Nature). You can then create the configuration files using New > Spring > Spring Bean Configuration File.

You can read more about the bundle structure in the Spring documentation: Bundle Format And Manifest Headers

Extender

Spring Dynamic Modules comes with a bundle org.springframework.osgi.extender that is responsible for instantiating the Spring application contexts. You need to make sure that this bundle is started, because only then the application contexts for your bundles will be available.

When you run your application from the Eclipse IDE, you can configure start levels in the Plug-ins tab of the run configuration:

Start levels for Spring DM extender in run configuration

For the exported product, you can specify start levels in the Configuration tab of the product. These settings are written to the file config.ini in your application configuration folder. Unfortunately, when you add a start level here, the default start levels are not applied any more (see bug #272768). So you need to configure the start levels of the framework bundles as well. This is the correct configuration for the start levels:

Start levels for Spring DM extender in product

An alternative to the config.ini file is to start the extender programmatically. This can be done by adding the following code in a place that is guaranteed to be used as part of the application startup (for example the Activator of the bundle that contains the RCP application or the Application class itself):

// Enforcing that Spring Dynamic Modules extender is started
Platform.getBundle("org.springframework.osgi.extender").start();

If you updated your run configuration and run your application, you should see the Spring extender startup in the console log:

30.07.2009 19:26:28 org.springframework.osgi.extender.internal.activator.ContextLoaderListener start
INFO: Starting [org.springframework.osgi.extender] bundle v.[1.2.0]
30.07.2009 19:26:28 org.springframework.osgi.extender.internal.support.ExtenderConfiguration <init>
INFO: No custom extender configuration detected; using defaults...

You can read more about the extender in the Spring documentation: The Spring Dynamic Modules Extender bundle

Injecting into view components

At this point, you can use Spring Dynamic Modules to declare beans and inject their dependencies in the same way as on the server-side. But how can you inject dependencies in your view components like View- or EditorParts? To inject their dependencies we need them to be Spring beans. Unfortunately, the Eclipse workbench is responsible for instantiating these objects. How can we delegate this responsibility to the Spring IoC container?

A little detail about the Eclipse extension mechanism comes in handy here. Wherever you can specify a class as attribute of an extension element, you can also specify a factory class (that implements IExecutableExtensionFactory). Then Eclipse will not instantiate the object itself but ask the factory instead.

Martin Lippert wrote a SpringExtensionFactory that will ask the bundle application context for the object. You can read about it here or download it here.

After you added the org.eclipse.springframework.util plug-in to your project and imported the package org.eclipse.springframework.util in your bundle manifest, you can use the SpringExtensionFactory like this:

<extension point="org.eclipse.ui.views">
   <view
         id="de.ralfebert.someview"
         class="org.eclipse.springframework.util.SpringExtensionFactory"
         name="SomeView"
         restorable="true">
   </view>
</extension>

The SpringExtensionFactory will look up the bean with the id de.ralfebert.someview and Eclipse will use this object. You can also specify the bean id independently from the id attribute of the contribution element using:

org.eclipse.springframework.util.SpringExtensionFactory:somebeanid

The bean has to be declared in the contributing plug-in. As view components are usually not singletons, you should use Spring’s prototype scope (this will make Spring instantiate a new instance every time this bean is requested):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="de.ralfebert.someview" class="de.ralfebert.SomeViewPart" scope="prototype"/>

</beans>

OSGi services

So far we have seen how to declare application contexts for bundles and how to inject dependencies in view components. Every bundle has its own, separate application context - what if multiple bundles want to act together? This is done using OSGi services.

You can export OSGi services in one bundle and import them in another bundle using a special XML namespace for OSGi:

Exporting a service

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
    xmlns:osgi="http://www.springframework.org/schema/osgi"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/osgi
        http://www.springframework.org/schema/osgi/spring-osgi.xsd">

    <bean id="someService" class="de.ralfebert.services.internal.SomeServiceImpl"/>

    <osgi:service ref="someService" interface="de.ralfebert.services.ISomeService"/>

</beans>

Importing a service

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:osgi="http://www.springframework.org/schema/osgi"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/osgi
        http://www.springframework.org/schema/osgi/spring-osgi.xsd">

    <osgi:reference id="someService" interface="de.ralfebert.services.ISomeService"/>

</beans>

You can read more about importing and exporting services in the Spring documentation: Exporting A Spring Bean As An OSGi Service, Defining References To OSGi Services

stefan, 22. November, 17:37 Uhr

Hi Ralf,
great blog.
Just an idea: To create a feature describing the example target platform. Including the necessary cspec and rmap for buckminster, to download everything from SpringSource Bundle Repo (Maven 2) and Eclipse Gallileo Update Site. Using Maven 2 repos should be no problem, since Buckminster has Maven ingration. Right?

Best Regards,
Stefan.

Ralf Ebert, 24. November, 00:21 Uhr

Stefan, thanks for the idea, that's indeed on my list of things to do. It just needs to be configured which might take some time, especially as the mvn support is quite new (and I have not used that before). I also plan to try the other way round, Tycho is a project to build Eclipse projects with Maven that is to be extended vastly in Maven 3. Jason van Zyl demoed building RCP apps at the Devoxx conference and it looked quite promising.

mac, 24. November, 21:03 Uhr

Great!
SpringExtensionFactory is what I was looking for!
Untill today I ve been using the delegate pattern to do this;
EclipseCreatedView delegating to SpringCreatedView but this was a bit painfull to use.

thx!

stefan, 25. November, 23:46 Uhr

Hi Ralph,

I had some success with using http://www.springsource.com/update/e3.5 . This p2 repository contains most of the Spring artifacts, since they are required by Spring IDE. With "some" success I mean, I could convince Buckminster to download org.springframework.osgi.core by adding this plugin to the platform feature.xml. However, transitive dependencies to other Spring libraries have not been resolved automatically (I guess since the dependencies are package imports and no require-bundle).
So, I guess I need to create some feature containing all required spring artifacts ...

Ralf Ebert, 26. November, 01:11 Uhr

@Stefan, very cool! Yeah, Spring bundles mostly use 'Import-Package', creating a Spring DM feature should solve this problem.

geejay, 19. Februar, 15:58 Uhr

For some reason I cannot get org.springframework.osgi.extender to be a dependency in the plug-in. I have tried the way described in the tutorial, I have also tried using the suggested update site for the SPringIDE plug-in - http://www.springsource.com/update/e3.5.

Does anybody have any suggestions as to why org.springframework.osgi.extender doesnt come up when I try to add it as a plug-in dependency???

Ralf Ebert, 19. Februar, 21:50 Uhr

@geejay: Did you add the Spring DM runtime bundles to your target platform?

Henno Vermeulen, 26. Februar, 12:39 Uhr

Very useful little tutorial! We also use Eclipse RCP and we use Spring-DM to connect to a server. I guess one disadvantage of the SpringExtensionFactory is having to wait for a slow Spring context to show up, which may slow down workbench startup. I guess in this case you should just use the normal way of requesting your beans using OSGi services from the publihed Spring context (or create a very small helper class independent of OSGi and Spring to do this).

As a related issue to using the SpringExtensionFactory to create ui handlers which block until a service is available, my splash screen doesn't show the progress bar yet, check this out http://www.eclipse.org/forums/index.php?t=msg&goto=517161&#msg_517161 if you feel like it.

Ralf Ebert, 26. Februar, 14:38 Uhr

Henno, this is one of the disadvantages of Spring DM; as far as I know it always creates lazy proxy objects that wait for the services to appear. Another way to achieve something similar without waiting is using the Wire mechanism from Riena Core which actively tracks a service and calls bind/unbind methods (annotated with @InjectService) when the service shows up / disappears. Have a look at my Address Book example and helper classes in rcputils to see an example how this can be utilized in RCP applications:

http://www.ralfebert.de/projects/rcp_addressbook/
http://github.com/ralfebert/rcputils/tree/master/de.ralfebert.rcputils.wired/src/de/ralfebert/rcputils/wired/

Henno Vermeulen, 26. Februar, 14:41 Uhr

Thanks to a prompt reply by Martin Lippert I already solved the problem of SpringExtensionFactory blocking. When I use cardinality="0..1" in my osgi:reference tag it does not block anymore because the service is not mandatory (I should have rtfm). I still use SpringExtensionFactory to create the handler from a Spring context, but the injected service now seems to be a lazy-loading proxy and it is not blocked anymore.

geejay, 03. März, 15:41 Uhr

Hi Ralf, yes. I installed the IDE. I then downloaded all the JARs that I found in your eclipse_rcp_de/spring_osgi directory. I put them in a directory and then added this directory to the target platform. But the extender never comes up in this directory. Is this what I should've done? Simply downloading the files from your directory into my own? Is there any other way I can download all the Spring stuff?

Ralf Ebert, 04. März, 09:38 Uhr

You can also find and download the Spring DM bundles in the SpringSource Bundle Repository: http://www.springsource.com/repository/app/bundle/version/detail?name=org.springframework.osgi.extender&version=1.2.1

I downloaded all the bundles from there and added them to the spring_osgi folder; the extender is clearly there (org.springframework.osgi.extender).

Henno Vermeulen, 08. März, 16:25 Uhr

I'm not sure if other people have the same issue, but in order for Spring-DM to read the XML schema definition files I also had to set some other bundles to auto start at level 4. Even worse, I didn't do this for org.springframework.beans or org.springframework.context (not sure which is the culprit), and my RCP app started up without exceptions in 17 seconds and spent a long time downloading from the internet. When added these bundles, startup time went to 6 seconds.

See also http://forum.springsource.org/newreply.php?do=newreply&noquote=1&p=287792

geejay, 09. März, 16:53 Uhr

Hi Ralf, ok, now I have a little bit more success. I downloaded the spring-osgi-1.2.1-with-dependencies zip from the Spring site, I then added the Dist and Lib dirs from this Zip into my target platform. I can now see the extender. I am now having problems running the plug-in. I get Missing Constraint: Import-Package: org.springframework.osgi.

It is getting frustrating getting this basic setup going.

Could you please let me know what version of Eclipse you were using at the time?

Ralf Ebert, 10. März, 15:09 Uhr

geejay: The main Spring Framework bundle seems to be missing in the target platform or in the run configuration. I used Eclipse 3.5, but that shouldn't really matter.

Diego, 18. März, 14:11 Uhr

Hello!, great tutorial, this is what I was looking for. But the problem is that I can't get it to work, maybe because I am trying to make it work with my whole project at once. The problem I am stuck in now, is that I use Hibernate along with Spring, and I get ClassNotFoundException: org.hibernate.cfg.Configuration. I was using and hibernate bundle made by myself that worked before this, but now it doesn't anymore, it seems like I should put some hibernate plugin in the Target Platform along with Spring? Please Help!

I'm looking forward to your comments:

Schulungen

Eclipse RCP

Ralf Ebert | Blog | Eclipse RCP | Tutorial: Spring OSGi + Eclipse RCP