ラベル OSGi の投稿を表示しています。 すべての投稿を表示
ラベル OSGi の投稿を表示しています。 すべての投稿を表示

2011年11月20日日曜日

OSGi で GoF の Strategy

久しぶりに OSGi でもいじってみるかと思い立つ。

Wikipedia に載ってる Strategy パターンの サンプル・コードでも OSGi で動かしてみよっかなと。今回は、Equinox でやってみる。

OSGi 環境を作る

  • 適当な場所に osgi-test フォルダを作る
  • osgi-test フォルダ 下に plugins フォルダ を作る
  • Eclipse の plugins下から以下のファイルを osgi-test/plugins フォルダ 下にコピーする。
    • org.eclipse.osgi_*.jar
    • org.eclipse.equinox.ds_*.jar
    • org.eclipse.equinox.util_*.jar
    • org.eclipse.osgi.services_*.jar
うちの環境だとこんな感じになる
$ cd /tmp/osgi-test
$ tree
.
└── plugins
    ├── org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
    ├── org.eclipse.equinox.util_1.0.300.v20110502.jar
    ├── org.eclipse.osgi.services_3.3.0.v20110513.jar
    └── org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar

起動して、他のプラグインも追加しておく

$ java -jar plugins/org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console

osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106

osgi> install file:///tmp/osgi-test/plugins/org.eclipse.osgi.services_3.3.0.v20110513.jar
Bundle id is 1
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.util_1.0.300.v20110502.jar
Bundle id is 2
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
Bundle id is 3

osgi> start 1
osgi> start 2
osgi> start 3
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701

環境が出来たので、以降ではプラグインを作って動かしてみる。先に書いたとおり、Wikipedia にある Strategy のサンプルコードをネタにして、Strategy、ConcreteStrategyAdd、ConcreteStrategyMultiply、Context を連動させてみる。

Strategy の作成〜インストール

まずは、Strategy プラグインプロジェクトの作成

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.strategy"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Runtime]タブを開いて、[Exported Packages]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下の Wikipedia から持ってきた、以下のコードを追加する
    package ex1.osgi.strategy.strategy;
    public interface Strategy {
       int execute(int a, int b);
    }
できたら以下のように Export しておく。
  • プロジェクトのコンテキストメニューから、[Export]->[Deployable plug-ins and fragments] を選択
  • [Destination] / [Directory]に、osgi-test フォルダを指定する
  • [Options] で、"Package plug-ins as individual JAR archives"のみチェックされている事を確認して[Finish]
エクスポートしたら、OSGi に install しておく
osgi> install file:///tmp/osgi-test/plugins/ex1.osgi.strategy.strategy_1.0.0.201111202011.jar
Bundle id is 4
osgi> refresh
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 RESOLVED    ex1.osgi.strategy.strategy_1.0.0.201111202011
ss や refresh といったコマンドの意味は、OSGi コンソールから help と打てば、説明が出てくる。

ConcreteStrategyAdd の作成〜インストール

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.add"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Required Plugins]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下のようにコード追加する。
    package ex1.osgi.strategy.add;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class ConcreteStrategyAdd implements Strategy {
        public int execute(int a, int b) {
            System.out.println("Called ConcreteStrategyAdd's execute()");
            return a + b;  // Do an addition with a and b
        }
    }
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information" / Class に ex1.osgi.strategy.add.ConcreteStrategyAdd を指定して[Finish]
  • component.xml の Service タブで、[Provided Services]に ex1.osgi.strategy.strategy.Strategy を追加する
これも Export して、OSGi にインストールしておく。

Context の作成〜インストール

