コマンドインストラクションを実装したxfyコンポーネントの開発

この文書では、XVCDやJava言語で定義するコマンドの部品となるコマンドインストラクションの作成方法を解説します。

1. はじめに

コマンドインストラクションは、xfyプラットフォーム上で処理を実行する単位であるコマンドを作成するための部品です。XVCDでは、下記の例のように、コマンドインストラクションを組み合わせてコマンドを定義できます。

<!-- 
    複数のコマンドインストラクションを組み合わせて、
    独自のコマンドを定義している例
-->
<xvcd:command name="sample:command">
    <!--
        メッセージボックスを表示し、
        ユーザーの選択結果を受け取るコマンドインストラクション
    -->
    <instruction:message-box type="question" option="yes-no" return-to="result">
        削除してもよろしいですか?
    </instruction:message-box>
    <!--
        条件分岐のコマンドインストラクションで、
        [Yes]が選択されたか確認
    -->
    <instruction:if test="$result/instruction:dialog-result = 'yes'">
        <!-- ノードを削除するコマンドインストラクション -->
        <xvcd:delete select="xvcd:caret-node()"/>
    </instruction:if>
</xvcd:command>

xfy technologyでは、開発者が独自のコマンドインストラクションを提供するxfyコンポーネントを作成することができます。このxfyコンポーネントを、インストラクションコンポーネントと呼びます。インストラクションコンポーネントを含むxfyプラグインをxfyユーザーエージェントの実行環境に配置すると、作成した独自のインストラクションをXVCDでコマンドを定義するときに使用できます。

この文書では、インストラクションコンポーネントをJava言語で作成する手順を説明します。

この文書内のサンプルコードは、xfy Developer's Toolkit 配布パッケージ内のdoc/samples/developers/tutorial/java_instruction_componentに収録されています。xfy Developer's Toolkitには、このほかにも豊富なサンプルが用意されています。ぜひ、それらのサンプルもご覧ください。

1.1. インストラクションコンポーネント作成の概要

インストラクションコンポーネントは以下の部品から構成されます。

インストラクションパーサーサービスプロバイダ
作成したインストラクションをxfyプラットフォームで使えるようにするためのクラスです。xfyプラットフォームからの問い合わせに対して、インストラクションの要素を解析し、対応するInstructionFactoryのインスタンスを作成して返します。インストラクションパーサーサービスプロバイダは、InstructionParserServiceインターフェイスを実装したクラスです。InstructionParserServiceがxfyプラットフォームにおける拡張ポイント(サービスカテゴリ)になっています。
インストラクションファクトリ実装クラス
インストラクションの要素を解析して得られた、属性や要素の内容などのパラメータを保持し、実行時にInstructionのインスタンスを作成するクラスです。インストラクションファクトリ実装クラスは、InstructionFactory抽象クラスを実装したクラスです。
インストラクション実装クラス
個々のインストラクションに対応する実装です。インストラクションの実行に対応するexecuteメソッドを持ちます。インストラクション実装クラスは、Instructionインターフェイスを実装したクラスです。

独自のインストラクションを作成してxfyプラットフォームで利用できるようにするには、これらのクラスを作成します。

インストラクション実装クラスとインストラクションファクトリ実装クラスは、それぞれ追加するインストラクションの数だけ作成します。インストラクションパーサーサービスプロバイダは、これらの複数のインストラクションをxfyプラットフォームで使えるようにするためのクラスです。このクラスは1つだけ作成します。作成したインストラクションパーサーサービスプロバイダの情報は、xfyプラグインのプラグインマニフェストファイルに記述します。

従って、インストラクションコンポーネントを作成するときは、次のファイルが必要です。

  • インストラクションパーサーサービスプロバイダ
    1つのインストラクションコンポーネントに1つだけ作成します。
  • プラグインマニフェストファイル
    1つのインストラクションコンポーネントに1つだけ作成します。
  • インストラクションファクトリ実装クラス
    1つのインストラクションコンポーネントに実装するインストラクションの数だけ作成します。
  • インストラクション実装クラス
    1つのインストラクションコンポーネントに実装するインストラクションの数だけ作成します。

これらのクラスをコンパイルするには、xfyCore.jarCategories.jarが必要です。そのため、インストラクションコンポーネントを作成する環境では、クラスパスにxfyCore.jarcommon/com.xfy/Categories.jarを追加します。これらのJARファイルは、xfyユーザーエージェントの実行環境の基点フォルダ内にあるbinフォルダ以下にあります。

1.2. xfyプラットフォームのインストラクション処理の流れ

xfyプラットフォームがインストラクションを処理する流れと、インストラクションコンポーネントで実装したクラスの役割は、次のとおりです。

  1. インストラクション要素を解析して、コマンドを作成する
    インストラクション列は次のような流れで解析されて、コマンドとしてまとめられます。
    1. xfyプラットフォームがインストラクション列を順次解析する
      個々のインストラクションを解析するときは、インストールされているインストラクションコンポーネントのInstructionParserService実装クラスに順次問い合わせます。 InstructionParserService実装クラスは、xfyプラットフォームから渡されたインストラクションの要素の名前をチェックします。知っているインストラクションであれば対応するInstructionFactoryのオブジェクトを作成して返します。このとき、必要ならば属性値や要素内容を解析し、InstructionFactoryに解析した結果を保持させます。
    2. コマンドを作成する
      xfyプラットフォームがインストラクション列を順次解析した結果、生成されたInstructionFactoryを1つのコマンドにまとめます。
  2. インストラクションを実行する
    ユーザー操作などをトリガーとしてコマンドが呼び出されると、実行時のコンテキストでコマンド内のインストラクション列が順次実行されます。
    1. InstructionFactoryからInstractionが作成される
    2. Instructionexecuteメソッドが実行される
    生成されたInstructionオブジェクトは、コマンド内のインストラクション列が順次実行されてコマンドの処理が終了したときに破棄されます。

