Eclipse で軽量の OSGi アプリケーションを構築する

OSGi は、Java の世界とその他多くの分野で、動的なモジュール・システムを構築するための事実上の業界標準となっています。この記事では、Eclipse で OSGi アプリケーションを開発する際のプロセス、シナリオ、ソリューション、プラクティスを、相関するサンプル・コードを用いて説明します。OSGi フレームワークとそのコア・サービスについて系統立てて理解してください。

Yuan Tao Sun, Software Performance Analyst, IBM

Photo of Sun, Yuan TaoSun, Yuan Tao はIBM China Software Development Lab の Lotus Shanghai パフォーマンス・チームの一員です。ソフトウェア・パフォーマンス・アナリストとして、Lotus Connections や LotusLive iNotes などの Lotus ソーシャル製品のパフォーマンスの測定および改善を中心に取り組んでいます。パフォーマンス・チームに加わる前は、ソフトウェア開発者としてソフトウェア・デザイン・パターン、Eclipse RCP、Java 2 Platform, Enterprise Edition アプリケーション開発に取り組んでいました。



2011年 11月 25日

はじめに

モジュール・システムを構築するには、数々の要件があります。Java の世界とその他多くの分野では、OSGi (Open Services Gateway initiative) がデスクトップ・アプリケーション、Web アプリケーション、モバイル・アプリケーション、そしてミドルウェアを始めとするモジュール・システムのための成熟したフレームワークとして認められています。OSGi は、モジュール式の動的なサービス指向アプリケーションを開発するためのインフラストラクチャーとなります。ほとんどの機能はサービスという形で OSGi の仕様および実装に定義され、提供されています。いくつかの中心的な OSGi サービスの概念および使用方法を理解することで、これらのサービスと Eclipse IDE を使用して、複雑な要件を満たす軽量のモジュール式アプリケーションを構築できるようになります。


サンプル・アプリケーション

この記事ではサンプル・アプリケーションとしてデータ・コレクターを作成します。このデータ・コレクターは、各種のデータ・ソースからデータを取得して構文解析し、その後の処理を行うために、事前に定義されている統一データ・フォーマットにします。データ・フォーマットの定義、そしてデータを取得する方法は、それぞれのシステムによってかなり異なります。一例として、このアプリケーションの作成者または所有者は、サード・パーティー・ベンダーがそのアプリケーションで公開している API をベースに特定のデータ・ソースのプロセス・ロジックを実装することを想定しています。その場合、データ・コレクターのクライアントはサード・パーティーのコードを一体化して、既存のコードや構成、あるいはデプロイメント構造を一切変更することなくコードを実行するはずです。これは、モジュール・システムを構築する際の一般的な要件なので、この要件を OSGi コア・サービスによって実現する方法を紹介します。

OSGi を最大限に活用するには、アプリケーションのアーキテクチャーを慎重に設計する必要がありますが、それは複雑なことではありません。OSGi アプリケーションの設計原則について説明している記事がいくつか公開されているので、これらの記事をひと通り読むことをお勧めします (「参考文献」を参照)。

図 1 に、サンプル・データ・コレクター・アプリケーションのアーキテクチャーを示します。このアプリケーションを構成するバンドルのタイプには、データ・コレクター・フレームワーク・バンドル、テキスト・パーサー・バンドル、コレクター・バンドルの 3 つがあります。

図 1. サンプル・データ・コレクター・アプリケーションのアーキテクチャー
この図は、データ・コレクター・フレームワーク・バンドルに含まれる、テキスト・パーサー用、データ・コレクター用、および構成ウィザード用の API が各種のコレクター・バンドルおよびテキスト・パーサー・バンドルとやりとりする様子を示しています。

データ・コレクター・フレームワーク・バンドルは、アプリケーション全体のなかでコア・コンポーネントとしての役割を果たします。このバンドルは、他のバンドルにコレクター API やウィザード UI API を提供するバンドルとして機能することもあれば、他の機能バンドルからのサービスを利用するクライアントとして機能することもあります。API を提供するバンドルとクライアントとして機能するバンドルを 1 つのバンドルにマージすることにより、サード・パーティーのデータ・コレクターの開発者が最小限の数のバンドルの jar を IDE にインポートすれば、API を実装してアプリケーション全体を Eclipse で実行およびテストできるようになります。コレクター・バンドルは、データ収集サービスとウィザード・ページ・サービスを提供する一方、クライアントとしても機能します。クライアントとしてのコレクター・バンドルは、フレームワーク・バンドルによって提供されるテキスト・パーサー API を呼び出します。このテキスト・パーサーには、別個のパーサー・バンドルがサービスを提供します。

