2011年11月13日

OSGI入門 その3 〜Serviceの動的アップデート〜


さて、前回はupdateが上手くいかないところでしたね。まずはOSGIの仕様はBundleのupdateについて
どのように規定しているのでしょうか。仕様を見てみます。
※ちなみに、仕様のドキュメントはここからダウンロードできます。

The update process supports migration from one version of a bundle to a newer version of the same
bundle. The exports of an updated bundle must be immediately available to the Framework. If none
of the old exports are used, then the old exports must be removed. Otherwise, all old exports must
remain available for existing bundles and future resolves until the bundle is refreshed, see Refreshing
on page 148, or the Framework is restarted.

(訳)
updateプロセスはある同一バンドルに関して、古いバージョンから新しいバージョンへの移行をサポート
します。updateされるバンドルがexportしているサービスは直ちにフレームワーク内で有効化されなければなりません。
もし、古いexportがすべて使われなくなったなら、それは取り除かれなければなりません。それ以外の場合、すべての古い
exportはそのバンドルがrefreshされるか、フレームワークが再起動されるまで、既存バンドルおよび以降にインストール
されるバンドルから使用可能な状態に残しておかなければなりません。

なるほど、つまり変更を完全に有効にするにはOSGIフレームワークを再起動するか、「refresh」をかけろ
ということですね。もちろん、フレームワークの再起動をせずにやりたいので、refreshコマンドを使ってみましょう。

・refreshコマンドを使おう!

前回のプログラムをそのまま使います。適当な文字列でMyServiceImpleを修正してからrefreshをかけます。
すると・・・

osgi>refresh 2

osgi> ServiceConsumerBundle stop
ServiceBundle stop
ServiceBundle start
Service registed
ServiceConsumerBundle start
This is my service! version 2!
This is my service! version 2!
This is my service! version 2!

でけました。でもその前に標準出力をよく見てみると、ConsumerBundleも再起動されていることに気づきます。
つまり・・・

refresh→ConsumerBundle停止→ServiceBundle再起動→ConsumerBundle起動

となるわけです。要するに、refreshコマンドを打つと、対象バンドルがexportしているパッケージに依存しているバンドル
を停止し、対象バンドルを再起動してから、依存バンドルを起動するということのようですね。

うーん。なんか嫌ですね。できることなら、updateしたバンドルだけ再起動して終わりたいものです。
上記の仕様を見るかぎりだと駄目そうなんですが、実はこれやり方があります。といっても大したことではなく
バンドルの構成を変更するだけです。どうするかというと「IFと実装を別バンドルにする」これだけです。

・IFと実装を別バンドルに!

ではさっそくやってみましょう。といってもただIFと実装を分けるだけです。以下のバンドルを作成します。

■IFだけのバンドル
ServiceIFBundle

■実装バンドル
ServiceBundle2

■消費バンドル
ServieConsumer2

中身は前回と全く一緒。ただIFをMyServiceIFに移しただけです。もちろんIFがServiceIFBundleに移ったので
exportしているのはServiceIFBundleで残りの二つはそれをimportすることになります。

この3バンドルを構成して実行しましょう。

osgi> ServiceBundle start
Service registed
ServiceConsumerBundle start
This is my service!
This is my service!

ここまでは一緒ですね。ではMyServiceImplを適当に修正して、updateしてみましょう。

osgi> update 3
ServiceBundle stop
ServiceBundle start
Service registed
This is my service! update version
This is my service! update version
This is my service! update version

おお!ちゃんと新しいの取れてますね。素晴らしいです。

・IFバンドルと実装を別にすることについての補足

私が見る限り、OSGIの仕様として「IFと実装のバンドルはわけなさい」と規定しているわけではなさそうです。
ただ、ネットを調べてみると、この話題は議論されています。

Different osgi bundles with implementations of the same interface - where to place that inferface?

ここでの回答もやはり「分けた方がいい」とあります。理由は実装を後で置き換えられるということと、それから
対象バンドルを消費バンドルの再起動なしにhot replaceできるという理由が挙げられています。後者はまさに
今われわれが実験したことですね。