2. インストラクションコンポーネントの作成

この章では、非常に単純なインストラクションを例に、インストラクションコンポーネントの作成方法を解説します。

例として、プラットフォームコマンドインストラクションのinstruction:beepと同じ仕様の、ビープ音を鳴らすだけのインストラクションを作成します。作成するインストラクションの仕様は、次のとおりとします。

名前空間URI参照
http://xmlns.example.com/developer/instruction
名前空間接頭辞
example
インストラクション名
beep
Javaパッケージ名
com.example.developer.instruction

このインストラクションには、属性や要素の内容、発生する例外はありません。

2.1. インストラクションパーサーサービスプロバイダの実装

インストラクションを追加するにはInstructionParserServiceインターフェイスを実装したクラスを作成します。InstructionParserServiceインターフェイスでは、インストラクション要素の記述に基づいてInstructionFactoryオブジェクトを作成するメソッドを実装します。InstructionParserServiceインターフェイスはサービスカテゴリです。

InstructionParserServiceインターフェイスは、common/com.xfy/Categories.jarに含まれています。インターフェイスの定義は、次のとおりです。

package com.xfy.common.framework.instruction;

import com.xfy.common.dom.Element;
import com.xfy.common.dom.util.ValidationException;
import com.xfy.common.xml.XmlSource;
import com.xfy.place.service.Service;

public interface InstructionParserService extends Service {

    public InstructionFactory createInstructionFactory(Element elem, XmlSource source)
        throws ValidationException;

}

createInstructionFactoryメソッドの引数にはインストラクション要素が設定されます。インストラクション要素はElementオブジェクトで表されます。createInstructionFactoryメソッドは、次のように実装します。まず、要素の名前空間URI参照とローカル名を調べます。その要素が、実装しているインストラクションコンポーネントで提供されるインストラクションのときは、対応するInstructionFactoryを作成して返します。そうでなければ、nullを返します。

2つめの引数のXmlSourceオブジェクトには、XMLソース情報が設定されています。このオブジェクトは、エラーメッセージに行番号などのXMLソース情報を出力するために使用します。

example:beepインストラクションを提供するときの実装は、次のとおりです。

package com.example.developer.instruction;

import com.xfy.common.dom.Element;
import com.xfy.common.dom.util.ValidationException;
import com.xfy.common.framework.instruction.InstructionFactory;
import com.xfy.common.framework.instruction.InstructionParserService;
import com.xfy.common.xml.XmlSource;
import com.xfy.place.XfyObject;

public class ExampleInstructionParser extends XfyObject implements InstructionParserService {

    public InstructionFactory createInstructionFactory(Element elem, XmlSource source)
        throws ValidationException {

        // 要素の名前空間 URL を調べる
        String namespaceUri = elem.getNamespaceURI();
        if ("http://xmlns.example.com/developer/instruction".equals(namespaceUri)) {
            // 要素のローカル名を調べる
            String localName = elem.getLocalName();
            if ("beep".equals(localName)) {
                // exapmle:beep インストラクションのファクトリを作成して返す
                return new BeepInstructionFactory(source);
            }
        }
        // 知らない要素の場合は null を返す
        return null;
    }
}

2.1.1. インストラクション要素解析時の例外処理について

インストラクションに必須の属性が設定されていないときや、XPath式の構文エラーがあるときなど、インストラクションの記述に誤りがあることが判明した場合は、例外ValidationExceptionをスローします。例外をスローすると、XVCD開発者がインストラクションの記述をまちがえたときは、実行時にエラーメッセージが表示されるようになります。example:beepインストラクションの場合は属性のない空要素なので、この実装例では例外はスローしません。

属性や要素の内容を解析する必要がある場合は、「属性や要素の内容の利用」をご覧ください。

2.2. インストラクションファクトリの実装

インストラクションファクトリは、インストラクション要素の解析結果を保持しておき、コマンド実行時にインストラクションのオブジェクトを生成する役目を持ったクラスです。インストラクションファクトリは、InstructionFactory抽象クラスから派生したクラスに実装します。InstructionFactory抽象クラスの定義は、次のとおりです。

package com.xfy.common.framework.instruction;

import com.xfy.common.xml.XmlSource;
import com.xfy.common.xpath.value.Value;
import com.xfy.common.xpath.value.ValueException;
import com.xfy.place.XfyObject;

public abstract class InstructionFactory extends XfyObject {

    protected final XmlSource source;

    public InstructionFactory(XmlSource source) {
        this.source = source;
    }

    public abstract Instruction createInstruction(InstructionContext c);

    public Value createEnabledValue(InstructionContext c) throws ValueException {
        return null;
    }

    public XmlSource getSource() {
        return this.source;
    }

}

インストラクションファクトリを実装するときは、次の作業をします。

  • getSourceメソッドでXmlSourceオブジェクトを返す
  • createInstructionメソッドでインストラクションを生成して返す
  • 必要に応じて、createEnabledValueメソッドでインストラクションの実行可否を判定するValueオブジェクトを返す