これらのバンドルはいずれも疎結合であり、バンドル間のやりとりは一貫して OSGi コア・サービスを介して行われます。したがって、フレームワーク・バンドルを除くすべてのバンドルは、アプリケーションをデプロイした後でも簡単に追加、削除、中断、あるいはアップグレードすることができます。これは、自動ソフトウェア配信プロセスにとって重要な機能です。

ここからは、この小さいながらも完全な OSGi アプリケーションを実装してデプロイする際のプロセスおよびプラクティスを具体的に説明します。


OSGi プロジェクト・レイアウトの定義

Eclipse では、それぞれのバンドルごとに新しいプラグイン・プロジェクトを作成する必要があります。以降の説明では、Eclipse で OSGi バンドルを作成した経験があること、あるいはその知識を持っていることを前提とします。また、必ず Eclipse IDE の最新リリースと単独の Equinox SDK をダウンロードしてください。Eclipse のバージョンは、3.4 以降が推奨されます。

図 1 のところで説明したように、少なくとも 3 つのバンドル・プロジェクト (フレームワーク・バンドル、テキスト・パーサー・バンドル、データ・コレクター・バンドル) が必要です。プロジェクトを作成する際には、レイアウト (パッケージとフォルダーの階層設計など) の定義が非常に重要になります。一例として、フレームワーク・バンドル・プロジェクトのレイアウトを検討してみましょう (図 2 を参照)。

図 2. プロジェクトのレイアウトの例
標準的なプロジェクトのディレクトリー構造を示すスクリーン・ショット

OSGI-INF フォルダーは、このバンドルに、すべての OSGi コンポーネント定義ファイルを保存するために作成されたフォルダーです (コンポーネントの概念については、後で紹介します)。パッケージは、機能のカテゴリーごとに作成しますが、バンドル同士が緊密に結合しないようにするためには、早い段階で他のバンドルに可視になるパッケージを計画しなければなりません。サンプルのフレームワーク・バンドルには、以下の 2 つのパッケージがエクスポートされます。

  • dw.sample.dc.api – 実装されるすべての API が含まれます。
  • dw.sample.dc.consts – イベント・パラメーターなど、バンドル間で共有される定数および列挙が含まれます。

OSGi 宣言型サービスによるサービスの注入

OSGi 仕様で主要な概念となっているのは、モジュール性とサービス指向性です。実装クラスを公開することも OSGi フレームワークではサポートしているものの、これは推奨される方法ではありません。プログラミングの観点からは、このよな方法の代わりに、バンドルがサービス (またはクラスのインスタンス) を公開し、利用することによって、バンドル同士がやりとりすることが期待されます。そのために、OSGi では以下の 2 つの手法を使用できるようになっています。

  • OSGi サービス層が提供する BundleContext オブジェクトを使用して、サービス・レジストリーへのサービスの登録、サービス・レジスリーからのサービスの取得を行います。
  • OSGi R4 仕様の宣言型サービスを利用します。

宣言型サービス (Declarative Services: DS) は、コンポーネント・モデルの概念を宣言します。コンポーネントとは、サービスを提供し、参照先サービスを取得するために使用される、コンポーネント定義ファイルによって定義された Java オブジェクトです。

コンポーネントのライフサイクルは、OSGi コンテナーが管理します。さらに、参照先サービス・インスタンスはコンポーネントに動的に注入されます。すると、宣言型サービスはサービス参照を処理し、事前に定義されたストラテジーに応じてコンポーネントとのバインドまたはバインド解除を行います。したがって、開発者は極めて動的なシステムを簡潔なコードで作成できるというわけです。

最初のサービス・コンポーネントを作成する

