Script to detect browser version
19. ADF UIXの拡張
Oracle ADF UIXは、そのままでも強力なフレームワークとして使用できますが、アプリケーションに必要なあらゆる機能が提供されるわけではありません。 作業を進めていく段階で、UIXで提供されない追加機能が必要になります。 UIXは、高度な拡張性を備えたフレームワークとして設計されており、独自のクラスおよびXML構文をほとんどの箇所で自由にプラグインできます。
UIXの次の拡張方法については、すでに説明しました。
XMLの<method>要素を使用すると、データを提供またはイベントを処理するJavaコードをアタッチできます。
UIXテンプレートAPIを使用すると、既存のコンポーネントから新規のコンポーネントを作成できます。
XSSを使用すると、スタイルをオーバーライドしたり、新規スタイルを追加したりできます。
ここでは、さらに高度な拡張方法について説明します。 まず、新しいユーザー・インタフェース・コンポーネントを最初から作成する、カスタムのRendererの記述方法を学びます。 次に、ユーザー・インタフェース・コンポーネントのみでなく、UIXで使用するすべてのJava型のための、UIX解析APIを使用したXMLパーサーの記述方法を学びます。 また、XMLを簡単にJavaBeansに変換できる解析APIの拡張について学びます。他のUIX機能を使用しない場合でも、この機能だけは使用する価値があります。 さらに、他の開発者と簡単に作業を共有できるよう、拡張した機能を1つのUIExtensionに統合する方法を学びます。 最後に、コードを変更せずに、他の開発者が定義した要素に属性と要素を追加できるParserExtension APIについて説明します。
ここでは、次の項目について説明します。
このチュートリアルでは、UIX拡張ライブラリとして、「Project Flipper」という架空のクラス・ライブラリを開発する手順について説明します。 Flipperライブラリは、現在UIXで定義されていない便利なBean、RendererおよびUIX要素のリポジトリとして使用するためのものです。 このカスタム・クラス・ライブラリの開発方法を、順を追って説明していきます。カスタム・コンポーネントを作成する際に独自のクラス・ライブラリを開発する場合、この手順に従ってください。 チュートリアルを終了する時点で、1つのカスタム・コンポーネントを含むクラス・ライブラリが完成します。 このカスタム・コンポーネントは、UINodeツリーでカスタムUINodeとして、またはUIX文書でカスタム・タグとして使用できます。
ネームスペースの選択
「ADF UIXでのページの作成」 でも説明したとおり、UIX Componentsは常にネームスペースで識別されます。 ネームスペースは通常URLとして定義されますが、そのURLに実際に文書が存在している必要はありません。 ネームスペースは一意である必要があり、インターネットではすでにURLの一意性の維持という問題が解決されていることが、XMLでこの方法を採用した理由の1つです。
まず、Flipperコンポーネントに対して新規のネームスペースを定義します。 ネームスペースは単にプロジェクト固有のURIであるため、Flipperのネームスペースはhttp://flipper.example.org/uiとします。 また、Flipperプロジェクトで新しいタイプのUINodeを定義するたびに、ローカル名を定義する必要があります。 不足している機能をMarlinで調べると、Flipperライブラリで実装する候補として様々なBeanがあります。 このチュートリアルの目的上、便利で実装も簡単なBeanが必要です。 このため、Flipperライブラリには最初にCopyrightBeanを追加します。 CopyrightBeanは、法律上重要な機能を持つBeanで、オラクル社の著作権表示をレンダリングします。
Flipperのコード・ベースを単純化するため、ネームスペースやローカル名などの重要な定数を定義するFlipperConstantsインタフェースを定義します。
package org.example.flipper.ui;
public interface FlipperConstants
{
/**
* Namespace used by the Flipper implementation
*/
public static final String FLIPPER_NAMESPACE =
"http://flipper.example.org/ui";
/**
* Name of our copyright bean
*/
public static final String COPYRIGHT_NAME = "copyright";
}
Rendererの記述
Rendererは、UIXがUINodeを出力に変換するために使用するインタフェースです。 このインタフェースは作成するすべてのHTMLのベースとなる重要な機能ですが、APIとしては最も単純な部類に入ります。
public interface Renderer
{
public void render(
RenderingContext context,
UINode node) throws IOException;
}
Rendererは、作成したノードのみでなく、ノードのすべての子に対する結果を生成します。 (CopyrightBeanの場合、子ノードはありません。) 次に、Rendererの完全なコードを示します。
package org.example.flipper.ui;
import java.io.IOException;
import oracle.cabo.ui.BaseRenderer;
import oracle.cabo.ui.RenderingContext;
import oracle.cabo.ui.UINode;
import oracle.cabo.ui.io.OutputMethod;
public class CopyrightRenderer extends BaseRenderer
{
protected void renderContent(
RenderingContext context,
UINode node
) throws IOException
{
OutputMethod out = context.getOutputMethod();
out.startElement("span");
out.writeAttribute("class", "OraCopyright");
// "a9" is the Unicode copyright symbol.
out.writeText("Copyright \u00a9 Oracle Corporation. All Rights Reserved.");
out.endElement("span");
}
}
これにより、次のようなHTMLのSnippetが出力されます。
<span class="OraCopyright">
Copyright © Oracle Corporation. All Rights Reserved.
</span>
記述は多くありませんが、このコードには、Rendererを記述する際の重要な手法であるOutputMethodインタフェースが含まれています。 OutputMethodによってマークアップ言語が抽出されるため、出力の適切なエスケープに必要な作業を省略できます。 PrintWriterのかわりにOutputMethodを使用することで、次の処理を自動化できます。
必要に応じて要素を開始または終了します。
レンダラを変更せずに、HTMLおよびXML/XHTML両方の空要素を正しく処理します。
自動的に文字を正しくエスケープし、テキスト、属性およびURI属性を正しく出力します(上のコードでは、Unicode文字00a9が自動的に©というHTML文字エンティティに変換されています)。
デバッグ作業を簡単に行えるよう出力内容を見やすくインデントし、実行時には解除して出力を簡潔にします。
一致していない開始タグと終了タグを捕捉します。
一般的なHTMLエラー(<form>要素の中に別の<form>要素を配置した場合など)を検出します。
Rendererに機能を追加する手順に進む前に、このサンプル・コードについて注意する点がもう1つあります。 このコードには、OutputMethodに対して開始タグ<span>を閉じるよう命令する記述はなく、要素を開始して属性を1つ記述し、要素内に配置するテキストを記述しているだけです。 OutputMethodを使用する場合、いつ要素を終了するかは実装で自動的に判断されるため、要素の終了を命令する必要はありません。
これらのメソッドをすべて無視し、HTMLを直接記述することもできます。 OutputMethodには、通常は使用しないwriteRawText()というメソッドがあります。 ただし、OutputMethodの利点がすべて失われるため、RAWテキストの使用はお薦めしません。
次に、CopyrightBeanに2つの属性を追加します。 著作権の年を示す整数のyear属性と、リンクを提供するdestination属性です。 UIXではAttributeKeyによってノード属性を格納および取得するため、この2つの属性に対する定数を定義する必要があります。 UIXではoracle.cabo.ui.UIConstantsインタフェースにDESTINATION_ATTR定数が組み込まれていますが、ここではFlipperConstantsにYEAR_ATTR定数を追加します。
package org.example.flipper.ui;
import oracle.cabo.ui.AttributeKey;
public interface FlipperConstants
{
/**
* Namespace used by the Flipper implementation
*/
public static final String FLIPPER_NAMESPACE =
"http://flipper.example.org/ui";
/**
* Name of our copyright bean
*/
public static final String COPYRIGHT_NAME = "copyright";
/**
* "Year" attribute key.
*/
public static final AttributeKey YEAR_ATTR =
AttributeKey.getAttributeKey("year");
}
次に、これらの属性を出力に取り込むためのコードをRendererに追加します。
package org.example.flipper.ui;
import java.io.IOException;
import oracle.cabo.ui.BaseRenderer;
import oracle.cabo.ui.RenderingContext;
import oracle.cabo.ui.UINode;
import oracle.cabo.ui.io.OutputMethod;
public class CopyrightRenderer extends BaseRenderer
{
protected void renderContent(
RenderingContext context,
UINode node
) throws IOException
{
OutputMethod out = context.getOutputMethod();
Object year = node.getAttributeValue(context,
FlipperConstants.YEAR_ATTR);
Object destination = node.getAttributeValue(context,
UIConstants.DESTINATION_ATTR);
if (destination != null)
{
out.startElement("a");
// URLs should be written out using writeURIAttribute(), because
// they're escaped differently than other attributes.
out.writeURIAttribute("href", destination);
}
out.startElement("span");
out.writeAttribute("class", "OraCopyright");
out.writeText("Copyright (c) ");
if (year != null)
out.writeText(year.toString());
out.writeText(" Oracle Corporation. All Rights Reserved.");
out.endElement("span");
if (destination != null)
out.endElement("a");
}
}
ここまではかなり単純な作業ですが、Rendererのみで処理できることは限られています。 まず最初に、CopyrightBeanクラスを記述する必要があります。
Beanクラスの記述
Rendererのコードをすべて記述し終えた時点では、このBeanはコンポーネントのネームスペースおよびローカル名を設定し、属性を取得および設定する多くの類型的なコードの集まりにすぎません。
package org.example.flipper.ui;
import oracle.bali.share.util.IntegerUtils;
import oracle.cabo.ui.UIConstants;
import oracle.cabo.ui.beans.BaseWebBean;
public class CopyrightBean extends BaseWebBean implements FlipperConstants
{
public CopyrightBean()
{
super(FLIPPER_NAMESPACE, COPYRIGHT_NAME, null);
}
final public int getYear()
{
return BaseWebBean.resolveInteger(
(Integer) getAttributeValue(YEAR_ATTR));
}
final public void setYear(int year)
{
setAttributeValue(YEAR_ATTR, IntegerUtils.getInteger(year));
}
final public String getDestination()
{
return (String) getAttributeValue(UIConstants.DESTINATION_ATTR);
}
final public void setDestination(String destination)
{
setAttributeValue(UIConstants.DESTINATION_ATTR, destination);
}
}
このコードでは、次の点に注目してください。
コンストラクタでネームスペースおよびローカル名を設定しています。 これらの名前は、BeanとRendererを結び付けるために使用します。 単にgetRenderer()をオーバーライドして、直接レンダラを返す方法も技術的には可能ですが、システムにより両者を結び付ける方が簡潔です。
値を取得および設定するためのコールは、すべて単純にgetAttributeValue()およびsetAttributeValue()へと渡されています。 これは重要なポイントです。 値を直接インスタンス変数として格納する場合、データ・バインディングが機能しないため、コンポーネントをテンプレート化できず、UIX XML内に簡単に埋め込むことができません。 また、Rendererがこれらの属性を取得する方法も記述しなおすことが必要になります。
getterメソッドおよびsetterメソッドは、すべてfinalとマークしてあります。 これは、Beanとは特に関係がありません。 ただし、コーディングの上では優れた方法です。 getterメソッドまたはsetterメソッドのいずれかをオーバーライドしようとする場合、開発者は、属性が設定または取得される時点でそのオーバーライドがコールされると想定します。 しかし、getAttributeValue()またはsetAttributeValueはいつでも直接コールでき、その場合オーバーライドは完全に無視されます。 ここでは、これらのメソッドをfinalとマークすることで、このような問題の発生を回避しています。
IntegerUtils.getInteger()コールでは、UINode属性をオブジェクトとして格納する必要があるため、intをIntegerに変換しています。 通常、Integerオブジェクトを作成する場合は、単純にnew Integer(int)をコールします。 しかしInteger は不変オブジェクトなので、自由に共有できます。 IntegerUtilsは使用頻度の高いIntegerオブジェクトのキャッシュを保持するため、オブジェクトを何度も作成する必要がなくなり、その結果時間を大幅に節約できます。 この手法は、Bean以外にも幅広く応用できます。
次に、CopyrightBeanを作成します。
CopyrightBean copyright = new CopyrightBean();
copyright.setYear(2001);
copyright.setDestination("http://www.oracle.com");
CopyrightBeanは単に説明の便宜上のクラスであり、コードの最後の部分はBaseMutableUINodeを使用することで、次のようにより簡潔に記述できます。
BaseMutableUINode copyright =
new BaseMutableUINode(FlipperConstants.FLIPPER_NAMESPACE,
FlipperConstants.COPYRIGHT_NAME);
copyright.setAttributeValue(FlipperConstants.YEAR_ATTR, new Integer(2001));
copyright.setAttributeValue(UIConstants.DESTINATION_ATTR, "http://www.oracle.com");
ここまでのコードをそのままコンパイルし、これらのBeanをレンダリングしても、あまり多くの出力は得られず、かわりに次のようなメッセージがエラー・ログに送られる結果となります。
No UIX Components (Marlin) RendererFactory registered for namespace
http://flipper.example.org/ui
作成したBeanを稼働させるには、もう1つ作業が残っています。 このBeanをレンダリングするには、作成したRendererをUIXシステムに連結する必要があり、そのためにはRendererFactoryクラスおよびUIExtensionクラスについて理解する必要があります。
レンダラの登録: RendererFactoryとUIExtension
レンダラを登録するには、次の手順を実行します。
RendererFactoryを作成します。
作成したファクトリを登録するUIExtensionを作成します。
UIExtensionを登録します。
UIXでは、RendererManager内を検索してRendererを検出します。 1つのRendererManagerがすべてのネームスペースに対するすべてのRendererを単独で処理する場合、その処理はかなり煩雑になります。 実際には、RendererManagerはネームスペースごとにRendererを分類し、それぞれのネームスペースに対して1つのRendererFactoryを使用します。
したがって、まず最初にこのRendererFactoryを作成します。 ここでは、作業を簡単にするためにUIXのRendererFactoryImplを使用します。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.RendererFactoryImpl;
public class FlipperRendererFactory extends RendererFactoryImpl
{
/**
* Return the shared instance of this factory.
*/
static public RendererFactory sharedInstance()
{
return _sInstance;
}
public FlipperRendererFactory()
{
// Register our one renderer.
registerRenderer(FlipperConstants.COPYRIGHT_NAME,
"org.example.flipper.ui.CopyrightRenderer");
}
static private final RendererFactory _sInstance =
new FlipperRendererFactory();
}
クラスそのものまたはレンダラ・クラスのインスタンスではなく、名前を使用してレンダラを登録している点に注意してください。 UIXでは、実際に必要になった時点で初めてCopyrightRendererクラスをロードします。
これでRendererFactoryが作成されたので、次はこのファクトリを登録します。 この作業には、UIExtensionインタフェースを使用します。 UIExtensionには必ず2つのメソッドがあります。 レンダリング・コードを登録するメソッドと、解析コードを登録するメソッドです。
package oracle.cabo.ui;
public interface UIExtension
{
public void registerSelf(LookAndFeel laf);
public void registerSelf(ParserManager manager);
}
ここでは、レンダラの登録のみを行います。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public FlipperUIExtension()
{
}
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
// For now, let's do nothing.
}
}
ここでもコードは単純ですが、さらにクラスを記述しています。 簡単な1つのBeanとしてはクラスの数が多くなっていますが、今後作成するBeanでは同じRendererFactoryクラスおよびUIExtensionクラスを再利用できます。
次に、UIExtensionを登録します。 登録方法は、UIX Controllerを使用している場合と、UIXサーブレットに直接記述している場合とで異なります。
UIXサーブレットでのUIExtensionの登録
UIXサーブレットを使用すると、簡単にUIExtensionを登録できます。 登録は、プログラムによって行うことも、uix-config.xmlを使用して宣言的に行うこともできます。 プログラムによって登録する場合は、BaseUIPageBrokerクラスに定義されているregisterUIExtension()メソッドを使用します。 たとえば、次のようにUIXPageBrokerのサブクラスを作成できます。
import oracle.cabo.servlet.xml.UIXPageBroker;
public class FlipperPageBroker extends UIXPageBroker
{
public FlipperPageBroker()
{
registerUIExtension(new FlipperUIExtension());
}
}
しかし、uix-config.xmlの<extension-class>要素を使用してUIExtensionを登録する方が、さらに簡単です。 サーブレット・エンジンとしてOracle9i AS Containers For J2EE(OC4J)(またはサーブレット2.2仕様を実装したその他のエンジン)を使用している場合、これらは<web app>/WEB-INF/uix-config.xmlファイルに置かれます。
<?xml version="1.0" encoding="ISO-8859-1"?>
<configurations xmlns="http://xmlns.oracle.com/uix/config">
<application-configuration>
<ui-extensions>
<extension-class>org.example.flipper.ui.FlipperUIExtension</extension-class>
<extension-class>org.example.someOtherPackage.AnotherUIExtension</extension-class>
</ui-extensions>
</application-configuration>
</configurations>
uix-config.xmlの詳細は、「ADF UIXの構成」 のトピックを参照してください。
UIExtensionを登録するこのどちらの方法も、BaseUIPageBrokerクラスか、またはそのサブクラスの1つ(UIXPageBrokerなど)を使用している場合にのみ有効です。 これらのクラスを使用していない場合には、直接UIExtensionを登録する必要があります。
UIExtensionの直接登録
UIXサーブレットを使用していない場合は、UIExtensionを自分でLookAndFeelManagerに登録する必要があります。 LookAndFeelManagerは、すべてのLookAndFeelを制御するエンティティです。 特に、各ページにどのLookAndFeelを使用するかを制御します。
最も簡単なUIExtensionの登録方法は、デフォルトのLookAndFeelManagerへの登録です。
import oracle.cabo.ui.laf.LookAndFeelManager;
...
LookAndFeelManager manager =
LookAndFeelManager.getDefaultLookAndFeelManager();
manager.registerUIExtension(new FlipperUIExtension());
この処理は、最初のページをレンダリングする前に1回だけ実行します。 使用するサーブレットのinit()メソッドなどに、このようなコードを配置することをお薦めします。
デフォルトのLookAndFeelManagerにFlipperUIExtensionを登録する際の問題は、このLookAndFeelManagerはサーバー上のあらゆるWebアプリケーションで共有される可能性があるという点です。 これらのWebアプリケーションのすべてが、Flipperを必要とするわけではありません。 もう1つの問題は、2つのWebアプリケーションがFlipperを使用すると、Flipperが2回登録されることです。 そのため、アプリケーションを追加すればするほど無駄が発生し処理速度は低下します。
この問題を解決するには、createDefaultLookAndFeelManager()を使用して専用のLookAndFeelManagerを作成する必要があります。 その後、Configuration API を使用してこのLookAndFeelManagerを格納します。
// Instead of using the default, create a brand new manager
LookAndFeelManager manager =
LookAndFeelManager.createDefaultLookAndFeelManager();
// Register the extension just as before
manager.registerUIExtension(new FlipperUIExtension());
// And now store the LookAndFeelManager on a Configuration
ConfigurationImpl config = new ConfigurationImpl("yourConfigKey");
config.putProperty(Configuration.LOOK_AND_FEEL_MANAGER, manager);
config.register();
Configuration API、およびレンダリングの際に特定のConfigurationオブジェクトを使用する方法の詳細は、「ADF UIXの構成」 のトピックを参照してください。
XMLでのRendererのサポート
Beanが完成しJavaで稼働できるようになったので、次は作成したBeanへのサポートをXML解析APIに追加します。
UIX解析APIは、レンダリングAPIに類似しています。 解析APIでは、RendererManager、RendererFactoryおよびRendererのかわりに、ParserManager、ParserFactoryおよびNodeParserを使用します。 これらのAPIの詳細は後述しますが、UINodeの解析のみの場合、簡単なAPIで十分です。
UINodeを解析するには、必ずUINodeParserFactoryクラスを使用します。 ほぼすべてのUINodeが同じ方法で解析されるため、Beanごとに新しいパーサーを記述する必要はありません。 ただし、Beanを記述するメタデータをUINodeParserFactoryに提供する必要があります。 具体的には、Beanがサポートする属性、各属性の型、およびBeanがサポートする名前の付けられた子についての情報が必要です。
UINodeのメタデータは、UINodeTypeオブジェクトにより記述されます。 UINodeTypeでは多くのことを制御できますが、ほとんどの場合はBaseUINodeTypeクラスのインスタンスを作成し、これをDictionaryに配置し、そのディクショナリをUINodeParserFactoryへ渡すだけです。 Project Flipperの<copyright>要素のためにParserFactoryを作成するコードは、次のようになります。
package org.example.flipper.ui;
import oracle.bali.share.collection.ArrayMap;
import oracle.cabo.share.xml.ParserFactory;
import oracle.cabo.share.xml.ParserManager;
import oracle.cabo.ui.xml.parse.UINodeParserFactory;
import oracle.cabo.ui.xml.parse.BaseUINodeType;
class FlipperUINodeParserFactory extends UINodeParserFactory
{
public FlipperUINodeParserFactory()
{
super(FlipperConstants.FLIPPER_NAMESPACE,
null,
_sFlipperTypes);
}
static private final ArrayMap _sFlipperTypes = new ArrayMap();
static
{
BaseUINodeType copyrightType =
new BaseUINodeType(FlipperConstants.FLIPPER_NAMESPACE,
null,
new Object[]{"year", Integer.class,
"destination", String.class},
BaseUINodeType.getDefaultUINodeType());
_sFlipperTypes.put(FlipperConstants.COPYRIGHT_NAME, copyrightType);
}
}
このコードを冒頭から順に確認します。
FlipperUINodeParserFactoryは、ParserFactoryであるUINodeParserFactoryを拡張します。
コンストラクタでは、次の3つのパラメータをUINodeParserFactoryに渡します。
ファクトリのネームスペースを渡します。
デフォルトで使用するUINodeTypeを識別します。 このノード・タイプは、このネームスペース内の明示的に記述されていないすべての要素で使用されます。 通常は、サポートしていない要素が解析エラーとなるようnullを渡します。
最後に、すべての要素名を定義するArrayMapを渡します。 このクラスはArrayMapという名前ですが、実際にはJava2のjava.util.Map APIではなく、JDK 1.1のjava.util.Dictionary APIのサブクラスです。 これは、過去の経緯からUINodeParserFactoryがMapを受け入れないためです。 ArrayMapクラスは、今回のような非常に小さなデータ・セットに対して最適化されます。
次に、タイプ・ディクショナリのstaticインスタンスを1つ作成します。
次に、staticブロック内で、<copyright>要素に対するUINodeTypeを作成します。 BaseUINodeTypeコンストラクタに、次の4つのパラメータを渡します。
再度、要素のネームスペースを渡します。
2つ目の引数は、Beanの名前の付けられた子すべての文字列配列です。 CopyrightBeanでは名前の付けられた子をサポートしないため、これはnullです。
3つ目の引数は、Beanのすべての属性を識別する配列です。 文字列の名前、続いてクラス・オブジェクト、という書式が繰り返されています。 文字列はBeanの各属性の名前であり、その後にその属性のJava型が続きます。
4つ目に、このBeanのスーパータイプを定義する引数を渡します。 すべてのノードがスーパータイプを必要とするわけではありませんが、BaseUINodeType.getDefaultUINodeType()を使用することで、レンダリングされた属性のみでなく、<boundAttribute>子要素に対してもビルトイン・サポートが得られます。
最後に、このUINodeTypeを正しい要素名"copyright"で格納します。 この要素名および要素のネームスペースは、Rendererの登録に使用した名前およびネームスペースと一致していることが重要です。
後は、レンダラに加えて解析コードも登録するよう、UIExtensionを変更するだけです。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public FlipperUIExtension()
{
}
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
}
}
変更点はごく一部です。パーサー・ファクトリを作成し、UINodeParserFactory.registerSelf()メソッドを使用して、それをParserManagerに登録しています。 UIXサーブレットとそのUIXPageBrokerクラスを使用している場合、パーサーは自動的に登録されます。 一方、UIXファイルを直接解析している場合は、使用しているParserManagerでUIExtension.registerSelf(ParserManager)メソッドをコールする必要があります。
これで、<flipper:copyright>要素を直接UIXで使用できます。
<pageLayout xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:flipper="http://flipper.example.org/ui">
<copyright>
<flipper:copyright year="2001" destination="http://www.oracle.com"/>
</copyright>
</pageLayout>
属性と名前の付けられた子の違い
名前の付けられた子の意味を正確に理解することが重要です。 UINodeには、UINodeを返す特別なgetNamedChild()メソッドがあります。 通常、名前の付けられた子をサポートするBeanは、その名前の付けられた子に対するgetterメソッドとsetterメソッドを持ちます。
public UINode getStart() { ... }
public void setStart(UINode start) { ... }
これらのメソッドは、たとえば次のように、属性の取得と設定に使用されるメソッドと見た目は類似しています。
public String getWidth() { ... }
public void setWidth(String width) { ... }
public ClientValidater getOnSubmitValidater() { ... }
public void setOnSubmitValidater(ClientValideter onSubmit) { ... }
Java APIの使用者には、これらのメソッドはほとんど同じように見えます。 ただし完全に同じではなく、属性はデータ・バインディングできますが、名前の付けられた子はデータ・バインディングできません(IncludeBeanで、名前の付けられた子のデータ・バインドをシミュレーションすることは可能です)。 しかし、見た目はほとんど同じです。
XMLの場合はさらに複雑です。 前の例のwidthのような属性は、UIXでは単純属性と呼ばれています。 これらの属性は、1つの文字列で簡単に記述できます。 文字列、数値、真/偽などの型がこれに該当します。 それ以外のonSubmitValidaterなどの属性は、複雑属性と呼ばれています。 複雑属性は、値の記述に単なる文字列以上の構文が必要であり、2つのレベルの子要素を使用して表されます。 最初に使用する最上位のレベルは、属性と同じ名前のエンベロープ要素です。 エンベロープ要素は、どの属性が解析されているかをUIXに伝えます。 次の例ではこの要素内で、その型の値を定義している要素を検索します。
<yourElement>
<!-- First, an envelope element identifying the attribute name -->
<onSubmitValidater>
<!-- Then, an element describing the value; that is, what
kind of ClientValidater this is -->
<ui:decimal/>
</onSubmitValidater>
</yourElement>
ClientValidaterや、UIXに組み込まれていないカスタムの型を含むその他の型のJavaオブジェクトを記述する要素の定義方法については、このトピックの後半で説明します。
名前の付けられた子は、複雑属性と同様の構文を使用します。 エンベロープ要素も同様に使用しますが、要素名には子の名前を使用します。 エンベロープ要素には、UINodeを定義する要素が1つ含まれます。
<yourElement>
<!-- First, an envelope element identifying the child name -->
<start>
<!-- Then, an element describing the UINode -->
<ui:button text="Press Me"/>
</start>
</yourElement>
つまりXMLの観点で見ると、複雑属性は名前の付けられた子に類似しています。 しかしUINodeType定義では、この2つは違う方法で定義します。 名前の付けられた子は名前の付けられた子のリストで定義し、複雑属性は属性リストで定義します。
UINodeType yourElementType =
new BaseUINodeType(YOUR_NAMESPACE,
new String[]{"start"},
new Object[]{"onSubmitValidater", ClientValidater.class,
"width", String.class},
BaseUINodeType.getDefaultUINodeType());
多少複雑ですが、ほとんどの場合この知識は不要です。 単にUIX XMLを使用している場合には、この点を理解する必要はまったくありません。 width属性があり、<start>要素および<onSubmitValidater>要素があることのみを理解してください。 ただしUIXにBeanを追加する場合は、独自のXML構文を記述できるよう、これらの点を理解しておく必要があります。
UIX用パーサーの記述
UIXに追加する要素がすべて新しいUINode型の場合については、必要な作業をすべて説明しました。 しかしUIXでは、さらに広範な拡張が可能です。 ここでは、CopyrightBeanにいくつかの機能を追加することによって、その拡張方法を説明します。 単一年のみをサポートするのではなく、カスタムの型を持つ新しい複雑属性を使用して、ある範囲の複数年をサポートすることにします。
public class CopyrightBean()
{
// ...
public YearRange getYears()
{
return (YearRange) getAttributeValue(YEARS_ATTR);
}
public void setYears(YearRange years)
{
setAttributeValue(YEARS_ATTR, years);
}
static public class YearRange
{
public int getStart()
{
return _start;
}
public void setStart(int start)
{
_start = start;
}
public int getEnd()
{
return _end;
}
public void setEnd(int end)
{
_end = end;
}
private int _start = -1;
private int _end = -1;
}
}
次のXMLをサポートします。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999" end="2001"/>
</flipper:years>
</flipper:copyright>
(Rendererに対する変更は練習としてユーザーが作業してください。) 解析コードには、これらのYearRangeオブジェクトのいずれかを作成する方法について何も記述されていません。 XMLからYearRangeオブジェクトを作成するには、APIを使用してコーディングする必要があります。
NodeParser API
NodeParserの基本原理は、要素とその属性、および子要素を受け取り、これらをまとめて1つのJavaオブジェクトに変えることです。 SAX 2.0(http://www.megginson.com/SAX/index.htmlを参照)の標準をベースにしたイベント・ドリブンのパーサーですが、複数のノード・パーサーが共同で動作し1つのオブジェクト・ツリーを作成できるため、SAXそのものよりもはるかに強力です。 解析機能は目的別の小さなクラスに整然と分解されているため、コードを全面的に変更することなく簡単に解析ロジックを拡張できます。
各NodeParserが、XML文書の1つのサブツリーを1つのJavaオブジェクトに変換します。 ただしその際、必要なオブジェクトを作成するために、別のNodeParserに対して下位のサブツリーを処理するよう指示することができます。 この手法によってコードのモジュール化が向上し、また、NodeParserの検索および作成方法が、作成されるコードの拡張性を高くしています。
ParserFactoryはネームスペースのみでなく、生成するJavaオブジェクトの型でも登録されます。 したがってUIXは、UINodeの作成用に1つ、BoundValueの作成用に1つ、ClientValidater用に1つなど複数のParserFactoryをUIXネームスペースに登録します。 FlipperUINodeParserFactoryをFlipperUIExtensionに登録した際には、UINode作成用のファクトリのみをFlipperネームスペースに登録しました。
NodeParserがある要素を、たとえばBoundValueに解析することを決定するたびに、そのNodeParserはParseContext対してBoundValueを作成できるNodeParserを指示します。ただしその際、その要素のネームスペースおよびローカル名を渡します。
NodeParser parser = context.getParser(BoundValue.class,
namespaceOfChild,
localNameOfChild);
型(BoundValue.class)およびネームスペースがParserFactoryを識別し、そのParserFactoryがローカル名を使用してNodeParserを作成します。 コール側では要素のネームスペースに関する情報は必要なく、そのコードと通信する必要はありません。 取得するノード・パーサーが正しくBoundValueを作成するかぎり、その要素の構造や属性を考慮する必要はありません。
つまり、UIX開発者は独自のBoundValue要素を作成でき、作成した要素はビルトインのBoundValue要素(<fixed>または<concat>など)が受け入れられる場所であればどこでも受け入れられます。 また、UINode解析コードにYearRangeオブジェクトについての情報がない場合でも、このオブジェクトは正常に作成され、years属性は正しく設定されます。
LeafNodeParser API
NodeParserを記述する最も簡単な方法の1つは、LeafNodeParserクラスのサブクラスを作成する方法です。 このクラスは、リーフ要素用のパーサーの記述を簡潔にします。リーフ要素とは、属性はあるが子要素またはプレーン・テキストはない要素です。 サブクラスは、1つのメソッドをオーバーライドするだけで作成できます。
abstract protected Object getNodeValue(
ParseContext context,
String namespaceURI,
String localName,
Attributes attrs) throws SAXParseException;
このメソッドには、解析時間のコンテキストをコードに提供するParseContext、および要素のネームスペースおよびローカル名を指定します。 さらに、XML要素のすべての属性の名前を列挙した属性リストを指定します。
package org.example.flipper.ui;
import org.xml.sax.Attributes;
import org.xml.sax.SAXParseException;
import oracle.cabo.share.xml.LeafNodeParser;
import oracle.cabo.share.xml.ParseContext;
class RangeNodeParser extends LeafNodeParser
{
protected Object getNodeValue(
ParseContext context,
String namespaceURI,
String localName,
Attributes attrs)
{
CopyrightBean.YearRange range = new CopyrightBean.YearRange();
String startString = attrs.getValue("start");
if (startString != null)
{
try
{
int start = Integer.parseInt(startString);
range.setStart(start);
}
catch (NumberFormatException nfe)
{
logWarning(context, "\"start\" attribute could not be parsed.");
}
}
String endString = attrs.getValue("end");
if (endString != null)
{
try
{
int end = Integer.parseInt(endString);
range.setEnd(end);
}
catch (NumberFormatException nfe)
{
logWarning(context, "\"end\" attribute could not be parsed.");
}
}
return range;
}
}
これはかなり単純なクラスです。 startとendの2つの属性を取得します。 どちらかが設定されていれば、それをintに解析し、YearRangeオブジェクトに設定します。 最後に、YearRangeを返します。
文字列の解析に失敗した場合、SAXParseExceptionをスローするかわりに警告をログに出力している点に注意してください(throws SAXParseException宣言をすべて削除しています)。 一般的には、ユーザーがすべてのエラーを同じページで一度に確認できるためエラーはログに出力することをお薦めします。 例外をスローするとその時点で解析が終了するため、ユーザーには最初のエラーのみが表示されます。 また、logWarning()メソッドにより行番号と列番号が自動的にメッセージに追加されるため、ユーザーはエラーの前後関係を把握できます。 発生箇所が明記されたエラー・メッセージを出力するパーサーの方が、ユーザーにとっては有用です。
次に、ParserFactoryを記述してこのNodeParserを作成する必要があります。 前述したとおり、この要素にはyearRangeという名前を付けます。
package org.example.flipper.ui;
import oracle.cabo.share.xml.NodeParser;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParserFactory;
class RangeParserFactory implements ParserFactory
{
public NodeParser getParser(
ParseContext context,
String namespaceURI,
String localName)
{
if ("yearRange".equals(localName))
return new RangeNodeParser();
return null;
}
}
これも単純なクラスです。 ただしこの数行の中にも、説明の必要な点がいくつかあります。 まず、ネームスペースはまったくチェックしていません。 このファクトリでは、要素名のみでなくネームスペースの検証も必要であると想定できます。 しかし、ファクトリ自体がネームスペースで登録されているため、ネームスペースのチェックは不要となります。 次に、ここでは新規のRangeNodeParserも返しています。 NodeParserのインスタンスは状態を持っているため、通常、共有または再利用はできません。 ただし、NodeParserは非常に軽量でもあります。 最後に注意するのは、不明な要素名の場合には単純にnullを返している点です。 不明な要素に対しては、中心となる解析コードが自動的にエラーをレポートするため、同じレポート・コードをここで繰り返し記述する必要はありません。
ただし、UINodeTypeの定義を一部のみ変更して、この属性がレポートされるようにする必要があります。
class FlipperUINodeParserFactory extends UINodeParserFactory
{
// Skipping down to the bit that changed...
BaseUINodeType copyrightType =
new BaseUINodeType(FlipperConstants.FLIPPER_NAMESPACE,
null,
new Object[]{"year", Integer.class,
"destination", String.class,
FlipperConstants.YEARS_ATTR,
CopyrightBean.YearRange.class},
BaseUINodeType.getDefaultUINodeType());
// ...
}
最終的に、このParserFactoryをUIExtension内に登録します。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
RangeParserFactory rangeFactory = new RangeParserFactory();
manager.registerFactory(CopyrightBean.YearRange.class,
FlipperConstants.FLIPPER_NAMESPACE,
rangeFactory);
}
}
これで、解析コードがYearRangeオブジェクトを必要とした際に、現在の要素がFlipperネームスペースにあれば、RangeParserFactoryにパーサーの生成が要求されます。
BeanParserおよびBeanDef API
RangeNodeParserクラスの記述はそれほど難解ではありませんでした。 しかし、YearRangeクラスに新規のプロパティを追加するたびに、パーサー・クラスの変更が必要です。 さらに、システムに新しいクラスを追加するたびに、新規のパーサーを最初から記述する必要があります。 作業自体は特に難しいものではありませんが、かなり煩雑です。 これよりも効率的な方法があります。
oracle.cabo.share.xml.beans.BeanParser APIです。 BeanParserを使用すると、UIXで自動的にクラスのコードを検証して属性を識別し、XMLをそのクラスのインスタンスに解析する方法を導き出すことができます。 UINodeParserと同様に、BeanParserは別のメタデータを使用してそのクラスの型情報を取得します。 このメタデータを提供するのが、BeanDef APIです。 BeanDefは実装を伴わない完全な抽象クラスであり、直接使用することはほとんどありません。 かわりに、多くの開発者は記述済の実装であるIntrospectionBeanDefを使用します。 このクラスはBeanParser APIの重要な部分であり、自動的にBeanをスキャンして属性を検索します。
BeanParser APIを使用するよう、ParserFactoryを次のように変更します。
package org.example.flipper.ui;
import oracle.cabo.share.xml.NodeParser;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParserFactory;
import oracle.cabo.share.xml.beans.BeanParser;
import oracle.cabo.share.xml.beans.IntrospectionBeanDef;
class RangeParserFactory implements ParserFactory
{
public NodeParser getParser(
ParseContext context,
String namespaceURI,
String localName)
{
if ("yearRange".equals(localName))
return new BeanParser(_sYearRangeDef);
return null;
}
static private final IntrospectionBeanDef _sYearRangeDef =
new IntrospectionBeanDef(CopyrightBean.YearRange.class.getName());
}
これですべてです。 ノード・パーサー・クラスは不要になります。 独自に記述したパーサーを使用するかわりに、BeanParserを使用します。 その際、YearRangeクラスをポイントするIntrospectionBeanDefが指定されています。 BeanDefインスタンスをキャッシュして再利用している点に注意してください。 IntrospectionBeanDefの再作成には相当の負荷がかかるため、BeanParserを使用する際にこれは重要なポイントになります。
解析機能はこれまでより向上します。 たとえば、独自に記述したパーサーでは、不明な属性が設定されても検知されませんでした。 ユーザーがstartやendのスペルを間違った場合でもそのエラーは警告なしに無視されるため、問題の追跡は非常に困難でした。 BeanParserはこのようなミスを自動的に検出し、ユーザーに対して警告します。
BeanParserは、単純属性をサポートするだけではありません。 BeanParserでは、複雑属性の処理もビルトインでサポートします。 BeanParserはエンベロープ要素を自動的に識別し、その子要素を解析します。 BeanParserは、さらに子要素の配列もサポートします。 たとえば、次のようなメソッドを持つBeanがあるとします。
public class YourBean
{
public void setValidaters(ClientValidater[] validaters) { ... }
public ClientValidater[] getValidaters() { ... }
}
BeanParserでは自動的に次のXMLをサポートします。
<yourElement>
<validaters>
<ui:date/>
<ui:decimal/>
<ui:date timeStyle="short"/>
<ui:regExp/>
<oneOfYourClientValidaters/>
</validaters>
</yourElement>
BeanParserは単にNodeParserの一種であるため、独自に作成したノード・パーサーとシームレスに共同で稼働することができます。 このAPIを使用すると複雑なJavaオブジェクトの解析が簡単になるため、XMLをJavaオブジェクトに解析する必要がある場合は、たとえ解析するオブジェクトまたはプロジェクト全体で他のUIXは使用しない場合でも検討する価値があります。
UIBeanDef
ここまでで<yearRange>要素は正しく解析されるようになりましたが、<yearRange>内でのデータ・バインディングはサポートされていません。 次のような構文のサポートが有効です。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999" end="${yearSource.today}" />
</flipper:years>
</flipper:copyright>
または、より複雑な次の構文も考えられます。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999">
<ui:boundAttribute name="end">
<!-- Some really complex set of bound values -->
</ui:boundAttribute>
</flipper:yearRange>
</flipper:years>
</flipper:copyright>
まだLeafNodeParserを使用している場合は、解析コードに大幅な追加が必要となります。 しかしBeanParserの場合の処理は簡単です。 まずend属性がBoundValueをサポートするよう、YearRangeクラスを編集します。
static public class YearRange
{
public int getStart()
{
return _start;
}
public void setStart(int start)
{
_start = start;
}
public int getEnd()
{
return _end;
}
public void setEnd(int end)
{
_end = end;
}
public void setEndBinding(BoundValue endBinding)
{
_endBinding = endBinding;
}
public int getEnd(RenderingContext context)
{
if (_endBinding != null)
{
Object o = _endBinding.getValue(context);
if (o instanceof Integer)
return ((Integer) o).intValue();
return -1;
}
return _end;
}
private BoundValue _endBinding;
private int _start = -1;
private int _end = -1;
}
ここでは、2つのメソッドを追加しています。 パーサーが使用するのはsetEndBinding()メソッドのみです。 ここで重要なのは、このメソッドの名前が、データ・バインディングしているプロパティ名にBindingという文字列を追加した名前と同じであり、パラメータとしてBoundValueをとる点です。 2つ目のメソッドは、作成したRendererから使用するメソッドで、end属性を取得します。
次に、解析コードを変更します。
import oracle.cabo.share.xml.NodeParser;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParserFactory;
import oracle.cabo.share.xml.beans.BeanParser;
import oracle.cabo.ui.xml.parse.UIBeanDef;
public class RangeParserFactory
{
public NodeParser getParser(
ParseContext context,
String namespaceURI,
String localName)
{
if ("yearRange".equals(localName))
return new BeanParser(_sYearRangeDef);
return null;
}
static private final UIBeanDef _sYearRangeDef =
new UIBeanDef (CopyrightBean.YearRange.class.getName());
}
太字で表示しなければ気付かない程度の変更です。 IntrospectionBeanDefのかわりにUIBeanDefを使用しています。 これですべてです。 これによって、単純なデータ・バインド用のdata:構文と、複雑なデータ・バインディング用の<ui:boundAttribute>構文の両方が完全にサポートされます。 どちらの構文も、次のような明示的に設定された値への自動的リセットをサポートします。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999"
end="${ui:defaulting(yearSource.today,'2001')}"/>
</flipper:years>
</flipper:copyright>
また、不明な属性が設定された場合に警告が出されるのと同様に、データ・バインディングをサポートしていないプロパティを開発者がデータ・バインディングすると警告が発せられます。 たとえば、YearRangeにsetStartBinding()メソッドは追加していません。 したがって、start属性をデータ・バインドしようとすると、この属性はデータ・バインドできないことを知らせる警告を受け取ることになります。
NodeParser: 詳細
LeafNodeParser、BeanParserまたはUINodeParserの3つの既存パーサーが、いずれも目的にあまり適さないことがあります。 このような場合は、NodeParser APIについて理解する必要があります。 警告のロギング、必要な属性の取得などの多数のユーティリティ・メソッドを提供するBaseNodeParserからサブクラスを作成することを強くお薦めしますが、NodeParserの動作は理解しておく必要があります。
UIX解析APIは、次のようにXML要素のツリーを反復処理していきます。
ルートのXMLノードに対し、UINode(またはコール側のコードが求めるその他の型のオブジェクト)を作成するParserFactoryを使用してNodeParserが取得されます。
NodeParser.startElement()がコールされ、この最上位の要素の属性が処理されます。
各子要素に対し、NodeParser.startChildElement()をコールします。 このメソッドでは、子の処理に必要なNodeParserを識別する必要があります。 コールは、次のいずれかの方法で処理されます。
ParserManagerに、NodeParserを要求します。 NodeParserは、対象となる子要素(およびその子すべて)の処理を受け持ちます。 処理が終了すると、親のNodeParser.addCompletedChild()でNodeParserがコールされ、処理結果が組み込まれます。
thisを返します。 その結果、パーサー自身が子要素の処理を受け持ちます。 この方法は、子要素が独立していないため一般のNodeParserでは処理できない場合に必要となります。 例としては、複雑属性で使用するエンベロープ要素や、名前の付けられた子があります。
特定のNodeParserの実装を返します。 これは、単にthisを返す方法よりも簡単で、目的を絞った方法です。
nullを返します。 これは、要素を処理できなかったことを表し、エラーが記録されます。
子要素の処理が終了すると、次のどちらかのメソッドがコールされます。
startChildElementがthisを返した場合、UIXではNodeParser.endChildElement()がコールされます。
それ以外の場合、NodeParser.addCompletedChild()がUIXでコールされます。 子要素のパーサーにより作成されたオブジェクトがこのメソッドに渡されるため、パーサーは必要に応じてそのオブジェクトを格納できます。 解析に失敗した場合には、このメソッドにnullが渡される場合もあるため、このメソッドの実装では、その場合への対処も必要です。
要素内のそれぞれのテキストに対し、NodeParser.addText()が1回コールされます。 (これらのコールは、start/endChildElement()へのコールとstart/endChildElement()へのコールの間に、想定される文書の順で挿入されます。) SAX APIと同様、XML文書では一続きのテキストに見えるものでも、NodeParser.addText()が複数コールされることもあるため、開発者は、NodeParser.endElement()、NodeParser.endChildElement()またはNodeParser.startChildElement()のいずれかがコールされるまでテキストを繰り返す必要があります。
一連の空白文字が、NodeParser.addText()と同じ方法でNodeParser.addWhitespaceに渡されます。 ほとんどのパーサーはこのメソッドを無視しますが、空白文字を考慮するパーサーは、引数をaddWhitespace()に渡してNodeParser.addText()をコールします。
最後に、NodeParser.endElement()がコールされます。 このメソッドは、完全に構成されたオブジェクトを表すオブジェクトを返す必要があります。
後続の各要素に対して、同じ処理を繰り返します。
カスタムEL関数の登録
前述 のように、UIXでは、多数のビルトインEL変数および関数をサポートしています。 UIXへの関数の追加は簡単です。これは、カスタム・ロジックでページを拡張する非常に強力な方法です。 著作権を表示する「Flipper」というカスタム関数を追加する例を次に示します。EL関数を一度追加すると、以降はこの関数を任意の場所で使用できます。
まず、この関数のコードを記述します。 通常、EL関数は、static関数として記述します。 この関数は、開始年と終了年を処理します。
static public String copyright(int startYear, int endYear)
{
if (startYear == endYear)
return "\u00a9 " + startYear;
return "\u00a9 " + startYear + "-" + endYear;
}
これで終わりです。次に、Java Reflection APIを使用して、この関数をMethodオブジェクトに変換します。
static private Method _copyrightMethod;
static
{
try
{
_copyrightMethod =
FlipperUIExtension.class.getMethod("copyright",
new Class[]{Integer.TYPE,
Integer.TYPE});
}
catch (NoSuchMethodException nsme)
{
// This had better never happen!
}
}
最後に、EL関数を登録する必要があります。 ParserManager.registerFunction()メソッドを使用して、EL関数をUIExtensionのregisterSelf(ParserManager)関数に登録します。
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
RangeParserFactory rangeFactory = new RangeParserFactory();
manager.registerFactory(CopyrightBean.YearRange.class,
FlipperConstants.FLIPPER_NAMESPACE,
rangeFactory);
manager.registerFunction(FlipperConstants.FLIPPER_NAMESPACE,
"copyright",
new JavaMethod(_copyrightMethod));
}
これで、ページの任意の場所で著作権関数を使用できます。
<styledText text="${flipper:copyright(1999,2003)} Flipper Co."/>
<link destination="http://www.flipper.org"
text="${flipper:copyright(2003,2003)}"/>
ParserExtension API
ここまでで、UIXが拡張可能であることを説明しました。 既存の型に対する新しい要素の追加や、システムへの新しい型の追加は簡単です。 しかし、まだ説明していない拡張方法が1つあります。 ParserExtension APIを使用すると、別の開発者が作成したコンポーネントに、属性や子要素さえ追加できます。
このAPIについて説明するため、Project Flipper用の様々なWebサイトをポイントするすべてのリンクBean、ボタンBeanなどに、flipper:destination属性へのサポートを追加します。 受け付ける値は次の2つです。 flipper:destinationをinternalに設定した場合、リンク先はhttp://flipper.example.orgとなります。 externalに設定した場合、リンク先はhttp://www.example.org/flipperとなります。 したがって、たとえば次のように記述できます。
<link text="Go to the Flipper Home" flipper:destination="internal">
ParserExtensionは、デフォルトの解析コードで処理されなかった属性および要素を通知し、その値をDictionaryに集めます。 親要素の処理が終了し(つまりNodeParser.endElement()がコールされ)、Javaオブジェクトが返されると、ParserExtensionはそのJavaオブジェクトと値のDictionaryを取得します。 取得したオブジェクトは変更することも、または完全に置き換えることも可能です。 次に、ここで使用するParserExtensionのコードを示します。
package org.example.flipper.ui;
import java.util.Dictionary;
import oracle.cabo.share.xml.BaseParserExtension;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.ui.MutableUINode;
import oracle.cabo.ui.UIConstants;
public class FlipperParserExtension extends BaseParserExtension
{
public Object elementEnded(
ParseContext context,
String namespaceURI,
String localName,
Object parsed,
Dictionary attributes)
{
if (parsed instanceof MutableUINode)
{
_extendUINode(context, (MutableUINode) parsed, attributes);
}
else
{
logWarning(context,
"extensions not supported on " +
parsed + " objects.");
}
return parsed;
}
private void _extendUINode(
ParseContext context,
MutableUINode node,
Dictionary attributes)
{
Object destination = attributes.get("destination");
if (destination != null)
{
if ("external".equals(destination))
node.setAttributeValue(UIConstants.DESTINATION_ATTR,
"http://www.example.org/flipper");
else if ("internal".equals(destination))
node.setAttributeValue(UIConstants.DESTINATION_ATTR,
"http://flipper.example.org/");
}
}
}
このコードを冒頭から順に確認します。
まず、BaseParserExtensionを拡張している点に注意してください。 このベース・クラスは、デフォルト実装のすべてのメソッドと、解析時の警告をロギングする便利なメソッドを提供します。
オーバーライドするのはelementEnded()メソッドのみです。 このメソッドは、拡張している要素が終了するとコールされ、次の5つのパラメータが渡されます。
解析時のコンテキストをコードに提供するParseContext。
拡張している要素のネームスペース。
拡張している要素のローカル名。
要素の解析後にインスタンス化されたJavaオブジェクト。
拡張値のDictionary。 各拡張属性および要素の値が、属性名または要素名をキーとしてここに格納されます。
次に、解析されたオブジェクトがMutableUINodeインタフェースを実装することを検証します。 ParserExtensionとParserFactoryの大きな違いの1つは、拡張が型では登録されない点です。 かわりに、型に関係なく、ネームスペース全体に対して1つのParserExtensionを使用します。 したがって、ここで型をチェックし、正しくない場合には警告をログに出力します。
elementEnded()は常にparsedを返します。 これは、ノードを置き換えているのではなく、単に修正しているだけであることを意味します。 これは最も安全な戻り値です。 nullを返すことも可能ですが、その場合オブジェクト全体が削除されます。
_extendUINode()メソッドで、destinationの値を取得およびチェックし、その内容に応じてMutableUINodeのリンク先を設定します。
ParserExtensionはParserManagerに登録されるため、再度UIExtensionを変更する必要があります。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
RangeParserFactory rangeFactory = new RangeParserFactory();
manager.registerFactory(CopyrightBean.YearRange.class,
FlipperConstants.FLIPPER_NAMESPACE,
rangeFactory);
FlipperParserExtension extension = new FlipperParserExtension();
manager.registerExtension(FlipperConstants.FLIPPER_NAMESPACE,
extension);
}
}
ParserExtensionは、子要素もサポートできます。 たとえば、現在のUIX-BC4J統合APIでは、次のような構文をサポートします。
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:bc4j="http://xmlns.oracle.com/uix/bc4j">
<bc4j:registryDef>
...
</bc4j:registryDef>
</page>
UIXサーブレットである<page>要素には<bc4j:registryDef>要素に関する情報は何もなく、またその必要もありません。 ParserExtensionで子要素をサポートするには、正しいNodeParserを返すようstartExtensionElement()メソッドを実装します。 このパーサーが返すオブジェクトは、最終的にelementEnded()へ渡されるDictionaryに格納されます。
ParserExtension APIには、1つだけ制限があります。 再コールすると、各NodeParserはXML文書のサブツリーを解析します。 拡張属性および要素は、これら各サブツリーのルートのUIX XML要素でのみサポートされます。 たとえば、次のようなUIX Components XMLがあるとします。
<stackLayout>
<separator>
<spacer>
<boundAttribute name="height">
<fixed text="5"/>
</boundAttribute>
</spacer>
</separator>
<contents>
<styledText text="First"/>
<styledText text="Second"/>
</contents>
</stackLayout>
拡張属性および要素は、<stackLayout>、<spacer>、<styledText>および<fixed>の各要素へは追加できますが、<separator>、<contents>または<boundAttribute>へは追加できません。 概して、ParserExtensionが変更できるのはJavaオブジェクトに直接対応する要素のみであり、UINodeおよびBoundValueはこれに該当しますが、エンベロープ要素は該当しません。
UIX以外でのUIX解析機能の使用
UIX解析APIは、どのUIX Componentsにも、またUIXサーブレットにも依存しておらず、前述したように拡張性およびビルトイン・イントロスペクション機能があるため、アプリケーション全体で他のUIXが1つも必要でない場合でもXMLを解析する優れた手段となります。 XML解析APIの基本についてはほとんど説明しましたが、ここではAPIを単独で使用する場合に必要ないくつかの手順について説明します。
XML解析APIを単独で使用するためには、次のオブジェクトを準備する必要があります。
ErrorLog: 警告またはエラーの出力先を識別します。 指定しない場合、エラーはコンソールに出力されます。
ParseContext: ParseContextImplのインスタンスをそのまま使用します。
ParserManager: 必要な各ParserFactoryおよびParserExtensionを登録する必要があります。
NameResolver: このインタフェースはXML文書のソースの検索方法を定義します。 単独のファイルの解析には、単純なSAXのInputSourceで十分ですが、UIX XML解析APIは別のXMLファイルを含むXMLファイルをサポートする必要があり、これらのファイルを元のファイルとの関連で検索する手段が必要です。 NameResolverの実装には、次のものがあります。
DefaultNameResolver: ベース・ファイルのディレクトリまたはURLのどちらか一方または両方を基準に、ファイルを検索できます。
ServletNameResolver: サーブレット・コンテキストを基準にファイルを検索できます。
ClassResourceNameResolver: (JAR内などの)クラスを基準にファイルを検索できます。
これらのオブジェクトの準備ができたら、解析するファイルの名前と、作成するオブジェクトの型も必要です。 後は、XMLUtils.parseSource()を1回コールするのみです。
import oracle.cabo.share.xml.XMLUtils;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParseContextImpl;
import oracle.cabo.share.xml.ParserManager;
import oracle.cabo.share.io.NameResolver;
import oracle.cabo.share.error.ErrorLog;
...
// Get the objects we need
ErrorLog log = ...;
ParseContext context = new ParseContextImpl(log);
ParserManager manager = ...;
NameResolver resolver = ...;
// And parse.
YourType result = (YourType)
XMLUtils.parseSource(context,
null,
manager,
resolver,
"yourFile.xml",
YourType.class);
組込みファイルの結果も必要な場合は、解析コードでXMLUtils.parseInclude()をコールします。 このメソッドに必要なパラメータは、すでに使用しているParseContext、必要なファイルの名前および作成するオブジェクト型の3つです。 その他は、解析コードが処理します。
Copyright © 2001, 2004, Oracle Corporation. All rights reserved.