次は ConcreteStrategyMultiply をやる前に、Context に行ってみる

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.context"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Imported Packages]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下のコードを追加する。Wikipedia のサンプルコードでは、Context#executeStrategy() を実行するのは StrategyExample#main() だけど、面倒だから strategy が関連付けられたときに Context 自身が executeStrategy() のテストコードを実行するようにした。
    package ex1.osgi.strategy.context;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class Context {
       private Strategy strategy;
     
       public synchronized void setStrategy(final Strategy strategy) {
          this.strategy = strategy;
          testExecuteStrategy();
       }
       public synchronized void unsetStrategy(final Strategy strategy) {
          if (strategy == this.strategy) this.strategy = null;
       }
       public int executeStrategy(int a, int b) {
          return strategy.execute(a, b);
       }
       public void testExecuteStrategy() {
          final long l = System.currentTimeMillis();
          final int a = (int) (l % 10);
          final int b = (int) (l / 10 % 10);
          final int result = executeStrategy(a, b);
          System.out.printf("execute(%d, %d) = %d", a, b, result);
       }
    }
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information"/Class にex1.osgi.strategy.context.Context を指定して[Finish]
  • component.xml の [Service]タブで、[Referenced Services] に Strategy を追加する
  • 追加した Strategy を選択して[Edit]、"Bind"にsetStrategy、"Unbind"にunsetStrategy を指定する
できたら、これもエクスポートして、OSGiにインストールしておく。


動作確認

とりあえず、ここまでで動かしてみる。

osgi> start 4
osgi> start 5
osgi> start 6
Called ConcreteStrategyAdd's execute()
execute(3, 1) = 4
osgi> 
期待通りに動作する。(実は、start 4 は無くても良いし、start 5 と start 6 の順番は逆でも良い。)

ConcreteStrategyMultiply の追加と動作確認

最後に、ConcreteStrategyMultiply を付け加えて、ConcreteStrategyMultiply と差し替えてみる。

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.multiply"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Required Plugins]に"ex1.osgi.strategy.strategy" を追加する。
  • 下のようにコード追加
    package ex1.osgi.strategy.multiply;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class ConcreteStrategyMultiply implements Strategy {
        public int execute(int a, int b) {
            System.out.println("Called ConcreteStrategyMultiply's execute()");
            return a * b;   // Do a multiplication with a and b
        }
    }
    
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information"/Class に net.yasuabe.osgi.ex1.strategy.multiply.ConcreteStrategyMultiplyを指定して[Finish]
  • component.xml の Service タブで、[Provided Services]に ex1.osgi.strategy.strategy.Strategy を追加する
これもエクスポートして、OSGiにインストールしておく。

エクスポートしたら、Context から実行される Strategy を、Add から Multiply に変えてみる。

osgi> ss
Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE      ex1.osgi.strategy.strategy_1.0.0.201111202011
5 ACTIVE      ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE      ex1.osgi.strategy.context_1.0.0.201111202042
7 RESOLVED    ex1.osgi.strategy.multiply_1.0.0.201111202123

osgi> stop 5
osgi> start 7
Called ConcreteStrategyMultiply's execute()
execute(3, 1) = 3
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE      ex1.osgi.strategy.strategy_1.0.0.201111202011
5 RESOLVED    ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE      ex1.osgi.strategy.context_1.0.0.201111202042
7 ACTIVE      ex1.osgi.strategy.multiply_1.0.0.201111202123
osgi> stop 7
osgi> start 5
Called ConcreteStrategyAdd's execute()
execute(4, 3) = 7
osgi> 
Context を起動したまま、Strategy を差し替えられるのが確認できる。

雑感

今まで、何回か、いわゆるパッケージ開発案件に関わってきたけど、納品先個別の要件の扱い方について、何パターンかの手法がある。

一番泥臭いのだと、トランクのソースコードに個別要件対応のコードを入れてしまって、条件文で切り分けるというベタベタのローテク手法がある。「バカじゃね?」って思う人も多いかもしれないけど、実際の現場では結構多い。

それよりは、少しはマシな手法だと、納品先別にブランチを切って個別要件はそこに入れていくという事になる。まあ、これも知的な方法とは言いにくいけど、割と普通に行われている。

もっと良い方法としては、パッケージ共通部分と個別にカスタマイズ可能な部分について最初に見通しを立てた上で、カスタマイズ可能な部分がプラガブルになるようにアーキテクチャを設計する手法がある。

この手法をとると、機能の差し替えや追加、また管理面においてもリスクが少なくて、柔軟なソフトウェアになるんだけど、この場合にもさらに、プラグインの方式を自分で作るか、既存の仕様や製品を使うかで道が分かれる。

