Friday, July 17, 2009

Consuming Scala, Groovy and Java OSGi Bundles with Spring DM

In Part 1 of this series, we used Maven and PAX to set up and run an OSGi project with three hello world bundles; one for Java, one for Groovy and one for Scala. In Part 2 we introduced Spring Dynamic Modules to help maintain a clean separation of our bundles in addition to abstracting away the plumbing normally required for developing OSGi applications. For this third installment, I'll show you how service consumption works in Spring DM so that our services can interact with each other.

If you'd prefer to look at the code as you go, you can grab it from bitbucket. Be sure to update your working copy to revision 2 to see the code as it would look at the completion of this third article in the series.

        hg clone http://bitbucket.org/brimurph/helloworld

        cd helloworld

        hg update -r 2
    

Refactoring

The examples I provided in part 1 were sufficient for demonstrating individual bundles written in Java, Groovy and Scala that could stand on their own. However, the way our interfaces are currently coded would force the bundles to explicitly import each other in order to interact. We don't want our bundles to be so tightly coupled so we'll embark on a bit of refactoring.

We're going to factor our Hello interface out into its own bundle. Remember that an OSGi bundle is, in its simplest form, just a JAR that incorporates OSGi-specific headers in the MET-INF/MANIFEST.MF. Continuing with the hello world theme, we'll call this bundle hello-osgi. Just as in Part 1, we'll let Maven and Pax do the dirty work. From the root directory of the project, execute:

        mvn org.ops4j:maven-pax-plugin:create-bundle -Dpackage=com.domain.osgi -DbundleName=hello-osgi
    

Our interface can be in any language you'd like. It's all bytecode at the end of the day. I've elected to use Scala for no particular reason. This interface is identical to the one used in Part 1 with the exception of the package declaration. We'll go ahead and remove the generated code under hello-osgi/src/main/java* and create ./hello-osgi/src/main/scala/com/domain/osgi/HelloOsgiService.scala like so:

        package com.domain.osgi
        
        trait HelloOsgiService {
          def hello()
        }
    

As a result of this new factored out interface as well as the introduction of Spring DM, there are several bits that we can now simply remove from our bundles. create-bundle generated a readme.txt file to help you get your bearings. My OCD won't allow me to leave a file in the source tree that's no longer useful so that's got to go. Our classes that implement the BundleActivator class will no longer be required since Spring DM provides it's own mechanisms for lifecycle management which we'll get to shortly. And since we were only using the BND configuration files to declare our BundleActivator classes, that too can go. Finally, the interfaces in each bundle have now been replaced by the HelloOsgiService trait above. All of this will allow us to prune 14 files from our source tree. Here's the final listing showing what should be removed:

        rm hello-osgi/src/main/resources/readme.txt;
        rm hello-osgi/osgi.bnd;

        rm hello-java/src/main/java/com/domain/osgi/java/HelloJavaService.java;
        rm hello-java/src/main/java/com/domain/osgi/java/internal/HelloJavaActivator.java;
        rm hello-java/src/main/resources/readme.txt;
        rm hello-java/osgi.bnd;

        rm hello-groovy/src/main/groovy/com/domain/osgi/groovy/HelloGroovyService.groovy;
        rm hello-groovy/src/main/groovy/com/domain/osgi/groovy/internal/HelloGroovyActivator.groovy;
        rm hello-groovy/src/main/resources/readme.txt;
        rm hello-groovy/osgi.bnd;

        rm hello-scala/src/main/scala/com/domain/osgi/scala/HelloScalaService.scala;
        rm hello-scala/src/main/scala/com/domain/osgi/scala/internal/HelloScalaActivator.scala;
        rm hello-scala/src/main/resources/readme.txt;
        rm hello-scala/osgi.bnd;
    

One final bit of work we need to do for this example is to ensure that Maven is using JDK 1.5 for compilation. This is easily accomplished by updating ./poms/compiled/pom.xml to include:

        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>1.5</source>
            <target>1.5</target>
          </configuration>
        </plugin>
    

Introduce a Dependency on the hello-osgi Bundle

