マッパー関数開発者ガイド

  1. マッパー関数開発について
    1. JDK・クラスパス
  2. Valueクラスについて
  3. Functionサブクラスの作成
    1. 必ず実装するメソッド
      1. マッパー関数名の命名規則について
    2. 必要に応じてオーバーライドするメソッド
    3. init/termメソッド実装時の注意
  4. プロパティ
    1. プロパティの登録
    2. プロパティの取得
  5. MultiOutputFunctionの作成
    1. サブ出力の設定
      1. 定義ファイル
  6. ライセンスチェック
  7. サンプル

1 マッパー関数開発について

フローサービスでは独自のマッパー関数をJavaで開発し追加することが可能になっています。
マッパー関数開発を行う場合、以下の作業が必要になります。

jarファイルの作成に関しては実際にはJavaInterpreter関数付属のSDKウィザードでほぼ自動化されています。
ここではFunctionクラスのソースコードの書き方に絞って説明します。
定義ファイルの作成に関しては定義ファイルリファレンスを、 jarファイルの作成とそのインストールに関してはツールガイドを参照してください。

1.1 JDK・クラスパス

マッパー関数の開発はJDK8.0以降の環境で行ってください。
また、「DESIGNER_HOME/lib」にある以下のjarファイルにクラスパスを通す必要があります。

※ascore-1610.0200.jarと asdesigner-1610.0200.jarの「-1610.0200」の部分はインストールしたバージョンにより異なります。

作成するマッパー関数の内容によってはさらに別のjarファイルがコンパイル/実行に必要になる場合があります。
その場合、flow-ctrlのshowlibコマンドを使用することで必要とするクラスがどのjarに含まれているかを確認することができます。


//showlibコマンドによって指定のクラスがどのjarに含まれているかが表示されます。
>showlib com.infoteria.asteria.flowlibrary2.stream.StreamDataXML
jar:file:C:\Program Files\asteria5\server\lib\ascore-1610.0200.jar!/com/infoteria/asteria/flowlibrary2/stream/StreamDataXML.class

必要に応じてここで表示されたjarファイルにもクラスパスを通してください。
実際にはウィザードで生成されるANT用のbuild.xmlでは「DESIGNER_HOME/lib/**/*.jar」にクラスパスが通っているので、通常はbuild.xmlの設定を変更する必要はありません。

2 Valueクラスについて

Valueとはフローサービスの基本データ型をラップするバリアント型の変数クラスのことです。
このクラスを使用することで変数の値をStringとして取り出したり、intとして取り出したりということが簡単にできます。

マッパー関数では入力されたフィールドの値がValueの配列として取得されます。

3 Functionサブクラスの作成

3.1 必ず実装するメソッド

Functionクラスでは次の3つのメソッドがabstractで宣言されているので必ず実装する必要があります。
public String getFunctionName();
マッパー関数の登録名を返すメソッド。
ここで返されるマッパー関数名はサーバー上に登録されるすべてのマッパー関数内でユニークでなければなりません。
protected void internalInit();
ここでプロパティの登録を行います。
作成するマッパー関数がプロパティを持たない場合は空で構いません。
public void execute(ExecuteContext context, Value[] in, Value out) throws MapperException;
マッパー関数の処理本体を記述します。
引数のinには入力された値の配列が設定されています。
処理結果は引数のoutに設定します。

メソッドがMapperExceptionをthrowした場合はMapperコンポーネントのExceptionとしてフローのExceptionフレームワークに処理されます。
以下にexecuteメソッドの実装例を何パターンか示します。

//入力2つの文字列を結合する
public void execute(ExecuteContext context, Value[] in, Value out) throws MapperException {
    out.setValue(in[0].strValue() + in[1].strValue());
}

//入力2つを整数として足す
public void execute(ExecuteContext context, Value[] in, Value out) throws MapperException {
    out.setValue(in[0].longValue() + in[1].longValue());
}

//入力文字列がLengthプロパティ値よりも長い場合はExceptionにする
public void execute(ExecuteContext context, Value[] in, Value out) throws MapperException {
    int len = (int)getPropertyInteger("Length");
    if (in[0].strValue().length() >= len)
        throw new MapperException("入力が長すぎます。");
    else
        out.setValue(in[0]);
}

3.1.1 マッパー関数名の命名規則について

マッパー関数名(getFunctionNameメソッドの返り値)には「<会社名>.」というプレフィクスをつけてください。
Asteria以外の会社が「.」を含まない名前を関数名に使用することは禁止事項とします。

3.2 必要に応じてオーバーライドするメソッド

以下のメソッドは必要に応じてオーバーライドします。
getMinInputCount()/getMaxInputCount()以外のメソッドはほとんどの場合実装する必要はありませんが、 処理を行うタイミングをコントロールすることでパフォーマンスが向上する可能性があります。

