Wednesday, June 4, 2008

Eclipse RCP Application using Spring DM

Last week I was searching the web for some example of Spring DM running on a Eclipse RCP application.
Didn't find one, so I thought I could write a post.

Download:
Eclipse 3.4 RC3
Spring Dynamic Modules for OSGi(tm) 1.0.2

Now create a new Plug-in-Project, using the settings below:
























As a next step you have to import the Spring DM Plug-ins.
File->Import->Plug-in Development->Plug-ins and Fragments.
Point the plug-in location to the extracted "spring-osgi-1.0.2/dist" directory.



On the next page, add the following plug-ins:
  • org.springframework.bundle.osgi.core
  • org.springframework.bundle.osgi.extender
  • org.springframework.bundle.osgi.io
You just imported the Spring DM OSGi part, you also need the actual Spring framework.
Do the same as above, but instead of the "dist" directory select the "spring-osgi-1.0.2/lib" dir and select those plugins:
  • org.springframework.bundle.spring.aop
  • org.springframework.bundle.spring.beans
  • org.springframework.bundle.spring.context
  • org.springframework.bundle.spring.core
  • org.springframework.osgi.aopalliance.osgi
We have to manually create a apache commons logging plugin.
Spring DM has some classloading problems with the existing plugin contained in the eclipse platform.
New->Plug-in Development->Plug-in from existing JAR archives.
Add External...
Now if you have the Spring framework including dependencies on your machine select the commons-logging.jar located at "spring-framework-2.5.x/lib/jakarta-commons", otherwise download the jar here.
Name the plugin "org.apache.commons.logging

Ok, so now your workspace is ready.
Let's create a Spring service.

package swissdev.springdm;

public interface IMyService {

String getSomething();
}


package swissdev.springdm;

public class MyService implements IMyService {

@Override
public String getSomething() {
return "something";
}

public void start() {
System.out.print("start service");
}

public void stop() {
System.out.print("stop service");
}
}

META-INF/spring/applicationContext.xml

<?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-2.5.xsd">

<bean id="myService"
class="swissdev.springdm.MyService"
init-method="start"
destroy-method="stop"/>

</beans>


META-INF/spring/applicationContext-osgi.xml

<?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:service id="myServiceOsgi" ref="myService" interface="swissdev.springdm.IMyService"/>

</beans>


Our application is basically ready to launch, so lets do that.
Right click on the swissdev.springdm plug-in: Run-As -> Eclipse Application.
A simple window containing a view is launched, nothing special.

Open the run configuration: Run -> Run Configurations...
Select your "swissdev.springdm.application" launch config and change to the "Arguments" tab.
Add "-console" to the Program arguments.
Switch to the Plug-ins tab and select all the "Workspace" plug-ins, hit the "Add Required Plug-ins" button and run again.
Type ss in the console, you will get the following output:



So our Spring plug-ins are not active.
Open the run configurations again and switch to the "Configurations" tab, change the Configurations File option to "Use existing config.ini file as a template" and enter ${workspace_loc}/swissdev.springdm/config.ini.
We can get the default generated config.ini in our workspace.
It's located at /.metadata/.plugins/org.eclipse.pde.core/swissdev.springdm.application/config.ini, copy it to your plug-in.
Right click on the ini file and select Open With -> Text Editor.
Ctrl+f Find: extender
You should find ....org.springframework.bundle.osgi.extender add @start at the end:
org.springframework.bundle.osgi.extender@start
Do the same with the swissdev.springdm plugin.

Launch again, you will see a lot of output and somewhere between "start service", thats the output we defined in the MyService class.
Spring DM is working!

To use our service we first need to modify our Activator:

private BundleContext context;

public BundleContext getContext() {
return context;
}

public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
this.context = context;
}



Open the generated View class and edit the createPartControl method:

public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setInput(getViewSite());
viewer.addDoubleClickListener(new IDoubleClickListener() {

@Override
public void doubleClick(DoubleClickEvent event) {
ServiceTracker tracker = new ServiceTracker(Activator.getDefault().getContext(), IMyService.class.getName(),null);
tracker.open();
IMyService service = (IMyService) tracker.getService();
System.out.println(service.getSomething());
}
});
}


Launch again and double click one of the itms in the window, in the console there should appear an "something" output.

That was it....

27 comments:

Anonymous said...

Thanks for this tutorial. It was the first on this topic which really worked for me.

One thing I had to change: in the source code for the createPartControl() method, you have

Activator.getContext()

while it should be

Activator.getDefault().getContext()

as the method getContext() is not static.

Flavio Donzé said...

thanks for the hint, fixed it.

Anonymous said...

Thanks for this tutorial !!!

