OSGi サービスを利用する Karaf コマンドを実装する – Bundleの初歩的な実装法を感覚的に理解するために(1/3)

この記事は、とても単純な OSGiベースのサービスと、そのサービスを利用する Apache Karaf のコマンドを作成する手順を通して、OSGiフレームワークの基本的な仕組みと、Bundleの初歩的な実装法を感覚的に理解できることを目的として記述しています。

この記事では、OSGi の実行環境として OSGi コンテナである Apache Karaf 3.0.3 を使っています。

この記事の中で作成するもの

この記事で作成するのは、”hello” という文字列を返すインターフェースを持った OSGi ベースのサービスと、そのサービスを利用する Karaf のコマンドです。
コマンドの名前は、”mycommand:hello” になります。

これらにより実現するシナリオは、Karaf のコンソールで、”mycommand:hello” というコマンドを実行すると、Karaf のコンソールに “hello”という文字列が出力されるというものになります。

プロジェクトを作成する

まず、適当なディレクトリの下に親プロジェクトを作成しましょう。

以下に示す mvn コマンドを端末から実行して作成します。
実行するディレクトリは、どこでも構いません。この記事では、”/home/gougi/デスクトップ/Karaf/” の下で実行します。

$ mvn archetype:generate -DarchetypeGroupId=org.codehaus.mojo.archetypes -DarchetypeArtifactId=pom-root -DarchetypeVersion=RELEASE

実行すると、端末に、次のように出力されます。

$ mvn archetype:generate -DarchetypeGroupId=org.codehaus.mojo.archetypes -DarchetypeArtifactId=pom-root -DarchetypeVersion=RELEASE
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<<
[INFO] 
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] Archetype repository missing. Using the one from [org.codehaus.mojo.archetypes:pom-root:1.1] found in catalog remote
Define value for property 'groupId': : com.wingnest.bundles
Define value for property 'artifactId': : com.wingnest.bundles.sample.hello
Define value for property 'version':  1.0-SNAPSHOT: : 
Define value for property 'package':  com.wingnest.bundles: : com.wingnest.bundles.sample.hello
Confirm properties configuration:
groupId: com.wingnest.bundles
artifactId: com.wingnest.bundles.sample.hello
version: 1.0-SNAPSHOT
package: com.wingnest.bundles.sample.hello
 Y: : 
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: pom-root:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.wingnest.bundles
[INFO] Parameter: artifactId, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: packageInPathFormat, Value: com/wingnest/bundles/sample/hello
[INFO] Parameter: package, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.wingnest.bundles
[INFO] Parameter: artifactId, Value: com.wingnest.bundles.sample.hello
[INFO] project created from Archetype in dir: /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 39.195s
[INFO] Finished at: Fri Feb 06 19:55:44 JST 2015
[INFO] Final Memory: 15M/240M
[INFO] ------------------------------------------------------------------------
$ 

実行が正常に終了すると、”com.wingnest.bundles.sample.hello” という名前のディレクトリと、その中に pom.xml ファイルがひとつ作成されます。

確認してみましょう。

$ cd com.wingnest.bundles.sample.hello/
$ ls
pom.xml
$ 

さて次は、”com.wingnest.bundles.sample.hello” というディレクトリの中に、サブプロジェクトとして、サービスの Bundle を実装するプロジェクトと、コマンドの Bundle を実装するプロジェクトを作成します。

まず、サービスを実装するプロジェクトをkaraf-bundle-archetypeを使って作成します。