まずは単純なケースから取り掛かりましょう。このアプリケーションでは、データ・コレクター・フレームワーク・バンドルがテキスト・パーサー API を定義し、テキスト・パーサー・バンドルがデフォルトのパーサー・サービスを提供します。

フレームワーク・バンドルでは、フレームワーク・バンドル自身からエクスポートされる dw.sample.dc.api パッケージの中でサービス API を定義します (リスト 1 を参照)。

リスト 1. テキスト・パーサー API
public interface ITextParser {
public String parseText(String input);
}

テキスト・パーサー・バンドル・プロジェクトでは、現行のバンドル・クラス・ローダーが確実にインターフェース・クラスをロードできるように、最初に dw.sample.dc.api パッケージをマニフェストにインポートする必要があります。次に、上記のインターフェースを実装するクラスを作成します。

最後の、そして最も重要なステップは、実装クラスをコンポーネントとして定義することです。それには、コンポーネント定義ファイルを作成して、そのファイルをバンドルのマニフェストに定義するというプロセスが必要になります。ここでは、このプロセスを単純にするために、Eclipse が提供しているウィザードを利用します (このウィザードは、古いリリースでは使用できない場合があります)。

  1. パーサー・バンドルの OSGI-INF フォルダーを右クリックして、「New (新規)」 > 「Component Definition (コンポーネント定義)」の順に選択します。
  2. 先ほど作成した実装クラスのファイルをコンポーネント・クラスとして指定し、コンポーネント定義ファイルとコンポーネント名を指定して「Finish (完了)」をクリックします。
  3. コンポーネントの定義を構成するグラフィック・ウィンドウで「Services (サービス)」タブに切り替え、このコンポーネントが提供するサービスとして、フレームワーク・バンドルのサービス・インターフェース・クラスを指定します。図 3 を参照してください。
図 3. 提供するサービスの指定
dw.sample.dc.api.ITextParser に対して構成された提供サービスを示すスクリーン・ショット

ウィザードは新規 textParserComponent.xml を生成し、このコンポーネントをバンドルのマニフェストに登録します (リスト 2 を参照)。

リスト 2. 新しく生成されたコンポーネントの構成
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
  name="dw.sample.textparser.defaultparser">
   <implementation class="dw.sample.textparser.service.DataCollectorDefaultTextParser"/>
   <service>
      <provide interface="dw.sample.dc.api.ITextParser"/>
   </service>
</scr:component>

MANIFEST.MF
...
Service-Component: OSGI-INF/textParserComponent.xml

これで、単純ながらも完全なコンポーネントの実装が完了しました。実行時にクライアントがフレームワーク・バンドルの API を呼び出すと、このコンポーネントがサービスを提供します。

今度は、これよりも複雑なコンポーネントを例に、他のコンポーネントが提供するサービスを利用する方法を紹介します。

より高度なケース

では、ユーザーが GUI を使用してデータ・コレクター・アプリケーションと対話するシナリオについて検討してみましょう。つまり、ユーザーが一連のウィザード・ページの指示に従って、必要なデータを入力できるようにします。データを入力した後、ユーザーは収集プロセスを起動して、UI を通じてプロセスの進行状況を見守ります。当然のことながら、フレームワークでは、サード・パーティーのデータ・コレクターがどのデータを必要としているかを認識していません。このシナリオの要件を満たすには、データ・コレクター・バンドルでも 1 つ以上の構成ウィザード・ページを提供する必要があります。さらに、ユーザー環境にデプロイされた後のアプリケーションでは、フレームワークがすべてのサード・パーティーのウィザードをシームレスかつ動的に統合して表示しなければなりません。かなり高度なアプリケーションだと思いませんか?これから、OSGi 宣言型サービスを利用してこのアプリケーションを実現する方法を見ていきます (図 4 を参照)。

図 4. 動的なウィザード・シーケンス
ウィザード・グループ 1 からウィザード・グループ 2 に進行し、さらにウィザード・グループ 4 に進行する様子を示す図。ウィザード・グループ 3 はグレーアウトされています。