Just as a side note, by installing SpringIDE (http://springide.org/blog/) including the OSGI plugin, and making my test plugin depends on org.springframework.bundle.spring, org.springframework.bundle.osgi.core, org.springframework.bundle.osgi.extender and org.springframework.bundle.osgi.io I managed to avoid importing spring modules to my workspace. I think this solution is a little more elegant.

I also had no problems with my org.apache.commons.logging so I skipped this part too.

Anonymous said...

In fact I also had the problem with org.apache.commons.logging, the config.ini I copied form your example was still pointing to the custom org.apache.commons.logging bundle.

BTW, I used

Bundle springDM = Platform.getBundle("org.springframework.bundle.osgi.extender");

springDM.start();


in my Activator#start() method to avoid using a custom config.ini

Flavio Donzé said...

Thanks mrmagne, I didn't like the config.ini either. That looks like a better solution!

Sylvain MOUQUET said...

Very good tutorial.

I am trying to seperate the project in 2 projets. So, my architecture is based on an UI plugin (eclipse rcp) and an CORE plugin (spring osgi/hiberate/server)

Can you explain how i must to do ?

Pedro Cavaléro said...

Thanks for this tutorial! I have a question to mrmagne, how did you get this dependencies to run? I have Spring IDE installed and i only can have these plugins if i change the target plataform to [spring plugin path]/release/target. Did you change the target plataform?
Thanks

Flavio Donzé said...

@Pedro Cavaléro
Which Eclipse Version are you using? I'm using 3.4 and Spring IDE 2.0.6 and I can select them in the dependency editor.

Anonymous said...

@Pedro Cavaléro
Sorry I forgot to follow the comments of this page ;)
I forgot to mention that with the spring IDE module for eclipse, there is an optional Spring IDE OSGI Extension that you can install. It will com with the org.springframework.bundle.osgi.extender that you will depend on.

Anjur Chan said...

Awesome ! This is my first Spring DM that met my RCP application ! To be frank I did googled and read lots of articles to get started. I would say none of the articles are as complete as yours ! Keep blogging new articles on Eclipse RCP & Spring DM.

Thanks,
CAN.

Anonymous said...

Thanks for this nice tutorial. I spend some hours to find a simple and nice introduction about how to use Spring with eclipse RCP. You really did a good job.

Mark Nuttall said...

Instead of:
org.springframework.bundle.spring

Use:
org.springframework.aop;bundle-version="2.5.6",
com.springsource.org.aopalliance;bundle-version="1.0.0"

Otherwise you will get unwanted toolbar items. :)

Anonymous said...

Thx a lot, that finally worked for me. Note that with spring-osgi-1.2.0 the steps to add org.apache.commons.logging don't seem to be necessary. I just added the com.springsource.slf4j.org.apache.commons.logging-1.5.0.jar from the /lib directory as additional workspace plugin. Indeed I'm not sure if even this is necessary. The generated ini shows a second reference to org.apache.commons.logging_1.0.4.v20080605-1930.jar. But it worked, I'm very happy!

Sean Rasmussen said...

Many thanks for the clear tutorial, Flavio; as others have already commented, it was the first such example that worked for me.

Unknown said...

Wonder if anyone is monitoring this any more. All looks cool but I can't get it to work. I think my problem is versions. I'm using Eclipse 3.5 and Spring 2.5 . I've added the Bundle springDM but it does not find my bundle "org.springframework.osgi.extender"

I had no luck with the custom config.ini either I didn't have the expected extender either. I'd sure like to get Spring and RCP to talk to each other. Just not able to know enough to make that happen.

Any guidance would be greatly appreciated.

Anonymous said...

Hello,
great tutorial, but i have little problem. My Service is always null!
frist xml
bean id="myService"
class="service.MyServiceImpl"
init-method="start"
destroy-method="stop"

osgi:service ref="myService" interface="service.MyService"

The second xml File
osgi:reference id="myService" interface="service.MyService"