public void init(MapperComponent mapper, ExecuteContext context) throws MapperException;
通常はマッパー関数が初期化される時に一度だけ呼び出されます。 (マッパーコンポーネントのinitメソッド内で呼び出されます。)
オーバーライドする場合、その先頭で、super.init(mapper, context);を呼び出す必要があります。
public void term(MapperComponent mapper, ExecuteContext context);
通常はマッパー関数の終末処理時に一度だけ呼び出されます。 (マッパーコンポーネントのtermメソッド内で呼び出されます。)
オーバーライドする場合、その最後で、super.term(mapper, context);を呼び出す必要があります。
protected void postCompile() throws MapperException;
マッパー関数のコンパイル時にプロパティが設定された後で呼び出されます。
プロパティ間の整合性のチェックや、コンパイル時に一度だけ実行すればよい処理がある場合はここに記述します。
このメソッド内でMapperExceptionをthrowした場合はコンパイルエラーとなります。
public int getMinInputCount();
作成するマッパー関数の実行に必要な接続数を返します。
入力接続数がこのメソッドの返り値未満の場合はコンパイルエラーとなります。
オーバーライドしない場合は 1を返します。
public int getMaxInputCount();
作成するマッパー関数に接続できる入力数の最大値を返します。
入力接続を無制限に受け入れる場合は -1を返すようにします。
それ以外の場合に入力接続数がこのメソッドの返り値を超えている場合はコンパイルエラーとなります。
オーバーライドしない場合は 1を返します。

マッパー関数もコンポーネントと同じく、フローのリクエスト時にcloneメソッドによって複製が作られますが、その実装は コンポーネントとは異なり、Object#cloneメソッドにより実装されています。
つまりpostCompileメソッド内でインスタンス変数に設定したObjectは複製時にポインタがコピーされます。

3.3 init/termメソッド実装時の注意

上記init/termメソッドの説明で「通常はマッパー関数の...時に一度だけ呼び出されます。」とありますが、 マッパー関数のinit/termメソッドはコンポーネントのそれと異なり実行時に複数回実行されることがあり得ます。
それは以下の場合です。