前のケースと同じように API の定義が必要ですが、この場合、インターフェースを慎重に設計して、ウィザード・システムが複雑にならないようにすると同時に、API がデータ・コレクター側からの主要な機能要件に対応できることを確実にしなければなりません。したがって、API の設計では少なくとも以下の要件を満たす必要があります。

  • それぞれのデータ・コレクターによって提供されるウィザード・サービスが互いに通信したり、依存関係を持ったりしないようにする一方、同じデータ・コレクター内のウィザード・サービスに対しては通信、依存関係を制限しないこと。
  • ユーザーがデータ・コレクターの最後のウィザード・ページから移動する時点で、ユーザーの入力を保存すること。

上記の要件を満たす API を設計するステップが、このケースで最も重要です。適格な API 設計とは、明確なインターフェースを提供する一方で、サービスを非常にまとまりのある状態で維持することです。実装に含まれるロジックやコードの複雑さは関係ありません。

SWT および JFace は、アプリケーションのウィザードや、他の GUI コンポーネントの基礎となるライブラリーとして使用されています。この 2 つのライブラリーに集められたパッケージのリソースおよびクラスを使用することを予定している場合は、この両方を必須バンドルとしてフレームワーク・バンドルとデータ・コレクター・バンドルのマニフェスト・ファイルに追加してください。また、org.osgi.service.component パッケージもインポートする必要があります。後で、このパッケージに含まれるクラスを呼び出すことになるためです。

フレームワーク・バンドルに定義するウィザード API は、リスト 3 に記載する 2 つのクラスからなります。

リスト 3. ウィザード・サービス API の定義
public interface ICollectorWizardGenerator {
public List<CollectorWizardPage> getWizardSequence();
public int getWizardPositionIndex();
}

---
/**
 * Inherit the jface WizardPage by adding two customized methods
 */
public abstract class CollectorWizardPage extends WizardPage {
    …
/**
* Trigger the customized logic when the next button on the wizard is
 * pressed
 * 
 * @return whether to jump to next page.
 */
public abstract boolean nextPressedTrigger();

/**
* Save user input in the current wizard.
 */
public abstract void saveWizardInput();    
}

上記のウィザード API をデータ・コレクター・バンドルに実装し、ICollectorWizardGenerator インターフェースを実装するクラスをサービス・コンポーネントとして定義します (リスト 4 を参照)。

リスト 4. ウィザード・サービス・コンポーネント
public class ProfileWizardGeneratorImpl implements ICollectorWizardGenerator {
    private int wizardPosition = 10;

    protected void activate(ComponentContext context) {
        try {
            wizardPosition = ((Integer) context.getProperties().get(
                    "wizardPosition")).intValue();
        } catch (NumberFormatException e) {
            wizardPosition = 10;
        }
    }
    public List<CollectorWizardPage> getWizardSequence() {
        List<CollectorWizardPage> wizardPageSeq = new ArrayList<CollectorWizardPage>();
        wizardPageSeq.add(new ProfileCollectorWizardPage1());
        wizardPageSeq.add(new ProfileCollectorWizardPage2());
        return wizardPageSeq;
    }
    public int getWizardPositionIndex() {
        return wizardPosition;
    }
}

---
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
  name="dw.sample.dc.profile.wizardgenerator">
   <implementation class="dw.sample.dc.profile.service.ProfileWizardGeneratorImpl"/>   
   <service>
      <provide interface="dw.sample.dc.api.ICollectorWizardGenerator"/>
   </service>
   <property name="wizardPosition" type="Integer" value="2"/>
</scr:component>

activate メソッドはコンポーネント・クラス内でオーバーライドされることに注目してください。このメソッドと deactivate メソッドは、コンポーネントがアクティブにされるとき、もしくは非アクティブにされるときに OSGi フレームワークによって呼び出されます。コンポーネント情報 (XML ファイルに定義されたコンポーネント・プロパティーなど) を取得できるように、activate メソッドには ComponentContext オブジェクトが渡されます。データ・コレクターのベンダーは、このオブジェクトにウィザードの位置インデックスを定義することができます。この定義に従って、フレームワークはウィザードの表示シーケンスを調整します。

ここまでの作業で、2 つのコンポーネントをサービス・プロバイダーとして作成しました。

サービスを利用する

宣言型サービス・モデルでは、サービスは動的にコンポーネントに注入されます。リスト 5 に記載するコードに、サービスを参照してバンドル内で利用する方法を示します。

