 | レベル: 中級 小杉 晋央 , WPLC & PvC ソリューション開発/ソフトウェア開発研究所 , IBM
2006年 6月 30日
前回(「Workplace Forms Designer 技術概説」)、前々回(「IBM Workplace Forms技術概説」)とIBM Workplace Forms製品の紹介を行いました。今回は、フォームを使ったプログラムの開発者向けに、この製品で用意されているAPIの紹介を行います。 IBM Workplace Forms APIは、フォームの取り扱い(作成、編集、保存)をProgrammableに行う手段や、compute attributeによって呼び出される関数の拡張を行う手段を提供します。APIライブラリには用途によっていくつかのカテゴリがありますが、それらの構成内容と、必ず通らなければならない基本機能を、サンプルコードを交えて紹介していきます。
IBM Workplace Forms API の概要
IBM Workplace Forms では、フォーム文書に対する処理の自動化、外部アプリケーションとのやり取りを簡便にするために、次のカテゴリのAPI群を用意しています。
- Form ライブラリ - XFDL文書を構造化されたフォームとして読み込み、プログラムによる各フィールドへのアクセス手段を提供します。
- FCI(Function Call Interface) ライブラリ - XFDL文書内で使用できる関数の拡張を行います。このAPIを使用して実装した関数を持つ拡張モジュールを登録することにより、既存のForm viewerから開いたXFDL文書から呼び出すことが可能になります。
- Web Form Server API - IBM Workplace Web Form serverが導入されたシステムで使用可能な、サーバーアプリケーションでXFDL文書を扱うためのインターフェースです。
APIを使用する前に
APIを使用したアプリケーションのビルド、実行のために準備をしておく必要があります。
- システムファイルのコピー
<APIインストールディレクトリ>\redist\msc32
|
の中にあるファイルを、Windowsのシステムディレクトリにコピーしてください。
- COMを使用する場合には、regsvr32を使用して、pe_com.dllを登録してください。
上記システムファイルのコピーを行った後に、コマンドプロンプトからたとえば、
regsvr32 c:\winnt\system32\pe_com.dll |
と入力します。
内容の詳細については、参考文献のWorkplace Forms Server API Installation and Setup Guideに記載されています。
Formライブラリ
Formライブラリで用意されるAPIは、XFDL文書を構造化されたインスタンスとして扱い、内部データへのアクセスを容易にします。単純には、XFDL文書を用いたStand Aloneのカスタムアプリケーションの作成にも使用しますが、他のカテゴリのAPIを使用したアプリケーション、モジュールを作成する上でも、処理中には必ずフォーム文書を扱うため、このライブラリの知識はIBM Workplace Forms APIを使用してプログラムを作るうえでの基礎となります。 このライブラリのインターフェースは、端的には、XML parserとDOM実装のwrapperといえ、それらのAPIを過去に使用したことのある人は構成を理解しやすいと思います。
以下、APIのサンプルとして提供されている、calculate_ageというアプリケーションを元に、XFDL文書を開き、フォームの値を取得、変更し、別のXFDL文書として保存するまでの処理を例示します。
このアプリケーションは、
- すでにあるフォームを開き、
- フォームに入力されている人の生年月日と、フォーム内に指定してある年、日付を参照し、
- 該当する人の年齢を別フィールドに書き出したフォームを保存します。
どの言語でも、大きく分けて以下のようなステップで処理を行います。
- APIの初期化
- フォームの読み込み
- フォームの編集
- フォームの保存
- フォームインスタンスの解放
このライブラリが使用可能な言語は、Java, C, COMです。COMでの実施例としては、VisualBasicを用います。 Calculate_ageアプリケーションのソースは、APIインストールディレクトリの下の、
Java版:samples\java\form\demo\calculate_age
C版:samples\msc32\form\demo\calculate_age
COM版:samples\msc32\COM\demo\calculate_age
|
にあります。
Formライブラリ – APIの初期化
後述するFCIライブラリを使用した拡張モジュールでない限り、APIの初期化が必要です。
APIの初期化 例-Java
private static void initialize() throws UWIException
{
DTK.initialize("CalculateAge", "1.0.0", "7.0.0");
} |
JavaでのAPI初期化には、DTKクラスを用います。このクラスはstaticなinitializeメソッドを持っており、アプリケーションはこれを呼ぶことで初期化を完了します。
引数には
- 使用するアプリケーション名
- アプリケーションのバージョンを識別する文字列
- このアプリケーションが使用するAPIのバージョン
を指定します。
APIの初期化 例-C
r_short initialize()
{
r_error error;
error = IFSInitialize("calculateAge", "1.0.0", "7.0.0");
if (error != OK)
{
reportError("IFSInitialize error %hd.\n", (r_short)error);
return(NOTOK);
}
return(OK);
} |
Cでは、IFSInitialize関数を用いてAPIの初期化を行います。引数の説明はJavaの場合と同じです。
APIの初期化 例-VisualBasic
Sub Initialize()
Dim DTK As DTK
Set DTK = CreateObject("PureEdge.DTK")
DTK.IFSInitialize "CalculateAge", "1.0.0", "7.0.0"
End Sub
|
COMオブジェクトを使用するアプリケーションの場合、”PureEdge.DTK”というオブジェクトを使用し、APIの初期化を行います。DTKオブジェクトは、Javaの場合と同様、initializeメソッドを持っており、同じ引数を用いて初期化を行います。
Formライブラリ – フォームのロード
このステップでは、XFDL文書を、Programmableに扱うために、構造化されたインスタンスに読み込む処理を行います。この処理を行うことで、formNodePという、XFDLをDOM treeにparseした、root nodeへの参照を取得します。Form内の各フィールドへのアクセスは、このformNodePから始まります。
フォームのロード 例-Java
private static void loadForm() throws Exception
{
XFDL theXFDL;
if ((theXFDL = IFSSingleton.getXFDL()) == null)
throw new Exception("Could not find interface");
if ((theForm = theXFDL.readForm("calculateAge.xfd", 0)) == null)
throw new Exception("Could not load form.");
}
|
FormはXFDL#readForm()により読み込まれます。XFDLのインスタンスはIFSSingletonというクラスで用意される、getXFDL()というstatic methodにより取得します。XFDL#readFormに用いる引数は以下のとおりです。
- InputStreamのインスタンス、またはファイル名
- ロードする上でのオプションフラグ
readFormメソッドは、FormNodePクラスのインスタンスを返します。
このインスタンスを使用して、Form内の値を取得します。
フォームのロード 例-C
r_short loadForm(formNodeP *form)
{
if ((*form = UFLReadForm("calculateAge.xfd", 0)) == NULL)
{
reportError("Could not load form.\n", 0);
return(NOTOK);
}
return(OK);
} |
FormはUFLReadFormというグローバル関数によって読み込まれます。
引数は基本的にJavaと同様ですが、InputStreamの型をとるバージョンは存在しません。
- ファイル名
- オプションフラグ
UFLReadFormは、生成されたformNodePインスタンスのアドレスを返します。 このアドレスを使用して、Form内の値を取得します。
フォームのロード 例-VisualBasic
Function LoadForm() As IFormNodeP
Dim XFDL As XFDL
Set XFDL = CreateObject("PureEdge.xfdl_XFDL")
Set LoadForm = XFDL.ReadForm("calculateage.xfd", 0)
End Function |
FormはXFDLというCOMオブジェクトによって読み込まれます。 VBではXFDLオブジェクトを取得した後、ReadFormメソッドにより処理を行います。 引数は、Cの場合と同様です。 ReadFormメソッドは、IFormNodePというインターフェースを持つオブジェクトを返します。Form内の値の取得はこのオブジェクトを使用します。
Formライブラリ – フィールドの値の取得
いよいよformNodePを用いてフォーム内の値にアクセスします。 formNodePは他のDOM interface実装と同様、子nodeを一覧したり、attributeを一覧したりするメソッドを持ち合わせており、それらを組み合わせることで目的の要素(node)が保持する値に到達することができますが、formで設定されるフィールドの値は実際のところ、あるnodeに含まれるtext elementの形で保持されており、既存のインターフェースでは必ずお決まりの数ステップが要求されることになります。 そこで、このライブラリでは、root nodeまたは特定のnodeから、一足に取得したいフィールドの値をとるためのメソッドが用意されています。 対象となるフィールドを特定するためにはreference文字列を使用します。reference文字列は、対象となるnodeにいたる階層のフィールド名を”.”で区切ることにより指定します。 具体的には、たとえば以下のような構造のフォームであった場合、
例-XFDL:抜粋
<?xml version="1.0" encoding="UTF-8"?>
<XFDL xmlns:custom="http://www.ibm.com/xmlns/prod/XFDL/Custom" xmlns:designer
="http://www.ibm.com/xmlns/prod/workplace/forms/designer/2.6" xmlns:xfdl
="http://www.ibm.com/xmlns/prod/XFDL/7.0" xmlns="http://www.ibm.com/xmlns/prod/XFDL/7.0">
...
<page sid="PAGE1">
<field sid="BIRTHMONTH">
<label>Month</label>
<value>9</value>
<format>
<datatype>month</datatype>
<constraints>
</constraints>
<presentation>
<style>short</style>
</presentation>
</format>
<size>
<width>4</width>
<height>1</height>
</size>
<itemlocation>
<x>210</x>
<y>206</y>
</itemlocation>
</field>
...
</page>
</XFDL>
|
PAGE1にある、BIRTHMONTHのvalueの値を取得するためのreference文字列は、 “PAGE1.BIRTHMONTH.value” となります。 対象nodeを特定した後にフィールドの値をとるべきか、それともroot nodeからreference文字列を使用して一足に値を取得するべきかは、要求されるパフォーマンスと、それ以外に値を取得すべきフィールドがあるか、などの条件により決定されると思いますが、ここでは、単純化のため、後者の例を示します。
フィールドの値の取得 例-Java
private static int getBirthDay() throws Exception
{
String temp;
temp = theForm.getLiteralByRefEx(null, "PAGE1.BIRTHDAY.value", 0, null, null);
if (temp.length() > 0)
{
return Integer.parseInt(temp);
}
else
{
throw new Exception("The birth day was not entered.");
}
} |
現在のnodeから相対的な位置を指定して、対象nodeの値を取得するために、前述のXFDL#ReadFormで得られたFormNodePインスタンスのgetLiteralByRefEx()メソッドを使用します。
引数の意味は
- 予約されているのでnull
- reference文字列
- 予約されているのでnull
- 取得する文字列の文字コード指定:UNICODE以外で取得したい場合に指定します。
- 第二引数で指定するreference文字列で対象とするnodeが、特別のnamespaceで定義されているときに、そのnamespaceを指定しているnodeのFormNodePを適用します。そうでない場合は、null
関数の戻り値にはフィールドの値がStringインスタンスで返されます。
フィールドの値の取得 例-C
r_short getBirthDate(formNodeP form, int *birYear, int *birMonth, int *birDay)
{
r_charP temp=NULL;
r_short error;
...
error = UFLGetLiteralByRefEx(form, NULL, "PAGE1.BIRTHDAY.value",
0, NULL, NULL, &temp);
if (error != OK)
{
reportError("UFLGetLiteralByRefEx error %hd.\n", error);
return(NOTOK);
}
if (temp != NULL)
*birDay = atoi((char *)temp);
else
{
reportError("The birth day was not entered.\n", 0);
return(NOTOK);
}
return(OK);
} |
Cでフィールドの値を取得する場合は、UFLGetLiteralByRefExを使用します。引数には以下のものを指定します。
- 検索の対象となる初めのnodeを指定します。
- null. JavaのFormNodeP#getLieteralByrefExの第一引数と同じです。
- reference文字列
- null. JavaのFormNodeP#getLieteralByrefExの第三引数と同じです。
- 取得する文字列の文字コード指定:ANSI文字列が期待される場合にはNULLになります。たとえばshift-JIS文字列を期待する場合には、”shift-jis”と指定します。
- namespaceを解決する必要がある場合のnodePインスタンス。JavaのFormNodeP#getLieteralByrefExの第五引数と同じです。
- 取得される値を保存するバッファへのポインタ。内容を保持するエリアはAPIによって確保されます。呼び出し側は、この値に戻ってきたポインタが示すバッファを、後にcp_freeを使用して開放する必要があります。
フィールドの値の取得 例-VisualBasic
Function GetBirthDay(TheForm) As Integer
Dim BDay As String
BDay = TheForm.GetLiteralByRefEx(vbNullString,
"PAGE1.BIRTHDAY.value", 0, vbNullString, Nothing)
If (Len(BDay) > 0) Then
GetBirthDay = CInt(BDay)
Else
MsgBox "The birth day was not entered.", vbCritical
Stop
End If
End Function |
IFormNodePインターフェースを持つオブジェクトの、GetLieteralByRefEx()メソッドにより、戻り値としてフィールドの値が取得できます。 引数の意味は、JavaのFormNodeP#GetLiteralByRefEx()と同じです。
Formライブラリ – フィールドの値の適用
フィールドへの値の適用手順は、最後に使用するメソッドと入出力の引数が違うだけで、フィールドの指定方法などに関しては、値の取得をする場合とほぼ同じです。
フィールドの値の適用 例-Java
private static int getBirthDay() throws Exception
{
String temp;
temp = theForm.getLiteralByRefEx(null, "PAGE1.BIRTHDAY.value", 0, null, null);
if (temp.length() > 0)
{
return Integer.parseInt(temp);
}
else
{
throw new Exception("The birth day was not entered.");
}
} |
Javaでは、FormNodeP#setLiteralByRefExでフィールドの値を設定します。 FormNodeP#getLiteralByRefExに対し、第六引数が追加され、そこでは適用すべき文字列を指定します。
フィールドの値の適用 例-C
r_short setBirthDate(formNodeP form, int birYear, int birMonth, int birDay)
{
r_short error;
char temp[100];
...
sprintf(temp, "%d", birDay);
error = UFLSetLiteralByRefEx(form, NULL, "PAGE1.HIDDENDAY.value",
0, NULL, NULL, (r_charP)temp);
if (error != OK)
{
reportError("UFLSetLiteralByRefEx error %hd.\n", error);
return(NOTOK);
}
return(OK);
} |
CではUFLsetLiteralByRefExを使用します。1~6番目の引数の意味は、UFLgetLiteranByRefExと同じですが、第七引数として適用すべき文字列が入っているバッファのアドレスを指定します。
フィールドの値の適用 例-VisualBasic
Sub SetBirthDay(BDay As Integer, TheForm As IFormNodeP)
Dim Day As String
Day = CStr(BDay)
TheForm.SetLiteralByRefEx vbNullString,
"PAGE1.HIDDENDAY.value", 0, vbNullString, Nothing, Day
End Sub |
VBでの実装も、Javaでの実装とほぼ同じです。IFormNodePのgetLiteralByRefExメソッドと比較して、適用すべき文字列のために第六引数が追加されています。
Formライブラリ – フォームの保存
フォームの保存は非常に簡単です。どの言語でも2、3個の引数を持つ1つのメソッドで処理が完了します。
フォームの保存 例-Java
private static void setBirthDay(int birDay) throws Exception
{
Integer day = new Integer(birDay);
theForm.setLiteralByRefEx(null, "PAGE1.HIDDENDAY.value",
0, null, null, day.toString());
}
|
JavaではFormNodeP#writeForm()でフォームの保存を行います。引数は次のとおりです。
- 保存に使用するファイル名、または、OutputStreamのインスタンス
- 送付オプションを指定するときに、対象となるnodeへの参照
- 保存オプション
フォームの保存 例-C
r_short saveForm(formNodeP form)
{
r_short error;
error = UFLWriteForm(form, "output.xfd", NULL, 0);
if (error != OK)
{
reportError("UFLWriteForm error %hd.\n", error);
return(NOTOK);
}
return(OK);
} |
Cでは、UFLWriteFormで、フォームの保存を行います。第一引数に保存の対象となるformNodeP変数が追加されている以外は、JavaのFormNodeP#writeForm()と同じです。
フォームの保存 例-VisualBasic
Sub SaveForm(TheForm)
TheForm.WriteForm "output.xfd", Nothing, 0
End Sub |
VBで実装する場合は、IFormNodePのWriteFormメソッドを使用します。この引数は、JavaでのFormNodeP#WriteForm()と同じです。
Formライブラリ – フォームインスタンスの解放
使用し終わったFormNodePのインスタンスを開放することを忘れないでください。これはどの言語でも必要な処理になります。 FormNodePのインスタンスの解放は、
Javaでは、FormNodeP#destroy(), Cでは、UFLDestroy(), COMでは、IFormNodePのdesotroy()
で行います。
Formライブラリ – 実行モジュールの作成
今回のコンパイル、実行モジュールの作成を行う際にいくつかの注意点があります。
Javaでの注意点: コンパイル時のclasspathにpe_api.jar, uwi_api.jarを追加することが必要です。これらのjarファイルは、
<APIインストールディレクトリ>\lib\java |
にあります。
Cでの注意点: WindowsのVisual Studioでプロジェクトを作成する場合、APIに必要なincludeディレクトリ、プリプロセッサ定義の指定などを行ったテンプレートファイルが
<APIインストールディレクトリ>\samples\msc32\form\template |
にあります。このプロジェクトファイルからアプリケーションのプロジェクトを作成するのが最も簡単な方法です。
それ以外の方法で実行モジュールを作成したい場合、必要なオプションの内容が、参考文献のWorkplace Forms C API User’s Manualに記述されています。
Formライブラリ – 作成したアプリケーションの設定
以上のステップで作成したアプリケーションを動作させるために、いくつかの設定が必要です。 使用可能な言語すべてに共通して必要なのが、PureEdgeAPI.iniの設定です。このiniファイルは、APIに必要な実装のディレクトリがどこにあるかを解決するためのものです。このファイルは、アプリケーションの実行モジュールと同じディレクトリに置きます。 通常であれば、唯一つ、エントリを追加するだけです。下に例を示します。
[API]
7.0.* = F:\Workplace Forms\Server\2.6\API\redist\msc32\PureEdge\70 |
APIというカテゴリのなかに、使用するバージョンのAPIの実装が存在するディレクトリを指定します。Workplace Forms 2.6 Windowsの場合は、
<APIパッケージのインストールディレクトリ>\redist\msc32\PureEdge\70 |
となります。
<Java固有の設定> コンパイルの時と同様に、実行時のclasspathにpe_api.jar, uwi_api.jarを追加します。
FCIライブラリ
FCIライブラリは、XFDL文書で使用できる関数を拡張できる仕組みを提供します。 決められたやり方で関数を実装し、エントリを登録することにより、Form Viewerを使用して開いたXFDL文書からその実装を呼ぶことが可能です。 様々なアプリケーションで用意されている、プラグインの仕組みを思い出すことで、このFCIライブラリを使用して作ったモジュールと、XFDL文書の関係が理解しやすくなると思います。 FCIライブラリを使用したモジュールは、Java, Cで作成可能です。 FCIモジュールは、基本的に
- 初期化要求時に自分が提供するもののエントリの詳細内容を登録し
- その登録内容に応じて起こった呼び出しに対して処理を行う
ことを主な役割とします。
したがって、モジュールのコーディングは、用意されたインターフェースに沿って、1 と 2 の実装を作成する、ということが主な作業になります。 Javaでは、convert_date, Cではmultiplyという、APIのサンプルとして提供されているモジュールを例に説明します。 ソースは、APIインストールディレクトリの下の
Java版:samples\java\fci\demo\convert_date
C版:samples\msc32\fci\multiply
|
にあります。
FCIライブラリ – 拡張関数の登録
拡張モジュールと認識されたモジュールは、ViewerなどのFormsアプリケーションにより、拡張モジュール用の初期化処理が呼び出されます。 その呼び出しの引数にはエクステンションマネージャのインスタンスが渡されるので、モジュール実装者はそれに対して提供する関数実装への情報や、その実装がどの関数に対するものなのかといった情報を登録します。
関数の登録 例1-Java
public class FCIExtension extends ExtensionImplBase implements Extension
{
public void extensionInit(IFX ifxMan) throws UWIException
{
FunctionCall theFunctionObject;
theFunctionObject = new FCIFunctionCall(ifxMan);
}
} |
Javaでは初期化処理の呼び出しは、com.PureEdge.ifx.Extensionインターフェースを実装したクラスのextensionInit()に対して行われます。 引数にはエクステンションマネージャ(com.PureEdge.ifx.IFX)のインスタンスが渡されてきますので、これを使用して登録処理を行います。 この例では実際の処理は、別クラスのコンストラクタで行われているので、そちらも引用しましょう。
関数の登録 例2-Java
public class FCIFunctionCall extends FunctionCallImplBase implements FunctionCall
{
public static final int CONVERTDATE = 1;
public FCIFunctionCall(IFX ifxMan) throws UWIException
{
FunctionCallManager theFCM;
java.io.FileWriter theWriter;
if ((theFCM = IFSSingleton.getFunctionCallManager()) == null)
throw new UWIException("Needed Function Call Manager");
ifxMan.registerInterface(this,
FunctionCall.FUNCTIONCALL_INTERFACE_NAME,
FunctionCall.FUNCTIONCALL_CURRENT_VERSION,
FunctionCall.FUNCTIONCALL_MIN_VERSION_SUPPORTED,
0x01000300, 0, null, theFCM.getDefaultListener( ));
theFCM.registerFunctionCall(this, "sample_package",
"convertDate", FCIFunctionCall.CONVERTDATE,
FunctionCall.FCI_FOLLOWS_STRICT_CALLING_PARAMETERS,
"S,S", 0x01000300, "Converts a date to a different locale");
}
… |
まず、渡されたエクステンションマネージャに対して、拡張関数の実体クラスを拡張エントリとして登録します。次に、FunctionCallManagerに対して、registerFunctionCallを呼び出すことによって、フォーム内で記述された関数名と、拡張エントリを登録することにより、その2つを結び付けます。
関数の登録 例-C
PRE_FUNCTION_DECL r_short POST_FUNCTION_DECL C_ExtensionInit(
Extension *theExtension, IFX *theIFXManager)
{
FunctionCall *theFunctionCall;
r_short theError;
IFXCriteriaMatchingHandler *returnPtr;
if ((theFunctionCall = (FunctionCall*)IFSObject_AllocateObject(
FUNCTIONCALL_INTERFACE_NAME,
FUNCTIONCALL_CURRENT_VERSION, NOTOK, 0)) == NULL)
return(NOTOK);
if ((theError = FunctionCall_SetObjectProc(
theFunctionCall, (r_voidP)FCISimpleFunctions,
FUNCTIONCALLEVALUATE)) != OK)
return(theError);
if ((theError = FunctionCall_SetObjectProc(
theFunctionCall, (r_voidP)FCISimpleHelp,
FUNCTIONCALLHELP)) != OK)
return(theError);
if ((returnPtr = FCMGetDefaultListener()) == NULL)
return(NOTOK);
if ((theError = IFXRegisterInterface(
theIFXManager,
(GenericInterface*)theFunctionCall,
FUNCTIONCALL_INTERFACE_NAME,
FUNCTIONCALL_CURRENT_VERSION,
FUNCTIONCALL_MIN_VERSION_SUPPORTED,
0X01000300L, 0, NULL, 0,
returnPtr)) != OK)
return(theError);
if ((theError = FCMRegisterFunctionCall(
theFunctionCall,
PACKAGE_NAME,
"multiply",
FCI_MULTIPLY_ID,
FCI_FOLLOWS_STRICT_CALLING_PARAMETERS,
"S,S",
FCI_MULTIPLY_VERSION,
FCI_MULTIPLY_DESCRIPTION)) != OK)
return(theError);
return(OK);
} |
CではC_ExtensionInit()関数をDLLのエクスポート関数として実装することにより、Formアプリケーションがそれを呼び出します。 JavaでFunctionCallインターフェースを実装したクラスのインスタンスを作成していた代わりに、Cでは、FunctionCall構造体をアロケートし、実装に関する情報を適用します。(IFSObject_AllocateObjectの呼び出しから、2度目のFunctionCall_SetObjectProcまで) そのようにして作成した構造体を、IFXRegisterInterfaceを使用して、拡張エントリとして登録します。 その後にFCMRegisterFunctionCallにより、関数名と拡張エントリを登録します。クラスメソッドとグローバル関数の違いはありますが、登録に必要な処理は基本的にJavaと同じといえます。
FCIライブラリ – 拡張関数の処理
面倒な呪文が終わったところで、いよいよ実際の処理内容の記述です。 Formsアプリケーションはフォームを読み込むときに、該当する関数に対するエントリの登録があれば、そのエントリに登録された関数実装の呼び出しを行います。 モジュール実装者は、ここで渡された情報に従って、任意の処理を行い、結果引数または戻り値に適用することにより、値がフォームにフィードバックされます。
関数の処理 例-Java
public void evaluate(String thePackageName,
String theFunctionName, int theFunctionID,
int theFunctionInstance, short theCommand,
com.PureEdge.xfdl.FormNodeP theForm,
com.PureEdge.xfdl.FormNodeP theComputeNode,
com.PureEdge.IFSUserDataHolder theFunctionData,
com.PureEdge.IFSUserDataHolder theFunctionInstanceData,
com.PureEdge.xfdl.FormNodeP [] theArgList,
com.PureEdge.xfdl.FormNodeP theResult) throws UWIException
{
String theDateString;
String theLocaleString;
String theAnswerString = null;
Date theDate = null;
Locale theLocale;
DateFormat theDateFormat;
if (theCommand == FunctionCall.FCICOMMAND_RUN)
{
if (theFunctionID == FCIFunctionCall.CONVERTDATE)
{
theDateString = theArgList[0].getLiteralEx(null);
theLocaleString = theArgList[1].getLiteralEx(null);
...
theResult.setLiteralEx(null, theAnswerString);
}
}
} |
Javaでは、登録された拡張関数がFormから呼び出されると、FunctionCallインターフェースのevaluate()の実装が呼び出されます。 evaluateには拡張実装が呼び出されたときのコマンドが、theCommandとして入力されています。 Formから呼び出されるときの実装を定義したい場合は、theCommandがFunctionCall#FCICOMMAND_RUNと同値であることを確認しましょう。 また、FunctionCallManager#registerFunctionCallの際に登録した、IDとtheFunctionIDの値が同値であることも確認したほうがよいでしょう。 Formの中で関数に渡された引数は、FormNodePの配列として、theArgList変数に代入されています。 関数が戻り値をとる場合は、その戻り値の代入先として、FormNodePクラスのtheResult変数が使われます。 この例でのconvert_dateは、第一引数に日付の入力、第二引数に表示すべきロケールをとります。したがって、処理はtheArgList[0]の値を、theArgList[1]の値のロケールでフォーマットしなおした結果をtheResultの文字列値に渡すことになります。
関数の処理 例-C
PRE_FUNCTION_DECL r_short POST_FUNCTION_DECL FCISimpleFunctions(
FunctionCall *theObject, r_charP thePackageName,
r_charP theFunctionName, r_u_long theFunctionID, r_long theFunctionInstance,
r_short theCommand, formNodeP theForm, formNodeP theComputeNode,
IFSUserData **theFunctionDataPtr, IFSUserData **theFunctionInstanceDataPtr,
formNodeP *theArgList, r_long theArgListSize, formNodeP theResult)
{
r_charP theFirstParam;
double firstNum;
r_charP theSecondParam;
double secondNum;
r_short theError;
double theMultiplyResult;
char theLiteralBuffer[50];
if ((thePackageName == NULL) || (theFunctionName == NULL))
{
reportError("Invalid parameters", 0);
return(NOTOK);
}
if (cp_strcmp(thePackageName, PACKAGE_NAME) != OK)
{
reportError("This function was called with the wrong package name", 0);
return(NOTOK);
}
switch (theCommand)
{
case FCICOMMAND_RUN:
switch (theFunctionID)
{
case FCI_MULTIPLY_ID:
if ((theError = UFLGetLiteralEx(theArgList[0], NULL, &theFirstParam)) != OK)
{
reportError("Unable to get the first parameter.", 0);
return(theError);
}
if ((theError = UFLGetLiteralEx(theArgList[1], NULL, &theSecondParam)) != OK)
{
reportError("Unable to get the first parameter.", 0);
return(theError);
}
if (theFirstParam == NULL)
theFirstParam = "";
if (theSecondParam == NULL)
theSecondParam = "";
firstNum = atof(theFirstParam);
secondNum = atof(theSecondParam);
theMultiplyResult = firstNum * secondNum;
sprintf(theLiteralBuffer, "%f", theMultiplyResult);
UFLSetLiteralEx(theResult, NULL, theLiteralBuffer);
break;
default:
reportError("The function was called with an unrecognized function", 0);
return(NOTOK);
}
break;
case FCICOMMAND_INSTANCEREGISTER:
break;
case FCICOMMAND_INSTANCEDEREGISTER:
break;
case FCICOMMAND_REGISTER:
break;
case FCICOMMAND_DEREGISTER:
break;
default:
break;
}
return(OK);
} |
Cでの実装がJavaと異なるのは、配列に対するlengthが、そのインスタンスだけではprogrammableに取得できないため、theArgListのエントリの数が、その直後の引数theArglistSizeとして渡されることです。 その他の部分は同じなので、関数に渡された引数の取得時にその2つの変数を使うことを気をつけながら、同じような流れで実装していきます。 multiplyは、第一引数と第二引数の乗算なので、theArgList[0]の値と、theArgList[1]の値の乗算結果を文字列として、theResultに代入することになります。 引数には、Formのroot nodeであるtheForm変数も渡されていますので、Form全体の処理をするような関数を作成することも可能でしょう。
FCIライブラリ – モジュールの作成
拡張モジュールであることを示すために、実行モジュールを作成する上で少し手を加える必要があります。
Javaでは、jarファイルとして実装クラスをパッケージしますが、その際にMETA-INFに含まれるMANIFEST.MFに最初の登録処理を行うクラス名を記述します。
MANIFEST.MS 例:
Manifest-Version: 1.0
Name: com/yourcompany/samples/FCIExtension.class
IFS-Extension: True |
Cでは、dll形式でモジュールをビルドしますが、拡張子を.ifxにします。
FCIライブラリ – モジュールの配備
Formsアプリケーションに最初の登録処理を呼び出してもらうために、作成したモジュールを一定の規則で配備する必要があります。 Form Viewerで拡張を有効にしたい場合、.jarファイルまたは.ifxを
<Viewerインストールディレクトリ>\2.6\extensions |
にコピーすることで、拡張が有効になります。
Web Form Server API
IBM Web Form Serverが導入されている場合、このAPIとして用意されたServletクラス、Portletクラスを継承し、クライアントに渡すべきXFDLをWeb Form Serverを使用して送付するアプリケーションが作成可能になります。 以下にIBM Web Form Serverで提供されているsampleのServletでの実施例を示します。 Sampleのソースは、<WebFormServerインストールディレクトリ>\ samples\servlet\SamplesSrcにあります。 ServletでXFDLを扱うアプリケーションを実装する場合、com.ibm.form.webform.framework.servlet.IBMWorkplaceFormsServerServletを継承するとさまざまな機能が使用できるため、実装の効率が上がります。
Web Form Server API – APIの初期化
Form APIでも行っていた、APIの初期化はinit()メソッドで行います。
APIの初期化 例-Java
public class FormViewServlet extends IBMWorkplaceFormsServerServlet
{
public void init() throws ServletException
{
super.init();
...
try
{
DTK.initializeWithLocale("Webform Server", "1.0.0", "7.0.0", null);
}
catch (UWIException e)
{
Object[] tokens = {(e.getStackTraceString())};
String pattern = SampleResources.
getString(SampleResources.ERROR_FAILED_TO_INITIALIZE_API, serverLocale);
String message = MessageFormat.format(pattern, tokens);
throw new ServletException(message);
}
} |
init()の実装では最初にsuper classの同メソッドを呼び出すことも忘れないようにしましょう。
Web Form Server API – フォームのダウンロード
クライアントにアプリケーションコンテキスト内にあるXFDLフォームをダウンロードするには、doGet/doPostでのHttpServletResponseクラス引数のOutputStreamを引数に、FormNodeP#writeFormを呼び出します。 フォームのロードは、前述のForm APIの項で述べたやり方で行いますが、アプリケーションの実装によっては、ここで背景にあるビジネスロジックの結果などから、FormNodeP#writeFormを呼び出す前に、FormNodePのフィールドに値を代入し、クライアント側での見え方を動的に変更することもあり得るでしょう。
フォームのダウンロード 例-Java
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
Locale serverLocale = SampleResources.getServerLocale();
FormNodeP theForm = null;
String mode = request.getParameter("mode");
if (mode != null)
{
if (mode.equalsIgnoreCase("xfdl"))
{
this.useXFDL(response, true);
}
else if (mode.equalsIgnoreCase("html"))
{
this.useHTML(response, true);
}
...
File file = SampleUtilities.getFile(serverLocale, dirname, filename,
this.startingDirectory);
theForm = prepopForm(serverLocale, file, request, dirname, filename);
// must set the filename so that if the user hits the save button,
it knows what to call it by default.
this.setFilename(response, filename);
response.setContentType("application/vnd.xfdl");
theForm.writeForm(response.getOutputStream(), null, 0);
}
...
} |
ここで、IBMWorkplaceFormsServerServlet#useXFDLまたはuseHTMLを呼び出すことで、フォームのダウンロード方法を容易に変更することができます。HTMLを指定した場合には、後にサブミットされるフォームと連動できるようなHTMLを、FormNodeP#writeFormを呼び出すだけで容易に生成できます。 とても強力な機能で、IBMWorkplaceFormsServerServletを継承する主な理由の一つです。
Web Form Server API – アップロードされたフォームの処理
クライアントからsubmitされたフォームは、doPostメソッドのHttpServletRequestから取得できます。 HttpServletRequestのInputStreamをXFDL#readForm()の第一引数に渡して呼び出します。
フォームの処理 例-Java
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
Locale serverLocale = SampleResources.getServerLocale();
Locale browserLocale = SampleResources.getBrowserLocale(request);
...
FormNodeP theForm = null;
String dirname = request.getParameter("dirname");
if (dirname == null) dirname= "";
String filename = request.getParameter("filename");
//if a filename was not provided, then use the currently known filename
if (filename == null) filename= this.getFilename(request);
try
{
XFDL theXFDL = IFSSingleton.getXFDL();
theForm = theXFDL.readForm(request.getInputStream(),
XFDL.UFL_SERVER_SPEED_FLAGS);
...
} |
アプリケーション側では、取得したFormNodePをアプリケーションコンテキスト内のストレージにwriteFormを使用して保存してもよいでしょうし、特定の部分の値によって、ビジネスロジックを呼び出したり、一部をそのまま送信したりすることもあり得るでしょう。
まとめ
XFDLはXML文書なので、既存のインターフェース実装を用いても、単純なフォームの内容をProgrammableに理解し、使用することは可能です。 しかしWorkplace Forms APIは、フォーム文書を扱うためにそれらのインターフェース群を最適化しており、また、今回説明したような強力なユーティリティ機能も備えています。 今回は例示しませんでしたが、フォームに追加された署名を取得し、確認を行うためのインターフェース(詳細は、各言語APIマニュアルのFormNodeP#getSignature()や、Signatureクラスに記述されています)など、ビジネスユースでは重要でありながら既存の実装では扱うことが難しい機能も用意されています。 また、FCIはフォームが適用できる範囲を、自由に拡張できる可能性も持っています。 これらの仕組みを使用して、フォームを活用した自由なアプリケーションを作成してください。
参考文献
著者について  | |  | 小杉晋央は、ソフトウェア開発研究所でWorkplace製品の開発を行っています。休みの日には庭弄りをよく行いますが、クラス設計と庭木の剪定に少し通じるものを発見し、日常の楽しみが増えたことを喜んでいます。 |
記事の評価
|  |