Tuesday, September 7, 2010

OSGi for Low Coupling and High Cohesion

Cohesion is an internal metric of a module’s focus. Coupling is an external metric of the degree to which each program module relies on other modules. The benefits of low coupling are (1.) little change is required to an application when a module is replaced with another and (2.) changes within a module do not effect other modules in the application.

Both Java and OSGi originally focused on small hardware devices. Java's attraction came from its promise "write once, run anywhere" which appealed to hardware vendors but, it was soon found that in Java applications dependencies were difficult to sort out, especially in regards to the classpath. Often it was found that different versions of the same jar were needed by different parts of the application. In short, jars are not a complete solution for providing modularity for Java applications.

OSGi is designed as a universal integration platform for the interoperability of applications and services. OSGi makes it possible for different components of an application to depend on different versions of a jar.

The use case of an application consuming services provided through OSGi requires both the consumer and the provider publish manifests. The manifest required is, in most ways, a standard jar manifest stored in the jar as META-INF/MANIFEST.MF. The jar which provides the service will export the package(s) in which the Java classes are found with a statement in the jar's manifest such as Export-Package: com.company.search. This statement only effects the Java components registered in the OSGi registry. It exposes all the public classes in the package.

The manifest in the jar file of the consumer will have an import like Import-Package: com.company.search. This statement imports all the public classes in the package.

Below I have created a MessagePrinter function that consumes messages produced by the HelloGoodbyeProducer. There are three modules, the activator, the producer and the consumer; each module is a separate project created in its own folder.
package com.activator;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

import com.producer.HelloGoodbyeProducer;
import com.producer.impl.HelloGoodbyeImpl;
import com.consumer.MessagePrinterService;
import com.consumer.impl.MessagePrinterServiceImpl;

public class HelloWorldActivator implements BundleActivator {

    public void start(BundleContext context) throws Exception {
        HelloGoodbyeProducer producer = getHelloGoodbyeProducer(context);
        MessagePrinterService printer = getMessagePrinter(context);
        printer.printMessage(producer.getHelloMessage());
    }

    public void stop(BundleContext context) throws Exception {
        HelloGoodbyeProducer producer = getHelloGoodbyeProducer(context);
        MessagePrinterService printer = getMessagePrinter(context); 
        printer.printMessage(producer.getGoodbyeMessage());
    }

    private HelloGoodbyeProducer getHelloGoodbyeProducer(BundleContext context) {
        ServiceRegistration reg = 
            context.registerService(HelloGoodbyeProducer.class.getName(), new HelloGoodbyeImpl(), null);
        HelloGoodbyeProducer producer = (HelloGoodbyeProducer) context.getService(reg.getReference());
        return producer;
    }

    private MessagePrinterService getMessagePrinter(BundleContext context) {
        ServiceRegistration reg = 
            context.registerService(MessagePrinterService.class.getName(), new MessagePrinterServiceImpl(), null);
        MessagePrinterService printer = (MessagePrinterService) context.getService(reg.getReference());
        return printer;
    }
}

src/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloActivator
Bundle-SymbolicName: com.activator.HelloWorldActivator
Bundle-Version: 1.0.3
Bundle-Activator: com.activator.HelloWorldActivator
Import-Package: com.producer, com.producer.impl, com.consumer, com.consumer.impl, org.osgi.framework 

ANT build.xml for the Activator

<project name="activator" default="jar" basedir=".">

    <property name="src.dir"           location="src"/> 
    <property name="build.dir"         location="bin"/>
    <property name="target.dir"        location="target"/>
    <property name="osgi.dir"          location="${user.home}/dev/bin/equinox-sdk-3"/>
    <property name="consumer.jar"      value="../consumer/target/consumer.jar"/>
    <property name="producer.jar"      value="../producer/target/producer.jar"/>
    <property name="manifest"          location="${build.dir}/META-INF/MANIFEST.MF"/>

    <path id="classpath">
        <pathelement location="${build.dir}"/>
        <pathelement location="${osgi.dir}/plugins/org.eclipse.osgi_3.5.2.R35x_v20100126.jar"/>
        <pathelement location="${consumer.jar}"/>
        <pathelement location="${producer.jar}"/>
    </path>

    <target name="clean">
        <delete dir="${build.dir}"/>
        <delete dir="${target.dir}"/>
    </target>

    <target name="init" depends="clean">
        <mkdir dir="${build.dir}"/>
        <mkdir dir="${target.dir}"/>
    </target>

    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" 
            destdir="${build.dir}" 
            classpathref="classpath"/>
        <copy file="src/META-INF/MANIFEST.MF" todir="${build.dir}/META-INF"/>
    </target>

    <target name="jar" depends="compile">
       <jar jarfile="${target.dir}/${ant.project.name}.jar" 
            basedir="${build.dir}" manifest="${manifest}"/>
    </target>