リスト 5. パーサー・サービス・ローダー・コンポーネント
public class TextParserLoader {
    private static TextParserLoader instance;
    ... ...
    private ITextParser parser;

    public static TextParserLoader getInstance() {
        return instance;
    }
    protected void activate(ComponentContext context) {
        try {
            locker.lock();
            instance = this;
        } finally {
            locker.unlock();
        }
    }
    public void setTextParser(ITextParser parser) {
        this.parser = parser;
    }
    public void unsetTextParser(ITextParser parser) {
        if (this.parser != parser) {
            return;
        }
        this.parser = null;
    }
    public ITextParser getTextParser() {
        return parser;
    }
}

---
<implementation class="dw.sample.dc.profile.service.TextParserLoader"/>
<reference bind="setTextParser" cardinality="1..1" 
  interface="dw.sample.dc.api.ITextParser" name="ITextParser" 
policy="dynamic" unbind="unsetTextParser"/>

上記では、シングルトン・パターンがコンポーネント・クラスに適用されています。バンドル内のコンポーネント以外のオブジェクトは、OSGi 専用のコードを排除して、固有の TextParserLoader インスタンスからサービスを取得して利用することができます。このローダー・クラスは何らかの形で、従来の Java アプリケーションでのファクトリー・クラスのような役割を果たしますが、その魅力はクラスのロード、リフレクション、インスタンスの開始といった厄介な処理をする必要がないところにあります。

宣言型サービスは、コンポーネント XML ファイルに定義された以下のサービス参照ストラテジーをサポートします。

  • ポリシー: policy プロパティーの値には、static (静的) および dynamic (動的) の 2 つがあります。デフォルトは静的ポリシーです。これは、参照されているサービスに何らかの変更が加えられると、コンポーネントが再びアクティブになることを意味します。一方、動的ポリシーは事前に定義された bind メソッドと unbind メソッドを呼び出すだけなので、こちらの値のほうが大いに推奨されます。
  • カーディナリティー: このプロパティーに使用できる値は、1..1、1..n、0..1、または 0..n の 4 つです。同じ API に対して使用可能なサービスが複数ある場合、終わりの数値が、コンポーネントにそのすべてのサービスを注入するか、あるいはそのうちの 1 つだけを注入するかを示します。先頭の数値が示すのは、コンポーネントをアクティブにするためには使用可能なサービスがなければならないかどうかです。

カーディナリティーのデフォルト値は 1..1 です。上記の例でもこのデフォルト値が使用されていますが、値が 0..n の場合でも、シングルトン・パターンでは問題なく処理することができます (リスト 6 を参照)。

リスト 6. 複数のサービスの注入
public class DataCollectorWizardSequence {
    ...
    private List<ICollectorWizardGenerator> wizardGeneratorSeq = 
	                            new ArrayList<ICollectorWizardGenerator>();
    ...
    public void addCollectorWizardGenerator(ICollectorWizardGenerator wizardGenerator) {
        wizardGeneratorSeq.add(wizardGenerator);        
    }
    public void removeCollectorWizardGenerator(ICollectorWizardGenerator wizardGenerator)
    {        
        wizardGeneratorSeq.remove(wizardGenerator);        
    }
    public List<CollectorWizardPage> getDataCollectorWizardSequence() {
        List<CollectorWizardPage> collectorWizardSequence = 
		                    new ArrayList<CollectorWizardPage>();
        try {
            locker.lock();
            /*
             * Arrange the wizard sequence according to position indexes 
	    * defined by each wizard services
	    */
            Collections.sort(wizardGeneratorSeq,
                    new CollectorWizardSequenceComparator());
            for (ICollectorWizardGenerator generator : wizardGeneratorSeq) {
                collectorWizardSequence.addAll(generator.getWizardSequence());
            }
            return collectorWizardSequence;
        }
        finally {
            locker.unlock();
        }
    }
}

---
<reference bind="addCollectorWizardGenerator" cardinality="0..n" 
       interface="dw.sample.dc.api.ICollectorWizardGenerator" 
	   name="ICollectorWizardGenerator" policy="dynamic" 
	   unbind="removeCollectorWizardGenerator"/>

