Let’s play with OSGi, Spring and Maven, part 2

In the first part of this article, we have finished and tested DAO part of the application. Today we are going to create the UI and connect all the parts using Service bundle.

If you think about it, you realize that UI bundle is exactly the same as the DAO bundle. It only exports a service. In this case it implements DataReceiver interface from the Common bundle. Therefore, pom.xml, generated MANIFEST.MF file and Spring configuration file will be similar to that we have seen in part 1. So I will skip this bundle and jump directly to the Service layer.

In the Service bundle we want to use both DAO and UI. We want to periodically pull the data from the DataLoader and push them to the DataReceiver service.

Let's start with bundle metadata.

	<plugin>
	    <groupId>org.apache.felix</groupId>
	    <artifactId>maven-bundle-plugin</artifactId>
	    <extensions>true</extensions>
	    <configuration>
	    	<instructions>
	    	 <Private-Package>
	    	    net.krecan.spring.osgi.service.*
	    	 </Private-Package>
	    	</instructions>
	    </configuration>
	  </plugin>

This configuration of the Maven OSGi plugin is going to generate following MANIFEST.MF file.


Manifest-Version: 1.0
Bundle-Name: demo-spring-osgi-service
Build-Jdk: 1.5.0_15
Built-By: lukas
Created-By: Apache Maven Bundle Plugin
Private-Package: net.krecan.spring.osgi.service
Import-Package: net.krecan.spring.osgi,org.apache.commons.logging
Bundle-ManifestVersion: 2
Bundle-SymbolicName: net.krecan.spring-osgi.demo-spring-osgi-service
Tool: Bnd-0.0.255
Bnd-LastModified: 1212320410936
Bundle-Version: 1.0.0.SNAPSHOT

You can see that we do not export any package. It is not needed. Service bundle only coordinates DAO and UI, it does not need to expose anything.

Import-Package directive is more interesting. Maven plugin analyzed Java classes and figured out that import of net.krecan.spring.osgi and org.apache.commons.logging package is needed. The first one is exported by our Common bundle, the other one is provided by the commons-logging package. Great, I do not have to specify dependencies by hand, Maven plugin does it automatically.

OK, OSGi is configured, we can try to use our services. Again, I will use Spring OSGi integration. I will create following Spring XML configuration in the META-INF/spring directory of the Service bundle

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

	<osgi:reference id="dataLoader" interface="net.krecan.spring.osgi.DataLoader"/>
	
	<osgi:list id="receivers"  interface="net.krecan.spring.osgi.DataReceiver"/>
	
	<bean id="dataDistributor" class="net.krecan.spring.osgi.service.DataDistributor">
		<property name="dataLoader" ref="dataLoader"/>
		<property name="receivers" ref="receivers"/>
	</bean>
	
	<!-- Timer config -->
	...	
	
</beans>

(Full version can be downloade here)

By osgi:reference element we say, that we want to find a service that implements DataLoader service. If such service is running in the OSGi engine, we can use the reference as a Spring bean with ID “dataLoader”. So we can inject it to the dataDistributor bean like any other Spring bean. That is great. The source code is not bound to OSGi at all. If you wish, you can switch form OSGi to a simple deployment, to remote invocation, web service or whatever you want. The source code will remain unchanged. The only thing we need to change is the configuration.

Do you wonder what happens, if there is no DataLoader service running? The answer is simple. Service bundle will wait for it. When DataLoader became available, the Service bundle will start to pull data from it. (This behavior can be changed by the cardinality attribute)

osgi:list element is similar to osgi:reference. It supports multiple references. It implements normal java.util.List. Again, we can inject it to an ordinary Spring bean. Moreover, the list is magic. If new service with given interface appears in the OSGi container, it is automagically added to the list. So I do not have to care about it.

The rest of the Spring config is not connected with OSGi. It just starts timer job that calls dataDistributor.loadAndSend method every 100ms.