やはり、イディオムとしてはIFバンドルと実装バンドル分けておいたほうがいいんだと思われます。また、他の
利点としてはA実装バンドル⇔B実装バンドルというようにお互いのサービスを呼び合うような場合、IFと実装が
同居していると相互参照でエラーになっちゃいます。分けておけば、たすきがけのように呼び合えますね。

さて、次はServiceTrackerあたりをやろうかと思います。たぶん。

では。
posted by sandman at 16:07| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする

OSGI入門 その2 〜Serviceについて〜


前回は導入として、Hello Worldをやりましたが、今回からはOSGIの肝であるサービスの話をしていきます。
前述の通り、OSGIはバンドルと呼ばれるプログラムコンポーネントの集合体を動かすためのフレームワークです。
Serviceとはそのひとつのバンドルが公開する一連の処理を意味します。Javaオブジェクトであればなんでも
サービスになりえます。しかし、実際にはまず公開するIFを作成して、サービスとして登録するのはそのIFの
実装クラスになるでしょう。

・サービスバンドルの作成
まずはサービスバンドルを作ります。前回と同様、プラグインプロジェクトにて以下のプロジェクトを作成します。

プロジェクト名:ServiceBundle
Activator:test.service1.activator.Activator

続いてIFの作成

package test.service1.serviceif;

/**
 * サービスinterface
 * @author rag-timer
 *
 */
public interface MyServiceIF {
	/**
	 * なんらかのサービス
	 */
	public void print();
}

お次に実装の作成

package test.service1.serviceimpl;

import test.service1.serviceif.MyServiceIF;

public class MyServiceImpl implements MyServiceIF {
	@Override
	public void print() {
		System.out.println("This is my service!");
	}
}

なんの意味もないサービスですが、とりあえずこれで登録するネタはそろいました。では、Activatorで登録します。
サービスの登録にはBundleContext#registerService()を使用します。

/*
 * (non-Javadoc)
 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
 */
public void start(BundleContext bundleContext) throws Exception {
	Activator.context = bundleContext;

	System.out.println("ServiceBundle start");

	//サービスの実体
	MyServiceIF service = new MyServiceImpl();

	//サービスの登録を行う
	context.registerService(MyServiceIF.class.getName(), service, null);

	System.out.println("Service registed");
}

registerService()の一つ目の引数はサービスのこのOSGIフレームワーク内で識別するための文字列です。
別に文字列であればなんでもかまいませんが、普通IFの名前を使用します。二つ目の引き数にはサービス
の実体を指定します。三つ目はこのサービスのプロパティを指定しますが今回は使いません。

続いて、このサービスを「公開する」ための作業を行います。バンドル間でのサービス呼び出しは一般の
Javaオブジェクトの呼び出しと大して変わりませんが、別バンドルが公開しているIFをそのままimportする
ことはできません。かならず「サービス公開者がexportし」「サービス消費者がimportする」という作業が
必要になります。この設定を行うのがMETA-INF/MANIFEST.MFです。なお公開はパッケージ単位で行います。

ではサービスの公開を行いましょう。MANIFEST.MFをダブルクリックし、[ランタイム」タブを選択して下さい。
左側の[エクスポートされるパッケージ]欄で追加を押下し、IFのパッケージを指定します。

1.png
これでサービス側のバンドルの準備は完了しました。

・サービス消費バンドルを作成

次にクライアントとなるサービス消費バンドルを作成します。

プロジェクト名:ServiceConsumerBundle
Activator:test.serviceconsumer1.activator.Activator

さて、まずはimportを行いましょう。これを行わないと、サービスIFが参照できません。
MANIFEST.MFをダブルクリックして、[依存関係]タブから[インポート済みパッケージ]を選んでサービスIFの
パッケージを選択します。なお、当然、exportをこの段階で行っていなければリストに出てきません。

2.png
続いて以下のようにActivatorを実装します。

package test.serviceconsumer1.activator;

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

import test.service1.serviceif.MyServiceIF;

public class Activator implements BundleActivator {

	private static BundleContext context;

	private boolean flg = true;

	private final Thread myThread = new Thread(){
		public void run(){

			while(flg){

				//ServeiceReferenceの取得
				ServiceReference ref = Activator.context.getServiceReference(MyServiceIF.class.getName());

				//サービスの生成
				MyServiceIF service = (MyServiceIF)Activator.context.getService(ref);

				//サービスの呼び出し
				service.print();

				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					break;
				}

			}
		}
	};

	static BundleContext getContext() {
		return context;
	}

	public void start(BundleContext bundleContext) throws Exception {

		System.out.println("ServiceConsumerBundle start");

		Activator.context = bundleContext;

		//スレッドのスタート
		myThread.start();
	}

	public void stop(BundleContext bundleContext) throws Exception {

		System.out.println("ServiceConsumerBundle stop");

		flg = false;
		Activator.context = null;
	}

}