$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello
$ mvn archetype:generate \
    -DarchetypeGroupId=org.apache.karaf.archetypes \
    -DarchetypeArtifactId=karaf-bundle-archetype \
    -DarchetypeVersion=3.0.3 \
    -DgroupId=com.wingnest.bundles.sample.hello \
    -DartifactId=com.wingnest.bundles.sample.hello.service \
    -Dversion=1.0-SNAPSHOT \
    -Dpackage=com.wingnest.bundles.sample.hello.service
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building com.wingnest.bundles.sample.hello 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ com.wingnest.bundles.sample.hello >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ com.wingnest.bundles.sample.hello <<<
[INFO] 
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ com.wingnest.bundles.sample.hello ---
[INFO] Generating project in Interactive mode
[INFO] Archetype repository missing. Using the one from [org.apache.karaf.archetypes:karaf-bundle-archetype:3.0.3] found in catalog remote
[INFO] Using property: groupId = com.wingnest.bundles.sample.hello
[INFO] Using property: artifactId = com.wingnest.bundles.sample.hello.service
[INFO] Using property: version = 1.0-SNAPSHOT
[INFO] Using property: package = com.wingnest.bundles.sample.hello.service
Confirm properties configuration:
groupId: com.wingnest.bundles.sample.hello
artifactId: com.wingnest.bundles.sample.hello.service
version: 1.0-SNAPSHOT
package: com.wingnest.bundles.sample.hello.service
 Y: : 
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: karaf-bundle-archetype:3.0.3
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: artifactId, Value: com.wingnest.bundles.sample.hello.service
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.wingnest.bundles.sample.hello.service
[INFO] Parameter: packageInPathFormat, Value: com/wingnest/bundles/sample/hello/service
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.wingnest.bundles.sample.hello.service
[INFO] Parameter: groupId, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: artifactId, Value: com.wingnest.bundles.sample.hello.service
[INFO] project created from Archetype in dir: /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.554s
[INFO] Finished at: Fri Feb 06 20:00:41 JST 2015
[INFO] Final Memory: 12M/175M
[INFO] ------------------------------------------------------------------------
$ ls
com.wingnest.bundles.sample.hello.service  pom.xml
$ 

次に、Karaf コマンドを実装するプロジェクトをkaraf-command-archetypeを使って作成します。

$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello
$ mvn archetype:generate \
    -DarchetypeGroupId=org.apache.karaf.archetypes \
    -DarchetypeArtifactId=karaf-command-archetype \
    -DarchetypeVersion=3.0.3 \
    -DgroupId=com.wingnest.bundles.sample.hello \
    -DartifactId=com.wingnest.bundles.sample.hello.command \
    -Dversion=1.0-SNAPSHOT \
    -Dpackage=com.wingnest.bundles.sample.hello.command
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] com.wingnest.bundles.sample.hello
[INFO] com.wingnest.bundles.sample.hello.service Bundle
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building com.wingnest.bundles.sample.hello 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ com.wingnest.bundles.sample.hello >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ com.wingnest.bundles.sample.hello <<<
[INFO] 
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ com.wingnest.bundles.sample.hello ---
[INFO] Generating project in Interactive mode
[INFO] Archetype repository missing. Using the one from [org.apache.karaf.archetypes:karaf-command-archetype:3.0.3] found in catalog remote
[INFO] Using property: groupId = com.wingnest.bundles.sample.hello
[INFO] Using property: artifactId = com.wingnest.bundles.sample.hello.command
[INFO] Using property: version = 1.0-SNAPSHOT
[INFO] Using property: package = com.wingnest.bundles.sample.hello.command
Define value for property 'command': : hello
Define value for property 'description': : hello command
Define value for property 'scope': : mycommand
Confirm properties configuration:
groupId: com.wingnest.bundles.sample.hello
artifactId: com.wingnest.bundles.sample.hello.command
version: 1.0-SNAPSHOT
package: com.wingnest.bundles.sample.hello.command
command: hello
description: hello command
scope: mycommand
 Y: : 
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: karaf-command-archetype:3.0.3
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: artifactId, Value: com.wingnest.bundles.sample.hello.command
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.wingnest.bundles.sample.hello.command
[INFO] Parameter: packageInPathFormat, Value: com/wingnest/bundles/sample/hello/command
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.wingnest.bundles.sample.hello.command
[INFO] Parameter: groupId, Value: com.wingnest.bundles.sample.hello
[INFO] Parameter: scope, Value: mycommand
[INFO] Parameter: description, Value: hello command
[INFO] Parameter: command, Value: hello
[INFO] Parameter: artifactId, Value: com.wingnest.bundles.sample.hello.command
[INFO] project created from Archetype in dir: /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.command
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] com.wingnest.bundles.sample.hello ................. SUCCESS [43.727s]
[INFO] com.wingnest.bundles.sample.hello.service Bundle .. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 44.182s
[INFO] Finished at: Fri Feb 06 20:03:45 JST 2015
[INFO] Final Memory: 15M/224M
[INFO] ------------------------------------------------------------------------
$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello
$ ls
com.wingnest.bundles.sample.hello.command  pom.xml
com.wingnest.bundles.sample.hello.service
$

これで、プロジェクトの準備は整いました。

自動生成したばかりのサービスを動かしてみる

