レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

BLOCKVROCKリファレンス目次はこちら

OSGi for Beginners翻訳(3)

OSGi for Beginners翻訳 第1回(http://d.hatena.ne.jp/iad_otomamay/20080513/p1
OSGi for Beginners翻訳 第2回(http://d.hatena.ne.jp/iad_otomamay/20080514/p1

A Simple Repository to put into a Bundle(バンドルの中に設置するための簡単なリポジトリ)


So let's create a bundle. I'd like a way to connect to a repository of information.
My initial interface should look something like this:


では、バンドルを作ってみましょう。

情報のリポジトリに接続する方法を説明したいと思います。

まずはじめのインターフェイスは、以下のようなものになります。

package repository;

public interface RepositoryService {
        Node put(String path, Node content);
        Node get(String path);

}



It's not much, but it's something to get started: I can use this to put information in, or get information out. My Node class is a simple POJO, which looks something like this:


このインターフェイスは大きくありませんが、これは始まりです。

これを使って、情報をこの内部に保持することや取り出すことができます。

このMyNodeクラスは、以下のようなシンプルなPOJOです。

package repository;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Node {
        Date created = new Date();
        String source = "unknown";
        String author = "unknown";
        List contents = new ArrayList<String>();
        String path;

        @Override
        public String toString() {
                String s = "node: path=" + getPath() + ", author=" + getAuthor()
                                + ", created=" + getCreated() + ", source=" + getSource()
                                + ", data=[";
                String separator = "";
                for (String d : getContents()) {
                        s += separator + d;
                        separator = ",";
                }
                s += "]";
                return s;
        }

        public Node() {

        }

        public Node(String content) {
                getContents().add(content);
        }

        public Node(String content, String context) {
                this(content);
                setSource(context);
        }

        // .. accessors and mutators go here.
}



This is a service that can be implemented and tested independently from OSGi; we have nothing that has anything to do with OSGi yet. With that, we probably should go ahead and have an implementation. I originally coded this as a Map:


これは実行可能なサービスであり、OSGiからは独立しています。

OSGiで実行するための仕掛けは、まだ何もありません。

そうして、たぶんさらに実装をするめ無くてはならないでしょう。

もともと、私はMapとして以下のコードを記述しました。

import java.util.HashMap;
import java.util.Map;

import repository.Node;
import repository.RepositoryService;

public class MapRepositoryService implements RepositoryService {
        Map<String, Node> data=new HashMap<String, Node>();

        public Node put(String path, Node content) {
                return data.put(path, content);
        }

        public Node get(String path) {
                return data.get(path);
        }

}



However, a Map provides poor search facilities, and any hierarchy of information is accidental. I'd like to use a DOM for this, instead. So I'll introduce a dependency on XOM for now, and add a new repository service implementation:


しかしながらMapは貧弱な検索機能しか提供しておらず、

しかも情報の階層は時々、思いがけない状態になります。

私は、代わりにDOMをを使いたいと思いました。

そこで、次に私はXOMの依存性を導入しようと思います。

では、新しいリポジトリサービスの実装を追加します。

package repository.impl;

import java.util.Date;

import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.Nodes;
import nu.xom.ParentNode;
import repository.Node;
import repository.RepositoryService;

public class XMLRepositoryService implements RepositoryService {
        Document document;
        Element data;

        public XMLRepositoryService() {
                data = new Element("data");
                document = new Document(data);
        }

        Node toNode(nu.xom.Element node) {
                if (node.getAttribute("source") == null) {
                        return null;
                }
                Node n = new Node();
                n.setAuthor(node.getAttributeValue("author"));
                n.setCreated(new Date(node.getAttributeValue("created")));
                n.setSource(node.getAttributeValue("source"));
                Elements e = node.getChildElements("contents");
                n.setPath(node.getAttributeValue("path"));
                for (int i = 0; i < e.size(); i++) {
                        Element elt = e.get(i);
                        for (int i1 = 0; i1 < elt.getChildCount(); i1++) {
                                n.getContents().add(elt.getChild(i1).getValue());
                        }
                }
                return n;
        }

        nu.xom.Element getElement(String path) {
                while (!path.startsWith("//")) {
                        path = "/" + path;
                }
                while (path.endsWith("/")) {
                        path = path.substring(0, path.length() - 1);
                }
                Nodes nodes = document.query(path);
                if (nodes.size() > 0) {
                        return (Element) nodes.get(0);
                }
                return null;
        }

        public Node get(String path) {
                Element e = getElement(path);
                if (e != null) {
                        return toNode(e);
                }
                return null;
        }

        public Node put(String path, Node content) {
                Element oldElt = getElement(path);
                if (oldElt != null) {
                        // need to remove this node!
                        ParentNode p = oldElt.getParent();
                        p.removeChild(oldElt);
                }
                StringBuilder pathBuilder=new StringBuilder("/");
                String[] tree = path.split("/");
                Element node = data;
                for (String t : tree) {
                        if (t.length() != 0) {
                                Element child = node.getFirstChildElement(t);
                                if (child == null) {
                                        //System.err.println("creating new "+t);
                                        child = new Element(t);
                                        node.appendChild(child);
                                }
                                pathBuilder.append('/');
                                pathBuilder.append(t);
                                node = child;
                        }
                }
                node.addAttribute(new Attribute("created", content.getCreated()
                                .toString()));
                node.addAttribute(new Attribute("source", content.getSource()));
                node.addAttribute(new Attribute("author", content.getAuthor()));
                content.setPath(pathBuilder.toString());
                node.addAttribute(new Attribute("path", content.getPath()));
                Element contents = new Element("contents");
                node.appendChild(contents);
                for (String c : content.getContents()) {
                        Element e = new Element("content");
                        e.appendChild(c);
                        contents.appendChild(e);
                }
                //System.out.println(data.toXML());
                return null;
        }

        public static void main(String[] args) {
                XMLRepositoryService s = new XMLRepositoryService();
                s.put("/foo/bar/baz", new Node("stuff"));
                Node n = new Node("bletch");
                n.setAuthor("jottinger");
                s.put("/foo/bar/bletch", n);
                System.out.println(s.get("foo/bar/baz/"));

                System.out.println(s.get("//foo/bar/baz"));
                System.out.println(s.get("//foo/bar/bletch"));
                System.out.println(s.get("foo/bar/"));
                System.out.println(s.get("//*[@author='jottinger']"));
        }
}



It's still far from perfect, but it's a good start. However, we still haven't done anything with respect to OSGi - we only have a slightly usable repository class. Let's look at how OSGi modules are built.


まだまだ完璧とはいえませんが、手始めにはちょうどよいでしょう。

しかしながら、いまだにOSGiの恩恵を受けられるようなことは何もしていません。

ただ単に、若干使えるリポジトリクラスを手に入れただけです。

さて、OSGiモジュールをどうやって組み立てていくのかを見ていきましょう!!

Sidetrack: Building an OSGi bundle with a Dependency(脱線:依存性を使ったOSGiバンドルを組み立てる)


An OSGi module is a .jar file. In this, it follows the standard .jar file specification , except it has a set of specific requirements in its manifest file .

OSGiモジュールは、jarファイルです。

このJarファイルの中は、マニフェストファイルでの特殊な要件を除けば、

あとは基本的なJarファイルの仕様に従っています。



Let's create a simple module, first. This will simple display a message on startup and shutdown, and along the way will include a .jar file as an internal dependency. This jar file will not be visible to any other bundle - it's just a resource for our tutorial bundle. However, this is a fairly simple and common requirement that is poorly documented. (Well, it's poorly documented for me - I looked for a simple example and couldn't find one.) The dependency will be in a jar, baselib.jar, and it will be one class: baselib.BaseService.


さて、まずは簡単なモジュールを作ってみましょう。

これは、スタートアップとシャットダウンのときに

簡単なメッセージを表示するだけのものです。

そしてこの処理は、内部の依存性にJarファイルを含めるやり方に従っています。

このJarファイルは、他のバンドルからは不可視です。

つまりこのチュートリアルのバンドルのためだけのリソースということです。

しかしながら、これは、貧弱なドキュメントで共通的に本来必要な、

完全にシンプルなサンプルです。

(ほんとうに貧弱なドキュメントであり、

 シンプルなサンプルを探しましたが、見つけることはできませんでした。)

依存性は、baselib.jarというJarの中の、おそらく

baselib.BaseServiceというクラスにあります。

package baselib;

import java.util.logging.Logger;

public class BaseService {
  Logger log=Logger.getLogger(this.getClass().getName());
  public void sayHello() {
    log.info("Hello, world!");
  }
}


Complex, earth-shattering stuff here, to be sure. What we need to do is build this file into a .jar of its very own, one I'll call baselib.jar.


ここでの驚くほど複雑な必要作業は、

このファイルを独自の.jarに組み込むことです、

そのJarの一つは筆者がbaselib.jarと呼んでいたものです。



Now, an OSGi bundle requires an "activator," a class that manages the lifecycle of the bundle. An activator implements the org.osgi.framework.BundleActivator interface, which means two methods: start(BundleContext) and stop(BundleContext). These lifecycle methods are where a bundle can register services or start processes, but for us, it's far simpler:


さてOSGiバンドルは、バンドルのライフサイクルを管理するクラスである"activator,"を求めます。

activatorは、org.osgi.framework.BundleActivatorインターフェイスを実装します。

このインターフェイスには、以下の2つのメソッドがあります。

  • start(BundleContext)
  • stop(BundleContext)

これらのライフサイクルメソッドは、バンドルがサービスに登録するか

または、プロセスを開始することができる場所ですが、

しかし、大いにシンプルです。

package tutorial;

import baselib.BaseService;

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

import java.util.logging.Logger;

public class TutorialActivator implements BundleActivator {
  Logger log=Logger.getLogger(this.getClass().getName());
  public void start(BundleContext bc) {
    log.info("started");
    new BaseService().sayHello();
  }

  public void stop(BundleContext bc) {
    log.info("stopped.");
  }
}



We can't just stuff this into a jar file and have it work for us, sadly (Spring-OSGi can help here, but it's far beyond scope for this article.) We need to build a tutorialbundle.jar with a specific set of files and a specific structure. First off, baselib.jar must be in the root directory of our new jar. As well, we need a MANIFEST.MF that includes some OSGi configuration and startup information:


残念ながら、これをJarファイルに、ただ詰め込んだだけでは動かすことはできません。

(Spring-OSGiはそれをサポートしていますが、この記事の対象からは外れるので割愛します)

そのため、特定のファイルを集め、特定の構造に則って、

tutorialbundle.jarを組み立てる必要があります。

まず第一に、baselib.jarを新しいJarのルートディレクトリ配置する必要があります。

そして、OSGiの設定と起動情報を表すMANIFEST.MFが必要です。
(MANIFEST.MFの内容は以下です。)

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.theserverside.tutorial.osgi.TutorialBundle
Bundle-Version: 1
Bundle-Activator: tutorial.TutorialActivator
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-ClassPath: .,baselib.jar


This manifest works on the assumption that our jar looks like this:


このマニフェストファイルを以下のJarファイルの場所に配置すると動作します。

$ jar tvf tutorialbundle.jar
     0 Thu Apr 17 11:57:14 EDT 2008 META-INF/
   391 Thu Apr 17 11:57:12 EDT 2008 META-INF/MANIFEST.MF
     0 Thu Apr 17 11:29:56 EDT 2008 tutorial/
   714 Thu Apr 17 11:51:02 EDT 2008 tutorial/TutorialActivator.class
   902 Thu Apr 17 11:15:28 EDT 2008 baselib.jar



The most important things in our manifest are the imported packages (just the OSGi base framework in this case), the activator class name, and the bundle classpath - a comma-separated set of resources in the jar file. If we can duplicate this structure, then we're ready to install this bundle and run it in Equinox.


マニフェストファイルにおいて最も重要なことは、以下です。

  • インポートされたパッケージ(この場合、OSGiベースのフレームワークだけです)
  • activatorクラス名
  • バンドルクラスパス(Jarファイルの中のカンマ区切りのリソース群)

この構造を写すことができたら、バンドルをインストールして、Equinoxで動かす準備は完了です。

$ java -jar org.eclipse.osgi_3.3.2.R33x_v20080105.jar -console
osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.3.2.R33x_v20080105

osgi> install file:///workspaces/osgi/tutorial/tutorialbundle.jar
Bundle id is 4

osgi> start 4
Apr 17, 2008 11:57:29 AM tutorial.TutorialActivator start
INFO: started
Apr 17, 2008 11:57:29 AM baselib.BaseService sayHello
INFO: Hello, world!

osgi> stop 4
Apr 17, 2008 1:29:25 PM tutorial.TutorialActivator stop
INFO: stopped.

osgi>



Now we can see how to build a bundle, and how to deploy it with dependent jars (a requirement, since our repository class above relies on XOM.)


ここでは、バンドルをビルドする方法と依存するJar(XOMリポジトリクラスに必要な要件のJar)に

デプロイする方法が確認できました。