After purging all of those files, we now have a bunch of code that won't compile. To get back on track, we'll use the mvn pax:import-bundle command so that hello-java, hello-groovy and hello-scala all depend on the hello-osgi-bundle. Change into ./hello-java/, ./hello-groovy/, and ./hello-scala/ respectively and execute the following:

        mvn pax:import-bundle -DgroupId="com.domain.osgi.helloworld" -DartifactId="hello-osgi" -Dversion="1.0-SNAPSHOT"
    

Once that's done, we'll update each of our implementation classes so that they implement our new interface and define a member variable so we can inject a HelloOsgiService.

Java Service

Here's our refactored HelloJavaServiceImpl. It's a nice simple POJO.

./hello-java/src/main/java/com/domain/osgi/java/internal/HelloJavaServiceImpl.java

        package com.domain.osgi.java.internal;
        
        import com.domain.osgi.HelloOsgiService;
        
        public final class HelloJavaServiceImpl implements HelloOsgiService {
            
            private HelloOsgiService helloOsgiService;
            
            public void hello() {
                System.out.println("Hello, in Java");
            }
            
            public void start() {
                if (helloOsgiService != null) {
                    helloOsgiService.hello();
                } else {
                    System.out.println("No service available to greet the java bundle");
                }
            }
            
            public void setHelloOsgiService(HelloOsgiService helloOsgiService) {
                this.helloOsgiService = helloOsgiService;
            }
            
        }
    

Groovy Service

Here's the updated Groovy implementation. It's a little more concise, but does the same as our Java implementation.

./hello-groovy/src/main/groovy/com/domain/osgi/groovy/internal/HelloGroovyServiceImpl.groovy

        package com.domain.osgi.groovy.internal
        
        import com.domain.osgi.HelloOsgiService
        
        class HelloGroovyServiceImpl implements HelloOsgiService {
            
            def helloOsgiService
            
            void hello() {
                println "Hello, in Groovy"
            }
            
            void start() {
                if (helloOsgiService != null) {
                    service.hello()
                } else {
                    println("No service available to greet the groovy bundle")
                }
            }
            
        }
    

Scala Service

Our refactored Scala service does have some interesting bits to discuss.

./hello-scala/src/main/scala/com/domain/osgi/scala/internal/HelloScalaServiceImpl.scala

        package com.domain.osgi.scala.internal;
        
        import reflect.BeanProperty
        
        import com.domain.osgi._
        
        class HelloScalaServiceImpl extends HelloOsgiService {
          
          @BeanProperty
          var helloOsgiService:HelloOsgiService = null
          
          def hello() = {
            Console.println("Hello, in Scala")
          }
          
          def start() = {
            if (helloOsgiService != null) {
              helloOsgiService.hello()
            } else {
              Console.println("No service available to greet the scala bundle")
            }
          }
          
        }
    

Spring DM relies on the JavaBeans pattern for dependency injection. Scala employs property access rather than JavaBeans style accessors so we'll want to provide a quick work around using scala.reflect.BeanProperty. The instance property, helloOsgiService is defined as a var so it's mutable and is annotated with @BeanProperty so that the generated bytecode contains a getter accessor and setter mutator. Without @BeanProperty, you would need to write your own setter like so:

        def setHelloOsgiService(helloOsgiService:HelloOsgiService):Unit = {
          this.helloOsgiService = helloOsgiService
        }
    

Service Consumption with Spring DM

Now that we've refactored our code base, we'll return to our Spring configurations. We've worked our way through a lot of concepts and we're now ready to configure these services to interact with each other. For demonstration purposes, our Java service will consume the Scala service and our Scala service will consume the Groovy service. The Groovy service won't consume any other services because we need to be careful to avoid circular dependencies.

Since we know that we're publishing multiple services that adhere to the same interface, we'll set properties (name / value pairs) that clients may use as a discriminator when consuming our services. This is a painless process with Spring DM courtesy of the service-properties tag. Update the bundle-context-osgi.xml configuration for Groovy so that our service specifies that its language is groovy.