Console:
Jan 25, 2010 7:31:40 PM org.springframework.osgi.extender.internal.activator.ContextLoaderListener start
INFO: Starting [org.springframework.osgi.extender] bundle v.[1.1.2.B]
Jan 25, 2010 7:31:40 PM org.springframework.osgi.extender.internal.support.ExtenderConfiguration
INFO: No custom extender configuration detected; using defaults...
Jan 25, 2010 7:31:40 PM org.springframework.scheduling.timer.TimerTaskExecutor afterPropertiesSet
INFO: Initializing Timer
Jan 25, 2010 7:31:40 PM org.springframework.osgi.extender.support.DefaultOsgiApplicationContextCreator createApplicationContext
INFO: Discovered configurations {osgibundle:/META-INF/spring/*.xml} in bundle [Client (client; singleton:=true)]
Jan 25, 2010 7:31:40 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext@45e228: display name [OsgiBundleXmlApplicationContext(bundle=client, config=osgibundle:/META-INF/spring/*.xml)]; startup date [Mon Jan 25 19:31:40 CET 2010]; root of context hierarchy
Jan 25, 2010 7:31:41 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from URL [bundleentry://106.fwk10039797/META-INF/spring/client-context.xml]
Jan 25, 2010 7:31:42 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext@45e228]: org.springframework.beans.factory.support.DefaultListableBeanFactory@704cf5
Jan 25, 2010 7:31:42 PM org.springframework.osgi.extender.internal.dependencies.startup.DependencyServiceManager findServiceDependencies
INFO: Adding OSGi service dependency for importer [&myService] matching OSGi filter [(objectClass=service.MyService)]
Jan 25, 2010 7:31:42 PM org.springframework.osgi.extender.internal.dependencies.startup.DependencyServiceManager findServiceDependencies
INFO: OsgiBundleXmlApplicationContext(bundle=client, config=osgibundle:/META-INF/spring/*.xml) is waiting for unsatisfied dependencies [[&myService]]

Flavio Donzé said...

Hello,

after you read the service in the second file:

osgi:reference id="myService" interface="service.MyService"

what do you do with it? pass it to a local service/bean in this bundle?

Or do you lookup the service using OSGi API?

The configuration looks fine and should work in my opinion.

Unknown said...

HI Flavio,

I found your article about Spring DM and RCP Applications. At the moment I try to implement something similar to your example.

Instead of using the OSGI Service Registry, we created our own Service-Registry-Class for the Business-Layer and initialize every service as an attribute with Spring DM.

If I do implement you example with our Service Registry and I double-click on the viewer there is a text output on the console (so everything works fine). But If I like to call the "getSomething()" methode after creating the viewer, there is a null pointer exception.

viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setInput(getViewSite());
ServiceTracker tracker = new ServiceTracker(Activator.getDefault().getContext(), IMyService.class.getName(),null);
tracker.open();
IMyService service = (IMyService) tracker.getService();
System.out.println(service.getSomething());

If I check the logs I see that Spring has not finished to initialize the services, but the guy stars and wants do display the viewer. Because of the null pointer exception, it fails.

Have you any solution for that problem? Any method to initialize all ".xml" files before beginning to initialize the gui?

Flavio Donzé said...

Hi Quiddix

There is some documentation found at:

http://static.springsource.org/osgi/docs/1.2.1/reference/html-single/
6.1. Bundle Format And Manifest Headers

Maybe this helps:
Spring-Context: *;create-asynchronously:=false

If you place a breakpoint at tracker.open(); and wait until all required bundles are started, does it work then?
Otherwise it might be some other problem?

Unknown said...

Hi,

that option helped for me.
Now I can start the application and load a service from my own BlServiceRegistry Class.

At that point I recognized, that I should use OSGI-Serives in by Project instead of implementing my own ServiceRegistry.

It is no problem to initialize all services in the BlServiceRegistry. But the BlServiceRegistry has to know about the DBServiceRegistry. And because they are in separate bundles, I dont have any bean definitions from the Database available in the Businesslayer. I think if I would use Services I could publish the serviceimpl. of the database as an osgi:service and reuse it in the businesslayer ".xml" Spring file.

Unfortunately I got a namespace error when trying to export my beans as an osgi:service.

Flavio Donzé said...

you have to put
xmlns:osgi="http://www.springframework.org/schema/osgi"

and
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
in
xsi:schemaLocation

Flavio Donzé said...

<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">

Unknown said...

So here`s my context_osgi_bl.xml files content:







SCHWERWIEGEND: Application context refresh failed (OsgiBundleXmlApplicationContext(bundle=de.wagner.eclipselink.bl, config=osgibundle:/META-INF/spring/*.xml))
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/osgi]
Offending resource: URL [bundleentry://176.fwk19621457/META-INF/spring/service_osgi_bl.xml]

Do I have to import the namespace from somewhere?

BTW thanks for your help ;)

Flavio Donzé said...

you have to replace the tag symbols (< and >) with lt gt otherwise the xml is not posted.

Unknown said...

&lt ?xml version="1.0" encoding="UTF-8"?&gt
&ltbeans 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"&gt

&ltosgi:service id="blDataRetreivalService"
ref="de.wagner.eclipselink.bl.implementations.BlDataRetreivalService"
interface="de.wagner.eclipselink.bl.interfaces.IBlDataRetreivalService" /&gt
&lt/beans&gt

Flavio Donzé said...

Hmm strange, do you have all the spring bundles in you launch?

Something I noticed, is this "ref" correct? Should be the ID of the bean not the class. (maybe you have the same name as the class)

ref="de.wagner.eclipselink.bl.implementations.BlDataRetreivalService"

John said...

Nice Blog and really thanks for the sharing information - Offshore Software Development