2.2.1. getSourceメソッドの実現

InstructionFactory派生クラスのgetSourceメソッドが XmlSourceオブジェクトを返すには、コンストラクタを実装する方法があります。ここではその方法を説明します。

XmlSourceオブジェクトを引数とするコンストラクタを作成します。そのコンストラクタで、派生元クラスのコンストラクタを呼び出します。example:beepインストラクションの場合、次のように実装します。

public class BeepInstructionFactory extends InstructionFactory {

    /** コンストラクタ */
    public BeepInstructionFactory(XmlSource source) {
        super(source);
    }

}

super(source)protectedフィールドにXmlSourceオブジェクトが設定されます。getSourceメソッドは、protectedフィールドに設定されたXmlSourceオブジェクトを返します。このように実装すると、getSourceメソッドをオーバーライドしなくてすみます。

2.2.2. インストラクションの生成

createInstructionメソッドは、Instructionオブジェクトを生成して返すように実装します。example:beepインストラクションの場合、次のように実装します。

package com.example.developer.instruction;

import com.xfy.common.framework.instruction.Instruction;
import com.xfy.common.framework.instruction.InstructionContext;
import com.xfy.common.framework.instruction.InstructionFactory;
import com.xfy.common.xml.XmlSource;

public class BeepInstructionFactory extends InstructionFactory {

    public BeepInstructionFactory(XmlSource source) {
        super(source);
    }

    public Instruction createInstruction(InstructionContext c) {
        return new BeepInstruction(c);
    }

}

引数のInstructionContextオブジェクトは、インストラクションが実行時のコンテキスト情報を参照できるように、インストラクションの引数に設定します。

2.2.3. 実行可否の判定

createEnabledValueメソッドは、インストラクションを実行できるかできないかを判断し、その結果を示す値を返すメソッドです。コンテキストに依存せず常に実行できる場合は、nullを返します。抽象クラスInstructionFactoryでは、createEnabledValueメソッドはnull を返すように実装されています。従って、常に実行できるインストラクションを作成するときはオーバーライドする必要はありません。

example:beepインストラクションの場合は常に実行できるので、createEnabledValueメソッドはオーバーライドしません。

インストラクションを実行できるかできないかを判断する必要がある場合は、「インストラクションの実行可否の判断」をご覧ください。

2.3. インストラクションの実装

個々のインストラクションは、Instructionインターフェイスを実装したクラスを作成します。Instructionインターフェイスの定義は、次のとおりです。

package com.xfy.common.framework.instruction;

import com.xfy.place.command.CommandException;

public interface Instruction {

    /**
     * 命令を実行する
     */
    public void execute() throws CommandException;

    /**
     * この命令を破棄する
     */
    public void destroy();

}

実際にインストラクションを作成するときは、Instructionインターフェイスを実装した抽象クラスAbstructInstructionクラスを継承して作成します。  AbstructInstructionクラスの定義は、次のとおりです。

package com.xfy.common.framework.instruction;

import com.xfy.place.XfyObject;

/**
 * インストラクションの抽象実装クラス
 */
public abstract class AbstractInstruction extends XfyObject implements Instruction {

    protected InstructionContext context;

    public AbstractInstruction(InstructionContext c) {
        this.context = c;
    }
    public void destroy() {
        this.context = null;
        super.destroy();
    }

}

インストラクションを実装するときは、次の作業をします。

  • InstructionContextオブジェクトを保持するように実装する
  • executeメソッドに、インストラクションで実行する処理を記述する
  • 必要に応じて、executeメソッドで例外処理をする

2.3.1. インストラクションのコンストラクタ

インストラクションが実行されるときにコンテキスト情報を利用できるように、InstructionContextを引数にとるコンストラクタを作成します。インストラクションファクトリは、インストラクションを生成するときに、このコンストラクタを使用します。

package com.example.developer.instruction;

import com.xfy.common.framework.instruction.AbstractInstruction;
import com.xfy.common.framework.instruction.InstructionContext;
import com.xfy.place.command.CommandException;

public class BeepInstruction extends AbstractInstruction {

    /**
     * コンストラクタ
     * @param c インストラクション実行時のコンテキスト情報
     */
    public BeepInstruction(InstructionContext c) {
        super(c);
    }
}

インストラクションで属性や要素の内容を利用する場合は、それらを解析した結果も引数リストに含めて、フィールドに保持するようにします。

2.3.2. インストラクションの実行

コマンドでインストラクションを実行するときは、Instructionオブジェクトのexecuteメソッドが呼び出されます。インストラクションで実行する処理は、executeメソッドに記述します。

example:beepインストラクションはビープ音を鳴らすインストラクションです。この機能を実現するため、Javaの標準APIを利用してビープ音を鳴らす処理を記述します。

public class BeepInstruction extends AbstractInstruction {

    public BeepInstruction(InstructionContext c) {
        super(c);
    }

    public void execute() throws CommandException {
        // ビープ
        java.awt.Toolkit.getDefaultToolkit().beep();
    }

}

2.3.3. インストラクション実行時の例外処理