まず、ソースを見てみます。

com.wingnest.bundles.sample.hello.service/src/main/java/com/wingnest/bundles/sample/hello/service/Activator.java
をエディタで開きます。

package com.wingnest.bundles.sample.hello.service;

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

public class Activator implements BundleActivator {

    public void start(BundleContext context) {
        System.out.println("Starting the bundle");
    }

    public void stop(BundleContext context) {
        System.out.println("Stopping the bundle");
    }

}

自動生成されたこのコードは、サービスが開始した時に “Starting the bundle” と出力し、停止した時に “Stopping the bundle” と出力しそうですね。あとから確認しましょう。

では、Maven のローカルリポジトリにインストールし、さらに Karaf にインストールして動かしてみましょう。

まずは、mvn install を実行して、Maven のローカルリポジトリにサービスの Bundle をインストールします。

$ cd com.wingnest.bundles.sample.hello.service
$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building com.wingnest.bundles.sample.hello.service Bundle 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.7:resources (default-resources) @ com.wingnest.bundles.sample.hello.service ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.2:compile (default-compile) @ com.wingnest.bundles.sample.hello.service ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.7:testResources (default-testResources) @ com.wingnest.bundles.sample.hello.service ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.2:testCompile (default-testCompile) @ com.wingnest.bundles.sample.hello.service ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ com.wingnest.bundles.sample.hello.service ---
[INFO] 
[INFO] --- maven-bundle-plugin:2.5.3:bundle (default-bundle) @ com.wingnest.bundles.sample.hello.service ---
[INFO] 
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ com.wingnest.bundles.sample.hello.service ---
[INFO] Installing /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service/target/com.wingnest.bundles.sample.hello.service-1.0-SNAPSHOT.jar to /home/gougi/.m2/repository/com/wingnest/bundles/sample/hello/com.wingnest.bundles.sample.hello.service/1.0-SNAPSHOT/com.wingnest.bundles.sample.hello.service-1.0-SNAPSHOT.jar
[INFO] Installing /home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service/pom.xml to /home/gougi/.m2/repository/com/wingnest/bundles/sample/hello/com.wingnest.bundles.sample.hello.service/1.0-SNAPSHOT/com.wingnest.bundles.sample.hello.service-1.0-SNAPSHOT.pom
[INFO] 
[INFO] --- maven-bundle-plugin:2.5.3:install (default-install) @ com.wingnest.bundles.sample.hello.service ---
[INFO] Installing com/wingnest/bundles/sample/hello/com.wingnest.bundles.sample.hello.service/1.0-SNAPSHOT/com.wingnest.bundles.sample.hello.service-1.0-SNAPSHOT.jar
[INFO] Writing OBR metadata
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.570s
[INFO] Finished at: Fri Feb 06 23:14:50 JST 2015
[INFO] Final Memory: 20M/251M
[INFO] ------------------------------------------------------------------------
$

次に、Maven のローカルリポジトリにインストールしたサービスの Bundleを Karaf の中にインストールします1)もし、ローカルリポジトリの設定を変更していて、Karaf がそれを見つけることができない場合は、Karaf の etc/org.ops4j.pax.url.mvn.cfg ファイルに、そのリポジトリの設定を加えてください。
Apache Karaf を ダウンロードして、適当なディレクトリに展開してください。この記事では、/home/gougi/デスクトップ/Karaf/ の下に展開しています。

$ pwd
/home/gougi/デスクトップ/Karaf/apache-karaf-3.0.3
$ bin/karaf
        __ __                  ____      
       / //_/____ __________ _/ __/      
      / ,<  / __ `/ ___/ __ `/ /_        
     / /| |/ /_/ / /  / /_/ / __/        
    /_/ |_|\__,_/_/   \__,_/_/         

  Apache Karaf (3.0.3)

Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown Karaf.

karaf@root()> install -s mvn:com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service/1.0-SNAPSHOT
Starting the bundle
Bundle ID: 64
karaf@root()> list
START LEVEL 100 , List Threshold: 50
ID | State  | Lvl | Version        | Name                                            
-------------------------------------------------------------------------------------
64 | Active |  80 | 1.0.0.SNAPSHOT | com.wingnest.bundles.sample.hello.service Bundle
karaf@root()> 

install コマンドを実行すると、先ほど確認した、Activator#start() メソッドの中にあった “Starting the bundle” という文字列が表示されました。

