読者です 読者をやめる 読者になる 読者になる

Apache Felix に自作のサービス(OSGi service)を登録する

今回は、Apache Felix に自作のサービス(OSGi service)を登録・実行してみる。

  • サービス登録用の Bundle 作成
  • サービス実行用の Bundle 作成

Bundle の作成手順やインストール方法は前回 id:fits:20081118 の通り。

サービス登録用の Bundle 作成

サービスを登録するための Bundle を作成する。
サービス登録用 Bundle 作成の特徴は以下の通り。

  • サービス用のインターフェースを定義
  • Bundle クラスでサービスを登録
  • MANIFEST.MF に Export-Package を追加

まず、サービス用のインターフェースを定義する。

sample/service/TestService.java
package sample.service;

//サービス用インターフェース
public interface TestService {
    String test(String msg);
}

次に、Bundle クラスの start メソッド内でサービスインターフェースのクラス名と共にサービスの実装オブジェクトを registerService する。

なお、registerService したサービスは Bundle の停止時に自動的に unregister されるようなので、そのための処理を実装する必要は無い。

sample/TestActivator.java
package sample;

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

import sample.service.TestService;

//サービス登録用 Bundle クラス
public class TestActivator implements BundleActivator {

    public void start(BundleContext context) {
        context.registerService(TestService.class.getName(), new TestServiceImpl(), null);
    }

    public void stop(BundleContext context) {
    }

    //サービスの実装クラス
    private class TestServiceImpl implements TestService {
        public String test(String msg) {
            return msg + "_test";
        }
    }
}

そして、サービス登録側ではサービスインターフェースのパッケージを MANIFEST.MF の Export-Package に設定する。

META-INF/MANIFEST.MF の例
・・・
Bundle-Name: Test Service
Bundle-Description: 
Bundle-Vendor: 
Bundle-Version: 1.0.0
Bundle-Activator: sample.TestActivator
Import-Package: org.osgi.framework
Export-Package: sample.service

こうして作成した Bundle(JAR ファイル)を Felix にインストール・開始し、Felix Shell の services コマンドでサービスが登録されている事を確認。

-> services
・・・
Test Service (8) provides:
--------------------------
sample.service.TestService

サービス実行用の Bundle 作成

サービスを利用する側も Bundle として作成する。

start メソッド内でサービスリファレンスを使ってサービスを getService し、使い終わると ungetService する。
なお、サービス登録用 Bundle の時と同様に、停止時には自動的にサービスがリリース(ungetService)される模様。

sample/client/TestClientActivator
package sample.client;

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

import sample.service.TestService;

//サービス実行用 Bundle クラス
public class TestClientActivator implements BundleActivator {

    public void start(BundleContext context) {
        //サービスリファレンス取得
        ServiceReference sr = context.getServiceReference(TestService.class.getName());

        if (sr != null) {
            //サービスの取得
            TestService service = (TestService)context.getService(sr);

            if (service != null) {
                //サービスの実行
                System.out.println("test service result : " + service.test("ABC"));
                context.ungetService(sr);
            }
        }
    }

    public void stop(BundleContext context) {
    }
}

サービスの利用側では、利用するサービスインターフェースのパッケージを MANIFEST.MF の Import-Package に追加する。

META-INF/MANIFEST.MF の例
・・・
Bundle-Name: Test Client
Bundle-Description: 
Bundle-Vendor: 
Bundle-Version: 1.0.0
Bundle-Activator: sample.client.TestClientActivator
Import-Package: org.osgi.framework, sample.service

こうして作成した Bundle(JAR ファイル)を Felix にインストール・開始すると、サービスの実行結果が出力される。

-> install file:///d:/felix_service_sample/dest/clienttest.jar
Bundle ID: 9
-> start 9
test service result : ABC_test

ビルドファイル

今回の JAR ファイル生成に使用した Gant ビルドファイルは以下の通り。
サービス登録・実行用の設定を BundleInfo クラスにまとめて、処理の共通化を図っている。
Gant ではこういう事ができるから使い勝手が非常によい。

build.gant
includeTargets << gant.targets.Clean

Ant.property(environment: "env")
felixHome = Ant.antProject.properties."env.FELIX_HOME"

destDir = "dest"
classesDir = "classes"
jarFile = "test.jar"

cleanDirectory << destDir

list = [
    //サービス登録用 Bundle 情報
    new BundleInfo(type: "service", name: "Test Service", 
        activator: "sample.TestActivator", 
        importPackage: "org.osgi.framework",
        exportPackage: "sample.service"),

    //サービス実行用 Bundle 情報
    new BundleInfo(type: "client", name: "Test Client", 
        activator: "sample.client.TestClientActivator", 
        importPackage: "org.osgi.framework, sample.service")
]

//classesDir のパスを取得
def getClassesDirPath = {
    "${destDir}/${it.type}/${classesDir}"
}


target("default": "") {
    compile()
}

target(init: "") {
    path(id: "project.classpath") {
        list.each {
            pathelement(path: getClassesDirPath(it))
        }

        fileset(dir: felixHome) {
            include(name: "bin/*.jar")
        }
    }
}

target(compile: "") {
    depends(init)

    list.each {
        def tempDir = getClassesDirPath(it)

        Ant.mkdir(dir: tempDir)

        Ant.javac(srcdir: "src_${it.type}", destdir: tempDir) {
            classpath(refid: "project.classpath")
        }
    }
}

target(build: "") {
    depends(compile)

    list.each { b ->
        Ant.jar(destfile: "${destDir}/${b.type}${jarFile}", basedir: getClassesDirPath(b)) {
            manifest {
                attribute(name: "Bundle-Name", value: b.name)
                attribute(name: "Bundle-Description", value: "")
                attribute(name: "Bundle-Vendor", value: "")
                attribute(name: "Bundle-Version", value: "1.0.0")
                attribute(name: "Bundle-Activator", value: b.activator)
                attribute(name: "Import-Package", value: b.importPackage)

                if (b.exportPackage != null) {
                    attribute(name: "Export-Package", value: b.exportPackage)
                }
            }
        }
    }
}

//Bundle 情報クラス
class BundleInfo {
    def type
    def name
    def activator
    def importPackage
    def exportPackage
}