IEEE1888 SDKをMacで使う: Javaプログラミング篇

前回SDKの構成について確認しましたが、今回は実際にWRITEするプログラムを作成してみます。

環境構築

基本的にはSDKに付属する「IEEE1888プログラミング・スタートアップ・マニュアル」の説明通りに進めます。

まず、Axis2のダウンロードをおこないます。マニュアルでは1.5系を使用するように指定されているので、その中で最新版を選択します。
今回は、axis2-1.5.6-bin を利用します。

ダウンロードしたら、以下のディレクトリ構成に展開します。

app/                # 開発のトップ
    + axis2-1.5.6/  # zipを展開してできるディレクト

Macjava が入っているかどうかは環境によります。java や javac コマンドを起動してみて、動作すれば良いですし、無ければ「インストールしますか?」と聞いてくるので、そこでインストールを選択します。(OSX 10.8.1の場合)
聞いてこなければ http://support.apple.com/downloads/#java からダウンロードしてインストールします。

次に WSDL からStub等のファイルを生成します。

$ cd app
$ export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
$ ./axis2-1.5.6/bin/wsdl2java.sh -uri http://192.168.11.12/axis2/services/FIAPStorage?wsdl

これで、以下のような構成になります。準備OKです。

app/
    + axis2-1.5.6/
    + build.xml
    + src/org/fiap/soap/FIAPStorageStub.java
    + src/org/fiap/soap/FIAPStorageCallbackHandler.java

コード

次にテストソースを記述します。ファイルは以下の場所に作成します。

app/
    + ....
    + src/kahnn/SampleFIAPApp.java

コードは以下のようになります。

package kahnn;

import org.fiap.soap.FIAPStorageStub;
import org.apache.axis2.databinding.types.URI;
import java.util.Calendar;
import java.util.Date;
import java.text.SimpleDateFormat;

public class SampleFIAPApp {

    static private String USAGE_MSG =
    "SampleFIAPApp id value [time [target_endpoint]]\n"
    + "  time: yyyy-MM-dd'T'HH:mm:ss.SSSZ\n"
    + "  target_endpoint: http://x.x.x.x/axis2/services/FIAPStorage/";

    /** WRITE */
    public static void write(String id, String data,
                             Calendar dtime, String endpoint)
        throws Exception
    {
        // Pointの作成 (1つだけ). Point[]を取り扱うことも可.
        FIAPStorageStub.Point point = new FIAPStorageStub.Point();
        {
            FIAPStorageStub.Value value = new FIAPStorageStub.Value();
            point.setId(new URI(id));
            value.setTime(dtime);
            value.setString(data);
            point.addValue(value);
        }

        // PointSetは使用しないが使う場合は以下のようになる. 配列でなくても可.
        //FIAPStorageStub.PointSet[] ps = new FIAPStorageStub.PointSet[n];
        //ps[0] = new FIAPStorageStub.PointSet();
        //ps[0].setId(new URI("http://.... pointset id ..../"));
        //ps[0].setPoint(point);
        // ....

        //  Body
        FIAPStorageStub.Body body = new FIAPStorageStub.Body();
        body.addPoint(point);  // -or- setPoint(points[])
        // body.setPointSet(ps);

        // Transport
        FIAPStorageStub.Transport transport = new FIAPStorageStub.Transport();
        transport.setBody(body);

        // DataRQ
        FIAPStorageStub.DataRQ dataRQ = new FIAPStorageStub.DataRQ();
        dataRQ.setTransport(transport);

        // Stub & WRITE REQUEST
        FIAPStorageStub server =
            ((null == endpoint) ? new FIAPStorageStub()
                                : new FIAPStorageStub(endpoint));
        FIAPStorageStub.DataRS dataRS = server.data(dataRQ);

        // Get Header
        transport = dataRS.getTransport();
        FIAPStorageStub.Header header = transport.getHeader();
        if (null == header) {
            exitErr("Fatal Error: Header object was not found.");
        }

        // Check OK -or- NG
        FIAPStorageStub.OK ok = header.getOK();
        if (ok != null) {
            System.out.println("Successfully uploaded "
                               + "\'" + id + "\'=\'" + data + "\' ...");
        }
        else {
            FIAPStorageStub.Error error = header.getError();
            if (error != null) {
                exitErr("Error: type=\'" + error.getType() + "\'"
                        + "; message=\'" + error.getString() + "\'");
            } else {
                exitErr("Fatal Error: Neither OK nor Error object was not found.");
            }
        }
    }

    //////////////////////////////////////////////////////////

    public static void usage() {
        System.err.println(USAGE_MSG);
        System.exit(-1);
    }

    public static void exitErr(String msg) {
        System.err.println(msg);
        System.exit(-2);
    }

    public static void main(String[] args)
        throws Exception
    {
        if (args.length < 2) {
            usage();
        }

        // Get pointID
        String id = args[0];

        // Get value data
        String data = args[1];

        // Set calender
        Calendar dtime = Calendar.getInstance();
        if (3 <= args.length) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
            dtime.setTime(sdf.parse(args[2]));
        }

        // Set endpoint
        String endpoint = null;
        if (4 <= args.length) {
            endpoint = args[3];
        }

        write(id, data, dtime, endpoint);
    }
}

Ant を使用して build します。Mac には標準でインストールされているので、何も考えずに利用できます。

$ export AXIS2_HOME=/Users/kahnn/Desktop/IEEE1888/app/axis2-1.5.6
$ ant
.....
jar.client:
      [jar] Building jar: /Users/kahnn/Desktop/IEEE1888/app/build/lib/FIAPStorage-test-client.jar

