プラグインとはデザイナーの動作を拡張し、コンポーネントやマッパー関数のプロパティ設定などの補助をするモジュールのことです。
コンポーネントやマッパー関数のクラスがフローの実行時にサーバー側で動作するモジュールであるのに対し、
プラグインはフローの設計時にデザイナー側で動作するのでフローの実行には影響を与えません。
プラグインはコンポーネント/マッパー関数の作成で必ず作らなければならないというものではありませんが、
多機能なコンポーネント/マッパー関数を作成する場合プラグインを作成するとデザイナーの操作性が向上し、
フロー作成の効率が大幅にアップすることもあります。
プラグイン作成では多くのクラスを使用します。
それらのクラス群はサーバーで使用しているクラスと同一の場合もありますが、ほとんどの場合プラグインで使用する
クラスとサーバーで使用するクラスは全く別物です。
たとえばプラグインでも「Component」というクラスを使用しますが、そのパッケージは
com.infoteria.asteria.flowbuilder2.component.Component
であり、サーバーで使用するクラス
com.infoteria.asteria.flowengine2.flow.Component
とは全く別のクラスです。
後者はサーバー上で実際にコンポーネントの行う処理を実行するクラスですが、
前者はデザイナーで使用されるxfpに保存されているコンポーネントの内容をラップしたクラスです。
プラグインの開発はJDK6.0以降の環境で行ってください。
また、「DESIGNER_HOME/lib」にある以下のjarファイルにクラスパスを通してください。
ほとんどの場合プラグインはそれを使用するサーバー側のクラスと同時に開発され、同じjarファイルに含められます。
そのため実際にはコンポーネント(またはマッパー関数)の作成に必要な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の設定を変更する必要はありません。
現在作成可能なプラグインの種類は次の4つです。
以下にそれぞれのプラグインの概要を説明します。
コンポーネントやマッパー関数がどのようなプロパティを持っているかは、 定義ファイルのProperty要素で定義されます。
プロパティの型は定義ファイル内でtype属性によって示されます。
int型のプロパティの場合は数値しか入力できす、
choice型のプロパティではドロップダウンリストからプロパティ値が選択できるようになっています。
またdate型やdatetime型のプロパティのように日付を選択するカレンダーが表示されるものもあります。
つまりプロパティの型によって値を設定するためのUIが決まっています。
そのプロパティ値を設定するためのUIがプロパティエディタです。
通常プロパティ型とプロパティエディタは1対1で対応していますが、 string型のようにeditor属性でプロパティエディタを選択できるものもあります。
| editor属性値 | プロパティエディタ |
|---|---|
| デフォルト(省略時) | 1行テキストダイアログ |
| multiline | 複数行テキストダイアログ |
| condition | 条件式の補完機能つき1行テキストダイアログ |
もっともわかりやすい例はBranchStartコンポーネントの条件式プロパティで使用されている
「condition」エディタでしょう。
このエディタは「$」や「.」をタイプした時に条件式の補完をしてくれます。
このようにプロパティ値を設定するための補助的な機能を付加したUIを自作できるプラグインが
プロパティエディタです。
プロパティリスナーはJavaプログラムで日常的に使用されるEventListenerの一種で、 プロパティ値の変更に反応するリスナーです。
プロパティ値の設定前後でイベントが発生するのでそこで何かしらの処理を行うことができます。
PropertyListenerの典型的な実装例としては、プロパティ値の変更に伴い別のプロパティの表示状態など をコントロールできるSimplePropertyControllerがあります。
コンポーネントエディタとは複数のプロパティ値やフィールド定義などをまとめて設定できるように
したものです。
コンポーネントエディタを使用している例としては
などがあります。
外部アプリケーション起動はその名のとおり外部アプリケーションを起動するためのプラグインです。
外部アプリケーション起動での処理の流れは以下のようになります。
外部アプリケーション起動を使用した例としてはExcelBuilder、PDFBuilderなどがあります。
プロパティエディタを作成するためにはPropertyEditorインターフェース
を実装したクラスを作成します。
このインターフェースはSwingのTableCellEditorインターフェースを拡張し、setPropertyメソッドを
追加したものです。
新しいPropertyEditorを作成する場合にインターフェースのメソッドをすべて自分で作成しなければ ならないケースはほとんどなく、通常は次のButtonTextCellEditorかButtonCellEditorを継承して作成します。
ButtonTextCellEditorはインスペクタの編集エリア内にテキストフィールドがあり、
その脇に「...」という小さなボタンがあるタイプのプロパティエディタです。
ボタンをクリックすることでそのプロパティ値を設定するための補助UI(ほとんどの場合はダイアログ)
が起動します。
ButtonTextCellEditorを継承したPropertyEditorの例としては
などがあります。
ButtonTextCellEditorを作成する場合実装しなければならないメソッドはdoButtonActionメソッド のみであり、そこにボタンがクリックされた場合の処理を記述します。
ButtonCellEditorはインスペクタの編集エリア全体がボタンで覆われたタイプのプロパティエディタです。
つまりインスペクタ内で値を設定することはできず、編集エリアをクリックすることでそのプロパティ値を設定するための補助UI(ほとんどの場合はダイアログ)
が起動します。
ButtonCellEditorを継承したPropertyEditorの例としては
などがあります。
ButtonCellEditorを作成する場合実装しなければならないメソッドはdoButtonActionメソッド のみであり、そこにボタンがクリックされた場合の処理を記述します。
ButtonTextCellEditorでもButtonCellEditorでもdoButtonActionメソッドの実装内容はほとんど同じになります。
典型的な例としてはダイアログを表示してそこでOKボタンがクリックされた場合にプロパティに値を設定します。
sampleフォルダに1行テキストダイアログの替わりに履歴機能つきComboBoxを使用するサンプル
(HistoryPropertyEditor.java)があります。
以下にそのコードの中からdoButtonActionの部分のポイントを説明します。
(コード中の「_combo」は履歴機能付きのComboBox、「_file」は履歴を保存するFileです。
完全なコードはsampleフォルダ内にあるjavaファイルを参照してください。)
/**
* ボタンが押された時の処理
* 履歴機能付きComboBoxの載ったダイアログを表示
*/
protected void doButtonAction(EventObject e) {
String value = getProperty().getValueAsString(); //1
_combo.setSelectedItem(value);
Frame owner = PluginUtil.getApplicationFrame(); //2
String title = getDialogTitle(); //3
MyDialog dlg = new MyDialog(owner, title);
try {
dlg.setLocationRelativeTo(owner);
dlg.setVisible(true);
if (dlg.isOK()) { //DialogがOKボタンで終わったか?
value = (String)_combo.getSelectedItem();
_combo.updateHistory();
setCellEditorValue(value); //4
stopCellEditing(); //5
if (_file != null)
_combo.saveToFile(_file);
} else
cancelCellEditing(); //6
} catch (IOException ex) {
ex.printStackTrace(); //7
} finally {
dlg.dispose();
}
}
作成したプロパティエディタを定義ファイルに組み込むには対象のProperty要素にeditorClass属性で指定します。
<Property displayName="ファイルパス" name="FilePath" required="true" toolTip="FilePath" type="string"
editorClass="com.infoteria.asteria.flowbuilder2.plugin.HistoryPropertyEditor" filename="exe.txt"/>
HistoryPropertyEditorはStringを扱うプロパティエディタですので、対象となるプロパティはstring型
またはその派生型でなければなりません。
またここでは説明しませんでしたがMetaDataインターフェースを実装しているので、
追加の属性としてfilename属性を定義しています。
MetaDataインターフェースについては後述します。
フローデザイナーでのフロー作成操作はすべてUndo/Redoを行うことができます。
次節以降で説明するプロパティリスナーやコンポーネントエディタを作成する場合にはそこで行った変更がUndo可能となるように
注意しなければなりません。
フローデザイナーのUndoはデザインパターンのCommandパターンによって実装されており、その実装クラスとしてSwingのUndoableEditを使用しています。
編集操作の場合にはその操作に対応するUndoableEditを作成しそれをUndoManagerに登録しなければなりません。
(ただし実際のUndoManagerへの登録はフローデザイナーが行うのでプラグイン開発者が直接UndoManagerのメソッドを実行することはありません。)
例えば「プロパティ値の設定」はUndo可能な操作です。
フローデザイナーで使用しているPropertyクラスではそのsetValueメソッドでUndoableEditを返すので、
UndoManagerに登録するUndoableEditとしてはそれをそのまま使用すればOKです。
複数の編集操作をひとまとめにして一つのUndo単位としたい場合があります。
例えば
のような場合です。
これらをハンドルするイベント(PropertyChangeEventまたはBaseObjectUndoableEvent)にはUndo可能な操作を合成するための
addUndoメソッドがあるので、行った編集操作に対応するUndoableEditをそのメソッドに渡せば適切にUndoが合成されます。
プロパティリスナーを作成するためにはPropertyChangeListenerインターフェース
を実装したクラスを作成します。
(ここでいうPropertyChangeListenerインターフェースはJavaBeansのPropertyChangeListenerではなく、
弊社独自のクラスです。)
このリスナーでは
の2つのイベントをハンドルすることができます。
propertyChangingではPropertyChangeVetoExceptionをthrowすることで、 プロパティ値の設定をキャンセルすることもできますので、 例えばプロパティ値の設定前に値をチェックして不正な場合はエラーとするようなプロパティリスナーを 作成することができます。
sampleフォルダに正規表現でプロパティ値をチェックして、
値が不正な場合にはエラーメッセージを表示した上で設定をキャンセルする
サンプル(RegexCheck.java)があります。
以下にその完全なコードを示しポイントを説明します。
package com.infoteria.asteria.flowbuilder2.plugin;
import com.infoteria.gui.property.event.PropertyChangeEvent;
import com.infoteria.gui.property.event.PropertyChangeListener;
import com.infoteria.gui.property.event.PropertyChangeVetoException;
import com.infoteria.gui.util.MetaData;
import org.w3c.dom.Element;
/**
* Property値の変更前に正規表現によるチェックを行うPropertyListenerです。
*/
public class RegexCheck implements PropertyChangeListener, MetaData, Cloneable {
private static final String A_REGEX = "regex";
private String _regex;
//MetaData //MetaDataインターフェースについては次章で説明します。
public void setup(Element el) {
_regex = el.getAttribute(A_REGEX);
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
//PropertyChangeListener
public void propertyChanged(PropertyChangeEvent e) { //1
}
public void propertyChanging(PropertyChangeEvent e) throws PropertyChangeVetoException {
Object o = e.getNewValue(); //2
if (o instanceof String) {
String str = (String)o;
if (str.length() == 0)
return;
if (!str.matches(_regex)) {
PluginUtil.showError("Invalid value: " + str); //3
throw new PropertyChangeVetoException(); //4
}
}
}
}
作成したプロパティリスナーを定義ファイルに組み込むにはPropertyListener要素を追加し、 class属性でクラス名を、target属性で対象となるプロパティ名で指定します。
<!-- メールアドレス形式のチェック -->
<PropertyListener class="com.infoteria.asteria.flowbuilder2.plugin.RegexCheck"
target="From" regex="[a-zA-Z0-9_\.\-]+@[A-Za-z0-9_\.\-]+"/>
RegexCheckはMetaDataインターフェースを実装しているので、
追加の属性としてregex属性を定義しています。
MetaDataインターフェースについては次章で説明します。
PropertyEditorやPropertyChangeListenerを実装したクラスがさらにMetaDataインターフェースを 実装していた場合、そのクラスでは追加の定義情報を定義ファイルから読み込めるようになります。
定義ファイルの解析時にそれが定義された要素を引数としてMetaDataインターフェースのsetupメソッド が実行されるのでその情報を読み出すことができるのです。
ここまでに説明したふたつのサンプルではいずれもMetaDataインターフェースを実装し、
追加の定義情報を要素の属性値から取得していました。
サンプルには現れませんでしたが、要素に子要素を定義してさらに複雑な構造体を定義情報として
持たせることも可能です。
MetaDataインターフェースではもうひとつcloneメソッドも実装する必要がありますが、 プラグインではほとんどの場合DeepCopyを行う必要がないので単純にCloneableを宣言するだけで大丈夫です。
この先で紹介するコンポーネントエディタと外部アプリケーション起動のふたつのプラグインでは 基底となるクラスでMetaDataインターフェースが実装されているので、 setupメソッドをオーバーライドする場合はその先頭で「super.setup(el);」を実行しなければなりません。
プロパティの編集と同時にストリーム情報を編集するためにはまず編集対象のストリーム定義(StreamDefinition)を
取得しなければなりません。
デザイナー上ではストリーム定義はコンポーネント定義の内容に応じてコネクタに関連付けられています。
プロパティリスナーからストリーム定義を取得するためのコードは以下のようになります。
public void propertyChanged(PropertyChangeEvent e) {
Component c = (Component)e.getProperty().getOwner();
//出力コネクタからストリーム定義を取得する場合
//分岐がある場合はOutputConnectorSetは複数存在し、サブコネクタはConnectorSetのgetSubConnectorメソッドで取得できます。
ComponentOutputConnector ocon = (ComponentOutputConnector)c.getOutputConnectorSet(0).getDefaultConnector();
StreamDefinition outSd = ocon.getStreamDefinition();
//入力コネクタからストリーム定義を取得する場合
ComponentInputConnector icon = (ComponentInputConnector)c.getInputConnectorSet().getDefaultConnector();
StreamDefinition inSd = icon.getStreamDefinition();
}
コンポーネントとコネクターの関係についての考え方はコンポーネント開発者ガイドのコンポーネントの構成要素の章を参考にしてください。
(ただしサーバー側とデザイナー側では考え方は同じですが実際に使用するクラスは異なります。)
操作対象のストリームがデフォルトコネクタ(最初の入出力コネクタ)のものである場合はコネクタの取得にはComponentクラスの
getDefaultOutputConnector(またはgetDefaultInputConnector)メソッドを使用することもできます。
コンポーネント定義によって入力ストリームをそのまま出力する、または別のコネクタを参照している場合でもComponentOutputConnector#getStreamDefinitionメソッド ではリンクをたどってそこから出力されるストリームのStreamDefinitionが取得されます。
以下にプロパティ変更に連動して出力ストリームのフィールド定義を変更するサンプルを示します。
import com.infoteria.asteria.flowbuilder2.component.Component;
import com.infoteria.asteria.flowbuilder2.component.ComponentOutputConnector;
import com.infoteria.asteria.flowbuilder2.plugin.PluginUtil;
import com.infoteria.asteria.flowbuilder2.stream.StreamDefinition;
import com.infoteria.asteria.flowbuilder2.stream.field.Field;
import com.infoteria.asteria.flowbuilder2.stream.field.FieldDefinition;
import com.infoteria.gui.property.event.PropertyChangeEvent;
import com.infoteria.gui.property.event.PropertyChangeListener;
import com.infoteria.gui.property.event.PropertyChangeVetoException;
import java.util.ArrayList;
import java.util.List;
/**
* プロパティ変更に連動してストリームのフィールド定義を変更します。
*/
public class SetField implements PropertyChangeListener {
public void propertyChanged(PropertyChangeEvent e) {
//ロード中はスキップ
Component c = (Component)e.getProperty().getOwner();
if (c.isLoading())
return;
//Undo中はスキップ
if (PluginUtil.inUndoProcess())
return;
List fieldNames = new ArrayList();
String value = (String)e.getNewValue();
if ("Pattern1".equals(value)) {
fieldNames.add("aaa");
fieldNames.add("bbb");
fieldNames.add("ccc");
} else if ("Pattern2".equals(value)) {
fieldNames.add("field1");
fieldNames.add("field2");
fieldNames.add("field3");
fieldNames.add("field4");
fieldNames.add("field5");
}
if (fieldNames.size() > 0)
doFieldChange(e, fieldNames);
}
public void propertyChanging(PropertyChangeEvent e) {
}
private void doFieldChange(PropertyChangeEvent e, List fieldNames) {
Component c = (Component)e.getProperty().getOwner();
StreamDefinition sd = ((ComponentOutputConnector)c.getDefaultOutputConnector()).getStreamDefinition();
FieldDefinition fd = sd.getFieldDefinition();
FieldDefinition fd2 = new FieldDefinition();
for (int i=0; i<fieldNames.size(); i++) {
String name = (String)fieldNames.get(i);
Field f = fd2.createField(name);
fd2.add(f);
}
e.addUndo(fd.importFieldDefinition(fd2, true));
}
}
フィールド定義を丸ごと差し換える場合は新しいFieldDefinitionを作成してそこで構築したField定義を元のFieldDefinitionにimportFieldDefinitionします。
フィールド定義の変更はUndoが必要な編集操作なのでPropertyChangeEvent#addUndoメソッドにUndo情報を渡しています。
あと注意が必要なのはpropertyChangedイベントの先頭で
としている点です。
プロパティ変更のイベントはロード中やUndo中であっても発生します。
これらの場合に処理を行うかどうかは処理内容によって判断する必要があります。
コンポーネントエディタはComponentEditorクラスのサブクラスとして作成します。
ComponentEditorは次のような処理が実装されています。
doActionメソッドはabstractメソッドとして定義されており、 プラグイン開発者がコンポーネントエディタの作成で実装しなければならないメソッドはこのメソッドのみです。
sampleフォルダのfcsample以下に標準のメールコンポーネントのプロパティをダイアログ上でまとめて 設定できるようにしたコンポーネントエディタのサンプルが入っています。
このサンプルコードには以下の処理が含まれています。
このサンプルで行っていることは基本的にはプロパティ値をダイアログのパーツにわりあてているだけであり、 ダイアログがOKボタンで終了した時にダイアログ上の値をそれぞれのプロパティに書き戻しています。
作成したコンポーネントエディタを定義ファイルに組み込むにはListener要素を追加し、 class属性でクラス名を、menuItem属性で右クリックメニューに追加するキャプションを指定します。
<!-- メールアドレス形式のチェック -->
<Listener class="com.infoteria.asteria.sample.plugin.MailEditor"
menuItem="プロパティの編集"/>
このサンプルでは使用していませんが、setupメソッドをオーバーライドして
独自の定義項目を追加することも可能です。
(setupメソッドをオーバーライドする場合は先頭でsuper.setupを行ってください。)
汎用コネクションは任意の名前と値のセットをコネクション定義として保存しておける仕組みです。
これを利用して特殊な接続方法を使用するプロダクトと連携するようなコンポーネントを作成することができます。
(汎用コネクションについての説明はコンポーネント開発者ガイドにもあります。)
pluginCallはサーバー側で実装したコードをデザイナーから呼び出して何らかの情報を返すための仕組みです。
これらを組み合わせることで例えば特定のプロダクトからAPI経由で情報を取得し、デザイナーにその情報を反映
させるようなプラグインを作成することができます。
通常プロダクトのAPIの実行のためには専用のjarファイルが必要となるため、デザイナーから直接APIを実行するように
作成した場合jarファイルの配置等のインストール作業が煩雑になりますが、この方法であればプロダクトのjarファイル
はサーバー側だけに配置すれば良くなります。
サーバー側でのpluginCallの実装はComponentクラスのpluginCallメソッドをオーバーライドすることで行います。
サーバー側コードのサンプルを以下に示します。
このサンプルではデザイナーからのリクエストが「Tables」であるか「Columns」であるかによって、処理を振り分けテーブル一覧または列一覧を返しています。
(実際の処理としては汎用コネクションに設定された情報を元にAPIでそれらの情報を取得する想定ですが、ここではダミーの情報を返しています。)
import com.infoteria.asteria.connection.CommonConnectionEntry;
import com.infoteria.asteria.flowengine2.execute.ExecuteContext;
import com.infoteria.asteria.flowengine2.execute.TestContext;
import com.infoteria.asteria.flowengine2.flow.InputConnector;
import com.infoteria.asteria.flowengine2.soap.PluginRequest;
import com.infoteria.asteria.flowengine2.soap.PluginResponse;
import com.infoteria.asteria.flowlibrary2.component.SimpleComponent;
import com.infoteria.asteria.flowlibrary2.component.ComponentException;
import com.infoteria.asteria.flowlibrary2.FlowException;
import com.infoteria.asteria.flowlibrary2.stream.StreamType;
import com.infoteria.asteria.util.xml.DOMUtil;
import com.infoteria.asteria.value.Value;
import com.infoteria.asteria.value.VariableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* 汎用コネクションの情報を使用して
* テーブル一覧とテーブルの列一覧を取得するサンプル
*/
public class PluginCallComponent extends SimpleComponent {
public static final String COMPONENT_NAME = "PluginCallSample";
public String getComponentName() { return COMPONENT_NAME;}
public PluginCallComponent() {
getInputConnector().setAcceptLinkCount(1);
getInputConnector().setAcceptType(StreamType.ALL);
getOutputConnector().setAcceptType(StreamType.ALL);
}
public boolean execute(ExecuteContext context) throws FlowException {
//プラグインのサンプルのため実際のコンポーネント実行コードは未実装
passStream();
return true;
}
public PluginResponse pluginCall(TestContext context, PluginRequest request) throws FlowException {
try {
VariableList param = request.getParams();
//パラメータから実行する処理を取得
Value method = param.getValue("Method");
if (method == null || !(method.strValue().equals("Tables") || method.strValue().equals("Columns")))
throw new ComponentException("Unknown method: " + method);
//パラメータから汎用コネクションを取得
Value conName = param.getValue("Connection");
if (conName == null)
throw new ComponentException("Connection not found: " + conName);
CommonConnectionEntry con = (CommonConnectionEntry)context.getConnectionEntry(CommonConnectionEntry.TYPE, conName.strValue());
if (con == null)
throw new ComponentException("Connection not found: " + conName);
List list = null;
if ("Tables".equals(method.strValue()))
list = getTables(con);
else {//Columns
Value tableName = param.getValue("TableName");
if (tableName == null)
throw new ComponentException("Table not found: " + tableName);
list = getTableColumns(con, tableName.strValue());
}
//結果をXML形式で生成
//※1 DOMUtilはXMLを扱うためのユーティリティクラス。DOMはJAXPのAPIを使用して作成
//※2 名前空間がない場合でもElementの作成にはNamespaceAPIを使用する
Document doc = DOMUtil.newInstance().createDocument();
Element root = doc.createElementNS(null, method.strValue());
doc.appendChild(root);
for (int i=0; i<list.size(); i++) {
String name = (String)list.get(i);
Element child = doc.createElementNS(null, "Name");
child.appendChild(doc.createTextNode(name));
root.appendChild(child);
}
return new PluginResponse(doc);
} catch (FlowException e) {
throw e;
} catch (Throwable e) {
//デバッグ用
context.error(e);
return new PluginResponse(e.toString());
}
}
private List getTables(CommonConnectionEntry con) throws FlowException {
List ret = new ArrayList();
Map map = con.getParameterMap();
if (map == null)
throw new ComponentException("Connection not defined");
//mapに設定されているパラメータから対象プロダクトとの接続に必要な情報を取得
String host = (String)map.get("Host");
String strPort = (String)map.get("Port");
//... ここで接続情報を元に対象プロダクトから情報を取得して返す
//ここではダミーを返している
ret.add("Table1");
ret.add("Table2");
ret.add("Table3");
return ret;
}
private List getTableColumns(CommonConnectionEntry con, String tableName) throws FlowException {
//ToDo getTablesと同様に接続情報を元に対象プロダクトから情報を取得して返す
//ここではダミーを返している
List ret = new ArrayList();
ret.add(tableName + "_Col1");
ret.add(tableName + "_Col2");
ret.add(tableName + "_Col3");
return ret;
}
}
pluginCallのリクエストはPluginRequest、レスポンスはPluginResponseというクラスを使用して
作成します。
実際にはPluginResponseはXMLをラップしているだけですので作り方次第でどのような情報でもデザイナー側に返すことができます。
デザイナー側ではPluginUtil#pluginCallメソッドでサーバー側のコードを呼び出します。
返り値のXMLをパースすることで情報を取得してダイアログを表示したり、フィールド定義を設定したりすることができます。
先のサーバー側コードを呼び出すデザイナー側のサンプルを以下に示します。
このサンプルではコンポーネントダブルクリック時にテーブル一覧を表示し、選択されたテーブルの列情報をコンポーネントの
入力ストリームに設定しています。(RDBPutライクなコンポーネントを想定しています。)
import com.infoteria.asteria.flowbuilder2.component.Component;
import com.infoteria.asteria.flowbuilder2.event.BaseObjectUndoableEvent;
import com.infoteria.asteria.flowbuilder2.plugin.ComponentEditor;
import com.infoteria.asteria.flowbuilder2.plugin.PluginUtil;
import com.infoteria.asteria.flowbuilder2.resource.UI;
import com.infoteria.asteria.flowbuilder2.stream.field.FieldDefinition;
import com.infoteria.asteria.flowengine2.soap.PluginRequest;
import com.infoteria.asteria.flowengine2.soap.PluginResponse;
import com.infoteria.asteria.util.xml.DOMUtil;
import com.infoteria.gui.property.Property;
import com.infoteria.gui.property.PropertyException;
import com.infoteria.gui.util.SpringLayoutUtil;
import com.infoteria.asteria.value.Value;
import com.infoteria.asteria.value.VariableList;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.FlowLayout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.infoteria.asteria.flowbuilder2.component.ComponentInputConnector;
import com.infoteria.asteria.flowbuilder2.dialog.MessageDialog;
/**
* コンポーネントダブルクリック時にテーブル選択のダイアログを表示し、
* 選択したテーブルの列情報を入力のフィールド定義に設定するサンプル
*/
public class PluginCallEditor extends ComponentEditor {
/**
* コンポーネントエディタが実際に行う処理
* @param e イベントオブジェクト
* @param bEditable コンポーネントが編集可能かどうか
*/
protected void doAction(BaseObjectUndoableEvent e, boolean bEditable) {
//イベントオブジェクトから対象のコンポーネントを取得するには
//BaseObjectUndoableEvent#getBaseObjectメソッドを使用する
//マッパー関数の場合はこのメソッドの返り値はFunctionになる
Component c = (Component)e.getBaseObject();
//Componentからのプロパティの取得はComponent#getPropertyメソッドを使用する
Property connectionProp = c.getProperty("Connection");
String conName = connectionProp.getValueAsString();
if (conName == null || conName.length() == 0) {
MessageDialog.showError("コネクションが指定されていません");
return;
}
//コンポーネントに渡すパラメータの設定
PluginRequest request = new PluginRequest("PluginCallSample");
VariableList param = new VariableList();
param.putValue("Method", new Value("Tables"));
param.putValue("Connection", new Value(conName));
request.setParams(param);
try {
PluginResponse res = PluginUtil.pluginCall(request);
List list = buildList(res.getDocument());
MyDialog dlg = new MyDialog(PluginUtil.getApplicationFrame(), "Sample", list);
try {
dlg.setVisible(true);
if (!dlg.isOK())
return;
//選択されたテーブルの列一覧をpluginCallから取得する
String tableName = dlg.getTableName();
param.clear();
param.putValue("Method", new Value("Columns"));
param.putValue("Connection", new Value(conName));
param.putValue("TableName", new Value(tableName));
res = PluginUtil.pluginCall(request);
FieldDefinition fd = ((ComponentInputConnector)c.getDefaultInputConnector()).getStreamDefinition().getFieldDefinition();
FieldDefinition fd2 = buildFieldDefinition(buildList(res.getDocument()));
e.addUndo(fd.importFieldDefinition(fd2));
} finally {
dlg.dispose();
}
} catch (IOException ex) {
MessageDialog.showError(ex);
}
}
/**
* 文書要素以下の要素の要素内容をListにする
*/
private List buildList(Document doc) {
List ret = new ArrayList();
Node node = doc.getDocumentElement().getFirstChild();
while (node != null) {
if (node.getNodeType() == Node.ELEMENT_NODE)
ret.add(DOMUtil.getChildText((Element)node));
node = node.getNextSibling();
}
return ret;
}
/**
* Listの文字列をフィールド名としてFieldDefinitionを作成する
*/
private FieldDefinition buildFieldDefinition(List list) {
FieldDefinition fd = new FieldDefinition();
for (int i=0; i<list.size(); i++) {
String name = (String)list.get(i);
fd.add(fd.createField(name));
}
return fd;
}
/**
* コンボボックスでテーブルを選択させるダイアログ
*/
private static class MyDialog extends JDialog {
private boolean _bOK = false;
private JButton _btnOK = new JButton(UI.OK);
private JButton _btnCancel = new JButton(UI.CANCEL);
private JComboBox _cmbTables;
public MyDialog(JFrame owner, String title, List list) {
super(owner, title, true);
JPanel main = new JPanel(new FlowLayout());
main.add(new JLabel("テーブル名: "));
_cmbTables = new JComboBox(list.toArray());
Dimension d = _cmbTables.getPreferredSize();
d.width += 20;
_cmbTables.setPreferredSize(d);
main.add(_cmbTables);
getContentPane().add(main);
getContentPane().add(createFooterPanel(), BorderLayout.SOUTH);
pack();
setLocationRelativeTo(owner);
}
private JPanel createFooterPanel() {
Action a = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
_bOK = e.getSource() == _btnOK;
setVisible(false);
}
};
_btnOK.addActionListener(a);
_btnCancel.addActionListener(a);
JPanel panel = new JPanel(new FlowLayout());
panel.add(_btnOK);
panel.add(_btnCancel);
getRootPane().setDefaultButton(_btnOK);
InputMap imap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Close");
ActionMap amap = getRootPane().getActionMap();
amap.put("Close", a);
return panel;
}
public void setEditable(boolean b) {
_btnOK.setEnabled(b);
}
public boolean isEditable() {
return _btnOK.isEnabled();
}
public boolean isOK() { return _bOK;}
public String getTableName() {
return (String)_cmbTables.getSelectedItem();
}
}
}
プラグインの作成ではすべての機能を自分で作成することもできますが、標準で提供されている部品を組み込むこともできます。
現在は以下の二つの部品のプラグインへの組み込み方法が公開されています。
いずれもRDB関連の補助UIクラスです。
これらのクラスはSQLToolというユーティリティクラスを介して使用することができます。
SQLBuilderはRDBGetコンポーネントで使用しているSELECT文を組み立てるためのUIです。
実際にはサーバー側のRDBGetコンポーネントが必要とするのはSQLのみなのでGUIでSELECT文を組み立てるという要件は必須ではありませんが、
これがあることによってユーザーはSQL文とそのフィールド定義を手動で行う煩わしさから解放されます。
SQLBuilderを起動するために必要な情報としては以下の二つがあります。
これらの情報は通常はコンポーネントのプロパティから取得します。
またSQLBuilderの実行結果から取得できる情報としては以下の3つがあります。
これらの情報は通常はSQLBuilderの終了時(OKボタンで終了した時)にプロパティまたはストリームのフィールド定義に設定されます。
これらのプロパティを定義した定義ファイルは以下のようになります。
<!-- RDBGetの定義ファイルより抜粋 --> <Property connection="RDBConnection" displayName="コネクション名" required="true" mapping="false" name="Connection" toolTip="Connection" type="connection"/> <Property displayName="SQL文" editor="multiline" name="SQL" required="true" toolTip="SQL" type="string"/> <Property class="com.infoteria.asteria.flowbuilder2.sqlbuilder.SQLBuilderProperty" displayName="SQL Builder" name="SQLBuilder" toolTip="SQLBuilder" visible="false"/> <Category key="Name" mapping="true" name="SQLParameter" displayName="SQLパラメータ" readonly="true" value="Default"> <Property displayName="パラメータ名" name="Name" toolTip="header name" type="string"/> <Property displayName="データ型" name="Type" toolTip="data type" type="choice"> <Value>String</Value> <ChoiceItem ref="DataType"/> </Property> <Property displayName="値" name="Default" toolTip="header value" type="string"/> </Category>
SQLBuilderのモデルの保存用にSQLBuilderという名前の専用プロパティを使用しています。
(デザイナーにプロパティ型として登録されていないためtypeではなくclassでプロパティを直接指定します。)
SQLBuilderを使用するコンポーネントが必ずこのセットのプロパティを持たなければならないわけではありませんが、
ほとんどの場合このようなプロパティ構成となります。
sampleディレクトリにある「SQLBuilderSample」を参照してください。
処理の流れとしては
となります。
値の設定にはSQLToolのユーティリティメソッドが使用できます。
ほとんどの場合SQLBuilderを組み込むプラグインのコードはこのサンプルそのままになります。
SQLBuilderで生成されるSQL文には文字列中に「?param1?」「$param2$」のような独自のパラメータが埋め込まれています。
このSQL文はUtilityクラスのparseSQLメソッドを通すことによってJavaのPreparedStatementで実行可能な形式に変換されます。
具体的には以下の変換がおこなわれます。
SQL文を取得した後はPreparedStatementなどのJDBCのAPIを用いて処理を行ってください。
テーブル選択ダイアログはRDBPutコンポーネントで使用しているテーブルとその列を選択するためのUIです。
選択した列情報をフィールド定義やCategoryPropertyに設定することでフィールド定義を簡略化することができます。
テーブル選択ダイアログを起動するためには「テーブル情報を取得するためのコネクション」の情報が必要です。
また通常は選択したテーブル名を設定するためのプロパティ、選択した列を設定するためのフィールド定義(またはCategoryProperty)も
必要になります。
これらのプロパティを定義した定義ファイルは以下のようになります。
<!-- RDBPutの定義ファイルより抜粋 --> <Property connection="RDBConnection" displayName="コネクション名" required="true" mapping="false" name="Connection" toolTip="Connection" type="connection"/> <Property displayName="テーブル名" class="com.infoteria.asteria.flowbuilder2.sqlbuilder.TableSelectPropertyEditor" required="true" name="TableName" mapping="true" toolTip="TableName" type="string"/> <Category displayName="入力" key="Name" mapping="false" name="Field(Input)" > <Property displayName="フィールド名" name="Name" readonly="true" toolTip="header name" type="string"/> <Property displayName="データ型" name="Type" readonly="true" toolTip="data type" type="choice"> <Value>String</Value> <ChoiceItem ref="DataType"/> </Property> <Property displayName="キーにする" name="Key" toolTip="Key" type="boolean">false</Property> </Category> <Input accept="Record" defineStream="true"> <FieldDef readonly="true"/> </Input>
RDBPutでは列情報はCategoryPropertyと入力ストリームのフィールド定義の両方に設定しています。
フィールド定義はテーブル選択ダイアログでしか設定できないようにするためreadonlyとして定義しています。
CategoryPropertyの方ではフィールド名とデータ型は変更できず、「キーにする」列のみ変更可能なように定義されています。
列情報をどこに設定するかはコンポーネントの種類によって変わってきますがほとんどの場合テーブル選択ダイアログ を使用するコンポーネントではこのようなプロパティ構成となります。
sampleディレクトリにある「TableSelectSample」を参照してください。
処理の流れとしては
となります。
値の設定にはSQLToolのユーティリティメソッドが使用できます。
列情報をどこに設定するかはコンポーネントの種類によって変わってきますがほとんどの場合テーブル選択ダイアログ
を組み込むプラグインのコードはこのサンプルのようなコードになります。
外部アプリケーション起動はLaunchExternalAppクラスのサブクラスとして作成します。
単純にプロパティ値をファイルに保存してアプリケーションを起動するだけならサブクラスを作成せずに
LaunchExternalAppクラスをそのまま使用することも可能です。
(定義ファイルリファレンスの外部アプリケーション起動を参照してください。)
外部アプリケーション起動はコンポーネントエディタの一種と考えることができます。
つまりコンポーネントエディタのdoActionメソッドの実装が以下の処理になっているものが外部アプリケーション起動です。
外部アプリケーションの起動中は以下のようなダイアログが表示され、デザイナーは外部アプリケーションが 終了するまでロックします。
LaunchExternalAppのサブクラスを作成する場合は通常はpreExecuteメソッドとpostExecuteメソッドを
オーバーライドします。
preExecuteでプロパティ値などのコンポーネント(マッパー関数)のプロパティなどの情報をファイルに書き出し、
postExecuteで外部アプリケーションが更新したファイルを読み込んでそれを書き戻します。
プロパティ値の取得や設定の方法自体はコンポーネントエディタの場合と同じです。
外部アプリケーション起動のサンプルは現在ありませんがこちらも興味のある方は御相談ください。