ためしに、このサービスの Bundle を stop してみます。

karaf@root()> stop 64
Stopping the bundle
karaf@root()> 

Activator#stop()メソッドの中にあった”Stopping the bundle”という文字列が表示されました。予想どおりですね。

では、ソースを次のように、修正してみましょう。
println() の中の文字列の末尾に、!マークを2つずつ加えています。

public class Activator implements BundleActivator {

    public void start(BundleContext context) {
        System.out.println("Starting the bundle!!");
    }

    public void stop(BundleContext context) {
        System.out.println("Stopping the bundle!!");
    }

}

加えたら、端末から mvn install し Maven のローカルリポジトリにインストールして、その後、続けて、Karaf コンソールから サービスの Bundle を update してみます。

まずは、mvn install をして、Maven のローカルリポジトリにインストールします。

$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service
$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building com.wingnest.bundles.sample.hello.service Bundle 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(省略)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.708s
[INFO] Finished at: Fri Feb 06 23:49:55 JST 2015
[INFO] Final Memory: 20M/248M
[INFO] ------------------------------------------------------------------------
$ 

そして、Karaf コンソールで updateします。

karaf@root()> update 64
Stopping the bundle
Starting the bundle!!
karaf@root()> 

開始した時に出力される文字列の末尾に”!!”がついて”Starting the bundle!!”になっていますね。このようになるのは、終了のタイミング(Stopping)ではまだ古いBundleが使われ、開始のタイミング(Staring)で更新された新しいBundleに置き換わるからですね。

もう一度updateしてみます。

karaf@root()> update 64
Stopping the bundle!!
Starting the bundle!!
karaf@root()> 

両方の文字列の末尾に”!!”が付きました。

updateは、まず、Bundleを停止して、そのあと、Bundleが更新されているかを調べて、もし更新されていれば、現在のインストールされているBundleをアンインストールして最新のものをインストールし、最後に、Bundleを開始しますので、このような動きになります。

サービスの実装を変更する

では、サービスの実装を変更しましょう。

まず、次に示す Hello というインターフェースを com.wingnest.bundles.sample.hello.service に作成します。

Hello.java:
---
package com.wingnest.bundles.sample.hello.service;

public interface Hello {

}

次に、そのインターフェースを使った実装クラス HelloImpl を Activator クラスの中に記述し2)もちろん専用のjavaファイルに記述しても構いません、BundleContext#registerService()を使いコンテキストに登録するコードを記述します。ついでに、変更が反映されていることを確認できるように、文字列の末尾の!マークを3つにしておきましょう。

Activator.java:
---
package com.wingnest.bundles.sample.hello.service;

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

public class Activator implements BundleActivator {

    private ServiceRegistration<Hello> registration;

    public void start(BundleContext context) {
        System.out.println("Starting the bundle!!!");
        registration = context.registerService(Hello.class, new HelloImpl(), null);
    }

    public void stop(BundleContext context) {
        System.out.println("Stopping the bundle!!!");
        registration.unregister();
    }

    public static class HelloImpl implements Hello {

        public String toString() {
            return "hello";
        }

    }

}

修正を終えたら、再度、mvn install してサービスの Bundle をインストールしなおします。
そして、Karaf コンソールで、サービスをその新しい Bundle に update します。

karaf@root()> update 64
Stopping the bundle!!
Starting the bundle!!!
karaf@root()> list
START LEVEL 100 , List Threshold: 50
ID | State  | Lvl | Version        | Name                                            
-------------------------------------------------------------------------------------
64 | Active |  80 | 1.0.0.SNAPSHOT | com.wingnest.bundles.sample.hello.service Bundle
karaf@root()> 

開始時に出力される文字列の末尾の!マークが3つになりましたね。うまく置き換わったようです。

list コマンド(=bundle:listコマンドと同等)の結果によると、サービスを入れた Bundle のステータスも、Active となっていて、問題なく稼働しているようです。

コマンドを修正する

さて、mycommand:hello コマンドを修正して、サービスの #toString() メソッドを呼び出すような実装にしていきましょう。

が、その前に、生成されたままの mycommand:hello をインストールして動作させてみましょう。

まず、mvn install で Maven のローカルリポジトリに、コマンドの Bundle をインストールします。

