Introduction to OSGi

This is a quick and practical task-based introduction to OSGi. We avoid any in depth discussion about the purpose or benefits of OSGi or even the tools referenced in this article. Many high level introductions on the technology are available; for examples, please see the webcasts at Eclipse. There’s also a good webcast on the OSGi efforts at Weblogic.

With OSGi it’s possible to run multiple versions of the same classes in the same container. We’ll start from scratch and build services that demonstrate this ability.

I used Apache Felix as an OSGi container. The other two well known open source OSGi container implementations are Equinox and Knopflerfish. Apache Felix actually seems to be the least popular of the three – I wanted to get some experience using the underdog!

Our immediate focus is to create and deploy applications in an OSGi server. We’ll do the following:

  • First we’ll set up a simple OSGi deployment environment.
  • We’ll create a simple OSGi bundle that will export classes and a service, making it available to other bundles. We’ll call this Bundle A, version 1. We’ll install and start the bundle in an OSGi container.
  • To demonstrate the power of OSGi, we’ll create a second version of the same bundle and install it alongside the first version. We’ll call this Bundle A, version 2. This will demonstrate OSGi technology’s ability to seamlessly manage multiple versions of the same classes at the same time, even though the class and package names of both versions will be identical.
  • We’ll create another set of bundles which will import and use the classes and service exported by the first set of bundles. We’ll call them Bundle B, versions 1 and 2. Bundle A, versions 1 and 2 will export services which will be imported by Bundle B versions 1 and 2 respectively. This will demonstrate OSGi technology’s ability to dynamically manage modularized applications.

We will use Spring Dynamic Modules for OSGi(tm) 1.0 (with dependencies), Apache Felix 1.0.3, Apache Felix Maven Bundle Plugin 1.2.0, Apache Maven 2.0.8 and Java 1.5.14. Please refer to the individual project websites for more information and background on these projects. We are avoiding discussion of these tools as we are only focusing on accomplishing the tasks outlined above.

I used Windows XP to create this tutorial. When writing the development and deployment steps it seems much easier and less error prone to reference full path names, rather than relative names. For example, I prefer referencing c:\dev\tools\felix-1.0.3, rather than “your Felix home directory”. You will notice I use the following directory structure for my development environment.

   c:\dev\
                  lib\   (Libraries)
                              \spring-framework-2.5.1
                              \spring-osgi-1.0
                              …etc…
                  tools\   (Executable tools)
                              \felix-1.0.3
                              \maven-2.0.7
                              …etc…

You can change the steps in the tutorial to match your environment, or copy the environment I lay out here. If you use a different layout, simply substitute path references to match your environment. For example, substitute “c:\dev\tools\felix-1.0.3” with whatever you chose for “your Felix home directory”.

To start out I’ll detail where to get the components to build the OSGi deployment environment. I will then use a command file, “setenv.cmd”, to configure my environment variables and system path when operating from a Windows command prompt. I’ll provide a sample “setenv.cmd” file.

Resources and Environment Setup

Download and install the following components from to the locations shown below.

Component Get File Extract or Install too
Felix 1.0.3 felix-1.0.3.zip C:\dev\tools\
Maven 2.0.8 apache-maven-2.0.8-bin.zip C:\dev\tools
Spring Dynamic Modules 1.0 spring-osgi-1.0-with-dependencies.zip C:\dev\lib\
JDK 5.0 Update 14 Default location: C:\Program Files\Java\jdk1.5.0_14

After downloading and installing the listed components, you should have the following folders on your machine.

    C:\dev\tools\felix-1.0.3\
                                    bin\
                                    bundle\
                                    …etc…
    C:\dev\tools\apache-maven-2.0.8\
                                    bin\
                                    boot\
                                    …etc…
    C:\dev\lib\spring-osgi-1.0\
                                    dist\
                                    docs\
                                    …etc…