必ずしも自作にこだわる必要がないとしたら、OSGi がかなりの有力候補なのではないかと思う。(個人的には、はっきり言って、かなり高い割合で自作の道を取るのは不正解だと思うけど。)

2009年11月2日月曜日

Apache Felix + Spring DM

Apache Felix + Spring DM の Getting Started。

前にApache Felix について書いたポストと同じように、名前を受け取って挨拶を返すサービスと、それを標準出力に表示するクライアントを作ってみる。ただし、Spring DM も併用するので、OSGi 定義のクラスの派生ではなく POJO でサービスを記述するという事になる。こういった、ちょっと新しいプロダクトの組み合わせは、最初期の段階でかなりハマる事がたまにあるので、この Getting Started で技術リスクを今のうちに減らしておきたい。

便宜上、下表のようなディレクトリを作業場所とした。

c:\work2\ルート作業ディレクトリ
  ├ client\クライアント用バンドル作業ディレクトリ
  ├ service\サービス用バンドル作業ディレクトリ
  └ felix\実験用 Felix 環境

■ 用意

  • felix を落としてどっかに展開しておく(以降 {felix base}と書く)
  • spring-osgi-1.2.0-with-dependencies.zipを落として、どっかに展開する。あとでspring-osgi-1.2.0の中身を使う。
  • jcl104-over-slf4j-1.4.3.jarをダウンロードしておく
※どれもググればすぐに在り処がわかる

■ 環境作り

解凍直後の もともとの felix の構成がゴチャゴチャすると嫌なので、c:\work2\felix\を実験用 Felix 環境とする。以下は c:\work2\felix\ 下での作業。必要なものは適宜 {felix base} からオリジナルをコピーする事にする。

  • {felix base}/bin/ から、felix.jar を持ってくる。
  • {felix base}/から以bundle/とconf/を持ってくる
  • spring-dm フォルダを作って以下のものを置く。
    • spring-osgi-1.2.0/lib 下から、以下をコピーする
      • com.springsource.org.aopalliance-1.0.0.jar
      • com.springsource.slf4j.api-1.5.0.jar
      • com.springsource.slf4j.log4j-1.5.0.jar
      • com.springsource.slf4j.org.apache.commons.logging-1.5.0.jar
      • log4j.osgi-1.2.15-SNAPSHOT.jar
      • org.apache.felix.main-1.4.1.jar
      • org.springframework.aop-2.5.6.A.jar
      • org.springframework.beans-2.5.6.A.jar
      • org.springframework.context-2.5.6.A.jar
      • org.springframework.core-2.5.6.A.jar
    • ダウンロードしておいた jcl104-over-slf4j を入れる
    • spring-osgi-1.2.0/dist 下から、以下をコピーする
      • spring-osgi-core-1.2.0.jar
      • spring-osgi-extender-1.2.0.jar
      • spring-osgi-io-1.2.0.jar
  • conf/config.properties のファイル末尾に以下を追加
    felix.auto.start.1=\
     file:bundle/org.apache.felix.shell-1.4.1.jar \
     file:bundle/org.apache.felix.shell.tui-1.4.1.jar \
     file:bundle/org.apache.felix.bundlerepository-1.4.2.jar \
     file:spring-dm/com.springsource.org.aopalliance-1.0.0.jar \
     file:spring-dm/jcl104-over-slf4j-1.4.3.jar \
     file:spring-dm/com.springsource.slf4j.org.apache.commons.logging-1.5.0.jar \
     file:spring-dm/log4j.osgi-1.2.15-SNAPSHOT.jar \
     file:spring-dm/org.apache.felix.main-1.4.1.jar \
     file:spring-dm/com.springsource.slf4j.api-1.5.0.jar \
     file:spring-dm/com.springsource.slf4j.log4j-1.5.0.jar \
     file:spring-dm/org.springframework.aop-2.5.6.A.jar \
     file:spring-dm/org.springframework.beans-2.5.6.A.jar \
     file:spring-dm/org.springframework.context-2.5.6.A.jar \
     file:spring-dm/org.springframework.core-2.5.6.A.jar \
     file:spring-dm/spring-osgi-core-1.2.0.jar \
     file:spring-dm/spring-osgi-extender-1.2.0.jar \
     file:spring-dm/spring-osgi-io-1.2.0.jar
    
  • log4j.properties を適当に書く(例えば以下のように)
    log4j.rootCategory=INFO, LOGFILE
    
    log4j.appender.LOGFILE=org.apache.log4j.FileAppender
    log4j.appender.LOGFILE.File=felix.log
    log4j.appender.LOGFILE.Append=true
    log4j.appender.LOGFILE.Threshold=INFO
    log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.LOGFILE.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
  • 立ち上げて確認してみる。
    C:\work2\felix>java -Dlog4j.configuration=file:log4j.properties -Dfelix.config.properties=file:config.properties -jar felix.jar
    
    Welcome to Felix
    ================
    
    Auto-properties start: org.osgi.framework.BundleException: Fragment bundles can not be started.
    -> ps
    START LEVEL 1
       ID   State         Level  Name
    [   0] [Active     ] [    0] System Bundle (2.0.1)
    [   1] [Active     ] [    1] Apache Felix Bundle Repository (1.4.2)
    [   2] [Active     ] [    1] Apache Felix Shell Service (1.4.1)
    [   3] [Active     ] [    1] Apache Felix Shell TUI (1.4.1)
    [   4] [Active     ] [    1] AOP Alliance API (1.0.0)
    [   5] [Active     ] [    1] jcl104-over-slf4j (1.4.3)
    [   6] [Active     ] [    1] SLF4J Jakarta Commons Logging Over SLF4J Binding (1.5.0)
    [   7] [Active     ] [    1] log4j.osgi (1.2.15.SNAPSHOT)
    [   8] [Active     ] [    1] Apache Felix (1.4.1)
    [   9] [Active     ] [    1] SLF4J API (1.5.0)
    [  10] [Resolved   ] [    1] SLF4J Log4J Binding (1.5.0)
    [  11] [Active     ] [    1] Spring AOP (2.5.6.A)
    [  12] [Active     ] [    1] Spring Beans (2.5.6.A)
    [  13] [Active     ] [    1] Spring Context (2.5.6.A)
    [  14] [Active     ] [    1] Spring Core (2.5.6.A)
    [  15] [Active     ] [    1] spring-osgi-core (1.2.0)
    [  16] [Active     ] [    1] spring-osgi-extender (1.2.0)
    [  17] [Active     ] [    1] spring-osgi-io (1.2.0)
    ->
    