$ cd ../com.wingnest.bundles.sample.hello.command
$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Apache Karaf :: Shell mycommand/hello Commands 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(省略)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.655s
[INFO] Finished at: Sat Feb 07 17:01:07 JST 2015
[INFO] Final Memory: 22M/308M
[INFO] ------------------------------------------------------------------------
$ 

次に、Karafのコンソールで次のように入力してインストールします。

karaf@root()> install -s mvn:com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.command/1.0-SNAPSHOT
Bundle ID: 65
karaf@root()> list
START LEVEL 100 , List Threshold: 50
ID | State  | Lvl | Version        | Name                                            
-------------------------------------------------------------------------------------
64 | Active |  80 | 1.0.0.SNAPSHOT | com.wingnest.bundles.sample.hello.service Bundle
65 | Active |  80 | 1.0.0.SNAPSHOT | Apache Karaf :: Shell mycommand/hello Commands  
karaf@root()> 

インストールを終えたら、mycommand:helloコマンドを実行してみます。

karaf@root()> mycommand:hello 
Executing command hello
Option: null
Argument: null
karaf@root()> 

この実行結果については、「Maven archetypeを使ってカスタムKarafコマンドを作成する」という記事を参照していただければ幸いです。

では、エディタで hello.java ファイルを開いて次のように修正します。

package com.wingnest.bundles.sample.hello.command;

import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.apache.karaf.shell.commands.Command;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

@Command(scope = "mycommand", name = "hello", description = "hello command")
public class hello extends OsgiCommandSupport {

    protected Object doExecute() throws Exception {
        BundleContext context = this.getBundleContext();
        ServiceReference<?> serviceReference = context.getServiceReference("com.wingnest.bundles.sample.hello.service.Hello");
        if (serviceReference != null ) {
            Object service = context.getService(serviceReference);
            System.out.println(service.toString()); // (注:後から修正する)
        }
        return null;
    }
}

com.wingnest.bundles.sample.hello.service.Hello インターフェースを実装したサービスをみつけて、そのサービスの#toString() メソッドを呼び出し、その結果を標準出力にプリントするように修正しています。

では、この修正したコマンドの Bundle を Maven のローカルリポジトリにインストールしましょう。

$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.command
$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Apache Karaf :: Shell mycommand/hello Commands 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(省略)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.496s
[INFO] Finished at: Sat Feb 07 17:22:07 JST 2015
[INFO] Final Memory: 16M/314M
[INFO] ------------------------------------------------------------------------
$

続いて、Karaf コンソールで、修正したコマンド の Bundle を更新(update)し、mycommand:hello コマンドを実行してみます。

karaf@root()> list
START LEVEL 100 , List Threshold: 50
ID | State  | Lvl | Version        | Name                                            
-------------------------------------------------------------------------------------
64 | Active |  80 | 1.0.0.SNAPSHOT | com.wingnest.bundles.sample.hello.service Bundle
65 | Active |  80 | 1.0.0.SNAPSHOT | Apache Karaf :: Shell mycommand/hello Commands  
karaf@root()> update 65
karaf@root()> mycommand:hello 
hello
karaf@root()> 

期待通りに “hello” と出力されました!!

Hello インターフェースをきちんと定義し利用する

お察しのことと思いますが、ここまでの実装では、Hello インターフェースをきちんと使っていませんでした。メソッドはひとつも定義していないのに、コマンドからは、そのインターフェースを実装しているサービスをインターフェース名を使って取得し、さらにそこで定義されていない #toString() メソッドを呼び出していました。この怪しい部分をそれらしく修正しましょう。

具体的には、Hello インターフェースに String hello() というメソッドを定義し、その実装クラス(HelloImpl)では呼び出されると “hello”という文字列を返すようにHelloImpl#hello()メソッドを実装します。

まず、サービスの方を修正します。

Hello インターフェースは次のようにします。

package com.wingnest.bundles.sample.hello.service;

public interface Hello {
    String hello();
}

この修正を受けて、Hello インターフェースの実装クラス(HelloImpl)は、次のようになります。

public class Activator implements BundleActivator {

(省略)

    public static class HelloImpl implements Hello {

        public String hello() {
            return "hello";
        }

    }
}

修正を終えたら、mvn install してローカルリポジトリに入れます。

$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.service
$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building com.wingnest.bundles.sample.hello.service Bundle 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(省略)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.663s
[INFO] Finished at: Sun Feb 08 10:51:56 JST 2015
[INFO] Final Memory: 20M/254M
[INFO] ------------------------------------------------------------------------
$ 