宣言型サービスに導入されているコンポーネントを利用してサービスを公開および利用する方法を理解できたところで、今度はこのモデルでその他の OSGi コア・サービスを有効利用する方法を説明します。


構成管理サービスによるバンドル構成の管理

構成管理サービス (Configuration Admin Service) は、OSGi アプリケーションがローカルまたはリモートの構成データをサービス・レベルまたはバンドル・レベルで管理する共通の手段です。まずは最も一般的なケースとして、バンドル構成データをローカルで保存、取得、更新します。

まず、OSGi 構成管理サービスの Equinox 実装である、org.eclipse.equinox.cm_<バージョン>.jar が Eclipse の plugins フォルダー内にあるかどうかを調べてください。見つからない場合は、個別にダウンロードした Equinox SDK からこのファイルを取得して plugins フォルダーに配置した上で、org.eclipse.equinox.cm パッケージをターゲット・バンドルのマニフェストにインポートします。

構成管理サービスを使用するために最も重要な最初のステップは、ManagedService API を実装し、ターゲット・バンドルによってサービスとして公開することです。実装するメソッドは updated 以外にはありません。各 ManagedService は、サービスとともに登録された SERVICE_PID によってそれぞれ区別されるため、サービスの構成が変更されると、該当するサービスがトリガーされます。バンドル・レベルの構成を管理するには、ManagedService を提供する適切なオブジェクトがバンドルのアクティベーターになります (リスト 7 を参照)。

リスト 7. ManagedService を提供するバンドル・アクティベーター
public class Activator implements BundleActivator {
    private ServiceRegistration cmSvrReg;

    public void start(BundleContext context) throws Exception {
cmSvrReg = context.registerService(ManagedService.class.getName(),
                    this, initMgrServiceProp());
}
    public void stop(BundleContext context) throws Exception {
        cmSvrReg.unregister();
    }
    public void updated(Dictionary properties) throws ConfigurationException {
        if (cmSvrReg == null) {
            return;
        }
        if (properties == null) {
            cmSvrReg.setProperties(initMgrServiceProp());
        } else {
            cmSvrReg.setProperties(properties);
        }
    }
    public static Dictionary initMgrServiceProp() {
        Dictionary result = new Hashtable();
        // Suppose that the Activator class name is unique across bundles…
result.put(Constants.SERVICE_PID, Activator.class.getName());
        return result;
    }
}

続いてリスト 8 に示すように、OSGi フレームワークが公開する構成管理サービスをロードするコンポーネントを作成します。

リスト 8. ConfigManager コンポーネント
public class DCFrameworkConfigManager {
    private ConfigurationAdmin cm;
    ...
    protected void activate(ComponentContext context) {
        ...
        Configuration config = getFrameworkConfig();
try {
            writeLock.lock();
            if (config.getProperties() == null) {
                config.update(context.getProperties());
            }
        } finally {
            writeLock.unlock();
        }
    }
    public void setConfigAdmin(ConfigurationAdmin cm) {
        this.cm = cm;
    }
    public void unsetConfigAdmin(ConfigurationAdmin cm) {
        this.cm = null;
    }
    public Configuration getFrameworkConfig() throws IOException {
        try {
	readLock.lock();
            return cm.getConfiguration(Activator.class.getName());
        } finally {
            readLock.unlock();
        }
    }
    public void updateFrameworkConfig(Dictionary<String, String> properties)
            throws IOException {
        try {
            writeLock.lock();
            Configuration config = getFrameworkConfig();
            if (config != null) {
                config.update(properties);
            }
        } finally {
            writeLock.unlock();
        }
    }
}

---
<reference bind="setConfigAdmin" cardinality="1..1" 
    interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin" 
    policy="dynamic" unbind="unsetConfigAdmin"/>
<properties entry="OSGI-INF/initConfig.properties"/>

ConfigManager コンポーネントはアクティブにされると、プロパティー・ファイルからバンドルの構成を開始します。さらに、実行時に構成を読み取って保存するためにバンドル内のあらゆる場所で使用できるメソッドを提供します。複数の場所からメソッドが同時に呼び出された場合に対処するため、メソッドはロックで保護されます。