インストラクションを実行するときに例外が発生した場合は、その種類により、com.xfy.place.command.CommandExceptioncom.xfy.place.command.CommandWarningExceptionを使い分けてスローします。

  • CommandWarningExceptionをスローする場合
    変数名が正しくないといったXPath式の評価エラーをはじめとするインストラクション記述の誤りによる実行時エラーの場合にスローします。この例外をスローすると、警告メッセージが表示され、XVCDに記述ミスがあることがXVCD開発者に通知されます。エラーにはXVCD開発者が対処します。XVCD開発者はXVCDの記述ミスを修正します。
  • CommandExceptionをスローする場合
    インストラクション記述に誤りはないが、インストラクション固有の処理で例外が発生した場合にスローします。例えば、ファイル操作系のAPIを呼び出したときに、IOExceptionが発生した場合です。このような場合は、発生した例外をCommandExceptionでラップしてスローします。CommandExceptionをスローすると、インストラクションを利用するXVCD開発者が、 instruction:tryインストラクション instruction:catch要素を使用して、例外を処理できるようになります。

2.4. プラグインマニフェストファイルの記述

インストラクションコンポーネントをxfyプラットフォームから利用できるようにするには、インストラクションコンポーネントを含むxfyプラグインのプラグインマニフェストファイルにサービスカテゴリInstructionParserServiceを実装したクラスをサービスプロバイダとして記述します。今回の実装例の場合は、プラグインマニフェストファイルに次のように記述します。

<provider class="com.example.developer.instruction.ExampleInstructionParser">
    <category class="com.xfy.common.framework.instruction.InstructionParserService"/>
</provider>

3. 属性や要素の内容の利用

XVCDでインストラクションを実行するときの条件や必要な情報は、インストラクション要素の属性や要素の内容に設定して受け渡すことができます。属性を利用するインストラクション要素の例を次に示します。

<xvcd:delete select="/foo:root/foo:list/foo:item[1]" />

また、要素の内容を利用するインストラクション要素の例を次に示します。

<xvcd:insert ref="/foo:root/foo:list" position="last-child">
    <foo:item>New Item</foo:item>
</xvcd:insert>

この章では、インストラクション要素の属性や要素の内容に設定された情報を解析し、利用する方法を解説します。

3.1. 属性や要素の内容を利用する手順

属性値や要素の内容を利用する場合、まずインストラクション要素に対応するインストラクションファクトリを作成する段階で、属性値や要素内容も解析します。必須の属性や要素がない場合や、形式が不正な場合は例外をスローします。解析結果を取得できた場合は、インストラクションファクトリにその解析結果を渡します。

public class FooInstructionParser extends XfyObject implements InstructionParserService {

    public InstructionFactory createInstructionFactory(Element elem, XmlSource source)
        throws ValidationException {
        :
        : 名前空間やローカル名を調べるところは省略
        :
        // bar 属性値を取得
        String bar = elem.getAttributeNS(null, "bar");

        // 属性値をチェックして不正なら例外をスローする
        if(...) {
            throw new ValidationException("...", elem);
        }

        // インストラクションファクトリに取得した値を渡す
        return new FooInstructionFactory(source, bar);
        :
        :
    }

}

インストラクションファクトリは属性や要素の内容の解析結果をフィールドに保持します。インストラクションを呼び出すときには、保存している解析結果を引数に設定します。

public class FooInstructionFactory extends InstructionFactory {

    /** bar属性値の値を覚えるためのフィールド */
    private String bar;

    /**
      * インストラクションファクトリのコンストラクタ
      * @param source
      * @param bar bar属性値
      */
    public BeepInstructionFactory(XmlSource source, String bar) {
        super(source);
        this.bar = bar;
    }

    public Instruction createInstruction(InstructionContext c) {
        // 生成するインストラクションにbar属性値を渡す
        return new FooInstruction(c, this.bar);
    }

}

インストラクションも同様に解析結果を保持します。executeメソッドでは、保持している解析結果を利用します。

public class FooInstruction extends AbstractInstruction {

    /** bar属性値の値を覚えるためのフィールド */
    private final String bar;

    /**
      * インストラクションのコンストラクタ
      * @param c
      * @param bar bar属性値
      */
    public FooInstruction(InstructionContext c, String bar) {
        super(c);
        this.bar = bar;
    }

    public void execute() throws CommandException {
        :
        // 属性値を使って処理
        obj.setMessage(this.bar);
        :
    }

}

以下、属性値や要素の内容を利用する代表的なパターンと、実装方法や使用するAPIを紹介します。

3.2. 属性値の利用

属性を利用する場合、利用方法は次の2つに分けられます。

  • 文字列や数値、QNameなどの値としてそのまま扱う
  • 属性値をXPath式として評価した値を使用する

前者はインストラクション要素を解析するときに値をチェックして確定することができます。後者は、インストラクション要素を解析するときはXPath式の解析だけをして、インストラクションを実行するときにXPath式を評価して値を取得するという段階を踏む必要があります。

属性値に属性値テンプレートを使えるようにする場合も、後者に含まれます。「エンコード済みURLを指定する。属性値テンプレートを記述できる。」仕様の場合は、実行時にXPath式を評価して文字列値を取得し、さらにエンコード済みURLとして解析することになります。

xfyプラットフォームのプラットフォームコマンドインストラクションで、属性値を利用するパターンの具体的な例を示します。