</project>

Java Code for the Message Producer

package com.producer; 

public interface HelloGoodbyeProducer {
   String getHelloMessage();

   String getGoodbyeMessage();
}
package com.producer.impl; 
import com.producer.HelloGoodbyeProducer;

public class HelloGoodbyeImpl implements HelloGoodbyeProducer { 
    
    public String getHelloMessage() {
        return "Hello World!"; 
    }
    
    public String getGoodbyeMessage() { 
        return "Goodbye World!";
    }
}

src/META-INF/MANIFEST.MF

Manifest-Version: 1.0 
Bundle-ManifestVersion: 2 
Bundle-Name: HelloGoodbye
Bundle-SymbolicName: com.producer.HelloGoodbyeProducer
Bundle-Version: 1.0.3
Bundle-Activator: com.activator.HelloWorldActivator
Export-Package: com.producer, com.producer.impl

ANT build.xml for the Producer

<project name="producer" default="jar" basedir=".">

    <property name="src.dir"           location="src"/> 
    <property name="build.dir"         location="bin"/>
    <property name="target.dir"        location="target"/>
    <property name="manifest"          location="${build.dir}/META-INF/MANIFEST.MF"/>

    <path id="classpath">
        <pathelement location="${build.dir}"/>
    </path>

    <target name="clean">
        <delete dir="${build.dir}"/>
        <delete dir="${target.dir}"/>
    </target>

    <target name="init" depends="clean">
        <mkdir dir="${build.dir}"/>
        <mkdir dir="${target.dir}"/>
    </target>

    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" 
            destdir="${build.dir}" 
            classpathref="classpath"/>
        <copy file="src/META-INF/MANIFEST.MF" todir="${build.dir}/META-INF"/>
    </target>

    <target name="jar" depends="compile">
       <jar jarfile="${target.dir}/${ant.project.name}.jar" 
            basedir="${build.dir}" manifest="${manifest}"/>
     </target>
</project>

Java Code for the Consumer

package com.consumer; 

public interface MessagePrinterService {
   public void printMessage(String message);
}
package com.consumer.impl; 
import com.consumer.MessagePrinterService;

public class MessagePrinterServiceImpl implements MessagePrinterService {
    public void printMessage(String message) {
        System.out.println(message);
    }
}

src/META-INF/MANIFEST.MF

Manifest-Version: 1.0 
Bundle-ManifestVersion: 2 
Bundle-Name: HelloGoodbye
Bundle-SymbolicName: com.producer.HelloGoodbyeProducer
Bundle-Version: 1.0.3
Bundle-Activator: com.activator.HelloWorldActivator
Export-Package: com.producer, com.producer.impl

build.xml for the Consumer

<project name="consumer" default="jar" basedir=".">
    
    <property name="src.dir"           location="src"/> 
    <property name="build.dir"         location="bin"/>
    <property name="target.dir"        location="target"/>
    <property name="manifest"          location="${build.dir}/META-INF/MANIFEST.MF"/>

    <path id="classpath">
        <pathelement location="${build.dir}"/>
    </path>

    <target name="clean">
        <delete dir="${build.dir}"/>
        <delete dir="${target.dir}"/>
    </target>

    <target name="init" depends="clean">
        <mkdir dir="${build.dir}"/>
        <mkdir dir="${target.dir}"/>
    </target>

    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" 
            destdir="${build.dir}" 
            classpathref="classpath"/>
        <copy file="src/META-INF/MANIFEST.MF" todir="${build.dir}/META-INF"/>
    </target>

    <target name="jar" depends="compile">
       <jar jarfile="${target.dir}/${ant.project.name}.jar" 
            basedir="${build.dir}" manifest="${manifest}"/>
     </target>
</project>

It's Showtime!

[greg:equinox-SDK-3] java -jar plugins/org.eclipse.osgi_3.5.2.R35x_v20100126.jar -console

osgi> ss

Framework is launched.

id     State       Bundle
0      ACTIVE      org.eclipse.osgi_3.5.2.R35x_v20100126

osgi> install file:producer.jar
Bundle id is 4

osgi> install file:consumer.jar
Bundle id is 5

osgi> install file:activator.jar
Bundle id is 6

osgi> ss

Framework is launched.

id     State       Bundle
0      ACTIVE      org.eclipse.osgi_3.5.2.R35x_v20100126
4      RESOLVED    com.producer.HelloGoodbyeProducer_1.0.3
5      RESOLVED    com.consumer.MessagePrinterService_1.0.3
6      RESOLVED    com.activator.HelloWorldActivator_1.0.3

osgi> start 6
Hello World!

osgi> stop 6
Goodbye World!

osgi>