And now we can test it. We will again use Spring OSGi support for integration tests. First of all, we have to say which bundles we are going to use.

	protected String[] getTestBundlesNames() {
		return new String[] {
			"net.krecan.spring-osgi, demo-spring-osgi-dao, "+DEMO_VERSION, 
			"net.krecan.spring-osgi, demo-spring-osgi-ui, "+DEMO_VERSION, 
			"net.krecan.spring-osgi, demo-spring-osgi-service, "+DEMO_VERSION, 
		};
	}

And now we can run the test.

	public void testAddUi() throws InterruptedException, BundleException, IOException
	{
		Thread.sleep(500);
		LOG.info("Starting alternative UI");
		Resource bundleResource = locateBundle("net.krecan.spring-osgi, demo-spring-osgi-ui-alternative, "+DEMO_VERSION);
		Bundle bundle = bundleContext.installBundle(bundleResource.getURL().toString());
		bundle.start();
		LOG.info("Alternative UI started");
		Thread.sleep(500);
		LOG.info("Stopping alternative UI");
		bundle.stop();
		LOG.info("Alternative UI stopped");
		Thread.sleep(500);
		
	}

To make it more interesting, we will run the test for 500ms and then we will add and start alternative UI bundle. After another 500ms we will stop the alternative UI bundle again. It will result into following log output.

2008-06-01 14:33:55,818 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:55 CEST 2008 )
2008-06-01 14:33:55,908 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:55 CEST 2008 )
2008-06-01 14:33:56,009 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,108 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,209 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,308 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,317 INFO [net.krecan.spring.osgi.dao.ServiceOsgiTest] {main} - Starting alternative UI
2008-06-01 14:33:56,384 INFO [net.krecan.spring.osgi.dao.ServiceOsgiTest] {main} - Alternative UI started
2008-06-01 14:33:56,408 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,508 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,508 INFO [net.krecan.spring.osgi.ui.alternative.AlternativeDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,609 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,609 INFO [net.krecan.spring.osgi.ui.alternative.AlternativeDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,708 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,708 INFO [net.krecan.spring.osgi.ui.alternative.AlternativeDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,809 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,809 INFO [net.krecan.spring.osgi.ui.alternative.AlternativeDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,884 INFO [net.krecan.spring.osgi.dao.ServiceOsgiTest] {main} - Stopping alternative UI
2008-06-01 14:33:56,909 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:56,909 INFO [net.krecan.spring.osgi.ui.alternative.AlternativeDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:56 CEST 2008 )
2008-06-01 14:33:57,009 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,009 INFO [net.krecan.spring.osgi.ui.alternative.AlternativeDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,101 INFO [net.krecan.spring.osgi.dao.ServiceOsgiTest] {main} - Alternative UI stopped
2008-06-01 14:33:57,108 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,209 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,311 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,409 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,509 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )
2008-06-01 14:33:57,614 INFO [net.krecan.spring.osgi.ui.DefaultDataReceiver] {timerFactory} - ***** Received data: Data ( text = Hallo Sun Jun 01 14:33:57 CEST 2008 )

It looks exactly how it should. Messages from the alternative UI appear and then disappear together with corresponding bundle. That's what OSGi is all about. It enables dynamic binding of components. We can replace or plug-in services dynamically in the runtime.

To reiterate. Today we have seen OSGi in action. We have connected several services using Spring support and we have seen dynamic nature of OSGi. And what's the best, our source code is not aware of OSGi. What a nice example of dependency injection.

Next time, we will try to play a bit with the example. I want to run it standalone, without Spring integration testing support and I'd like to test how it is resistant to class loader leaks.

Source code is accessible in here.

Tags:

One Response to “Let’s play with OSGi, Spring and Maven, part 2”

  1. Kyle Says:

    I found this article very interesting. I am fairly familiar with OSGi and have a bit knowledge of spring dm, but know almost nothing about maven. Lately I've been trying to write some integration tests for my OSGi application and found that it uses maven behind the scenes. This example cleared it up for me a bit. Thanks!