イベント管理サービスによるイベントの処理

多くのシステムでは、イベント処理モデルの実装が不可欠です。OSGi のサービス指向フレームワークは、イベント管理サービス (Event Admin Service) によって、イベント処理モデルの実装メカニズムを提供します。

イベント管理サービスを正しく利用するためには、以下の 3 つの点に注意してください。

  • イベント・パブリッシャーは、参照先 EventAdmin サービスを使用して Event オブジェクトを送信しなければなりません。
  • イベント・サブスクライバーは、受信したイベントを処理する handleEvent メソッドを実装することにより、 EventHandler サービスを提供します。
  • Event オブジェクトの event.topics プロパティーによって、イベントを受信できるイベント・ハンドラーが決まります。

OSGi イベント管理サービスは、従来のイベント処理プログラミング・モデルをより動的な方法で実装しながらも、このモデルに新しい概念は導入していません。この記事で紹介した他の OSGi サービスと同じく、イベント管理サービスを取得するための手段は、コンポーネント・オブジェクトです。宣言型サービスがサポートするコンポーネントは、サービス・プロバイダーにも、サービス・コンシューマーにもなります。そのため、コンポーネントをイベント・パブリッシャー兼イベント・ハンドラーにすることができます。

例えば、ユーザーがデータ収集プロセスの進行状況をアプリケーション UI で監視し、UI コントロールによって随時このプロセスを停止できることが要件となっているとします。データ・コレクター・バンドルの場合、この要件は技術的に言うと、イベントを双方向で送信して、アプリケーション・フレームワーク・バンドル側とデータ・コレクター・バンドル側の両方で処理できるようにしなければならないことを意味します (リスト 9 を参照)。

データ・コレクター・バンドル側を例に取ると、この場合も同じく Eclipse の plugins フォルダーをチェックし、org.eclipse.equinox.event_<バージョン>.jar が配置されていない場合にはこのファイルをコピーし、パッケージをバンドルのマニフェストにインポートすることになります。

リスト 9. イベント・パブリッシャー兼ハンドラー・コンポーネント
public class ProfileDataCollectorImpl implements IDataCollector, EventHandler {
    public final static String EVENT_TOPIC =
                    "dw/sample/dc/event/collector/ProfileDataCollectorImpl/status";
    private EventAdmin eventAdmin;
    ...
    public int collectAndOutput() {        		
        while (...) {
            if (isCancelled()) {
                cancelProcess(false);
                return -1;
            }
            ...
            publishEvent(GUIMsg.STATUS_AND_PROGRESS.flag(), 
                "Collecting profile data...", 0, true);
            ...
        }
        ...
    }
    private void publishEvent(int status, String statusMsg, int progress,
        boolean async) {
        Dictionary<String, String> props = new Hashtable<String, String>();
        props.put(FrameworkEventConsts.PARAM_STATUS, String.valueOf(status));
        props.put(FrameworkEventConsts.PARAM_STATUS_MSG, statusMsg);
        props.put(FrameworkEventConsts.PARAM_PROGRESS, String.valueOf(progress));
        if (!async) {
            eventAdmin.sendEvent(new Event(EVENT_TOPIC, props));
        } else {
            eventAdmin.postEvent(new Event(EVENT_TOPIC, props));
        }
    }
    public void handleEvent(Event event) {
        String action = (String) event
                .getProperty(FrameworkEventConsts.PARAM_ACTION);
        if (FrameworkEventConsts.VAL_CANCEL.equalsIgnoreCase(action)) {
            cancelProcess(true);
        }
    }
    ...
}

---
<property name="event.topics" type="String" 
    value="dw/sample/dc/event/framework/UserCmdEventPublisher/uicmd"/>
<service>
   <provide interface="org.osgi.service.event.EventHandler"/>
   <provide interface="dw.sample.dc.api.IDataCollector"/>
</service>
<reference bind="setEventAdmin" cardinality="1..1" 
    interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="dynamic" 
    unbind="unsetEventAdmin"/>

上記のサンプル・コードでは、event.topics プロパティー値のフォーマットに注意してください。このコードからは、イベント管理サービスでは開発者が Event オブジェクトで送信するデータを定義できることも見て取れます。


