フローサービスでは独自のマッパー関数をJavaで開発し追加することが可能になっています。
マッパー関数開発を行う場合、以下の作業が必要になります。
jarファイルの作成に関しては実際にはJavaInterpreter関数付属のSDKウィザードでほぼ自動化されています。
ここではFunctionクラスのソースコードの書き方に絞って説明します。
定義ファイルの作成に関しては定義ファイルリファレンスを、
jarファイルの作成とそのインストールに関してはツールガイドを参照してください。
マッパー関数の開発はJDK8.0以降の環境で行ってください。
また、「DESIGNER_HOME/lib」にある以下のjarファイルにクラスパスを通す必要があります。
作成するマッパー関数の内容によってはさらに別のjarファイルがコンパイル/実行に必要になる場合があります。
その場合、flow-ctrlのshowlibコマンドを使用することで必要とするクラスがどのjarに含まれているかを確認することができます。
//showlibコマンドによって指定のクラスがどのjarに含まれているかが表示されます。 >showlib com.infoteria.asteria.flowlibrary2.stream.StreamDataXML jar:file:C:\Program Files\asteriawarp\flow\lib\flowengine2.jar!/com/infoteria/asteria/flowlibrary2/stream/StreamDataXML.class
必要に応じてここで表示されたjarファイルにもクラスパスを通してください。
実際にはウィザードで生成されるANT用のbuild.xmlでは「DESIGNER_HOME/lib/**/*.jar」にクラスパスが通っているので、通常はbuild.xmlの設定を変更する必要はありません。
Valueとはフローサービスの基本データ型をラップするバリアント型の変数クラスのことです。
このクラスを使用することで変数の値をStringとして取り出したり、intとして取り出したりということが簡単にできます。
マッパー関数では入力されたフィールドの値がValueの配列として取得されます。
メソッドが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]); }
マッパー関数名(getFunctionNameメソッドの返り値)には「<会社名>.」というプレフィクスをつけてください。
Infoteria以外の会社が「.」を含まない名前を関数名に使用することは禁止事項とします。
以下のメソッドは必要に応じてオーバーライドします。
getMinInputCount()/getMaxInputCount()以外のメソッドはほとんどの場合実装する必要はありませんが、
処理を行うタイミングをコントロールすることでパフォーマンスが向上する可能性があります。
マッパー関数もコンポーネントと同じく、フローのリクエスト時にcloneメソッドによって複製が作られますが、その実装は
コンポーネントとは異なり、Object#cloneメソッドにより実装されています。
つまりpostCompileメソッド内でインスタンス変数に設定したObjectは複製時にポインタがコピーされます。
上記init/termメソッドの説明で「通常はマッパー関数の...時に一度だけ呼び出されます。」とありますが、
マッパー関数のinit/termメソッドはコンポーネントのそれと異なり実行時に複数回実行されることがあり得ます。
それは以下の場合です。
この場合マッパーコンポーネントのinitメソッドの実行がエラーになったことになるので、コンポーネントの仕様的には termメソッドは実行されません。(コンポーネント開発者ガイド#termメソッドを参照)
ですが、マッパーコンポーネントの場合既にinitを実行したマッパー関数のクリーンアップを行わなければならないのでinitメソッド内で
エラーが発生した場合はinitを実行した関数についてtermを実行してからエラーをthrowします。
(もちろんここで終了した場合はフロー終了時にこのマッパーコンポーネントのtermメソッドは実行されないのでそのタイミングで
マッパー関数のtermメソッドが実行されることもありません。)
こうしたエラーが発生した場合にさらに
には、その中にあるマッパー関数のinitメソッドが再度実行されることになります。
(ほとんどの場合は先にエラーとなった個所で同じようにエラーとなるのでこのようなフローはほぼ100%フロー設計の誤りです。)
実際のところでいえばinitとtermを常に対で作成し、initで初期化したものをtermでクリーンアップするように心がけておけば この動作が問題となることはほとんどありません。
プロパティの登録は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); }
プロパティ値を取得するには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);
MultiOutputFunctionとは出力が複数あるマッパー関数のことです。
出力がひとつだけの通常のマッパー関数とは次の点が異なります。
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のように動的にコネクタ数の増減する関数として作成することもできますが、そこは好みの問題でありどちらを選択した場合でもほとんど差はありません。
デザイナー側でプロパティ値に連動して出力数を変更するためにはSimplePropertyControllerかFunctionOutputPropertyListenerを使用します。
DateSplit関数のようにあるプロパティの設定値によって出力数が固定的に決まる場合はSimplePropertyControllerで値に応じたOutput要素を定義します。
JavaInterpreter関数やSplit関数のようにあるプロパティの設定値がそのまま出力数になる関数の場合はFunctionOutputPropertyListenerをそのプロパティに対して設定します。
いずれの場合も初期状態の出力数に応じてFunction定義のOutput要素を定義しなければなりません。
(DateSplit関数では初期状態の出力数は「4」、Split関数では初期状態の出力数は「2」です。)
マッパー関数でライセンスチェックを行いたい場合は、ライセンスチェックに必要な情報、
例えばライセンスファイルはマッパー関数のjarファイルと同じ場所に置くことを推奨します。
マッパー関数のjarファイルは、
[install dir]/flow/lib/flowlib
に配置しますので、ライセンスファイルも同じフォルダーに配置します。
実際のライセンスチェックの処理は、Function#checkLicenseをオーバーライドして実装します。
/** ライセンスチェック処理を記述します */ public void checkLicense() throws LicenseException { }
このメソッドの中でFunction#getFlowlibPathメソッドを実行すると前述のflowlibフォルダーのパスを取得できます。
これにより、同じフォルダーに配置したライセンスファイルを取得することができます。
ライセンスファイルを取得したら、後はライセンスチェック処理を実装します。
もしもライセンス違反となった場合は、LicenseExceptionをthrowします。
ライセンスチェックの基本的な実装は次のようになります。
public void checkLicense() throws LicenseException { // ライセンスファイルを取得します File licenseFile = new File(getFlowlibPath(), "ライセンスファイル名"); // licenseFileを読み込んでライセンスチェックを行います // ライセンス違反の場合にはLicenseExceptionをthrowします if (licenseError) { throw new LicenseException(); } }
どのような方法でライセンスチェックをする場合でも、ライセンス違反となった場合にはLicenseExceptionをthrowするようにしてください。
サンプルとして標準で提供されているマッパー関数のいくつかのソースを公開します。