※BundleException は、多分、SLF4J Log4J Binding 関連だと思うけど、本筋に関係なさそうなので、差し当たり放置。 と、実験用の Felix 環境はこんな感じ。

■ バンドル作り

前と同じように、Greeting Service と Greeting Service Client を連携させるが、ただし Clientはもっとシンプルに、固定文字列"Client"を Greeting Serviceに渡して結果を得るだけにする。つまり start コマンドでクライアントバンドルを実行すると、Felixのコンソールに固定文字列 "Hello Client"が表示されるのがゴール。

◆ Greeting Service

※以下、c:\work2\service\下の作業 ・以下のような2つのソースを書いて、適切なファイル名で適切なディレクトリに置く

package greeting.service;

public interface GreetingService {
 public String greet(String name);
}
package greeting.service.impl;

import greeting.service.GreetingService;

public class GreetingServiceImpl implements GreetingService {
   public void start() {
      System.out.println("Greeting Service registered");
   }
   public String greet(String name) {
     return "Hello " + name;
   }
}
・コンパイルする。classes ディレクトリを作ってそこに入れる。
$ javac -d classes greeting/service/*.java greeting/service/impl/*.java
・bean 定義ファイルを書いて、META-INF/spring 下に置く greetingservice.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean name="greetingService" 
    class="greeting.service.impl.GreetingServiceImpl" init-method="start"/>
</beans>
greetingservice-osgi.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  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">

  <osgi:service id="greetingOSGiService" ref="greetingService"
    interface="greeting.service.GreetingService">
  </osgi:service>
</beans>
・以下のようなmanifest.mf を書く
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Greeting Service
Bundle-SymbolicName: greetingservice
Bundle-Version: 1.0.0
Import-Package: org.springframework.beans.factory.xml,
 org.springframework.aop,
 org.springframework.aop.framework,
 org.aopalliance.aop,
 org.xml.sax,
 org.osgi.framework,
 org.springframework.osgi.service.importer.support,
 org.springframework.beans.propertyeditors,
 org.springframework.osgi.service.exporter.support,
 org.springframework.osgi.service.exporter
Export-Package: greeting.service
・jar にする
$ jar cvfm service.jar manifest.mf META-INF -C classes greeting
・念のため jar tvf service.jar で、怪しいところがないか見ておく

◆ Greeting Service Client

※以下、c:\work2\client\下の作業 ・以下のようなソースを書いて、適切なファイル名で適切なディレクトリに置く

package greeting.client;

import greeting.service.GreetingService;

public class GreetingClient {
   private GreetingService greetingService;

   public void setGreetingService(GreetingService greetingService) {
      this.greetingService = greetingService;
   }
   public void removeService() {
      System.out.println("service removed");
      this.greetingService = null;
   }
   public void start() {
      System.out.println(greetingService.greet("Client"));
   }
}
・サービス側と同様に、spring 定義ファイルと、manifest.mf を書く。 greetingclient.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
                      http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean name="greetingClient"
        class="greeting.client.GreetingClient" init-method="start">
      <property name="greetingService" ref="greetingService"/>
  </bean>
</beans>
greetingclient-osgi.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  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">
    <osgi:reference id="greetingService" interface="greeting.service.GreetingService"/>
</beans>
manifest.mf
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Greeting Service Client
Bundle-SymbolicName: greetingclient
Bundle-Version: 1.0.0
Import-Package: org.springframework.beans.factory.xml,
 org.springframework.aop,
 org.springframework.aop.framework,
 org.aopalliance.aop,
 org.xml.sax,
 org.osgi.framework,
 org.springframework.osgi.service.importer.support,
 org.springframework.beans.propertyeditors,
 org.springframework.osgi.service.importer,
 org.springframework.osgi.service.exporter.support,
 greeting.service
・コンパイル
$ javac -cp ../service/service.jar -d classes greeting/client/GreetingClient.java
・jar にする
$ jar cvfm client.jar manifest.mf META-INF -C classes greeting
以上、service.jar とclient.jar が作成され、デプロイできる状態になった。

■ 動かす

◆ Greeting Service ・インストールは以下のような感じ

-> install file:../service/service.jar
Bundle ID: 18
・start 18 で以下の出力。ちゃんと bean 定義の init-method で指定した start() が呼ばれたのがわかる。
-> start 18
-> Greeting Service registered

◆ Greeting Service Client ・インストールは以下のような感じ

-> install file:../client/client.jar
Bundle ID: 19
・開始すると、以下のように Greeting Service と協働しているのが分かる。
-> start 19
-> Hello Client

■ まとめ

  • 参考にした記事は→「OSGi と Spring:第 2 回
  • 正直、上の記事のアプリを思い通りに動かすまでちょっと苦労したけど、おかげで大分 Felix + Spring DMに慣れた。
  • 最初は何かと面倒だけど、バンドルが増えてくると段々と効果が出てくるのだと思う。特に POJO でサービスを書く事による、テスト効率向上に期待できそう。
  • 今回使ったSpring DM は 1.x で、Java 1.4ベースという事になる。Tiger ベースの2.x系があるらしい
  • 今回やってみて、やはり Spring の XML がちょっと面倒くさい。Spring DM 2.x 系は未見だけど、できれば EoD の方向でアノテーション・ベースになってくれてたら嬉しい。

Felix → Equinox

前回のポストで Apache Felix 用に作ったバンドルを、そのまま Equinox で動かしてみる。
  • 以下のようなフォルダ構成になっている前提
    c:\work
    client\
    client.jar
    ・・・
    service\
    service.jar
    ・・・
  • {eclipse base}/plugins/org.eclipse.osgi_3.5.0.vxxx.jar を c:\work\下にコピー
  • java -jar org.eclipse.osgi_3.5.0.vxxx.jar -console で Equinox を起動
  • プロンプトが出るので、先に作った service.jar と client.jar をインストール
    osgi> install file:service/service.jar
    Bundle id is 3

    osgi> install file:client/client.jar
    Bundle id is 4

    osgi> ss

    Framework is launched.

    id State Bundle
    0 ACTIVE org.eclipse.osgi_3.5.0.v20090520
    3 INSTALLED unknown_1.0.0 [3]
    4 INSTALLED unknown_1.0.0 [4]
    ※バンドル名が表示されないが後回し
  • 動作確認
    osgi> start 4
    Enter name: World
    no available service

    osgi> stop 4

    osgi> start 3

    osgi> start 4
    Enter name: World
    Hello World!
    Felix で動かしたときと同様、id=3 の依存サービスが起動していないときは"no available service"が表示され、起動しておくと正しく連携し"Hello World!"が表示された。

参考URL
  • http://www.eclipse.org/equinox/documents/quickstart.php

Apache Felix: getting started

Eclipse が OSGi ベースになった頃からか、何かといろいろ、OSGi 化し始めている。

と言うわけで、とっかかりになるような簡単な OSGi バンドルを作ってみる。ただし一個のバンドルで"Hello World"をやっても何が OSGi なのか分からないので、サービス用とクライアント用の二つのバンドルを、OSGiを介在させて連携させてみる。

モノは、Apache の OSGi release4 実装、Felix を使用。

  ■ こんな作り
Greeting Service
名前を受け取り、その名前を含むあいさつを返すメソッドを持つサービス
Greeting Service Client
標準入力から読み取った名前を Greeting Service に渡し、結果を標準出力に表示する。ただし、Greeting Service が動いてなかったら、サービスが無い旨を表示する。
環境
felix-framework-2.0.1 jdk1.6.0_13


  ■ 作り方
今回は IDE 等を使わずに、cygwin で vi、javac、jar を使って実装してみる。 c:\work\の下で作業すると仮定する。

◆ Greeting Service
適当な作業ディレクトリ(仮に c:\work\service とする)を作る。 以下のようにソース群を書き、パッケージ名と適当に対応させたディレクトリに置く。
  • GreetingService.java: サービスの I/F を定義
    package trial1.service;
    
    public interface GreetingService {
        public String greet(String name);
    }
  • GreetingServiceImpl.java: サービスの I/F を実装
    package  trial1.service.impl;
    
    import  trial1.service.GreetingService;
    
    public class GreetingServiceImpl implements GreetingService {
        public String greet(String name) {
            return "Hello " + name + "!";
        }
    }
  • Activator.java:OSGi コマンドの処理の実装
    package  trial1.service.activator;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    
    import  trial1.service.GreetingService;
    import  trial1.service.impl.GreetingServiceImpl;
    
    public class Activator implements BundleActivator {
        public void start(BundleContext context) {
            context.registerService(
                GreetingService.class.getName(), new GreetingServiceImpl(), null);
        }
        public void stop(BundleContext context) {}
    }
  • この時点で javac でコンパイルできるので、classes 下にでも classファイルを作っておく。(クラスパスに felix.jar が含まれている事が必要。)
    $ javac -d classes trial1/service/activator/Activator.java
    
  • 以下のような manifest.mfを書く: 上3行は適当だけど、下3行は大事。
    Bundle-Name: Greeting Service
    Bundle-Description: A bundle that registers an Greeting service
    Bundle-Version: 1.0.0
    Bundle-Activator: trial1.service.activator.Activator
    Export-Package: trial1.service
    Import-Package: org.osgi.framework
  • ここで service.jar に丸めて、とりあえずサービス側は終わり
    $ jar cfm service.jar manifest.mf -C classes trial1/service/
    jar の中身はこんな風になる
    $ jar tvf service.jar
         0 Mon Nov 02 02:03:10 JST 2009 META-INF/
       312 Mon Nov 02 02:03:10 JST 2009 META-INF/MANIFEST.MF
         0 Mon Nov 02 01:58:44 JST 2009 trial1/service/
         0 Mon Nov 02 01:58:44 JST 2009 trial1/service/activator/
       736 Mon Nov 02 01:58:44 JST 2009 trial1/service/activator/Activator.class
       183 Mon Nov 02 01:58:44 JST 2009 trial1/service/GreetingService.class
         0 Mon Nov 02 01:58:44 JST 2009 trial1/service/impl/
       546 Mon Nov 02 01:58:44 JST 2009 trial1/service/impl/GreetingServiceImpl.class
◆Greeting Service Client
  • 別の作業ディレクトリ(仮に c:\work\client とする)を適当に作る。
  • Activator.java を書いて、適当にパッケージに合わせたフォルダに置く。
    package trial1.client.activator;
    
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    import org.osgi.framework.Filter;
    import org.osgi.util.tracker.ServiceTracker;
    
    import trial1.service.GreetingService;
    
    public class Activator implements BundleActivator {
    
        public void start(BundleContext context) throws Exception {
            Filter filter = context.createFilter(
                    "(&(objectClass=" + GreetingService.class.getName() + "))");
            ServiceTracker tracker = new ServiceTracker(context, filter, null);
    
            tracker.open();
    
            try {
                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    
                System.out.print("Enter name: ");
                String name = in.readLine();
    
                GreetingService service = (GreetingService) tracker.getService();
                if (null == service) System.out.println("no available service");
                else System.out.println(service.greet(name));
    
            } catch (Exception ex) { }
        }
        public void stop(BundleContext context) {}
    }
  • ここでコンパイル。但し、クラスパスには felix.jar と上で作った service.jar を含めておく。
    javac -d classes trial1/client/activator/Activator.java
  • manifest.mf は以下のように書く。大事なのは下4行。
    Bundle-Name: Greeting Service Client
    Bundle-Description: A greeting service client
    Bundle-Version: 1.0.0
    Bundle-Activator: trial1.client.activator.Activator
    Import-Package: org.osgi.framework,
     org.osgi.util.tracker,
     trial1.service
  • client.jar に丸める。
    $ jar cfm client.jar manifest.mf -C classes trial1/client/
    以下のような中身になっている事を確認
    $ jar tvf client.jar
         0 Mon Nov 02 02:12:44 JST 2009 META-INF/
       308 Mon Nov 02 02:12:44 JST 2009 META-INF/MANIFEST.MF
         0 Mon Nov 02 02:10:56 JST 2009 trial1/client/
         0 Mon Nov 02 02:10:56 JST 2009 trial1/client/activator/
      1652 Mon Nov 02 02:10:56 JST 2009 trial1/client/activator/Activator.class
以上、作りこみ終了

  ■ 確認

◆デプロイ
-> install file:/c:/work/service/service.jar
Bundle ID: 23
-> install file:/c:/work/client/client.jar
Bundle ID: 24
ここで ps と打ち込んでみると以下のような感じ
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (2.0.1)
[   1] [Active     ] [    1] Apache Felix Bundle Repository (1.4.2)
[   2] [Active     ] [    1] Apache Felix Shell Service (1.4.1)
[   3] [Active     ] [    1] Apache Felix Shell TUI (1.4.1)
[  23] [Installed  ] [    1] Greeting Service (1.0.0)
[  24] [Installed  ] [    1] Greeting Service Client (1.0.0)
Greeting Service(23) が Active になっていないのを知りつつ、あえてGreeting Service Client(24) を開始してみる。
-> start 24
Enter name: World
no available service
ちゃんと必要なサービスが無い旨が表示されていて、期待通り。 次にGreeting Service(23) を起動してから、Client(24)を開始してみる。
-> stop 24
-> start 23
-> start 24
Enter name: World
Hello World!
できた。ちゃんとサービスとクライアントが連携して"Hello World!"が表示された。
 
■ 資料
・参考にしたチュートリアル→Apache Felix OSGi Tutorial
・OSGi のJavaDoc →OSGi"! Service Platform Release 4 Version 4.2.0