アプリケーション管理サービスによる起動モデルの指定

これまで、OSGi アプリケーションを開発する際の主要な側面を取り上げて、サンプル・アプリケーションを実行可能な状態にしました。図 1 に示されているように、ネイティブ OSGi アプリケーションは、OSGi コンテナーをベースにして実行されるバンドルで構成されます。デフォルトでは、OSGi アプリケーションの起動時には OSGi コンソールが表示されます。サーバー側で実行するアプリケーションの場合には、これで問題はありませんが、アプリケーションがクライアント・アプリケーションとして配布されるとしたら、問題が持ち上がってきます。それは、モジュール性と動的な性質を内部に維持しつつ、最近のアプリケーションと同じく「プロフェッショナル」な方法で起動できるようにアプリケーションをパッケージ化するにはどうすれば良いかという問題です。

そこで紹介するのが、OSGi アプリケーション管理サービスをベースに Equinox アプリケーション・モデルを実装するという単純な方法です。

最初のステップでは、アプリケーション・フレームワーク・バンドルのマニフェストに org.eclipse.equinox.app バンドルを Require-bundle として追加し、IApplicationインターフェースを実装するクラスを作成します (リスト 10 を参照)。

リスト 10. IApplication インターフェースの実装
public class GUIApplication implements IApplication {    
    public Object start(IApplicationContext context) {
        // Invoke the entry GUI object here
        ...
        return IApplication.EXIT_OK;        
    }
    public void stop() {
        // Add the logic when the application quits.
    }
}

次に、アプリケーション・フレームワーク・バンドルを Eclipse アプリケーションとして定義します。それには、その拡張をplugin.xml で宣言します (リスト 11 を参照)。

リスト 11. plugin.xml
<plugin>
    <extension id="GUIApp"
         point="org.eclipse.core.runtime.applications">
      <application cardinality="1">
         <run
               class="dw.sample.dc.launcher.GUIApplication">
         </run>
      </application>
   </extension>      
</plugin>

アプリケーションのカーディナリティーで、アプリケーションが一度に使用できるインスタンス数を 1 つに指定します。このバンドルは拡張を宣言しているため、Bundle-Symbolic マニフェスト・ヘッダーに、以下のようなシングルトン・ディレクティブを追加する必要があります。

Bundle-SymbolicName: dw.sample.dc;singleton:=true

今度は、Eclipse IDE で OSGi アプリケーションを起動するための構成を新規に作成します (図 5 を参照)。

図 5. 新規構成の作成
新規構成に追加する引数を示すスクリーン・ショット

Bundles (バンドル)」タブで「Add Required Bundles (必須バンドルの追加)」をクリックして、すべての必須依存関係をプラットフォーム・バンドルとして含めるようにします。「Arguments (引数)」タブでは、アプリケーション・モデルを使用可能にするために、2 つの新しい引数を追加します。-application 引数の値は、<バンドル・シンボリック名>.<拡張 ID> の形にしなければならないことに注意してください。

Eclipse でアプリケーションを実行して、すべての機能が正常に動作することを確認できたら、OSGi アプリケーションを構築するための開発作業は完了したことになります。その後の作業については、この記事では説明しませんが、各バンドル・プロジェクトを JAR ファイルにエクスポートし、実際の環境にデプロイして結合試験を実行することになります。


まとめ

この記事では、OSGi コア・サービスを利用してモジュール式アプリケーションを Eclipse で開発し、起動する手順を詳しく説明しました。記事に記載したサンプル・コードから明らかなように、宣言型サービスに伴うサービス指向コンポーネント・モデルは、開発者が宣言型サービス以外の OSGi コア・サービスを有効に利用して、モジュール式アプリケーションの原動力を強化する上で重要な役割を果たします。

参考文献

学ぶために

製品や技術を入手するために

  • Eclipse Marketplace は、オープンソースおよび商用の Eclipse 関連のオファリングを検索できる便利なポータルです。
  • Eclipse および Equinox SDK: 最新リリースをダウンロードしてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Information Management
ArticleID=775851
ArticleTitle=Eclipse で軽量の OSGi アプリケーションを構築する
publish-date=11252011