属性値の文字列をそのまま使う
<instruction:file-chooser type="save" .../>
属性値をQNameとして扱う
<instruction:file-chooser return-to="qname" .../>
属性値をXPath式として扱う
<xvcd:delete select="/foo:root/foo:list/foo:item[1]" .../>
属性値テンプレートを記述でき、その評価結果の文字列をそのまま使う
<instruction:file-chooser title="{xvcd:string-resource('title')}" .../>
属性値テンプレートを記述でき、その評価結果の文字列をエンコード済みURLとして扱う
<instruction:file-chooser directory="{$directory-url}" .../>

属性を利用する方法に応じたユーティリティメソッドがcom.xfy.common.dom.util.ValidationUtilitiesクラスに用意されています。用意されているユーティリティメソッドの用途は、次のとおりです。

  • 属性値の文字列を取得する
  • 属性値をQNameとして解析し取得する
  • 属性値を属性値テンプレートが使える文字列として解析し、XPath 式を取得する
  • 属性値をXPath式として解析し取得する

属性値を解析するときは、これらのユーティリティメソッドを使用します。また、ユーティリティメソッドには次に挙げる機能も備えられています。

  • 必須の属性が設定されていない場合は、例外ValidationExceptionをスローする
  • 属性が省略された場合は、引数で与えたデフォルト値を返す
  • 記述に誤りがある場合は、例外ValidationExceptionをスローする

3.2.1. 属性="XPath式" の場合

属性値をXPath式として解析・取得するには、ValidationUtilitiesクラスの次のメソッドを使用します。返値はXPath式を表すcom.xfy.common.xpath.Exprクラスのオブジェクトです。

  • public static Expr getAttributeXPath(boolean needed, Element elem, String attrName) throws ValidationException
  • public static Expr getAttributeXPath(Element elem, String attrName, String defValue) throws ValidationException

1つめのメソッドでは、必須属性が設定されているかいないかをチェックできます。例えば、xvcd:deleteのように、XPath式をとるselect属性が必須の場合は、次のように記述します。

public InstructionFactory createInstructionFactory(Element elem, XmlSource source)
    throws ValidationException {
    :
    // 必須の select 属性の値を XPath 式として解析し取得する
    Expr select = ValidationUtilities.getAttributeXPath(true, elem, "select");
    :
}

XPath式の記述に誤りがあるときには例外ValidationExceptionがスローされます。また、最初の引数にtrueを設定すると、必須の属性が省略されたときに例外ValidationExceptionがスローされます。これらの例外は、インストラクションの記述に誤りがあるときに発生します。従って、この例外はキャッチする必要はないので、そのままスローします。このようにすると、正しく記述されていないインストラクションを実行したときに、記述ミスを警告するメッセージが表示されます。

Instruction実装クラスのexecuteメソッドでは、実行時のコンテキストでXPath式を評価して値を取得します。XPath式は XPathUtilitiesクラスでXPath評価サービス(XPathEvaluationService)を取得して評価します。XPath式を評価するときの実装例は、次のとおりです。

import com.xfy.common.xpath.evaluate.XPathEvaluationService;
import com.xfy.common.xpath.value.ValueException;
import com.xfy.common.xpath.XPathUtilities;

public class FooInstruction extends AbstractInstruction {

    private Expr select;

    :

    public void execute() throws CommandException {
        try {
            // XPath 評価サービスを取得
            XPathEvaluationService evaluator = XPathUtilities.evaluator();
            // select 属性の XPath 式を評価
            Value value = evaluator.evaluate(this.select, this.context);
            // ノードセットとして取得
            NodeSet nodeSet = value.getAsNodeSet();
            :
        } catch (ValueException e) {
            throw new CommandWarningException(e);
        }
    }

}

XPath式を評価するときにValueExceptionがスローされるのは、インストラクション利用者が属性値のXPath式を正しく記述していないのが原因です。従って、キャッチした例外をCommandWarningExceptionでラップしてスローします。CommandExceptionではなく、CommandWarningExceptionであることに注意してください。このようにすると、XPath式が正しく記述されていないときに、記述ミスを警告するメッセージが表示されます。

3.2.2. 属性="{属性値テンプレート}"の場合

属性に属性値テンプレートを記述できるようにする場合、属性値の解析にはValidationUtilitiesの次のメソッドを使用します。

  • public static Expr getAttributeValueTemplate(boolean needed, Element elem, String attrName) throws ValidationException
  • public static Expr getAttributeValueTemplate(Element elem, String attrName, String defValue) throws ValidationException

返値はXPath式を表すExprオブジェクトなので、以降は属性値にXPath式を記述するようにした場合と同じです。Instruction実装クラスのexecuteメソッドの中で、以下のようにして文字列値を取得します。

public void execute() throws CommandException {
    try {
        XPathEvaluationService evaluator = XPathUtilities.evaluator();
        // 文字列として取得
        Value value = evaluator.evaluate(this.expr, this.context);
        String string = value.getAsString();
        :
    } catch (ValueException e) {
        throw new CommandWarningException(e);
    }
}

実行時に評価して取得した文字列をQNameとして解析するには、com.xfy.place.xml.Nameクラスのparseメソッドを利用します。また、エンコード済みURLとして解析するには、com.xfy.place.util.EncodedURLクラスのgetInstanceメソッドを利用します。

3.2.3. 属性="QName" の場合

属性値をQName文字列として解析・取得するには、属性値の解析にはValidationUtilitiesクラスの次のメソッドを使用します。

  • public static Name getAttributeName(boolean needed, Element elem, String attrName, boolean defNamespace) throws ValidationException
  • public static Name getAttributeName(Element elem, String attrName, boolean defNamespace, String defValue) throws ValidationException