BUILD SUCCESSFUL
Total time: 3 seconds

これで、Client 用の FIAPStorage-test-client.jar ができました。

実行

さっそく、テストプログラムを実行してみます。axis2 に含まれる axis2.sh を利用すると必要なクラスパスの設定が楽になります。
まずは USAGE の確認。

$ axis2-1.5.6/bin/axis2.sh -cp build/lib/FIAPStorage-test-client.jar kahnn.SampleFIAPApp
 Using AXIS2_HOME:   /Users/kahnn/Desktop/IEEE1888/app/axis2-1.5.6
 Using JAVA_HOME:       /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
SampleFIAPApp id value [time [target_endpoint]]
  time: yyyy-MM-dd'T'HH:mm:ss.SSSZ
  target_endpoint: http://x.x.x.x/axis2/services/FIAPStorage/

大丈夫のようなので、いよいよデータ投入。

$ axis2-1.5.6/bin/axis2.sh -cp build/lib/FIAPStorage-test-client.jar kahnn.SampleFIAPApp http://test.kahnn/ps/point_int_1 100 2012-09-05T21:20:56.001+0900
....
Successfully uploaded 'http://test.kahnn/ps/point_int_1'='100' ...

例のごとく、「Sensors in this FIAPStorage」の画面で確認します。上手く登録できたようです。

ここまでできれば FETCH の方も簡単ですね。後は実際に使用する際に色々とやれば良さそうです。

PointID登録の仕様について

色々とさわっていたら、pointSetTree テーブルに存在しないPointIDを指定しても、データが登録できることに気づきました。PointIDが未登録でWRITEした場合、error(POINT_NOT_FOUNDとか)が返されると思っていたのですが、そうでは無いようです。

あらためて、ドキュメントを見直しても、このあたりの仕様についてはハッキリとしません。(やはり正式な仕様が必要なのでしょうかね)
それでも、SDK (IEEE1888SDK-20120617) に付属のドキュメント「IEEE1888 Storageとの通信仕様.pdf」(2011-09-04) を確認してみますと、「第6章 エラーの種類」に以下のように記載されています。

6.3. type=”POINT_NOT_FOUND”
  key 要素で id 属性により指定していたポイントが、サーバ側に存在しないことを示す。
  一点でも該当するケースがあれば、このエラーが返ることがある(その id 属性の読出
  しがトライされるまでは、必ずしもこのエラーが返るとは限らない)。

6.4. type=”FORBIDDEN”
  指定されたポイントへのアクセスが禁止されていることを示す。FETCH の場合であれば、
  key 要素の id 属性で指定したポイントの読出しが禁止されていることを意味し、WRITE 
  の場合であれば、point の id 属性で指定したポイントへの書込みが禁止されている
  ことを 意味する。通常は、クライアントが認証された上で、ポイントへの読出し・書込み
  権限の 検証が行われるため、現バージョンの東大版 IEEE1888 ストレージでは、この
  エラーが返ることはない。将来のために用意されている。

この説明からは、POINT_NOT_FOUND は query の場合に返される可能性のあるエラーで、WRITE で PointID の登録までは許さない場合は(将来的には)FORBIDDENが使用できるように思えます。
SDKで使っているStorageの参照実装では、使い勝手を考えて、特別な指定なしに任意のIDが登録可能なようにしているという事なのでしょう。

実際に参照実装(fiap-reference-201205)を確認しても、POINT_NOT_FOUND(に対応するPointNotFoundException) は、query時に指定された id が pointsettree テーブルに無い場合に返されるようですし、FORBIDDEN(に対応するForbiddenException)は定義されているけど使用されていないようです。org.fiap.storage 内のソースでは pointsettree に登録する部分も確認できました。やはり、無ければ追加する処理となっています。

データのクリーンアップ

まちがって登録してしまったポイントやデータを削除する場合は、SQL を使うしか無いようです。
pointsettree 以外のテーブルについては、pointeettree を親とする参照制約がついています。ですから、あるidとそれに紐づくデータを全部消したい場合は、子供から順番に削除する必要があります。 (テーブル作成時の REFERENCES 指定に ON DELETE CASCADE があれば、一気に消せるはずなんですが、残念ながらそうはなっていません。まあ、ALTERで制約をつけ直したり、最初からやり直す場合は、テーブル定義を最初から変更しても良いかもしれません)
試しに psql を使用して http://test.kahnn/ps/point_int_X を削除してみます。(これは Pointsetは存在しません)

gut_storage=# \set PID '\'http://test.kahnn/ps/point_int_X\''
gut_storage=# \set
...
PID = ''http://test.kahnn/ps/point_int_X''
...

gut_storage=# delete from pointValueLatest where id = :PID;
DELETE 0
gut_storage=# delete from pointValue where id = :PID;
DELETE 1
gut_storage=# delete from pointAttribute where id = :PID;
DELETE 0
gut_storage=# delete from pointSetTree where id = :PID;
DELETE 1

gut_storage=# select count(*) from pointvalue where id = 'http://test.kahnn/ps/point_int_X';
 count 
-------
     0

gut_storage=# select count(*) from pointsettree where id = 'http://test.kahnn/ps/point_int_X';
 count 
-------
     0

この要領で既存のデータを全て削除して、新しく追加した Point にデータを追加してみます。
このようにすっきりします。

次回

java も良いですが、スクリプト系でも一つ試しとくと、ちょっとした確認などで便利そうです。マニュアルにはサンプルがない python あたりでしょうか。