./hello-groovy/src/main/resources/META-INF/spring/bundle-context-osgi.xml

        <beans:beans xmlns="http://www.springframework.org/schema/osgi" 
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:beans="http://www.springframework.org/schema/beans"
                        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">
            
            <service ref="helloGroovyService" interface="com.domain.osgi.HelloOsgiService">
                <service-properties>
                    <beans:entry key="language" value="groovy"></beans:entry>
                </service-properties>
            </service>
            
        </beans:beans>
    

In Spring DM, we can get a handle on a service from the OSGi service registry using the reference declaration. The reference declaration requires a unique id and the fully qualified interface that the service is published under. The optional filter attribute is used to specify an OSGi filter expression to bind only the service instance we want. Here's how our bundle-context-osgi.xml configuration evolves for our Scala bundle.

./hello-scala/src/main/resources/META-INF/spring/bundle-context-osgi.xml

        <beans:beans xmlns="http://www.springframework.org/schema/osgi" 
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:beans="http://www.springframework.org/schema/beans"
                        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">
            
            <reference id="helloOsgiService" interface="com.domain.osgi.HelloOsgiService" filter="(language=groovy)">
            </reference>
            
            <service ref="helloScalaService" interface="com.domain.osgi.HelloOsgiService">
                <service-properties>
                    <beans:entry key="language" value="scala"></beans:entry>
                </service-properties>
            </service>
            
        </beans:beans>
    

Our Java configuration follows the same structure as our Scala configuration.

./hello-java/src/main/resources/META-INF/spring/bundle-context-osgi.xml

        <beans:beans xmlns="http://www.springframework.org/schema/osgi" 
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:beans="http://www.springframework.org/schema/beans"
                        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">
            
            <reference id="helloOsgiService" interface="com.domain.osgi.HelloOsgiService" filter="(language=scala)">
            </reference>
            
            <service ref="helloJavaService" interface="com.domain.osgi.HelloOsgiService">
                <service-properties>
                    <beans:entry key="language" value="java"></beans:entry>
                </service-properties>
            </service>
            
        </beans:beans>
    

Lifecycle Management with Spring DM

As I mentioned previously, we're no longer making use of BundleActivators and will instead rely upon Spring DM to manage the lifecycle of our bundles. The Spring DM extender finds bundles in the RESOLVED state and initializes the bundle's spring context. We can make use of the init-method declaration to specify what code should be executed when our beans are instantiated. Update bundle-context.xml for the Groovy bundle so that the start() method is called when the bundle is started.

./hello-groovy/src/main/resources/META-INF/spring/bundle-context.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:p="http://www.springframework.org/schema/p"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                                      http://www.springframework.org/schema/beans/spring-beans.xsd">
            
            <bean id="helloGroovyService" class="com.domain.osgi.groovy.internal.HelloGroovyServiceImpl" init-method="start">
            </bean>
            
        </beans>
    

Next, add init-method="start" for the Scala service implementation. This is also where we ensure that the Groovy reference declaration we defined above gets injected.

./hello-scala/src/main/resources/META-INF/spring/bundle-context.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:p="http://www.springframework.org/schema/p"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                                      http://www.springframework.org/schema/beans/spring-beans.xsd">
            
            <bean id="helloScalaService" class="com.domain.osgi.scala.internal.HelloScalaServiceImpl" init-method="start">
                <property name="helloOsgiService" ref="helloOsgiService"></property>
            </bean>
            
        </beans>
    

Finally, we'll configure an init-method for our Java service implementation and we inject the Scala service.

./hello-java/src/main/resources/META-INF/spring/bundle-context.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:p="http://www.springframework.org/schema/p"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                                      http://www.springframework.org/schema/beans/spring-beans.xsd">
            
            <bean id="helloJavaService" class="com.domain.osgi.java.internal.HelloJavaServiceImpl" init-method="start">
                <property name="helloOsgiService" ref="helloOsgiService"></property>
            </bean>
            
        </beans>
    

Provision One Last Time

Let's call upon Maven and PAX on last time to see our bundles in action.

        mvn install pax:provision -Dprofiles=spring.dm
    