3.2.4. 属性値の文字列をそのまま取得する

ValidationUtilitiesgetAttributeメソッドを使うと、属性値をそのまま文字列として取得できます。

  • public static String getAttribute(boolean needed, Element elem, String attrName)
  • public static String getAttribute(Element elem, String attrName, String defValue)

属性値を数値や特定のフォーマットの文字列として解釈する場合は、このメソッドで属性値を取得して解析します。属性値を文字列として取得する実装例は、次のとおりです。

// foo="yes" なら true, それ以外なら false
String foo = ValidationUtilities.getAttribute(true, elem, "foo");
boolean isFoo = "yes".equals(foo);

また、属性値をint型の数値として取得する実装例を次に示します。

// 属性値文字列を取得する
String string = ValidationUtilities.getAttribute(true, elem, "number");
// int として解析
try {
    int number = Integer.parseInt(string);
    :
} catch (NumberFormatException e) {
    // エラー
    throw new ValidationException("message", e, elem);
}

3.3. 要素内容の利用

インストラクション要素の内容を利用する場合、利用方法は次の2つに分けられます。

  • 要素の内容に記述されたテキストや要素をそのまま利用する
    次の記述例のような場合は、要素の内容をそのまま利用しています。
    <instruction:file-chooser ...>
        <instruction:filter .../>   <-- instruction:filter 要素として解析する -->
    </instruction:file-choose>
  • VCのテンプレートルールとして解析し、インストラクションを実行するときに評価した結果を利用する
    次の例の場合は、まず要素の内容をVCのテンプレートルールとして解析します。インストラクションを実行するときは、テンプレートルールを実行時のコンテキストで評価した結果を利用します。
    <instruction:tree-message>
        <xvcd:copy-of select="$result"/>  <-- 実行時に評価した結果フラグメントを利用する -->
    </instruction:tree-message>

要素の内容をそのまま解析する場合は単純にDOM APIを使用します。一方、VCのテンプレートルールを記述できるようにする場合は、あらかじめ要素の内容をVCのテンプレートルールとして解析しておきます。そのあとインストラクションを実行するときに、テンプレートルールを実行時のコンテキストで評価する必要があります。

3.3.1. 要素の内容をチェックするユーティリティメソッド

要素の内容をチェックするためのユーティリティメソッドが、ValidationUtilitiesクラスにいくつか用意されています。用意されている主なメソッドは、次のとおりです。

public static void checkNeedContents(Element elem) throws ValidationException
要素に内容が記述されているかいないかを調べます。要素の内容が空ならば、例外をスローします。要素が必ず内容を持つ場合の、最初のチェックに利用できます。内容に含まれる要素名や、テキストの書式などの詳細は、個別にチェックする必要があります。
public static void checkEmptyElement(Element elem) throws ValidationException
空要素かそうでないかを調べます。空要素でなければ、例外をスローします。要素が常に内容を持たない場合のチェックに利用できます。
public static void checkUnknownText(Node n, Element elem) throws ValidationException
空白文字だけからなるテキストノードかそうでないかを調べます。空白文字だけからなるテキストノードでなければ、例外をスローします。最初の引数には、空白文字だけからなるテキストノードであるはずのノードを設定します。

3.3.2. 要素の内容が特定のフラグメントの場合

instruction:file-chooserのように、要素の内容が特定の要素や文字列に限られる場合は、DOM APIで内容をチェックして値を取得します。instruction:file-chooserが、子要素がinstruction:filter要素かそうでないかをチェックしている部分を例に挙げます。

// instruction:file-chooser 要素の内容を解析
for (Node child = elem.getFirstChild(); child != null; child = child.getNextSibling()) {
    // instruction:filter 要素なら、要素を解析してフィルタオブジェクトを生成
    // そうでないなら例外を投げる
    if (child.getNodeType() == Node.ELEMENT_NODE) {
        if (((Element) child).getCompleteName().equals(FILTER)) {
            // instruction:filter 要素の場合
            FileChooserFilter filter = ...;
            :
        } else {
            // instruction:filter 以外の未知の要素
            throw new ValidationException("エラーメッセージ", child);
        }
    } else {
        // 空白文字なら無視、他の文字なら例外を投げる
        ValidationUtilities.checkUnknownText(child, elem);
    }
}

3.3.3. 要素の内容がテンプレートルールの場合

要素内容がVCのテンプレートルールの場合は、次の手順で処理します。

  1. インストラクション要素の内容に記述されたテンプレートルールを解析し、解析結果を表すSequenceEvaluatorオブジェクトを作成する
  2. 作成したSequenceEvaluatorオブジェクトをインストラクションファクトリーやインストラクションの引数に設定する

実装例を次に示します。

インストラクションパーサーサービスプロバイダでは、インストラクション要素の内容に記述されたテンプレートルールを解析します。その結果をインストラクションファクトリの引数に設定します。

import com.xfy.common.vc.VcUtilities;
import com.xfy.common.vc.SequenceEvaluator;
import com.xfy.common.vc.SequenceParserService;
:
public class FooInstructionParser extends XfyObject implements InstructionParserService {

    public InstructionFactory createInstructionFactory(Element elem, XmlSource source)
        throws ValidationException {
        :
        SequenceParserService parser = VcUtilities.sequenceParser();
        SequenceEvaluator evaluator = parser.parseSequence(elem, source);
        return new FooInstructionFactory(source, evaluator);
        :
    }

}

