Saturday, 30 October 2010

OSGi Tutorial: 4 ways to activate code in OSGi bundle

Hello everybody!

I would like to present a tutorial in which I show 4 ways how to activate/start code inside your OSGi bundle. All those ways are part of OSGi 4.2 specifications. The goal of the tutorial is to explain in a short form some OSGi specifications chapters with samples. I do not like to make any deep comparison of activation ways, just overview with workable examples. All sources you can find here.

Update 21.10.2013: examples were updated (Fixed an issue with missed Equinox Maven artifacts)

Contents:
  1. Requirements
  2. Use Case details
  3. Bundle Activator
  4. Declarative Services
  5. Blueprint Services
  6. Web Application Bundle
  7. How to run examples
  8. References

  1. Requirements. There are minimum requirements:

  2. Use Case details. The tutorial use case is very simple:
    • There is interface org.knowhowlab.osgi.tips.activation.core.Echo
      public interface Echo {
                          String ECHO_TYPE_PROP = "echo_type";
      
                          String echo(String str);
                      }
                  
      This interface exported by core bundle.
    • This interface should be implemented
    • Implementation should be registered as OSGi service only when org.osgi.service.prefs.PreferencesService service is available in OSGi registry

  3. Bundle Activator. This is the simplest and oldest way to activate code in your bundle.
    There are some details of this activation way:
    • There is only one BundleActivator per bundle
    • Any class could implement org.osgi.framework.BundleActivator interface
    • This class should have empty constructor
    • This class should be provided with MANIFEST OSGi header Bundle-Activator to be called on OSGi bundle STARTING stage
    • The best way to use it (from my own experience) in low-level bundles (e.g. implementation of any OSGi API) or when you like to control everything yourself
    • This way could be also used when your code does not have a lot of dependencies to external services. org.osgi.util.tracker.ServiceTracker could be used to track external services. As many external services you track as more logic code you have to write to cover all cases.

    Here is code of my BundleActivator that implements required tutorial use case:
    public class Activator implements BundleActivator, Echo {
                // ServiceTracker for PreferencesServices
                private ServiceTracker serviceTracker;
                // BundleContext
                private BundleContext bc;
                // Registration of Echo service
                private ServiceRegistration registration;
    
                // activation
                public void start(BundleContext context) throws Exception {
                    bc = context;
                    // init and start ServiceTracker to track PreferencesService
                    serviceTracker = new ServiceTracker(context, PreferencesService.class.getName(), new Customizer());
                    serviceTracker.open();
                }
    
                // deactivation
                public void stop(BundleContext context) throws Exception {
                    // stop ServiceTracker to track PreferencesService
                    serviceTracker.close();
                    serviceTracker = null;
                }
    
                public String echo(String str) {
                    return str;
                }
    
                // customizer that handles tracked service registration/modification/unregistration events
                private class Customizer implements ServiceTrackerCustomizer {
                    public Object addingService(ServiceReference reference) {
                        System.out.println("PreferencesService is linked");
                        // register Echo service
                        Dictionary<String, String> props = new Hashtable<String, String>();
                        props.put(ECHO_TYPE_PROP, "BundleActivator");
                        registration = bc.registerService(Echo.class.getName(), Activator.this, props);
    
                        return bc.getService(reference);
                    }
    
                    public void modifiedService(ServiceReference reference, Object service) {
                    }
    
                    public void removedService(ServiceReference reference, Object service) {
                        // unregister Echo service
                        registration.unregister();
                        System.out.println("PreferencesService is unlinked");
                    }
                }
            }
        

    Bundle MANIFEST.MF:
    Manifest-Version: 1.0
            Bundle-Name: KnowHowLab Tips&Tricks: Bundle Activation - Activator
            Bundle-Version: 1.0.0.SNAPSHOT
            Bundle-ManifestVersion: 2
            Bundle-Activator: org.knowhowlab.osgi.tips.activation.activator.Activa
             tor
            Bundle-Description: KnowHowLab Tips and Tricks: Bundle Activation - Ac
             tivator
            Import-Package: org.knowhowlab.osgi.tips.activation.core,org.osgi.fram
             ework;version="1.5",org.osgi.service.prefs;version="1.1",org.osgi.uti
             l.tracker;version="1.4"
            Bundle-SymbolicName: org.knowhowlab.osgi.tips.activation.activator
        

  4. Declarative Services. Declarative Services specification is the next higher-level way of activation your code in bundle. It helps developer to concentate on application logic and takes responsibility for almost all OSGi aspects of tracking and registering services.
    There are some details of this activation way:
    • Declarative Services do not work with bundle as atomic item (as in BundleActivator way). Service Component model is used instead of Bundle model
    • OSGi bundle can contain any count of Service Components
    • Every component should have Component Description - XML file with declaration of component
    • Every Component Description should be listed in MANIFEST OSGi header Service-Component to be available for Service Component Runtime, that manages components and their life cycle

    Here is code of my EchoComponent:
    public class EchoComponent implements Echo {
                // Reference to PreferencesService
                private PreferencesService preferencesService;
    
                public String echo(String str) {
                    return str;
                }
    
                // Called to bind PreferencesService
                public void bindPreferencesService(PreferencesService preferencesService) {
                    System.out.println("PreferencesService is linked");
                    this.preferencesService = preferencesService;
                }
    
                // Called to unbind PreferencesService
                public void unbindPreferencesService(PreferencesService preferencesService) {
                    this.preferencesService = null;
                    System.out.println("PreferencesService is unlinked");
                }
            }
        

    Here is Component Description:
    <?xml version="1.0" encoding="UTF-8"?>
            <components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.0.0">
                <!-- Echo Component -->
                <scr:component enabled="true" immediate="true" name="Echo">
                    <!--Component Class name-->
                    <implementation class="org.knowhowlab.osgi.tips.activation.ds.EchoComponent"/>
                    <!-- Echo Service description -->
                    <service servicefactory="false">
                        <provide interface="org.knowhowlab.osgi.tips.activation.core.Echo"/>
                    </service>
                    <!-- Service registration properties -->
                    <property name="echo_type" type="String" value="Declarative Services"/>
                    <property name="service.pid" value="Echo"/>
                    <!-- PreferencesService dependency description -->
                    <reference name="preferencesService" 
                               interface="org.osgi.service.prefs.PreferencesService" 
                               cardinality="1..1"
                               policy="static" 
                               bind="bindPreferencesService" 
                               unbind="unbindPreferencesService"/>
                </scr:component>
            </components>
        

    Bundle MANIFEST.MF:
    Manifest-Version: 1.0
            Service-Component: OSGI-INF/serviceComponents.xml
            Export-Package: org.knowhowlab.osgi.tips.activation.ds;uses:="org.know
             howlab.osgi.tips.activation.core,org.osgi.service.prefs"
            Bundle-Name: KnowHowLab Tips&Tricks: Bundle Activation - DS
            Bundle-Version: 1.0.0.SNAPSHOT
            Bundle-ManifestVersion: 2
            Import-Package: org.knowhowlab.osgi.tips.activation.core,org.osgi.serv
             ice.prefs;version="1.1"
            Bundle-SymbolicName: org.knowhowlab.osgi.tips.activation.ds
        

    There is Component Descrition annotations library that generate Component Description during OSGi bundle build and makes development of Service Components easier. Here is code with annotations:
    // Component description
            @Component(name = "Echo", immediate = true)
            // Service description
            @Service(value = Echo.class)
            // Service properties
            @Property(name = Echo.ECHO_TYPE_PROP, value = "Declarative Services")
            public class EchoComponent implements Echo {
                // Reference to PreferencesService
                @Reference(name = "preferencesService", referenceInterface = PreferencesService.class,
                        cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.STATIC)
                private PreferencesService preferencesService;
    
                public String echo(String str) {
                    return str;
                }
    
                // Called to bind PreferencesService
                public void bindPreferencesService(PreferencesService preferencesService) {
                    System.out.println("PreferencesService is linked");
                    this.preferencesService = preferencesService;
                }
    
                // Called to unbind PreferencesService
                public void unbindPreferencesService(PreferencesService preferencesService) {
                    this.preferencesService = null;
                    System.out.println("PreferencesService is unlinked");
                }
            }
        




  5. Blueprint Services. Blueprint Services specification is derived from Spint Dynamic Modules specification and very similar to Declarative Services specification (developer concentrates more on application-specific code).
    There are some details of this activation way:
    • Blueprint Services do not work with bundle as atomic item (as in BundleActivator way). Component/Bean model is used instead of Bundle model
    • OSGi bundle can contain any count of Components/Beans
    • Every component/bean should have Component Definition - XML file with definition
    • All Component/Bean Definitions should be stored in OSGI-INF/blueprint directory

    Here is code of my EchoBean:
    public class EchoBean implements Echo {
                // Reference to PreferencesService
                private PreferencesService preferencesService;
    
                public String echo(String str) {
                    return str;
                }
    
                // Called to bind PreferencesService
                public void bindPreferencesService(PreferencesService preferencesService, Map props) {
                    this.preferencesService = preferencesService;
                    System.out.println("PreferencesService is linked");
                }
    
                // Called to unbind PreferencesService
                public void unbindPreferencesService(PreferencesService preferencesService, Map props) {
                    this.preferencesService = null;
                    System.out.println("PreferencesService is unlinked");
                }
            }
        

    Here is Bean Definition (OSGI-INF/blueprint/echo.xml):
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
            <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
                <!-- Bean definition -->
                <bean id="echoservice" class="org.knowhowlab.osgi.tips.activation.blueprint.EchoBean"/>
                <!-- Service definition -->
                <service ref="echoservice" interface="org.knowhowlab.osgi.tips.activation.core.Echo"
                         depends-on="preferencesService">
                    <service-properties>
                        <entry key="ECHO_TYPE_PROP" value="Blueprint"/>
                    </service-properties>
                </service>
                <!-- PreferencesService reference definition -->
                <reference id="preferencesService" interface="org.osgi.service.prefs.PreferencesService"
                           availability="mandatory">
                    <reference-listener bind-method="bindPreferencesService" unbind-method="unbindPreferencesService">
                        <ref component-id="echoservice"/>
                    </reference-listener>
                </reference>
            </blueprint>
        

    I found very nice project BlueprintAnnotation under Apache Aries project. It provides runtime annotations for Blueprint beans that replaces Component/Bean Definition files. Here is EchoBean with Blueprint Annotations:
    // Bean definition
            @Bean(id = "echoservice")
            // Service definition
            @Service(interfaces = Echo.class,
                    serviceProperties = @ServiceProperty(key = Echo.ECHO_TYPE_PROP, value = "Blueprint-Annotations"))
            @ReferenceListener
            public class EchoBean implements Echo {
                // Reference definition 
                @Reference(availability = "mandatory", referenceListeners = @ReferenceListener(ref = "echoservice"))
                private PreferencesService preferencesService;
    
                public String echo(String str) {
                    return str;
                }
    
                @Bind
                public void bindPreferencesService(PreferencesService preferencesService, Map props) {
                    this.preferencesService = preferencesService;
                    System.out.println("PreferencesService is linked");
                }
    
                @Unbind
                public void unbindPreferencesService(PreferencesService preferencesService, Map props) {
                    this.preferencesService = null;
                    System.out.println("PreferencesService is unlinked");
                }
            }
        




  6. Web Application Bundle. Web Application Bundle (WAB) was introduced in OSGi Enterprise 4.2 Specification. WAB is a combination of JEE Web Application and OSGi bundle.
    There are some details of this activation way:
    • WAB has the same structure as standard WAR + OSGi MANIFEST.MF
    • WAB uses the same life cycle and class/resource loading rules as standard OSGi bundle
    • WAB context path should be described in MANIFEST OSGi header Web-ContextPath
    • /classes and all JARs/ZIPs in /lib folder should be listed in MANIFEST OSGi header Bundle-Classpath
    • OSGi BundleContext is provided to WAB as ServletContext attribute "osgi-bundlecontext". It is available for Servlets/JSPs/Listeners/Filters.

    Here is code of my ContextListener:
    public class ContextListener implements ServletContextListener, Echo {
                private ServiceTracker serviceTracker;
                private BundleContext bc;
                private ServiceRegistration registration;
    
                public void contextInitialized(ServletContextEvent sce) {
                    bc = (BundleContext) sce.getServletContext().getAttribute("osgi-bundlecontext");
                    serviceTracker = new ServiceTracker(bc, PreferencesService.class.getName(), new Customizer());
                    serviceTracker.open();
                }
    
                public void contextDestroyed(ServletContextEvent sce) {
                    serviceTracker.close();
                    serviceTracker = null;
                }
    
                public String echo(String str) {
                    return str;
                }
    
                private class Customizer implements ServiceTrackerCustomizer {
    
                    public Object addingService(ServiceReference reference) {
                        System.out.println("PreferencesService is linked");
    
                        Dictionary<String, String> props = new Hashtable<String, String>();
                        props.put(ECHO_TYPE_PROP, "WAB");
                        registration = bc.registerService(Echo.class.getName(), ContextListener.this, props);
    
                        return bc.getService(reference);
                    }
    
                    public void modifiedService(ServiceReference reference, Object service) {
                    }
    
                    public void removedService(ServiceReference reference, Object service) {
                        registration.unregister();
                        System.out.println("PreferencesService is unlinked");
                    }
                }
            }
        

    Here is web.xml:
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     metadata-complete="true"
                     version="2.5">
                <listener>
                    <listener-class>org.knowhowlab.osgi.tips.activation.wab.ContextListener</listener-class>
                </listener>
    
                <!-- welcome file mapping -->
                <welcome-file-list>
                    <welcome-file>index.htm</welcome-file>
                </welcome-file-list>
            </web-app>
        

    Here is MANIFEST.MF:
    Manifest-Version: 1.0
            Export-Package: org.knowhowlab.osgi.tips.activation.wab;uses:="org.kno
             whowlab.osgi.tips.activation.core,org.osgi.util.tracker,org.osgi.fram
             ework,javax.servlet,org.osgi.service.prefs";version="1.0.0.SNAPSHOT"
            Bundle-Classpath: WEB-INF/classes
            Bundle-Name: KnowHowLab Tips&Tricks: Bundle Activation - WAB
            Web-ContextPath: /
            Bundle-Version: 1.0.0.SNAPSHOT
            Bundle-ManifestVersion: 2
            Import-Package: javax.servlet,org.knowhowlab.osgi.tips.activation.core
             ,org.osgi.framework;version="1.5",org.osgi.service.prefs;version="1.1
             ",org.osgi.util.tracker;version="1.4"
            Bundle-SymbolicName: org.knowhowlab.osgi.tips.activation.wab
        




  7. How to run examples
    1. Download samples from here
    2. Compile with Maven command
      mvn clean install
    3. Run BundleActivator sample
      mvn -f run.xml -P activator
    4. Run Declarative Services sample
      mvn -f run.xml -P ds
    5. Run Blueprint Services sample
      mvn -f run.xml -P blueprint
    6. Run Blueprint Annotations sample
      mvn -f run.xml -P blueprint-annotations
      Note: It does not work for me (even Aries BlueprintAnnotation sample). I'll try with the next version.
    7. Run WAB sample
      mvn -f run.xml -P wab
    8. Run All sample
      mvn -f run.xml -P all
      Framework started:
      id      State       Bundle
      0       ACTIVE      org.eclipse.osgi_3.6.0.v20100517
      1       ACTIVE      org.eclipse.osgi.services_3.2.100.v20100503
      2       ACTIVE      org.eclipse.equinox.common_3.6.0.v20100503
      3       ACTIVE      org.eclipse.equinox.preferences_3.3.0.v20100503
      4       ACTIVE      org.eclipse.equinox.cm_1.0.200.v20100520
      5       ACTIVE      org.eclipse.equinox.util_1.0.200.v20100503
      6       ACTIVE      org.eclipse.equinox.ds_1.2.0.v20100507
      7       ACTIVE      org.apache.geronimo.specs.geronimo-servlet_2.5_spec_1.2.0
      8       ACTIVE      org.apache.geronimo.specs.geronimo-jaspic_1.0_spec_1.1.0
      9       ACTIVE      org.eclipse.jetty.aggregate.jetty-all-server_7.2.0.v20101020
      10      ACTIVE      org.eclipse.jetty.osgi.boot_7.2.0.v20101020
      11      ACTIVE      slf4j.api_1.6.1
                          Fragments=12
      12      RESOLVED    slf4j.jdk14_1.6.1
                          Master=11
      13      ACTIVE      org.apache.aries.blueprint_0.2.0.incubating
      14      ACTIVE      org.knowhowlab.osgi.tips.activation.core_1.0.0.SNAPSHOT
      15      ACTIVE      org.knowhowlab.osgi.tips.activation.activator_1.0.0.SNAPSHOT
      16      ACTIVE      org.knowhowlab.osgi.tips.activation.ds_1.0.0.SNAPSHOT
      17      ACTIVE      org.knowhowlab.osgi.tips.activation.blueprint_1.0.0.SNAPSHOT
      18      ACTIVE      org.knowhowlab.osgi.tips.activation.wab_1.0.0.SNAPSHOT
                  
      All Echo services registered:
      osgi> services (objectClass=org.knowhowlab.osgi.tips.activation.core.Echo)
      {org.knowhowlab.osgi.tips.activation.core.Echo}={echo_type=BundleActivator, service.id=41}
        Registered by bundle: org.knowhowlab.osgi.tips.activation.activator_1.0.0.SNAPSHOT [15]
        No bundles using service.
      {org.knowhowlab.osgi.tips.activation.core.Echo}={service.pid=Echo, echo_type=Declarative Services, component.name=Echo, component.id=0, service.id=42}
        Registered by bundle: org.knowhowlab.osgi.tips.activation.ds_1.0.0.SNAPSHOT [16]
        No bundles using service.
      {org.knowhowlab.osgi.tips.activation.core.Echo}={osgi.service.blueprint.compname=echoservice, ECHO_TYPE_PROP=Blueprint, service.id=46}
        Registered by bundle: org.knowhowlab.osgi.tips.activation.blueprint_1.0.0.SNAPSHOT [17]
        No bundles using service.
      {org.knowhowlab.osgi.tips.activation.core.Echo}={echo_type=WAB, service.id=49}
        Registered by bundle: org.knowhowlab.osgi.tips.activation.wab_1.0.0.SNAPSHOT [18]
        No bundles using service.
                  
      Stop PersistenceService bundle, all Echo services are unregistered:
      osgi> stop 3
      PreferencesService is unlinked
      PreferencesService is unlinked
      PreferencesService is unlinked
      PreferencesService is unlinked
      
      osgi> services (objectClass=org.knowhowlab.osgi.tips.activation.core.Echo)
      No registered services.
                  



  8. References:
    1. OSGi specifications
    2. OSGi API
    3. Bnd tool - tool to help you diagnose and create OSGi bundles
    4. maven-bundle-plugin - Maven plugin for Bnd tool
    5. maven-scr-plugin - Maven plugin to ease the development of OSGi component and services
    6. SCR annotations - How to use annotations for OSGi components
    7. Aries Blueprint - Blueprint "Hello World" tutorial
    8. Aries BlueprintAnnotations - BlueprintAnnotation tutorial
    9. Pax Runner - tool to provision OSGi bundles in all major open source OSGi framework implementations (Felix, Equinox, Knopflerfish, Concierge)