Create a file called “setenv.cmd” in c:\dev\. The file should contain the following:

  @echo off

  SET JAVA_HOME=c:\Progra~1\Java\jdk1.5.0_14

  SET PATH=%JAVA_HOME%\bin
  SET PATH=%PATH%;C:\dev\tools\apache-maven-2.0.8\bin
  SET PATH=%PATH%;C:\WINDOWS
  SET PATH=%PATH%;C:\WINDOWS\system32

  echo on
  @echo PATH=%PATH%

The following several sections show in detail how to create and deploy OSGi bundles from scratch.

Bundle A, version 1

Open a command prompt and execute “c:\dev\setenv.cmd”. In the same command prompt, create a directory c:\dev\osgi\ and change to this directory.

At the command prompt, create a new maven project using the spring bundle archetype. The following command creates Bundle A, version 1. It should be entered as a single command (on one line). For your convenience, you can copy & paste the tiny text below – it contains the same command, but on a single line with no line breaks.

  c:\dev\osgi>mvn archetype:create
    -DarchetypeGroupId=org.springframework.osgi
    -DarchetypeArtifactId=spring-osgi-bundle-archetype
    -DarchetypeVersion=1.0
    -DgroupId=com.example.osgi
    -DartifactId=bundle-a -Dversion=1.0.0
mvn archetype:create -DarchetypeGroupId=org.springframework.osgi -DarchetypeArtifactId=spring-osgi-bundle-archetype  -DarchetypeVersion=1.0  -DgroupId=com.example.osgi  -DartifactId=bundle-a -Dversion=1.0.0

(Usage of the above command was gleaned from here).

The above maven command creates a new folder, bundle-a, containing a new template project. The group Id determines the package hierarchy. We arbitrarily chose “com.example.osgi”.

To build the project, cd to bundle-a and run “mvn package”. The maven-bundle-plugin in the pom.xml contains instructions for creating the OSGi bundle, bundle-a-1.0.0.jar, under the C:\dev\osgi\bundle-a\target folder. The jar file contains the following files:

   META-INF/MANIFEST.MF
   META-INF/maven/com.example.osgi/bundle-a/pom.properties
   META-INF/maven/com.example.osgi/bundle-a/pom.xml
   META-INF/spring/bundle-context-osgi.xml
   META-INF/spring/bundle-context.xml
   com/example/osgi/Bean.class
   com/example/osgi/impl/BeanImpl.class

In addition to the class files from your project, the jar contains a META-INF/MANIFEST.MF file which is read by the OSGi server when we install the bundle. The server reads entries such as Bundle-Name, Bundle-Version, Export-Package, etc. from the manifest file. This gives the OSGi server information to resolve bundle dependencies, wire bundles together, etc.