インストラクションファクトリ実装クラスでは、生成するインストラクションの引数にSequenceEvaluatorオブジェクトを設定します。

public class FooInstructionFactory extends InstructionFactory {

    private SequenceEvaluator evaluator;

    public FooInstructionFactory(XmlSource source, SequenceEvaluator evaluator) {
        super(source);
        this.evaluator = evaluator;
    }

    public Instruction createInstruction(InstructionContext c) {
        return new FooInstruction(c, this.evaluator);
    }

}

インストラクション実装クラスでは、executeメソッドの中でSequenceEvaluatorオブジェクトを評価します。評価結果をXML文書フラグメントとして取得する実装例は、次のとおりです。

public class FooInstruction extends AbstractInstruction {

    private SequenceEvaluator evaluator;

    public FooInstruction(InstructionContext c, SequenceEvaluator evaluator) {
        super(c);
        this.evaluator = evaluator;
    }

    public void execute() throws CommandException {
        // 評価結果フラグメントを
        // コンテキストノードのオーナードキュメントにインポート
        DocumentFragment fragment = this.evaluator.importAsFragment(this.context);
        // 以下、DOM API でフラグメントを利用する
        :
    }
}

SequenceEvaluatorオブジェクトは、最後にdestroyメソッドを呼び出して破棄する必要があります。SequenceEvaluatorオブジェクトの破棄は、InstructionFactory派生クラスのdestroyメソッドをオーバーライドして実装します。インストラクション実装クラスではなく、解析結果を保持するファクトリクラスで破棄する点に注意してください。

SequenceEvaluatorオブジェクトの破棄の実装例を、次に示します。

public class FooInstructionFactory extends InstructionFactory {

    private SequenceEvaluator evaluator;

    public FooInstructionFactory(XmlSource source, SequenceEvaluator evaluator) {
        super(source);
        this.evaluator = evaluator;
    }

    public Instruction createInstruction(InstructionContext c) {
        return new FooInstruction(c, this.evaluator);
    }

    public void destroy() {
        this.evaluator.destory();
        super.destroy();
    }

}

4. 実行結果を返すインストラクションの作成

XPath関数と異なり、インストラクションには返値はありません。従って、インストラクションの実行結果を関数の返値のように受け取って利用したい場合は、何らかの工夫が必要になります。xfyプラットフォームが提供するダイアログボックス系のインストラクションでは、実行結果をXML文書フラグメントにして、変数に格納しています。こうすることで、インストラクションの実行結果を後続のインストラクションでも参照できるようになります。

この章では、実行結果のXMLフラグメントを変数に格納して、後続のインストラクションで参照できるようにしたインストラクションの実装方法を解説します。

インストラクションの実行結果を参照するXVCDの例を、 instruction:message-boxインストラクションを使用して示します。

<xvcd:command name="...">
    <!--
        確認メッセージ[はい][いいえ]を表示。
        結果は result という名前の変数に格納。
    -->
    <instruction:message-box title="確認" type="question" option="yes-no" return-to="result">
        ●●してもよろしいですか?
    </instruction:message-box>

    <!-- 後続のインストラクションで結果を参照する -->
    <instruction:if test="$result = 'yes'">
          :
    </instruction:if>

</xvcd:command>

このようなインストラクションを作成するには、次の作業が必要です。

4.1. 変数名を指定する属性を用意する

変数名を指定する属性の名前を"return-to"とする場合の実装例を以下に示します。

インストラクションパーサーサービスプロバイダでは、"return-to"属性の値をQNameとして解析・取得して、インストラクションファクトリの引数に設定します。

public class FooInstructionParser extends XfyObject implements InstructionParserService {

    public InstructionFactory createInstructionFactory(Element elem, XmlSource source)
        throws ValidationException {
        :
        // return-to属性値をQNameとして取得
        Name returnTo = ValidationUtilities.getAttributeName(true, elem, "return-to", false);
        // インストラクションファクトリに取得した値を渡す
        return new FooInstructionFactory(source, returnTo);
        :
    }

}

インストラクションファクトリ実装クラスでは、生成するインストラクションの引数に解析結果を設定します。

public class FooInstructionFactory extends InstructionFactory {

    private Name returnTo;

    public BeepInstructionFactory(XmlSource source, Name returnTo) {
        super(source);
        this.returnTo = returnTo;
    }

    public Instruction createInstruction(InstructionContext c) {
        // 生成するインストラクションにreturn-to属性値を渡す
        return new FooInstruction(c, this.returnTo);
    }

}

インストラクション実装クラスでは、解析結果をフィールドに保持し、executeメソッド内で利用できるようにしておきます。

public class FooInstruction extends AbstractInstruction {

    /** 結果フラグメントを割り当てる変数の名前 */
    private Name returnTo;

    /** コンストラクタ */
    public FooInstruction(InstructionContext c, Name returnTo) {
        super(c);
        this.returnTo = returnTo;
    }

    /** インストラクション実行時に呼ばれるメソッド */
    public void execute() throws CommandException {
        :
    }

}

4.2. XML文書フラグメントの作成

インストラクションのexecuteメソッドでXML文書フラグメントを作成する実装の例を、次に示します。