Your comments are welcome.
Thank you.
Dmytro

15 comments:

  1. Hello,

    Not sure if you should care (you've done a lot of good to the community with this posting), I just noticed that reference #7, the 'Blueprint Hello World' tutorial is broken. It should take you here http://aries.apache.org/documentation/tutorials/blueprinthelloworldtutorial.html

    Thank you for the post,

    Ytsejammer

    ReplyDelete
  2. Very nice blogpost, complete and concise.
    Thx

    ReplyDelete
  3. Hey! No iPojo?

    ReplyDelete
    Replies
    1. Covered ways are from OSGi Specs. iPojo is a custom solution.

      Delete
  4. Hello Dmitro,

    first, thanks for the nice tutorial.

    I am currently fighting with the WAB part. When I compile and run it, it works as described - visiting http://localhost:8080/ shows "Hello World!".

    But if I go to the OSGi console, and stop the wab bundle with "stop 9", I expect that the service is no longer available. But it still returns the same page...
    Besides that, if I remove the listener from src and web.xml, then do clean rebuild and run again, I get the same behavior - "Hellow World!" is printed no matter if the bundle is active or not.
    Am I doing something wrong ?
    TIA, Petr

    ReplyDelete
    Replies
    1. Hello Petr,

      thank you for your comment.

      it's an error in jetty version.

      replace lines 167-168 in run.xml with:

      <param>mvn:org.apache.geronimo.specs/geronimo-annotation_1.1_spec/1.0.1</param>
      <param>mvn:org.eclipse.jetty.aggregate/jetty-all-server/7.5.1.v20110908</param>
      <param>mvn:org.eclipse.jetty.osgi/jetty-osgi-boot/7.5.1.v20110908</param>

      Regards,
      Dmytro

      Delete
  5. 11:28:22,702 ERROR [org.apache.aries.blueprint.container.BlueprintContainerImpl]
    (Blueprint Extender: 2) Unable to start blueprint container for bundle org.knowhowlab.osgi.tips.activation.blueprint due to unresolved dependencies [(objectClass=org.osgi.service.prefs.PreferencesService)]: java.util.concurrent.TimeoutException
    at org.apache.aries.blueprint.container.BlueprintContainerImpl$1.run(BlueprintContainerImpl.java:288)

    any ideas on this? I don't know why it's able to find the preferences service on deploy, but then not get a reference to it?

    ReplyDelete
    Replies
    1. Using JBoss 7.1/7.2. Guess it doesn't implement Preferences Service API.

      Delete
    2. You can use Preferences API implementation from Felix. Check that OSGi compendium is installed as well

      Delete
  6. The org.eclipse.equinox.* references/dependencies from 2010 can not be loaded from the maven repositories anymore. Is it possible to update the examples? This article is too good not to do it :-),
    Alternatively, I have tried a felix equivalent examples, but I have not get it running for wab and blueprint annotations example. Thanks at all for this good job.

    ReplyDelete
    Replies
    1. Thank you for your comment. The sources are updated!

      Delete
  7. Outstanding article! It's helping me connect the dots between the various different approaches I've been studying. This has to be one of the most helpful articles on OSGi that I've encountered. Thank you very much!

    ReplyDelete
  8. Thank you so much for providing this article. It clearly explains various ways to publish as OSGi components/bundles. I will save this as my reference...

    ReplyDelete