Activator#startでは、定期的にサービスを使用するスレッドを起動してます。そのスレッドのrun()の中で
BundleContext#getServiceReference()でServiceReferenceオブジェクトを作成します。引数で渡すのは
先ほどサービスの登録に使用したサービス識別用の文字列、すなわちサービスのクラス名です。
作成したServiceReferenceを引数に、BundleContext#getService()でサービスが取得できます。
とりあえずはさらっと流します。

これで実行構成からこの2つのバンドルを構成し、実行すると・・・

osgi> ServicConsumerBundle start
ServiceBundle start
Service registed
This is my service!
This is my service!
・・・

てな感じで表示されるはずです。ちゃんとサービス呼べてますね。

・でもサービスをupdateすると・・・

では前回のようにサービスを書き換えてみましょう。適当にprintlnの文字列を変更して保存し、ssで
IDを確認後

update サービスバンドルのid

を実行します。すると・・・

osgi> Exception in thread "Thread-2" java.lang.NullPointerException: A null service reference is not allowed.
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.getService(BundleContextImpl.java:660)
at test.serviceconsumer1.activator.Activator$1.run(Activator.java:22)

こんなんなっちゃいました。update後、スレッド内の処理でServiceReferenceが取れていないようです。
この解決策は次回にします。

では。
posted by sandman at 00:07| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする

2011年11月06日

OSGI入門 その1 〜Hello OSGI World〜


・OSGIとは何なのか?
OSGIとはOpen Services Gateway initiativeの略であり、もともとはサービスゲートウェイの仕様を
策定する目的で作られたものでした。サービスゲートウェイとして想定されるのはホームサービス
ゲートウェイによるネットワーク家電の制御プラットフォームなどが挙げられますが、実際はそれ以外
にも広く応用されている技術です。Javaプログラムを組む人ならば、まず間違いなくEclipseを使用した
経験があるでしょうが、このEclipse IDEもOSGIプラットフォームを採用しています。

OSGIはJavaベースのプラットフォームであり、その主な特徴として、JavaVMを上げ下げすることなしに
「bundle」と呼ばれるプログラムコンポーネントを起動/停止/インストール/アンインストールすること
ができます。Eclise IDEで言うところのbundle(バンドル)とはpluginのことでです。我々はEclipseの
「新規ソフトウェアのインストール」からネットワークを通じて自由にpluginをインストールできますが
それを可能にしているのがこのOSGIということになります。

・まずはバンドルを作ってみよう!
と、まあ堅苦しい話はこれくらいにして、実際に手を動かしてみましょー。
まずは恒例のHello Worldから。あ、すでにEclipseは入っている前提で話します。持ってないひとはインスコしてね。
私はEclipse3.6使ってます。

Eclipseにはバンドルを作成するための仕組みが標準で存在します。それが「プラグイン・プロジェクト」です。
パッケージエクスプローラから[新規]-[その他]-[プラグイン・プロジェクト]でプロジェクトを作成します。
プロジェクト名:HelloOSGIBundle
実行ターゲット:Equinox
で、次へをクリック、Activatorを生成のところで、パッケージ名やクラス名を好きに設定する。(別にdefaultで
もいいですけど)