public class FooInstruction extends AbstractInstruction {
    :
    /** インストラクション実行時に呼ばれるメソッド */
    public void execute() throws CommandException {
        // インストラクション固有の何らかの処理を行う
        :
        : 省略
        :
        // 結果を格納するXML文書フラグメントを作成する
        DomService dom = (DomService) XfyPlace.getServiceBroker().getProvider(DomService.class);
        DocumentFragment resultFragment = dom.createDocumentFragment();
        // インストラクションの結果をXML文書フラグメントに入れる
        Element e = resultFragment.getOwnerDocument().createElementNS(..);
        resultFragment.appendChild(e);
        :
        : 省略
        :
    }

}

DocumentFragmentDomServiceクラスのcreateDocumentFragmentメソッドで作成します。

作成したDocumentFragmentオブジェクトは後続するインストラクションで使用するので、releaseメソッドを呼ばないようにします。

4.3. XML文書フラグメントの変数への格納

作成したXML文書フラグメントを指定された名前の変数に格納する実装の例を、次に示します。

public class FooInstruction extends AbstractInstruction {

    /** 結果フラグメントを割り当てる変数の名前 */
    private final Name returnTo;

    /** コンストラクタ */
    public FooInstruction(InstructionContext c, Name returnTo) {
        super(c);
        this.returnTo = returnTo;
    }

    /** インストラクション実行時に呼ばれるメソッド */
    public void execute() throws CommandException {
        :
        : 省略
        :
        // XML文書フラグメントを変数に格納
        this.context.setVariable(this.returnTo, new NodeSetValue(resultFragment));
    }

}

インストラクションの属性で指定された変数に値を格納するには、ContextクラスのsetVariable(Name, Value)メソッドを使用します。変数の値として処理結果のXML文書フラグメントを格納することで、後続のインストラクションで実行結果を参照することができるようになります。XML文書フラグメントは DocumentFragmentオブジェクトに格納されています。一方、ContextクラスのsetVariable(Name, Value)メソッドにはXPath関数の値を表すValueオブジェクトで値を設定します。従って、そのままではXML文書フラグメントを格納できません。そこで、NodeSetValueクラスのNodeSetValue(Node)コンストラクタでValueオブジェクトに変換しています。

5. インストラクションの実行可否の判断

xfyプラットフォームでは、状況によってコマンドを実行できない場合があります。コマンドを実行できないときは、そのコマンドが割り当てられたメニュー項目やツールバーのボタンを選択できなくなります。このとき、そのメニュー項目やツールバーのボタンは、グレーアウト表示されます。

XVCDのxvcd:command要素で定義したコマンドの場合、そのコマンドを実行できるかできないかの判断は、enabled属性の設定によって異なります。

xvcd:command要素のenabled属性が省略された場合のコマンドインストラクションを実行できるかできないかの判定には、InstructionFactoryクラスのcreateEnabledValueメソッドが使用されます。

5.1. createEnabledValueメソッド

InstructionFactoryクラスのcreateEnabledValueメソッドのシグネチャは以下のようになっています。

public Value createEnabledValue(InstructionContext c) throws ValueException;

引数で渡されるコンテキスト情報から必要な情報を参照し、インストラクションが実行できるかできないかを判断して結果を返すように実装します。参照する情報としては、次のような情報が挙げられます。

  • キャレット位置
  • ソースDOMツリーの状態
  • インストラクションの属性値や内容

返値はJavaのboolean型の値ではなく、XPath式の評価値を表すValueオブジェクトで返します。

実行時のコンテキストに依存せず、常に実行できる場合はnullを返します。InstructionFactory抽象クラスのcreateEnabledValueメソッドはnullを返すように実装されています。従って、常に実行できるインストラクションを作成するときは、このメソッドをオーバーライドする必要はありません。

createEnabledValueメソッドをオーバーライドする例として、select属性にXPath式を記述して操作対象のノードセットを指定する仕様のインストラクションの例で説明します。このインストラクションでは、select属性のXPath式を評価した結果のノードセットか空かそうでないかで、インストラクションを実行できるかできないかを判定できます。そこで、createEnabledValueメソッド内でselect属性のXPath式を評価し、その結果をValueオブジェクトで返すように実装します。

public class FooInstructionFactory extends InstructionFactory {
    :
    /** select 属性の XPath 式 */
    private Expr select;
    :
    public Value createEnabledValue(InstructionContext c) throws ValueException {
        // select 属性値の XPath 式の評価結果を返す
        return XPathUtilities.evaluator().evaluate(this.select, c);
    }
}

xfyプラットフォームでは、返されたValueオブジェクトのgetAsBooleanメソッドを呼び出して、実行できるかできないかを判断します。select属性のXPath式を評価した結果が空のノードセットのときは、getAsBooleanメソッドはXPathの仕様に従ってfalseを返します。従って、インストラクションは実行できないと判断されます。select属性のXPath式を評価した結果のノードセットが空でないときは、getAsBooleanメソッドがtrueを返すので、インストラクションは実行できると判断されます。

警告:createEnabledValueの返値に関わらずインストラクションが実行される場合があります

createEnabledValueメソッドの返値は、UI項目のグレーアウト判定のために利用されます。createEnabledValueメソッドが返すValueオブジェクトの値がfalseになる場合でも、次の場合ではインストラクションが実行されます。

  • インストラクションがコマンドの先頭に記述されていない場合。
  • コマンドがUI項目以外から実行される場合。
    例えば、xvcd:action要素で記述したイベント処理で、instruction:callインストラクションを使って呼び出される場合。