If everything has gone according to plan, you'll see the output from each bundle's start method buried in between all the DEBUG logging that Spring DM spits out.

        ...
        
        [SpringOsgiExtenderThread-4] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking init method  'start' on bean with name 'helloGroovyService'
        No service available to greet the groovy bundle
        
        ...
        
        [SpringOsgiExtenderThread-5] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking init method  'start' on bean with name 'helloScalaService'
        Hello, in Groovy
        
        ...
        
        [SpringOsgiExtenderThread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking init method  'start' on bean with name 'helloJavaService'
        Hello, in Scala
        ...
    

I'd encourage you to play around with the configurations and see how easy it is to have these three services consume each other.

Conclusion

In this post we refactored the bundles we built in parts 1 and 2 to ensure that our final product would be loosely coupled and got a feel for how one can consume OSGi services with Spring DM. In addition, thanks to Spring DM, we didn't have to code against the OSGi API directly /at all/. We also saw how simply we could define an interface that can be implemented in a variety of languages with minimal code or configuration magic. Throughout this series, we've seen how useful the offerings from the OPS4J folks are for developing OSGi applications all the while leaning heavily on Maven for managing our runtime. We've only scratched the surface however. There seem to be several books popping up that go much deeper on the subject of OSGi with Spring Dynamic Modules and the push to use these technologies for web development continues to drive innovation. I'm very curious to see how long it will take adoption to catch up with the buzz.

Wednesday, July 15, 2009

Publishing Scala, Groovy and Java OSGi Bundles with Spring DM

In Part 1 of this series, we used Maven and PAX to set up and run an OSGi project with three hello world bundles; one for Java, one for Groovy and one for Scala. This time around, I'd like to show you how to incorporate Spring Dynamic Modules so we can build a Spring application that is deployed in an OSGi framework. Spring DM will help maintain a clean separation of our bundles in addition to abstracting away the plumbing normally required for developing OSGi applications.

There have been minor releases for several of the components highlighted in this tutorial including Pax Runner and Scala since the last post. In the interest of consistency, I'm not going to upgrade anything at this point. The only new component we're adding is version 1.2.0 of Spring DM.

If you'd prefer to look at the code as you go, you can grab it from bitbucket. Be sure to update your working copy to revision 1 to see the code as it would look at the completion of this second article in the series.

        hg clone http://bitbucket.org/brimurph/helloworld

        cd helloworld

        hg update -r 1
    

Spring Dynamic Modules for OSGi

Using Spring DM implies that we're using Spring framework JARs as OSGi bundles as well as these OSGi Spring bundles: spring-osgi-extender, spring-osgi-core, spring-osgi-io and spring-osgi-annotation.

The first thing we need to do is update our maven configuration to include the SpringSource Enterprise Bundle Repository.

        mvn pax:add-repository -DrepositoryId=com.springsource.repository.bundles.release -DrepositoryURL=http://repository.springsource.com/maven/bundles/release
        
        mvn pax:add-repository -DrepositoryId=com.springsource.repository.bundles.external -DrepositoryURL=http://repository.springsource.com/maven/bundles/external
    

When the Spring DM Extender bundle is started, it examines all of the resolved OSGi bundles to determine if they contain a Spring context. META-INF/spring is the default location the Spring DM extender will scan looking for these application context definition files so let's create that directory for each bundle:

        mkdir -p hello-java/src/main/resources/META-INF/spring;
        mkdir -p hello-groovy/src/main/resources/META-INF/spring;
        mkdir -p hello-scala/src/main/resources/META-INF/spring;
    

Within each bundle, we'll create two spring definition files. We want to separate our basic bean definitions from those that need to be defined within an OSGi namespace. This convention is especially useful when writing tests as it allows you to draw a distinction between tests that must take place within an OSGi framework and those that may take place outside of OSGi. bundle-context.xml will be our standard Spring configuration and bundle-context-osgi.xml will contain Spring DM specific configuration.

Publishing Hello Java with Spring DM

In the vanilla spring context configuation, ./hello-java/src/main/resources/META-INF/spring/bundle-context.xml, we define our POJO implementation.

        <beans xmlns="http://www.springframework.org/schema/beans"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:p="http://www.springframework.org/schema/p"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans.xsd">
            
            <bean id="helloJavaService" class="com.domain.osgi.java.internal.HelloJavaServiceImpl">
            </bean>
            
        </beans>
    

And in the file ./hello-java/src/main/resources/META-INF/spring/bundle-context-osgi.xml we will define our helloJavaService service for consumption by other bundles. This service definition refers to the implementation above.

        <beans:beans xmlns="http://www.springframework.org/schema/osgi"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:beans="http://www.springframework.org/schema/beans"
                        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">
            
            <service ref="helloJavaService" interface="com.domain.osgi.java.HelloJavaService">
            </service>
            
        </beans:beans>
    

That's all there is to it. helloJavaService will be published by Spring DM without any need to use the OSGi API directly.

Publishing Hello Groovy with Spring DM

The configuration for Groovy is identical to what we've done for Java. We simply reference the Groovy code that parallels our Java example.

./hello-groovy/src/main/resources/META-INF/spring/bundle-context.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:p="http://www.springframework.org/schema/p"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans.xsd">
            
            <bean id="helloGroovyService" class="com.domain.osgi.groovy.internal.HelloGroovyServiceImpl">
            </bean>
            
        </beans>
    

./hello-groovy/src/main/resources/META-INF/spring/bundle-context-osgi.xml

        <beans:beans xmlns="http://www.springframework.org/schema/osgi"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:beans="http://www.springframework.org/schema/beans"
                        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">
            
            <service ref="helloGroovyService" interface="com.domain.osgi.groovy.HelloGroovyService">
            </service>
            
        </beans:beans>
    

Publishing Hello Scala with Spring DM

Our scala configuration follows suit. It's exactly the same as both our Java and Groovy configurations. This is all nice and simple to get going.

./hello-scala/src/main/resources/META-INF/spring/bundle-context.xml

        <beans xmlns="http://www.springframework.org/schema/beans"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:p="http://www.springframework.org/schema/p"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans.xsd">
            
            <bean id="helloScalaService" class="com.domain.osgi.scala.internal.HelloScalaServiceImpl">
            </bean>
            
        </beans>
    

./hello-scala/src/main/resources/META-INF/spring/bundle-context-osgi.xml

        <beans:beans xmlns="http://www.springframework.org/schema/osgi"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:beans="http://www.springframework.org/schema/beans"
                        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">
            
            <service ref="helloScalaService" interface="com.domain.osgi.scala.HelloScalaService">
            </service>
            
        </beans:beans>
    

Putting It All Together

At this point, the basics are in place. Once again, PAX is really going to shine as we'll install and provision our bundles. Aside from adding the appropriate Spring repositories in the beginning of this post, we haven't done anything to satisfy the Spring DM dependencies. Thanks to PAX Runner's notion of profiles we can provision all of the bundles required for Spring DM with a simple command line switch:

Update: Newer versions of PAX Runner don't install the compendium bundle by default so we need to explicitly import it:

        pax-import-bundle -g org.osgi -a org.osgi.compendium -v 4.2.0
    

Then we can safely start everything up with pax:provision:

        mvn install pax:provision -Dprofiles=spring.dm
    

Voilà! As the logging indicates, Maven was able to fetch all the bundles we require and our three services have been published by Spring DM.

        ...
        
        [SpringOsgiExtenderThread-5] INFO org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean - Publishing service under classes [{com.domain.osgi.scala.HelloScalaService}]
        
        ...
        
        [SpringOsgiExtenderThread-4] INFO org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean - Publishing service under classes [{com.domain.osgi.groovy.HelloGroovyService}]
        
        ...
        
        [SpringOsgiExtenderThread-6] INFO org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean - Publishing service under classes [{com.domain.osgi.java.HelloJavaService}]
        
        ...
    

Conclusion

We've seen how we can incorporate Spring DM to completely abstract away the OSGi API for defining and publishing OSGi services. Spring DM is wiring our bundles together using compiled bytecode so we needn't do anything fancy whether we're using use Java, Groovy, Scala or any other language than may be run on the JVM. Up next, we'll walk through the Spring DM configuration for consuming these services.

Check out Part 3 here