The META-INF/spring/*.xml files are read by Spring OSGi support services. In the next section we’ll see how to install and activate the Spring OSGi support in Apache Felix. A Spring service monitors all new bundles loaded into the OSGi container looking for the META-INF/spring folder. If this folder is found, then the xml files in the folder are used to initialize the new bundle’s Spring context.

The XML files also tell Spring how the new bundle wants to interact with other OSGi bundles. For example, which OSGi services are referenced by the new bundle, which services in the bundle should be registered with the OSGi server, how the bundle should be notified of events, etc.

The jar also contains a META-INF/maven folder. I don’t know why is included, or when it is used. I presume it is packaged in the jar file in order to give detailed information on how it was created.

Running Felix

To run Felix, open a second command prompt, cd to c:\dev and execute “setenv.cmd”. Then cd to tools\felix-1.0.3 and execute “java -jar bin\felix.jar” from the command prompt.

You will be prompted for a profile name. Enter “test” and hit enter. Felix creates a folder called “test” under “%HOME%\.felix\”. All bundles installed into Felix while it is running under the “test” profile, will be copied to this folder. You never need to manually manipulate this folder, although it is sometimes useful clean out the profile by deleting its folder.

At the Felix console prompt, type “ps” and hit enter. Felix lists the bundles that were started by default:

  ID   State         Level  Name
  [   0] [Active     ] [    0] System Bundle (1.0.3)
  [   1] [Active     ] [    1] Apache Felix Shell Service (1.0.0)
  [   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.0)
  [   3] [Active     ] [    1] Apache Felix Bundle Repository (1.0.2)

Looking in the “%HOME%\.felix\test” folder shows three corresponding folders (bundle0 through 3).

Type “ps -l” to list the bundles and the jar files that they were initially loaded from. Type “help” to see a list of all commands.

Note that bundles 1 and 2 are Shell services. They provide the Felix console interface you are using now. If you stop either of these bundles, the Felix console will stop working – all other bundles will keep running, but you won’t be able to interact with them. If you stop either of the shell services you can restart Felix to start them back up.

Felix knows which bundles to load at startup thanks to the C:\dev\tools\felix-1.0.3\conf\ config.properties file. Let’s edit this file and modify it to load the Spring services every time that Felix is started.

Look for the line starting with “felix.auto.start.1=”. This line is followed by three lines that start with file:bundle. At the end of the 3rd line, append a backslash and then add following lines below it:

file:${SPRING_OSGI}/lib/aopalliance.osgi-1.0-SNAPSHOT.jar \
file:${SPRING_OSGI}/lib/asm.osgi-2.2.3-SNAPSHOT.jar \
file:${SPRING_OSGI}/lib/backport-util-concurrent.osgi-3.1-SNAPSHOT.jar \
file:${SPRING_OSGI}/lib/framework-2.0.3.jar \
file:${SPRING_OSGI}/lib/jcl104-over-slf4j-1.4.3.jar \
file:${SPRING_OSGI}/lib/junit.osgi-3.8.2-SNAPSHOT.jar \
file:${SPRING_OSGI}/lib/log4j.osgi-1.2.15-SNAPSHOT.jar \
file:${SPRING_OSGI}/lib/org.apache.felix.main-1.0.1.jar \
file:${SPRING_OSGI}/lib/slf4j-api-1.4.3.jar \
file:${SPRING_OSGI}/lib/slf4j-log4j12-1.4.3.jar \
file:${SPRING_OSGI}/lib/spring-aop-2.5.1.jar \
file:${SPRING_OSGI}/lib/spring-beans-2.5.1.jar \
file:${SPRING_OSGI}/lib/spring-context-2.5.1.jar \
file:${SPRING_OSGI}/lib/spring-core-2.5.1.jar \
file:${SPRING_OSGI}/lib/spring-test-2.5.1.jar \
file:${SPRING_OSGI}/dist/spring-osgi-core-1.0.jar \
file:${SPRING_OSGI}/dist/spring-osgi-extender-1.0.jar \
file:${SPRING_OSGI}/dist/spring-osgi-io-1.0.jar \
file:${SPRING_OSGI}/dist/spring-osgi-mock-1.0.jar \
file:${SPRING_OSGI}/dist/spring-osgi-test-1.0.jar

(I gleaned the above by combining what I learned from watching Pax Runner – Screencast – Spring OSGi with the Apache Felix configuration documentation. It is also listed in Section 4.2 of the Spring Dynamic Modules reference guide – spring-dm-reference.pdf.

Some of the jars above may not actually be required for everything in this tutorial to work).While you are editing the config.properties file, replace the line “#felix.cache.profile=foo” with “felix.cache.profile=test” – be sure to remove the # at the start of the line. This will avoid the need to type the profile name “test” every time that you restart Apache Felix.

Save and close the config.properties file. We now need to define the SPRING_OSGI system variable referenced in the properties file. Create a new file called system.properties in the C:\dev\tools\felix-1.0.3\conf folder. Place this line in system.properties:

SPRING_OSGI=C:/dev/lib/spring-osgi-1.0

Now restart Apache Felix – in the Felix console, type “shutdown” and then type “java -jar bin\felix.jar” at the Windows command prompt.

When you restart Felix it should show a very long list of bundles wired ether. Typing “ps” should show that 23 bundles are now installed and “Active”. Amidst the startup message will also see a log4j warning:

log4j:WARN No appenders could be found for logger (org.springframework.util.ClassUtils).
log4j:WARN Please initialize the log4j system properly.

We will deal with that in a moment as it is important to get logging working so we can see any Spring related configuration errors.

First though, we will install Bundle A, Version 1. In the Felix console, type:

-> install file:C:\dev\osgi\bundle-a\target\bundle-a-1.0.0.jar

You should see a response:

Bundle ID: 24

You can type “headers 24” to see all the information that Felix read from the bundle’s Manifest file.

Running “ps” will show that bundle 24 is “Installed”. Start the bundle by running “start 24”. The bundle starts up OK, but unfortunately a long list of warnings of the format:

WARNING: *** Class 'org.springframework.aop.TargetSource' was not found because bundle 24 does not import 'org.springframework.aop' even though bundle 14 does export it. To resolve this issue, add an import for 'org.springframework.aop' to bundle 24.

We’ll deal with these in a moment too.

Note that Felix has copied the bundle into the profile directory (under “%HOME%\.felix\test”). If you shut down Felix and start it up again it will restart all the previously imported bundles, including Bundle A, version 1.

­

Housekeeping

In the following couple of sections we’ll deal with the warning messages we saw when running Felix.

Configuring Log4J

To remove the log4j warning messages (“log4j:WARN No appenders could be found for logger (org.springframework.util.ClassUtils) and “log4j:WARN Please initialize the log4j system properly.”), we need to create a log4j configuration file.

Create a file called “log4j.xml” in the C:\dev\tools\felix-1.0.3 folder. Here’s a basic log configuration which you can use in this file:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
            value="%d{ISO8601}; %-5p [%t] %c{1}: %m%n" />
    </layout>
  </appender>
  <root>
    <priority value="info" />
    <appender-ref ref="console" />
  </root>
</log4j:configuration>

When we restart Felix we must define the system variable “log4j.configuration” to tell log4j where to find its configuration file. To do this, Start Felix with these options:

java -Dlog4j.configuration=file:/dev/tools/felix-1.0.3/log4j.xml -jar bin\felix.jar

Since that’s a lot to type every time, create a cmd file called “felix.cmd” in the “C:\dev\tools\felix-1.0.3” folder that contains the above line.

Restart Felix using the “felix.cmd” script. You should see the log4j warnings replaced with a properly formatted log4j message:

2008-02-13 09:44:00,618; INFO  [FelixStartLevel] ContextLoaderListener: Starting org.springframework.osgi.extender bundle v.[1.0.0]

Among many other messages, you will now also see messages from Spring as it initializes bundle A.

2008-02-13 09:52:34,254; INFO  [SpringOsgiExtenderThread-9] XmlBeanDefinitionReader: Loading XML bean definitions from URL [bundle://24.0:0/META-INF/spring/bundle-context-osgi.xml]
2008-02-13 09:52:34,301; INFO  [SpringOsgiExtenderThread-9] XmlBeanDefinitionReader: Loading XML bean definitions from URL [bundle://24.0:0/META-INF/spring/bundle-context.xml]

And further down:

2008-02-14 09:55:54,162; INFO  [SpringOsgiExtenderThread-4] DefaultListableBeanFactory: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@8ab708: defining beans [myBean]; root of factory hierarchy
2008-02-14 09:55:54,162; INFO  [SpringOsgiExtenderThread-4] OsgiBundleXmlApplicationContext: Publishing application context with properties (org.springframework.context.service.name=com.example.osgi.bundle-a)

Define Bundle Imports

To get rid of the bundle import warnings that have the form “WARNING: *** Class ‘<classname>’ was not found because bundle 24 does not import <’package name’> even though bundle 15 does export it” we will make a small configuration change to bundle A and redeploy it.

The warnings are caused by a missing “Import-Package” specification in the bundle’s manifest file, which should list all the packages used by the bundle. We are using the Apache Felix maven-bundle-plugin to generate the bundle and the manifest file entries.

Open C:\dev\osgi\bundle-a\pom.xml and find this line:

<Import-Package>*</Import-Package>

This line is part of the maven-bundle-plugin configuration. According to the plugin documentation, this should suffice to generate the Import-Package entry for all packages referred to in the project. However, my understanding is, that the Spring extender bundle injects classes into our bundle at runtime (please help me out if you know that my understanding is skewed!). During the packaging phase, the maven plugin does know that a dependency on Spring will be created at runtime and therefore cannot generate the correct import package list. We will manually specify the package list.

Replace the <Import-Package>*</Import-Package> line with the full list of packages imported by our bundle:

  <Import-Package>
    *,
    org.xml.sax,
    org.osgi.framework,
    org.springframework.aop,
    org.springframework.aop.framework,
    org.springframework.osgi.service.importer.support,
    org.springframework.beans.factory.xml,
    org.springframework.beans.propertyeditors,
  </Import-Package>

In the Windows command prompt, rebuild the bundle by changing to the “C:\dev\osgi\bundle-a” directory and executing “mvn packge” again (btw, you should keep two command prompts open – one to run the Felix console, and one to execute the maven commands. I’ll refer to them as “the Felix console” and “the Windows command prompt).

Because of the new imports we added, you’ll see this warning during compilation:

[WARNING] Warning building bundle com.example.osgi:bundle-a:bundle:1.0.0 : Importing packages that are never refered to by any class on the Bundle-Classpath[Jar:dot]: [org.osgi.framework, org.springframework.aop, org.springframework.aop.framework, org.springframework.beans.factory.xml, org.springframework.beans.propertyeditors, org.springframework.osgi.service.importer.support, org.xml.sax]

We are faced with a tradeoff between getting some warnings during runtime and versus packaging time. I prefer these package time errors over the barrage of runtime errors.

In the Apache Felix console, type “update 24” to reload Bundle A from its original URL (file:C:\dev\osgi\bundle-a\target\bundle-a-1.0.0.jar).

The bundle should reload, displaying several informational messages (mostly generated by Spring) but no warning messages should be displayed.

Do Something!

We now have a bundle activated in Apache Felix, but we still need to make the bundle do something.

We’ll keep things simple. We’ll have the bundle (1) log a message to a logger and (2) expose an OSGi service. Later we’ll create Bundle B, which will consume this service.

Let’s add a method to the com.example.osgi.Bean interface (in C:\dev\osgi\bundle-a\src\main\java\com\example\osgi\Bean.java):

  String getName();

Add the implementation to com.example.osgi.imp.BeanImpl (in C:\dev\osgi\bundle-a\src\main\java\com\example\osgi\impl\BeanImpl.java).

    public String getName() {
        return "Bundle A, version 1";
    }

Also add the following import to BeanImpl:

    import java.util.Map;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

Slf4j is a logging façade which appears to be better suited for an OSGi environment. Spring OSGi depends on Slf4j and the dependencies were automatically added to pom.xml when it was created.

Add the static logger variable at the top of the class:

    private Logger logger = LoggerFactory.getLogger(BeanImpl.class);

Add service registration listeners to the class:

    public void serviceRegistered(BeanImpl serviceInstance,
                                            Map serviceProperties) {
        logger.info("serviceRegistered called in [" + serviceInstance.getName() + "]");
    }

    public void serviceUnregistered(BeanImpl serviceInstance,
                                              Map serviceProperties) {
        logger.info("serviceUnregistered called in [" + serviceInstance.getName() + "]");
    }

The entire class should look like this:

package com.example.osgi.impl;

import com.example.osgi.Bean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;

public class BeanImpl implements Bean {

    private Logger logger = LoggerFactory.getLogger(BeanImpl.class);

    public boolean isABean() {
        return true;
    }

    public String getName() {
        return "Bundle A, version 1";
    }

    public void serviceRegistered(BeanImpl serviceInstance,
                                            Map serviceProperties) {
        logger.info("serviceRegistered called in [" + serviceInstance.getName() + "]");
   }

    public void serviceUnregistered(BeanImpl serviceInstance,
                                              Map serviceProperties) {
        logger.info("serviceUnregistered called in [" + serviceInstance.getName() + "]");
    }
}

We have to instruct Spring to register the BeanImpl and Bean as a service, and to call the serviceRegistered and serviceUnregistered methods.

Edit “C:\dev\osgi\bundle-a\src\main\resources\META-INF\spring\ bundle-context-osgi.xml” and add the following before the closing tag (</beans>):

    <osgi:service id="myBeanInBundleA" ref="myBean"
              interface="com.example.osgi.Bean">
    <osgi:registration-listener ref="myBean"
        registration-method="serviceRegistered"
        unregistration-method="serviceUnregistered" />
    </osgi:service>

This tells Spring to register the bean “myBean” as an OSGi service. It also tells Spring to call certain methods when the service is registered or unregistered. Note that “myBean” was already defined as a bean in the bundle-context.xml file.
Rebuild the bundle by running “mvn package” in the Windows command prompt. In the Felix console, enter “update 24” to load a new copy of Bundle A. Among the log message you should see the line:

2008-02-13 12:15:57,445; INFO  [SpringOsgiExtenderThread-50] BeanImpl:
serviceRegistered called in [Bundle A, version 1]

Note with dismay though that more message of the form “WARNING: *** Class ‘<classname>’ was not found…” is again being printed. This time the warnings refer to “–BeanInfo” classes that do not exist. I do not know how to work around these log messages and I have to ignore them.

Type “services 24” to show the services exported by the bundle. The bundle exports two services – the Spring context created by the Spring extender bundle, and the new com.examlpe.osgi.Bean instance.

Let’s change the bundle name (Spring OSGi Bundle) to something more descriptive. In the project pom.xml file (C:\dev\osgi\bundle-a\pom.xml), replace “<name>Spring OSGi Bundle</name>” with ““<name>Bundle A</name>”.

Since we know we are going to run multiple versions of Bundle A’s classes in Apache Felix, let’s make sure the different versions are well differentiated. In the maven-bundle-plugin tag in the pom.xml, find the line containing “<Export-Package>com.example.osgi.*</Export-Package>”. Change it to “<Export-Package>com.example.osgi.*;version=1.0.0</Export-Package>”.

We also prevent this bundle from importing its own packages from other packages (i.e., from other versions of itself). Add the following line as the first line under <Import-Package> (just before the line containing just “*”):

!com.example.osgi*,

The exclamation mark instructs the maven-bundle-plugin to not import this package from other bundles in the OSGi container. If you leave out this line, you’ll end up with Bundle A, version 1 importing packages from Bundle A, version 2. This is undesirable for our exercise.

Run “mvn package” at the command line and “update 24” in the Felix console. Type “services 24” again in Felix and note the new name for the bundle.

Bundle A, version 2

We’ll now create version 2 of Bundle A and deploy it alongside version 1.

Before creating version 2, install version 1 A into the maven repository by running “mvn install” at the command prompt in C:\dev\osgi\bundle-a\. (We’ll reference Bundel A, version 1 in the maven repository later when we build Bundle B, version 1).

Now, in the C:\dev\osgi\bundle-a\pom.xml, change “<version>1.0.0</version>” (right under <name>Bundle A</name>) to “<version>2.0.0</version>”. Also change “<Export-Package>com.example.osgi.*;version=1.0.0</Export-Package>” to “<Export-Package>com.example.osgi.*;version=2.0.0</Export-Package>”. Finally, edit com.example.osgi.impl.BeanImpl (in C:\dev\osgi\bundle-a\src\main\java\com\example\osgi\impl\BeanImpl.java) and change the getName method so it returns “Bundle A, version 2” (instead of “Bundle A, version 1”).

In the Windows command prompt, run “mvn package”. Note that a new jar file is created in the target folder: “bundle-a-2.0.0.jar”. At the Felix console, install the new bundle by running:

install file:C:\dev\osgi\bundle-a\target\bundle-a-2.0.0.jar

You should see the response:

Bundle ID: 25

Start the new bundle by running “start 25”. You should now see the message: “2008-02-14 10:14:20,339; INFO [SpringOsgiExtenderThread-10] BeanImpl: serviceRegistered called in [Bundle A, version 2]”.

You can stop and start bundles 24 and 25 by running “stop 24 25” and “start 24 25” in the Felix console. You should see the massages from both versions 1 and 2. Also run “services 24 25” to see versions 1 and 2 of the com.example.osgi.Bean.

Bundle B, version 1

Here are the steps for creating a new bundle which will import the service exported by Bundle A.

First, run “mvn install” in the “C:\dev\spikes\spring-osgi\bundle-a” directory to install the latest version of Bundle A in the maven repository.

At the windows command prompt, change to the “C:\dev\osgi” directory and enter the following on a single line (you can cut ‘n paste the small text below the command):

mvn archetype:create
-DarchetypeGroupId=org.springframework.osgi
-DarchetypeArtifactId=spring-osgi-bundle-archetype
-DarchetypeVersion=1.0
-DgroupId=com.example.bundleb
-DartifactId=bundle-b -Dversion=1.0.0
mvn archetype:create -DarchetypeGroupId=org.springframework.osgi -DarchetypeArtifactId=spring-osgi-bundle-archetype  -DarchetypeVersion=1.0  -DgroupId=com.example.bundleb -DartifactId=bundle-b -Dversion=1.0.0

This creates the new project in “C:\dev\osgi\bundle-b”.

Edit the file “C:\dev\osgi\bundle-b\pom.xml” and change “<name>Spring OSGi Bundle</name>” to “<name>Bundle B</name>”.

In the pom.xml’s “<dependencies>” section (around line 16), add the following dependency on Bundle A:

<dependency>
<groupId>com.example.osgi</groupId>
<artifactId>bundle-a</artifactId>
<version>1.0.0</version>
</dependency>

In the maven-bundle-plugin, change the line “<Export-Package>com.example.bundleb*</Export-Package>” to “<Export-Package> com.example.bundleb*;version=${project.version}</Export-Package>”.

Replace “<Import-Package>*</Import-Package>” with the following:

<Import-Package>
!com.example.bundleb*,
com.example.osgi;version="[1,2)",
*,
org.xml.sax,
org.osgi.framework,
org.springframework.aop,
org.springframework.aop.framework,
org.springframework.osgi.service.importer.support,
org.springframework.beans.factory.xml,
org.springframework.beans.propertyeditors,
org.springframework.osgi.config,
org.springframework.osgi.service.exporter.support    </Import-Package>

The first line in the Import-Package tag – !com.example.bundleb* – similar to what we did in Bundle A. It prevents Bundle B from importing its own packages from other versions of the same bundle.

The second Import-Package line – com.example.osgi;version=”[1,2)” – imports the packages we’ll use from Bundle A. Specifically, it ensures that we get packages from Bundle A that has at least version 1, but also no more than version 1. Square brackets are inclusive, round brackets are exclusive. Specifying a single version, for example “com.example.osgi;version=1.0.0”, would be synonymous to asking for any version >= 1.0.0, in which case we might end up with a reference to version 2.0.0. For this tutorial we want to explicitly avoid that.

In C:\dev\osgi\bundle-b\src\main\java\com\example\bundleb\, create a new class file BeanClient.java containing the following:

package com.example.bundleb;

import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.example.osgi.Bean;

public class BeanClient {

    private Logger logger = LoggerFactory.getLogger(BeanClient.class);
    private Bean bean;

    // set the bean from bundle a
    public void setBean(Bean bean) {
        this.bean = bean;
    }

    public void clientInitialized() {
        logger.info("This is Bundle B, version 1. " +
                       "I have a reference to [" +
                       bean.getName() + "]");
        }
}

Add the following before </beans> in the file C:\dev\osgi\bundle-b\src\main\resources\META-INF\spring\bundle-context-osgi.xml:
­
<osgi:reference id=”beanFromBundleA”
interface=”com.example.osgi.Bean”/>

The “osgi:reference” tag above tells the OSGi container (via Spring) that this bundle wants a reference to the bean exported by bundle A.

In C:\dev\osgi\bundle-b\src\main\resources\META-INF\spring\bundle-context.xml, add the following bean definition:

    <bean name="myBeanClient" class="com.example.bundleb.BeanClient"
              init-method="clientInitialized">
        <property name="bean" ref="beanFromBundleA"/>
    </bean>

The above line tells Spring where to wire up the reference to the bean from Bundle A. We also define an init method so the bundle gets a chance to do something (like write a message to the log file).

The “beanFromBundle” will be provided by the OSGi container during runtime. However, we also need a reference to this been when running the unit tests. We can accomplish this as follows:

Create a new file, C:\dev\osgi\bundle-b\src\test\resources\test-context.xml, with the following content:

<?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="beanFromBundleA" class="com.example.osgi.impl.BeanImpl"/>
    </beans>

Edit BeanIntegrationTest.java (in “C:\dev\osgi\bundle-b\src\test\java\com\example\bundleb\impl\ “) and change the line:return new String[] {“META-INF/spring/bundle-context.xml” };

to include the new test context as follows:

return new String[] {"META-INF/spring/bundle-context.xml",
"test-context.xml"};

At the Windows command prompt, run “mvn package” to package bundle B.

In the Felix console, run:

install file:C:\dev\osgi\bundle-b\target\bundle-b-1.0.0.jar

You should see a response:

  Bundle ID: 26

Type “start 26”. Among the other log message you should see the message: “2008-02-14 11:02:38,353; INFO [SpringOsgiExtenderThread-22] BeanClient: This is Bundle B, version 1. I have a reference to [Bundle A, version 1]”.

Now let’s create Bundle B, version 2 with a reference to Bundle A, version 2.

Edit bundle B’s pom.xml and change the <version> at the top of the file from 1.0.0 to 2.0.0. Change the dependency reference to bundle-a from version 1.0.0 to version 2.0.0.
Note that “<Export-Package>com.example.osgi.client; version=${project.version}</Export-Package>” will now export version 2.0.0. Change the Import-Package line “com.example.osgi;version=”[1,2)”,” to “com.example.osgi;version=2.0.0,”.

Edit the BeanClient.java source file and change the log message from “This is Bundle B, version 1…” to “This is Bundle B, version 2…”.

Run “mvn package” at the command prompt. In the Felix console, run:

install file:C:\dev\osgi\bundle-b\target\bundle-b-2.0.0.jar

You should see the response:

Bundle ID: 27

Type “start 27”. Among the warnings and other message you should see: “2008-02-14 16:29:33,836; INFO [SpringOsgiExtenderThread-26] BeanClient: This is Bundle B, version 2. I have a reference to [Bundle A, version 2]”.

You can stop and start bundles 24 through 27 independently. Service 27 (Bundle B, version 2) depends on bundle 25 (Bundle A, version 2) and bundle 26 depends on bundle 24. Note that if you execute “stop 25 27” and then “start 27”, then the log message “This is Bundle B, version 2…” is not printed. At least not until you also restart bundle 25.

If the dependant service is not started before a timeout expires, the context creation fails. The default timeout is 300ms. See the Spring documentation for more details on this.