ここで試しに、 Karaf コンソールでサービスを update し、コマンドを実行してみます。

karaf@root()> update 64
Stopping the bundle!!!
Starting the bundle!!!
karaf@root()> mycommand:hello 
com.wingnest.bundles.sample.hello.service.Activator$HelloImpl@64950b18
karaf@root()> 

サービスを update する以前は、”hello”と出力されていたのに、update すると “com.wingnest.bundles.sample.hello.service.Activator$HelloImpl@64950b18” と出力されるようになりました。このようになったのは、#toString() メソッドが、Object クラスのデフォルトの実装に戻ったからですね。

では、コマンドの中で呼び出しているメソッドを #toString() メソッドから #hello() メソッドに変更しましょう。

まず、コマンドの pom.xml に、サービスの dependency を加えます。

(省略)
    <dependencies>
        <dependency>
            <groupId>com.wingnest.bundles.sample.hello</groupId>
            <artifactId>com.wingnest.bundles.sample.hello.service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
(省略)

次に、com.wingnest.bundles.sample.hello.command.hello クラスを次のように修正します。

package com.wingnest.bundles.sample.hello.command;

import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.apache.karaf.shell.commands.Command;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

import com.wingnest.bundles.sample.hello.service.Hello;

@Command(scope = "mycommand", name = "hello", description = "hello command")
public class hello extends OsgiCommandSupport {

    protected Object doExecute() throws Exception {
        BundleContext context = this.getBundleContext();
        ServiceReference<Hello> serviceReference = context.getServiceReference(com.wingnest.bundles.sample.hello.service.Hello.class);
        if (serviceReference != null ) {
            Hello service = context.getService(serviceReference);
            System.out.println(service.hello());
        }
        return null;
    }
}

主な修正は、次の2つです。
ひとつは、Hello インターフェースの参照が、修正前は文字列だったのですが、修正後はClassインスタンスによる参照に変わっています。
もうひとつは、呼び出すメソッドも、修正前は#toString()でしたが、修正後は#hello()に変わっています。

では、修正したこのコマンドを mvn install して、Karaf コンソールから実行してみましょう。

まずは、mvn install。

$ pwd
/home/gougi/デスクトップ/Karaf/com.wingnest.bundles.sample.hello/com.wingnest.bundles.sample.hello.command
$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Apache Karaf :: Shell mycommand/hello Commands 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(省略)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.727s
[INFO] Finished at: Sun Feb 08 11:17:39 JST 2015
[INFO] Final Memory: 21M/255M
[INFO] ------------------------------------------------------------------------
$ 

うまく、コンパイルに成功しインストールされました。

次に、Karaf コンソールで update します。

karaf@root()> list
START LEVEL 100 , List Threshold: 50
ID | State  | Lvl | Version        | Name                                            
-------------------------------------------------------------------------------------
64 | Active |  80 | 1.0.0.SNAPSHOT | com.wingnest.bundles.sample.hello.service Bundle
65 | Active |  80 | 1.0.0.SNAPSHOT | Apache Karaf :: Shell mycommand/hello Commands  
karaf@root()> update 65
karaf@root()> 

では、コマンドを実行してみましょう。

karaf@root()> mycommand:hello
hello
karaf@root()> 

期待した通り、ちゃんと “hello”と出力されました!

ここで、mvn install(または mvn package) で、生成されるサービスとコマンドの Bundle(ファイル形式はjar) の中にある、META-INF/MANIFEST.MF の内容と それぞれの pom.xml の中の maven-bundle-plugin 要素の中の記述について触れたいところですが、随分と長くなったのでこの記事はこの辺で。

あとがき

Hello インターフェースは、あまりに単純なのであれですが、サービスのインターフェースだけを集めた Bundle を別に用意すれば、サービスとコマンドをそれぞれ異なる人や組織が開発する場合や、サービスの複数の異なる実装を用意する場合に、都合が良さそうです。
この記事では、この辺りまでは踏み込んでいませんのでので、ご注意ください。

Footnotes

Footnotes
1 もし、ローカルリポジトリの設定を変更していて、Karaf がそれを見つけることができない場合は、Karaf の etc/org.ops4j.pax.url.mvn.cfg ファイルに、そのリポジトリの設定を加えてください。
2 もちろん専用のjavaファイルに記述しても構いません