この場合マッパーコンポーネントのinitメソッドの実行がエラーになったことになるので、コンポーネントの仕様的には termメソッドは実行されません。(コンポーネント開発者ガイド#termメソッドを参照)

ですが、マッパーコンポーネントの場合既にinitを実行したマッパー関数のクリーンアップを行わなければならないのでinitメソッド内で エラーが発生した場合はinitを実行した関数についてtermを実行してからエラーをthrowします。
(もちろんここで終了した場合はフロー終了時にこのマッパーコンポーネントのtermメソッドは実行されないのでそのタイミングで マッパー関数のtermメソッドが実行されることもありません。)

こうしたエラーが発生した場合にさらに

には、その中にあるマッパー関数のinitメソッドが再度実行されることになります。
(ほとんどの場合は先にエラーとなった個所で同じようにエラーとなるのでこのようなフローはほぼ100%フロー設計の誤りです。)

実際のところでいえばinitとtermを常に対で作成し、initで初期化したものをtermでクリーンアップするように心がけておけば この動作が問題となることはほとんどありません。

4 プロパティ

4.1 プロパティの登録

プロパティの登録はinternalInitメソッド内でregistPropertyメソッドを呼び出すことによって行います。


    private static final String PROPERTY_DATA = "Data";
    private static final String PROPERTY_ENABLE_METACHARACTER = "EnableMetaCharacter";

    protected void internalInit() {
        registProperty(PROPERTY_DATA, Value.TYPE_STRING, false);
        registProperty(PROPERTY_ENABLE_METACHARACTER, Value.TYPE_BOOLEAN, false);
    }

4.2 プロパティの取得

プロパティ値を取得するにはgetPropertyメソッドを使用します。
あるいはgetPropertyXXXXメソッドを使用することでValueオブジェクトとしてではなく、プリミティブ型として取得することもできます。

マッパー関数のプロパティには「入力がn本以上ある場合にはプロパティの設定値の代わりにm番目の入力値を使用する」という仕様のものがありますが、このためのメソッドも標準で提供されています。
この場合はgetPropertyまたはgetPropertyXXXXメソッドの第2引数に入力値の配列(executeメソッドの引数 in)を、第3引数に何番目の値がプロパティを置換するのかを指定します。


    //Dataプロパティの内容をValueとして取得
    Value v = getProperty("Data");
    
    //入力値の配列(executeメソッドの引数 in)が2つ以上あればその2番目を、
    //それがない場合はCountプロパティの値をintとして取得
    int count = (int)getPropertyInteger("Count", in, 1);

5 MultiOutputFunctionの作成

MultiOutputFunctionとは出力が複数あるマッパー関数のことです。
出力がひとつだけの通常のマッパー関数とは次の点が異なります。

5.1 サブ出力の設定

setSubValueCountメソッドはマッパー関数の出力数を設定するメソッドです。
通常はコンストラクタの中で実行して出力数を設定します。

引数となる数字には2番目以降のサブ出力の数を設定することに注意してください。
つまり全部で4つの出力があるマッパー関数を作成する場合、設定する値は3です。

同様にgetSubValueメソッドの引数も2番目の出力以降のインデックスとなっています。
つまりgetSubValue(0)は全体で2番目の出力値です。



//出力が4つあるマッパー関数
public class FourOutputFunction extends MultiOutputFunction {

    public FourOutputFunction() {
        setSubValueCount(3);
    }
    
    public void execute(ExecuteContext context, Value[] in, Value out) {
        out.setValue("a");
        getSubValue(0).setValue("b");
        getSubValue(1).setValue("c");
        getSubValue(2).setValue("d");
    }
}

JavaInterpreterのようにプロパティ値によって動的に出力数の変わるマッパー関数ではコンストラクタでbDynamicをtrueにした上で postCompileメソッドで出力数を設定します。



//JavaInterpreter(抜粋)
public class JavaInterpreter extends MultiOutputFunction
{

	private static final String PROPERTY_COUNT = "Count";

	public JavaInterpreter() {
		super(true);
	}
	
	protected void postCompile(AbstractCompiler compiler) {
		int count = (int)getPropertyInteger(PROPERTY_COUNT);
		if (count > 1) 
			setSubValueCount(count);
	}
    
    ...
}

MultiOutputFunctionではメインの出力とサブ出力の複数の出力コネクタが画面に表示されるわけですが、そのうちのどれかひとつのコネクタに対してリンクがあればその関数は実行されます。
例えばDateSplit関数で曜日(4番目の出力コネクタ)にのみリンク線を繋いで、残りのコネクタは空であっても何の問題もありません。

このことをさらに進めて考えると「空のコネクタは画面に表示されていなくても良い」ということになります。

DateSplit関数もプロパティ値によって動的に出力数が変わる関数ですが、こちらはJavaInterpterとは違いコンストラクタ内で静的に出力数を設定しています。(サンプル参照)
つまり実際にはDateSplit関数の出力は常に8つあるわけですが、定義ファイルの設定で画面に4つしか見えてないことがあるということです。
もちろんJavaInterpreterのように動的にコネクタ数の増減する関数として作成することもできますが、そこは好みの問題でありどちらを選択した場合でもほとんど差はありません。

5.1.1 定義ファイル

デザイナー側でプロパティ値に連動して出力数を変更するためにはSimplePropertyControllerかFunctionOutputPropertyListenerを使用します。

DateSplit関数のようにあるプロパティの設定値によって出力数が固定的に決まる場合はSimplePropertyControllerで値に応じたOutput要素を定義します。

JavaInterpreter関数やSplit関数のようにあるプロパティの設定値がそのまま出力数になる関数の場合はFunctionOutputPropertyListenerをそのプロパティに対して設定します。

いずれの場合も初期状態の出力数に応じてFunction定義のOutput要素を定義しなければなりません。
(DateSplit関数では初期状態の出力数は「4」、Split関数では初期状態の出力数は「2」です。)

6 ライセンスチェック

マッパー関数でライセンスチェックを行いたい場合は、ライセンスチェックに必要な情報、 例えばライセンスファイルはマッパー関数のjarファイルと同じ場所に置くことを推奨します。
マッパー関数のjarファイルは、

[data dir]/system/lib/components

に配置しますので、ライセンスファイルも同じフォルダーに配置します。

実際のライセンスチェックの処理は、Function#checkLicenseをオーバーライドして実装します。

/** ライセンスチェック処理を記述します */
public void checkLicense() throws LicenseException {
}

このメソッドの中でFunction#getComponentLibPathメソッドを実行すると前述のcomponentsフォルダーのパスを取得できます。 これにより、同じフォルダーに配置したライセンスファイルを取得することができます。
ライセンスファイルを取得したら、後はライセンスチェック処理を実装します。
もしもライセンス違反となった場合は、LicenseExceptionをthrowします。

ライセンスチェックの基本的な実装は次のようになります。

public void checkLicense() throws LicenseException {
    // ライセンスファイルを取得します
    File licenseFile = new File(getComponentLibPath(), "ライセンスファイル名");

    // licenseFileを読み込んでライセンスチェックを行います

    // ライセンス違反の場合にはLicenseExceptionをthrowします
    if (licenseError) {
        throw new LicenseException();
    }
}

どのような方法でライセンスチェックをする場合でも、ライセンス違反となった場合にはLicenseExceptionをthrowするようにしてください。

7 サンプル

サンプルとして標準で提供されているマッパー関数のいくつかのソースを公開します。