これでとりあえず空のバンドルが作成できました。プロジェクトの中を見ていくと、まず先ほど作成した
Activatorがあるはずです。このActivatorはバンドルが起動/停止された際に実行する処理を記述するクラスです。
すなわち
Activator#stop()
Activator#start()
ですね。

ではまず、ここにそれぞれこんな感じで実装しましょう。



	public void start(BundleContext bundleContext) throws Exception {
		Activator.context = bundleContext;

		System.out.println("Hello OSGI World!!!");
	}


	public void stop(BundleContext bundleContext) throws Exception {
		Activator.context = null;

		System.out.println("Good Bye OSGI World!!!");
	}


これで準備完了ですが、ひとつ重要なことがあります、プロジェクトの中にMETA-INF/MANIFEST.MFというファイル
がありますね?このファイルはこのバンドル自体の情報やバンドル間の依存関係など様々なことを規定するものです。
今後非常に重要になってくるファイルです。ダブルクリックすると、そのファイルの内容だけでなく、ファイルに設定
するためのGUIも用意されていることに気づくはずです。便利ですね。ただ、いまはまだ使いません。

・さっそく実行!
実行は[実行]-[実行構成]から[OSGI フレームワーク]で右クリックし、[新規構成]を選択します。
すると、なんだかごちゃごちゃとした画面が出てきますが、まず[バンドル]欄に注目してください。以下の二つのグループが
あると思います。
・ワークスペース・・・あなたのEclipseワークスペース内のバンドル群です。今回はHelloOSGIBundleですね。
・ターゲット・プラットフォーム・・・OSGIフレームワークである「Equinox」が提供するバンドル群です。
今回は作成したHelloOSGIBundleとosgi本体であるorg.eclipse.osgiしか要りませんので以下のように構成します。
ちなみに、右側にある必須バンドルの追加ボタンやOnly show selectedを使うと便利ですよ。

1.png

したらば、実行ボタンをおします。
以下のように表示されたでしょうか?

osgi> Hello OSGI World!!!

これでまずは実行できました。でもこれじゃpublic static void main(String args[])でHello Worldしたのと変わりま
せんね。なのでosgiっぽいことしてみましょう。

・状態表示してみる
結果が表示されているコンソールにカーソルをあわせ、Enterキーを押してみてください。すると
osgi>
という文字列が現れると思います。その状態で「ss」と入力し、Enterキーを押してください。
こんなんが現れたと思います。

Framework is launched.

id State Bundle
0 ACTIVE org.eclipse.osgi_3.6.2.R36x_v20110210
1 ACTIVE HelloOSGIBundle_1.0.0.qualifier


すでにお気づきでしょうが、この「ss」は現在のOSGIプラットフォームにインストールされているバンドルの一覧表示
コマンドです。このコマンド自体はOSGIフレームワークによって異なります。equinoxではssですね。

idというのはこのOSGIフレームワーク上で1つのバンドルに一意でふられるIDです。操作もこのIDを通じて行います。
Stateはバンドルの状態を意味します。Activeは稼動中ってことですね。

・バンドルを停止してみる
んじゃ、バンドルを止めてみましょう。以下のコマンドを打ってください。

osgi>>stop 1


すると、こんな感じで表示されるはずです。

Good Bye OSGI World!!!


先ほど実装したActivator#stop()が呼ばれたわけですね。ただしあくまでも、バンドルが停止しただけですので、
JavaVMは落ちていません。つまりこれがOSGIの特徴なわけです。

・バンドルを更新してみる
では、こんどはバンドル実装を修正して、更新をかけてみましょう。さきほどのSystem.out.printlnに
適当な文字列を追加します。修正が完了して、コンパイルが通ったら以下を入力。
osgi> update 1


んで、そのままstartコマンドを打つと・・・

osgi> start 1
update - Hello OSGI World!!!


「update - 」の文字列が追加されてますね!!しつこいですがJAVAVMはあがりっぱなしです。
※今回は停止している前提で書きますが、もし、updateをかけたバンドルが起動していた場合、
updateの際、自動的に停止/起動されます。

次回からはしばらく、OSGIの肝であるサービスのお話になります。
ではまた。
posted by sandman at 18:07| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。