Friday, September 25, 2009

Getting started with Spring-DM on eclipse

Introduction


These are few notes about getting started with a (very) simple Spring-DM bundle in Eclipse. It assumes at least some understanding of what the OSGI framework is and its benefits, as well as regular Spring knowledge for the Spring part. I start by implementing the solution directly against the OSGI apis, and then move on to its Spring-DM counterpart.


Installation





First steps :






  • Get a fresh install of Eclipse Galileo with Plugin Development Environment (PDE) from http://www.eclipse.org/downloads/. (I like to get the minimal Java IDE and install PDE on top but you can also grab the Eclipse RCP + PDE download)





    Creation of two bundles


    The main purpose of OSGI being to develop modular applications and dynamically use services from other bundles, we're going to create two Bundles :






    • org.naiade.messages : exposes a service that provides messages
    • org.naiade.hello : consume the former bundle to print messages in the console
    Now that's gonna be a pretty sophisticated version of Hello Spring-DM osgi modular world...

    Create a new org.naiade.messages project as a Plug-In developement / Plug-in Project.






    • On the first screen choose Equinox as a target platform instead of Eclipse
    • On the second screen uncheck Generate An Activator (Spring-DM will give that for free...)


     






    Message provider bundle


    In the org.naiade.messages we provide a package and the interface for our service :


    package org.naiade.messages;

    public interface HelloMessageProvider {

    public String getMessage() ;
    }


    and its default implementation :


    package org.naiade.messages.impl;

    import org.naiade.messages.HelloMessageProvider;

    public class HelloMessageProviderImpl implements HelloMessageProvider {

    public HelloMessageProviderImpl() {
    super();
    }

    @Override
    public String getMessage() {
    return "Hello, Spring OSGI world !";
    }

    }



    If you're not familiar with OSGI already you'll see that it enforces the good practice of always designing by interface. You can also note that the default implementation is in a different package. The main reason for this is that bundles specify which packages they expose to other bundles, while org.naiade.messages.HelloMessageProvider obviously will be exposed, there is no need to expose its implementation

    In order to expose the interface, open the project's META-INF/MANIFEST.mf in Eclipse's
    plugin development perspective, go to the Runtime tab and add org.naiade.messages as an exported package.













    Consumer bundle



    Create a new Plug-in project org.naiade.hello with the following class :

    package org.naiade.hello;

    import org.naiade.messages.HelloMessageProvider;

    public class HelloSayer {

    private HelloMessageProvider messageProvider;

    public HelloSayer() {
    super();
    }

    public void init() {
    System.out.println(messageProvider.getMessage());
    }

    public void setMessageProvider(HelloMessageProvider messageProvider) {
    this.messageProvider = messageProvider;
    }

    }


    Now Eclipse will complain that org.naiade.messages.HelloMessageProvider cannot be found. Edit hello Plug-in MANIFEST in the Dependencies tab, add the org.naiade.messages Plugin as a dependency (you may have to refresh your project afterwards). Note that you can access only the packages from org.naiade.messages that it has specifically exposed. (try importing the class from org.naiade.messages.impl and Eclipse will complain).







    Starting the Bundles



    In order to illustrate strating the Bundles without Spring, we need to deal with the OSGI api by hand. To deal with the api we need to access the OSGI packages so in both projects, add a dependency on the org.eclipse.osgi plugin


    In both projects, create (in the first package) an Activator class implementing BundleActivator.



    Here's the class for org.naiade.messages :

    package org.naiade.messages;

    import org.naiade.messages.impl.HelloMessageProviderImpl;
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;

    public class MessagesActivator implements BundleActivator {

    @Override
    public void start(BundleContext context) throws Exception {
    context.registerService(HelloMessageProvider.class.getName(),
    new HelloMessageProviderImpl(), null);
    }

    @Override
    public void stop(BundleContext context) throws Exception {

    }

    }


    Obviously what we're doing is telling the OSGI framework that we expose an implementation of HelloMessageProvider and that this implementation is the object
    new HelloMessageProviderImpl(). Note that we don't do anything in the stop, that method is meant to perform cleanup of resources, closing connections etc (you know, for bundles that actually do something).



    For the org.naiade.hello bundle :

    package org.naiade.hello;

    import org.naiade.messages.HelloMessageProvider;
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    import org.osgi.framework.ServiceReference;

    public class HelloActivator implements BundleActivator {

    @Override
    public void start(BundleContext context) throws Exception {
    ServiceReference ref = context
    .getServiceReference(HelloMessageProvider.class.getName());
    HelloMessageProvider messageProvider = (HelloMessageProvider) context
    .getService(ref);

    HelloSayer sayer = new HelloSayer();
    sayer.setMessageProvider(messageProvider);
    sayer.init();
    }

    @Override
    public void stop(BundleContext context) throws Exception {

    }

    }


    That code is requesting the context for a HelloMessageProvider and prints the provided message.



    Reference the Activator for both projects in the first tab of MANIFEST.mf













    Running the Bundle



    Create a new Run configuration of type OSGI framework. What happens with the default target platform in Eclipse is that you are presented with all OSGI plugins of Eclipse itself to include or not in your build. Just uncheck all of the target platform for now and check your 2 own plugins, then run


    You should be presented with a nice message






    Springifying the bundles



    While it's perfectly ok to use activators in order to publish and request services to and from the OSGI runtime, you are going to end up managing all the services by hand. That approach is to OSGI what the factory pattern by hand is to regular java. Hopefully if you are used to handling IOC with the Spring framework there is the Spring-Dynamic modules that (amongst other solutions) allows for
    both declarative exposure of your Spring beans as OSGI services and dependecy injection of services grabbed from other OSGI bundles.


    We're now going to see how you could implement the above projects using Spring-DM






    Installing the platform



    It is worth pointing that there are several ways to bootstrap Spring-DM projects, such as using maven archetypes, or installing Spring-IDE in Eclipse and using one of its predefined targets. However for the purpose of understanding what's going on, I like at least for the first time to try from scratch.



    Download Spring-DM with dependancies from http://www.springsource.org/osgi. (lastest GA is spring-osgi-1.2.0 as of today) and unzip it somewhere.



    Then go to the preferences of your eclipse, Plug-in Development - Target Platform, add a new platform of type "Nothing (Start with an empty definition)",
    using the Locations tab, add 2 directories from where you installed Spring-osgi :





    • spring-osgi-1.2.0/dist
    • spring-osgi-1.2.0/lib

    If you activate "Show Plug-in content", your target platform should now look like this





    Writing the bundles contexts



    First thing in Spring OSGI you won't need (at least not right now) to deal with Bundles activator. When the org.springframework.osgi.extender Bundle from Spring is started, it will listen for loading of all other bundles and manage them according to their context. Just delete both activators class and remove them from your bundle's MANIFESTs


    We are now going to create in each bundles the directory META-INF/spring and
    create there an applicationContext.xml. Note that you should be able to name the context anything, spring-osgi is going to try sourcing as context any xml file in
    META-INF/spring. It is also possible to put it elsewhere than META-INF/spring but you would have to put that location as an attribute in the bundle MANIFEST.mf. We'll stick with the standard for now.



    So the org.naiade.messages context is going to be :

    <?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 name="messageProvider" class="org.naiade.messages.impl.HelloMessageProviderImpl"
    scope="prototype" />

    <osgi:service id="messageProviderOsgi" ref="messageProvider"
    interface="org.naiade.messages.HelloMessageProvider" />

    </beans>


    Notice that the MessageProvider bean is defined in the standard Spring way. The <osgi:service> part tells spring-osgi that this bean is to be exposed as a
    org.naiade.messages.HelloMessageProvider service into the OSGI context.



    You may note that Spring-osgi introduces a new namespace for osgi elements : http://www.springframework.org/schema/osgi. Spring context namespaces, which were introduced in Spring 2.0 to allow for spring contexts expansion and customization (things such as <util:list>, <context:component-scan> etc...) so that should make sense if you're already familiar with the concept.



    Here is the context for the org.naiade.hello bundle :

    <?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="messageProvider" interface="org.naiade.messages.HelloMessageProvider" />

    <bean id="helloSayer" class="org.naiade.hello.HelloSayer" scope="singleton"
    init-method="init">
    <property name="messageProvider" ref="messageProvider" />
    </bean>

    </beans>


    The context is pretty straightforward : a reference to a org.naiade.messages.HelloMessageProvider is imported into that bundle, and that service is injected into the bean that's using it






    Starting the SpringDM project



    Go back to eclipse's preferences, Plug-in development - Target platform and make the spring target platform we defined earlier as default. Now if you've followed that tutorial from the beginning, your two bundles should complain. That's because Spring-osgi lib directory came with a version of Equinox (Eclipse's OSGI implementation) of 3.2.2. Just manually edit the bundle's manifest and remove the specific "3.5.0" version for the dependecy on equinox



    Create a new OSGI Run Configuration by selecting your two bundles, the org.springframework.osgi.extender one, and clicking the add required bundles to
    add only the extender's minimal set of dependencies.








    Run it and you should see our Hello message !


    to be improved and continued...




  • 0 comments:

    Post a Comment