ナビゲーション・ヘッダーをスキップ
Oracle ADF UIX開発者ガイド 目次へ
目次
前のページへ戻る
前へ
次のページへ進む
次へ

5. ADF UIXでのエラーの処理

堅牢なアプリケーションでも常にエラーは発生します。 Webアプリケーションのエラー処理では、従来のクライアント/サーバー・アプリケーションには見られない方法が使用されることがあります。 このトピックでは、Oracle ADF UIXでのデータ・バインディングおよびイベント処理中のエラーの処理方法、クライアントへのエラーの表示方法、ユーザーの作業状況を改善するためのクライアント側の検証の使用方法を学びます。

ここでは、次の項目について説明します。

データ・バインディングでのエラー

UIXのデータ・バインディングに関して、注意すべき問題は次のようなことです。 データ・バインディングは、ページのレンダリング時に行われます。 値は、必要な場合のみ取得されます。また、DataProvidersDataObjectをリクエストされるのは、最初にそのDataObjectが必要となった場合のみです。 ただし、HTMLの行が一度インターネット上に送信されると、取り戻すことはできません。 そうすると、エラー・ページにリダイレクトすることはできず、失敗した部分に適切なエラー・セクションをレンダリングすることもできません。 処理を続行し、最善を尽くしてページを完成させるしかありませんが、ページの途中にスタック・トレースを書き出すことになり、ユーザーを混乱させる原因にもなるので問題があります。

DataProviderDataObjectBoundValue、およびアプリケーションのその他すべてのデータ・バインディング・コードが雑に作成されているように見えます。 これらのいずれでも障害が認識されず、常に有効な結果を返すことができるかのようです。 しかし、これは事実ではありません。 代替手段が豊富にあります。

第1の選択肢: エラーの捕捉、記録および削除

最も単純なオプションは何もしないことです。 例外を捕捉し、ErrorLog APIに記録してから削除します。 次に例を示します。

  public class SimpleDataObject implements DataObject
  {
    public Object selectValue(RenderingContext context,
                              Object key)
    {
      try
      {
        // Try to get the value
        return _aMethodThatMayFail(context, key);
      }
      catch (SQLException sqle)
      {
        // Oh no - an error!  But just log it...
        context.getErrorLog().logError(sqle);
        // and return null.
        return null;
      }
    }
  }

このようなコードを見ると、問題点が理解しやすいはずです。 障害が発生しても何も知らせないよりも、適切なエラー・メッセージをユーザーに表示する方が賢明です。 空白のセルやフィールドを表示するとユーザーが混乱するのみでなく、さらに悪いことに、ユーザーが誤ったデータを送信する場合もあります。

ただし、高水準に合せてすべてのコードを作成するのは、逆効果の場合もあります。 多くのエラーを適切に処理しようとすればするほど、大量のエラー処理コードを記述することになります。 また、エラー処理コードは通常アプリケーション内で最もテストされないコードです。このため、最も問題の多いコードだとも言えます。

処理していない例外がすでにある可能性もあります。 たとえば、NullPointerExceptionを常に捕捉していない可能性もあります。 この例外は、予期しないイベントが発生したことを意味します。 予期されるエラー(データベース接続の障害など)のみではなく、予期しないエラー(NullPointerExceptionまたはMissingResourceExceptionなど)をユーザーに示す方法をアプリケーションに設計していると、いつまでたってもコーディングは終了しません。 どこで線を引くかを決めるのは困難ですが、code>RuntimeExceptionの処理をしないのは妥当な選択でしょう。

データ・バインディング・コードからスローされるNullPointerExceptionおよびその他のRuntimeExceptionが、UIXではどのように処理されるか説明します。 DataProviderおよびDataObjectでスローされるすべての例外は、前述の例と同様に、自動的に捕捉され、エラーが記録されて、nullが返されます。 例外が届くと、これが唯一の最適な代替手段になります。

このエラーはまったく処理されずに削除されるわけではなく、サーブレット・エンジンのエラー・ログに記録されます。 (デフォルトでは、UIXのエラー・ログはメッセージとエラーをサーブレット・エンジンのエラー・ログに送信しますが、別の方針で置き換えることもできます。) 本番アプリケーションでも、エラー・ログを監視する必要があります。 ただし、エラーはユーザーには表示されないため、問題があることをユーザーが画面から知ることはできません。

第2の選択肢: エラーの捕捉、記録およびマーク

第1の方法の大きな問題は、エラーが発生したときにユーザーへのフィードバックがないことです。 エラーを示すヒントもなく空の表やフィールドが表示されるので、ユーザーが疑問を抱くことになります。 最初の方法を少し変更することで、この問題を解決できます。 エラーが発生するとすぐに、エラーを記録するのみではなく、エラーを格納し、データからエラーを返すことをサポートします。 たとえば、次のUIX XMLを使用します。

  <dataScope>
    <!-- Get some data -->
    <provider>
      <data name="dataForTable">...</data>
    </provider>
    <contents>
      <!-- Put the data in a table -->
      <table>...</table>
    </contents>
  </dataScope>

ここで、表の後にコードを追加します。

     ...
    <contents>
      <!-- Put the data in a table -->
      <table>...</table>

      <!-- And warn the user of an error if one happened, -->
      <header messageType="error" rendered="${dataForTable.error}">
        <contents>
           An error occurred:
          <styledText text="${dataForTable.exception}"/>
      </header>
    </contents>
     ...

このUIX XMLコードによってエラー・メッセージが追加されます。 これがレンダリングされるのは、errorフラグが設定された場合のみです。 (rendered属性の詳細は、「ADF UIXページの動的構造」を参照してください。) 次に例外が表示されます。必要に応じて、出力内容をわかりやすくできます。 この処理を行うためのデータソースの更新方法を次に示します。

  public class SimpleDataObject2 implements DataObject
  {
    public Object selectValue(RenderingContext context,
                              Object key)
    {
      // Have we encountered an error yet?
      if ("error".equals(key))
      {
        return (_exception == null) ? Boolean.FALSE : Boolean.TRUE;
      }
      // What exception have we seen?
      else if ("exception".equals(key))
      {
        if (_exception == null) return null;
        return _exception.toString();
      }

      try
      {
        // Try to get the value
        return _aMethodThatMayFail(context, key);
      }
      catch (SQLException sqle)
      {
        // Oh no - an error!  But log it...
        context.getErrorLog().logError(sqle);

        // ...store it...
        _exception = sqle;

        // and return null.
        return null;
      }
    }

    // Cached exception
    private Exception _exception;
  }

これは前述の例によく似ていますが、ログを記録した後に例外をユーザーに対して解放しないで一度格納します。 また、このエラーの状態を公開する2つの新しいキー("error"、"exception")をDataObjectに追加しています。

この方法はかなり優れています。 エラーがない場合のオーバーヘッドが大変少なく、エラーが発生するとユーザーに知らされます。 短所としては、誤っている可能性があるデータが表示されること、ユーザーがページをかなり下までスクロールしないとエラーに気付かない可能性があることと、各DataObjectにエラー処理コードを設定する必要があることです。 最後の問題は、エラー格納コードを汎用にすれば解消できます。ただし、ユーザー側の問題はこの方法特有の現象です。

この方法を使用する場合のもう1つの改善案は、エラーをレンダリングするUIX XMLのブロックをUIX XMLのテンプレートに含めることです。テンプレートの詳細は、「ADF UIXでのインクルードおよびテンプレート定義」のトピックを参照してください。

第3の選択肢: 事前にすべてを取得

ページまたはセクションの一番上に、つまりデータが実際に使用される前にデータ・バインディング・エラーを表示するには、2つの方法があります。 1つは、すべてのデータを一度に取得して、レンダリングの途中に障害が発生しないようにすることです。 この1番目の方法について説明し、2番目の方法については次のセクションで説明します。

UIX XMLの<method>要素(またはJavaのMethodDataProviderクラス)でこの方法をどのように実行するのか説明します。 データを取得するメソッドを定義します。このメソッドは取得に失敗すると、エラー・コードのみを含む単純なDataObjectをかわりに返します。

import oracle.cabo.ui.data.DictionaryData;

...

  static public DataObject getResultSet(
    RenderingContext context, String namespace, String name)
  {
    DictionaryData data = new DictionaryData();
    try
    {

      ResultSet result = _getJDBCResultSet(...);

      data.put("ename", result.getObject("ENAME"));
      data.put("id", result.getObject("ID");
      data.put("manager", result.getObject("MANAGER");
      // .. etc...
      // And mark that we didn't have any errors
      data.put("errors", "no");
    }
    catch (SQLException sqle)
    {
      // No - an error.  Mark the error case, store
      // the exception, and return.
      data.put("errors", "yes");
      data.put("exception", sqle.toString());
    }

    return data;
  }

このコードでは、次の処理が実行されます。

  1. DictionaryDataを作成します。 このクラスは、少量の固定データ・セットを格納するための単純な方法です。例外はスローされません。
  2. JDBCのResultSetを取得します。正常に取得した場合は、次の処理を続けます。
    1. 結果セットの各値をコピーします。
    2. 各値が正常に取得されたら、noerrorsキーに格納します。これをUIで使用して、エラーのないUIをレンダリングします。
  3. いずれかの時点で失敗した場合は、次の処理を実行します。
    1. yeserrorsキーに格納します。これをUIで使用して、エラー発生時のUIをレンダリングします。
    2. 前述のように例外のテキストを格納すると、UIをわかりやすくすることができます。

このデータを使用するUIを次に示します。

 <dataScope>
   <provider>
     <!-- Get the result set.  Note that this _is not_ enough
           to trigger actually calling getResultSet();  that won't
           happen until we try to access a piece of the result -->
     <data name="resultSet">
       <method class="...." method="getResultSet"/>
     </data>
   </provider>
   <contents>
     <!-- Now: did we have an error?  Ask for the "errors" key
              first - this will at last trigger the call to
              getResultSet(), which will get all of the data and
              set the "errors" key. -->
     <switcher childName="${resultSet.errors}">
       <case name="no">
         <!-- No errors: show the result set -->
         <labeledFieldLayout>
           <contents>
             <messageStyledText name="id" prompt="Employee ID:"
                                text="${resultSet.id}"/>
             <messageTextInput name="ename" prompt="Employee name:"
                                text="${resultSet.ename}"/>
           </contents>
         </labeledFieldLayout>
       </case>
       <case name="yes">
         <!-- An error.  Show the exception -->
         <header messageType="error">
           <contents>
             <styledText text="${resultSet.exception}"/>
           </contents>
         </header>
       </case>
     </switcher>
   </contents>
 </dataScope>

ここでは適切なUIX XMLのコードが使用されています。 内容を説明します。

  1. 最初に、データ・プロバイダをロードします。
         <data name="resultSet">
           <method class="...." method="getResultSet"/>
         </data>
    ここで重要なのは、データ・プロバイダを参照するだけではデータがロードされないということです。 そのプロバイダからデータがリクエストされるまで、ロードされません。
  2. ここで、まだ説明していない新しいタグ、<switcher>を使用してコンテンツのレンダリングを開始します。 (このBeanはJavaのSwitcherBeanに相当します。) この詳細は、「ADF UIXページの動的構造」のトピックで説明しますが、ここでは、childName文字列属性に基づいて、いくつかのケースに分岐するということを覚えておいてください。
         <switcher childName="${resultSet.errors}">
           <case name="no">
             ...
           </case>
           <case name="yes">
             ...
           </case>
         </switcher>
    ここでは、errorsキーを使用してchildName属性を実行します。 前述したように、このキーは、すべてのデータを正常に取得したときにnoに設定され、エラーが発生するとyesに設定されます。
  3. 残りの部分、つまり各<case>内のUIX XMLは、理解できるかと思います。 エラーが発生した場合には、エラー・メッセージを表示します。 正常に処理された場合は、予期されている結果を表示します。

(この方針を説明するためにDataProviderを使用しましたが、独自にDataObjectを記述する場合でも、 同様にDataProviderを使用すると簡単です。 この場合、それぞれのデータをDictionaryDataにコピーする必要はありません。この方針によって、最初から最後までのすべてのリクエストが正常終了するという規則を通せるようになります。)

この選択肢では、最も優れた出力結果が得られます。 エラーがあると、出力が即時に中断され、ユーザーはすぐにエラー・メッセージを確認できます。 ただし、開発に時間がかかり、アプリケーションのパフォーマンスのコストも高くなります。 1つか2つの少量のデータのみが必要な場合でも、Javaコードに事前にすべてのデータを取得する必要があります。 さらに、コードで作成可能なすべての情報のリストを記述し、そのリストを更新し続けることが必要になります。 このようなコードの記述は煩雑な上にメンテナンスも手間がかかります。

第4の選択肢: <try>および出力バッファリング

これまでのすべての選択肢では、すべてのDataProviderおよびDataObjectにインテリジェント機能を追加する必要がありました。 インテリジェント機能を追加せずに、UIXのフレームワークでエラー処理が可能になれば、処理は非常に簡単になります。 これを簡単に実行できる<try>要素があります。これはJavaのTryBeanに相当します。

データ・プロバイダにエラー条件を処理させるかわりに、セクションを<try>要素で囲みます。 次のDataProviderは必ず失敗します。

  static public DataObject throwException(
    RenderingContext context,
    String        namespaceURI,
    String        name) throws Exception
  {
    throw new java.sql.SQLException("Big problem");
  }

次に、このコードを使用するUIXを示します。

 <try>
   <contents>
     <dataScope>
       <provider>
         <data name="noGood">
           <method class="..." method="throwException"/>
         </data>
       </provider>
       <contents>
         <text text="This won't work: "/>
         <text text="${noGood.notAChance}"/>
       </contents>
     </dataScope>
   </contents>
 </try>

この例を実行しても何も表示されません。 <try>を入力すると、出力のバッファリングがすぐに開始されます。 ネットワーク上にデータを直接送信するかわりにバッファに格納します。 セクションが終了してエラーがなければ、すぐにバッファがフラッシュされ、ネットワークに送信されます。 (ただし、<try>自体が別の<try>の内側にない場合です。このように、<try>はネストすることができます。) ただしエラーが発生すると、セクション全体が削除されます。

ユーザーにエラーを伝える場合(通常は適切)は、<try><catch>セクションを使用します。 また、エラー・オブジェクトがプロパティとしてRenderingContextに格納されるため、そのデータをエラー・メッセージで使用できます。 次に、<catch>セクションも含めて完成させたUIXの例を示します。

 <try>
   <contents>
     <dataScope>
       <provider>
         <data name="noGood">
           <method class="..." method="throwException"/>
         </data>
       </provider>
       <contents>
         <text text="This won't work: "/>
         <text text="${noGood.notAChance}"/>
       </contents>
     </dataScope>
   </contents>
   <!-- Catch the error if it happens -->
   <catch>
     <header messageType="error">
       <!-- And for the text of the error message, just show
                the exception that caused it -->
       <boundAttribute name="text">
         <contextProperty select="ui:currentThrowable"/>
       </boundAttribute>
     </header>
   </catch>
 </try>

レンダリング・コンテキストのui:currentThrowableキーでThrowableを取得したことに注意してください。 <try>によってエラーがここに格納されます。 Javaでは、次のコードを使用してエラーにアクセスできます。

  import oracle.cabo.ui.UIConstants;

  ...
  RenderingContext ctxt = ...;
  Throwable error = (Throwable)
    ctxt.getProperty(UIConstants.MARLIN_NAMESPACE,
                     UIConstants.CURRENT_THROWABLE_PROPERTY);

エラーの表示を簡単にするために、UIX XMLでは<catch>の内部で使用できる<displayException>要素が用意されています。 例外のタイプおよびメッセージ(前述の例で表示できるものはこれのみです)を表示するのみでなく、スタック・トレースも参照できます。

 <try>
   <contents>
     <!-- etc. -->
   </contents>
   <!-- Catch the error if it happens -->
   <catch>
     <displayException/>
   </catch>
 </try>

このようなバッファリングは、JSPで現在すでに行っている処理と類似しています。このため、JSPに精通している開発者は、Webアプリケーションのこの種の解決方法は当然だと思うでしょう。 ビルトインJSPソリューションでは、ページ全体をバッファリングするか、まったくバッファリングしないかという2つの選択肢しかないという問題があります。 ページの一部を選択してバッファリングするための簡単な方法がありません。 (JSP 1.2仕様には、TryCatchFinallyタグ・インタフェースが含まれています。これを使用すると、カスタム・タグで問題に対処できます。UIXのソリューションはすべてのJSPバージョンで機能します。) ページ全体をバッファリングする場合は、ページ全体が生成されるまで、データがWebに送信されてブラウザに表示されるのをユーザーは待機する必要があります。 選択式バッファリングでは、結果を得てからすぐに送信できます。

それでは、なぜ常に<try>を使用しないのでしょうか。 エラーをまったく無視することを除けば、すでに存在していたページにこの要素を追加するのは、確かに他のどの方法よりも簡単です。 <try>の最大の短所は効率が悪いことです。 不必要に出力をバッファリングしなければ処理速度は速くなります。 つまり、全ページのすべてのコンテンツを常にバッファリングするのは時間がかかります。 そこで、選択した範囲に<try>を指定します。つまり、<<table>または一連のフォーム要素を囲むように指定します。 ただし、選択範囲を細かくしすぎないでください。 また、個々のフォーム要素の前後に指定しないでください。バッファリングのオンとオフの切替えに時間がかかるためです。

2番目の短所は、1つのエラーによって、<try>の内側のすべての出力が廃棄されることです。 通常は、これは短所ではありません。誤った出力よりは出力がない方がよいからです。 ただし、出力がないために問題の識別が困難になることがあります。特に、<catch>セクションがない場合や、エラー・ログにアクセスできない場合です。 これは、ユーザー使用時よりも開発時に問題になります。

TryBean: 検討事項

BoundValueDataObjectを独自に作成して、DataProviderとともにTryBeanを利用しようとすると問題に出会う場合があります。 BoundValueDataObjectのインタフェースでは実際に例外をスローできないからです。 これらのJavaのメソッドでは、常にRuntimeExceptionのサブクラスをスローできますが、検出する可能性があるIOExceptionSQLExceptionSAXExceptionまたはその他の実際の例外をスローすることはできません。 これらのメソッドで例外をスローできれば、処理は非常に簡単になります。 よって、これに対処する方法が必要になります。

これを解決するのは、oracle.bali.share.utilパッケージのUnhandledExceptionクラスです。 これはRuntimeExceptionのサブクラスであり、どのコードからもスローできます。 また、任意のThrowableまたはExceptionをラップしてコンストラクタに渡すことができます。 説明のためにDataObjectの簡単な例を示します。

例:

  public class SomeDataObject implements DataObject
  {
    public Object selectValue(RenderingContext context, Object key)
    {
      try
      {
        ...
        Try to get the data...
        ...
      }
      catch (SQLException sqle)
      {
        // We can't throw a SQLException from a DataObject.  But
        // we can throw an UnhandledException!
        throw new UnhandledException(sqle);
      }
    }
  }

前述DataProviderの例ではどのようにSQLExceptionをスローできているのか、疑問に思うかもしれません。 DataProvider<method>要素を介してアクセスされるために少し違った形で設計されています。 DataProviderは実際にインタフェースを実装していないため、メソッドのthrows宣言内にあるかぎり自由に例外をスローできるのです。

まとめ

データ・バインディングの問題を処理するための様々な選択肢について説明しました。 多数の選択肢があるため、すべてに対応できる1つの方針を示すのは困難です。 場合によっては開発者の個人的な好みで決定する場合もあります。 ただし、賢明かつ単純な方針は、次のように<try>と何もしないことを組み合せることです。

もちろん、<try>を利用するばかりでは、堅牢なコードを記述することはできません。 どのエラー処理コードを使用する場合でも、エラーを少なくすることが最も重要です。

イベント処理でのエラー

イベント処理でのエラー処理はより単純です。 出力はまだ生成されていないため、適当と判断する任意の方法で対応できます。 一般的には2つの選択肢があります。汎用エラー・ページを使用するか、インライン・エラーを表示することです。 インライン・エラーは、コンテンツと同じページ内でユーザーにエラーを表示するために使用されます。次のセクションで処理方法を説明します。

イベント処理でのエラーの多くは予測不可能です(データベースの接続拒否、問合せの失敗、サーバーのメモリー不足など)。 これらは予測できないため、ユーザーが問題を解決する方法はありません。 ユーザーに単純なエラー・ページを表示して、何が発生したかを知らせるのが最適です。UIX Controllerを使用すると簡単に実行できます。

まず、イベント・ハンドラは例外を自由にスローできることに注意してください。 ただし、これによってエラー処理が不要になるわけではありません。 ユーザーが正しい数値を入力しなかった場合にスローされるNumberFormatExceptionなど、エラーによっては、必ず捕捉して適切に処理する必要があります。 ただし、予測できない例外が発生したときは、EventHandlerからエスケープするようにします。

例外がスローされると、UIXサーブレットはPageBroker.renderError(BajaContext, Throwable)をコールします。 このメソッドはデフォルトで次の手順を実行します。

  1. Throwableoracle.cabo.servlet.eventErrorキーでServletRequest属性として保存します。
  2. エラー・ページが設定されている場合はエラー・ページを取得し、レンダリングを要求します。
  3. エラー・ページが設定されていない場合は、エラーを記録し、HTTPエラー・メッセージを送信します。

エラー・ページは、setErrorPage()をコールして設定するか、oracle.cabo.servlet.errorPageサーブレット構成パラメータによって設定することができ、いつでも取得できます。

エラーの表示

エラーを表示するためには、エラー内容を書き出す必要があります。 UIXには、この指定のための標準的な形式があります。MessageDataクラスです。 MessageData APIを使用すると、イベント・ハンドラで、1つのページに対して複数のエラーを指定でき、それらのエラーとページ内のコンポーネントとの対応を指定できます。 ユーザー・インタフェースでは、<messageBox>によってエラーがまとめられます。また、UIXのインライン・メッセージ・コンポーネントを使用すると、コンポーネントとともにインライン・エラーを表示できます。

エラーの指定: MessageData

MessageDataクラスは特殊なDataObjectであり、これを使用すると、サーバー側のエラー・メッセージを簡単にページに表示できるようになります。

1つのMessageDataオブジェクトで、任意の数のメッセージのリストが収集されます。 MessageDataオブジェクトを作成し、各メッセージを続けて追加して、レンダリング時にそのMessageDataを使用します。

個々のメッセージは、ページのコンテンツ全体に適用する場合(たとえば「ユーザー名とパスワードの組合せが正しくありません」など)はページ・レベル、または単一のコンポーネントのメッセージのみに適用する場合(たとえば「本日以降の日付を入力してください」など)はコンポーネント・レベルになります。 各メッセージに含まれる情報は次のとおりです。

例:

次の例では、SQLExceptionに応答する一連のページ・レベル・エラー・メッセージが構築されます。

  static public MessageData getMessages(SQLException exception)
  {
    MessageData msgs = new MessageData();

    while (exception != null)
    {
      // Create a title and get the message
      String title = "ORA-" + exception.getErrorCode();
      String message = exception.getLocalizedMessage();

      // Add the message
      msgs.addError(null,    // null select - a page-level error
                    message, // the error message
                    null,    // description URL - only for component-level
                    title,   // the error title
                    null);   // message description - let it default to the
                             //   message itself
      // And now go to the next exception in the chain.
      exception = exception.getNextException();
    }

    return msgs;
  }

MessageDataクラスには、エラー、警告および情報メッセージを追加するための便利なメソッドが他にも多く含まれています。この例で使用されているメソッドは1つのみです。 詳細は、このAPIのドキュメントを参照してください。

ここで重要なのは、このクラスおよびその他の関連するクラスや要素は、非常に便利なオプションであるということです。 すべて標準のデータ・バインディングであり、特に複雑なものではありません。 MessageDataおよびUIX XMLの要素(後述)を使用することによって、誤って発生するエラーを防ぐことができるでしょう。

一度MessageDataオブジェクトを作成すると、それをページに取得する必要があります。 これは、UIXの<method>要素など、通常のデータ・バインディング技術を使用して実行できます。 ただし、UIX Controllerのイベント・ハンドラを使用している場合は、さらに簡単な方法があります。 イベント・ハンドラには、DataProviderをイベント・ハンドラからページに渡すための簡単な方法があり、MessageDataには、自身をDataProviderに変換するための便利なメソッドがあります。 使用例を次に示します。

  // An EventHandler that may produce an error
  static public EventResult doSomething(
    BajaContext context,
    Page        page,
    PageEvent   event)
  {
    try
    {
      // .... Do the usual stuff ....
      return ...;
    }
    // An error!
    catch (SQLException sqle)
    {
      // Turn the exception into a MessageData object
      MessageData msgs = getMessages(sqle);

      DataProvider provider = msgs.toDataProvider();

      UIEventResult result = new UIEventResult();
      result.setDataProvider(provider);

      return result;
    }
  }

この例について簡単に説明します。 すべてのイベント・ハンドラの標準コードについては省き、catchセクションについて説明します。

エラーのサマリー表示: MessageBoxBean

<messageBox> XML要素およびMessageBoxBean Javaクラスは、ページのメッセージすべてのサマリーの表示に使用します。

ほとんどの場合、このBeanにはautomatic属性を設定するのみです。 automaticをtrue(JavaではMessageBoxBean.setAutomatic(true))に設定すると、<messageBox>が自動モードになります。 デフォルトの場所でMessageDataインスタンスが自動的に検索されます。 1つも検出されない場合、(現在のUI仕様に基づき)何もレンダリングされません。 検出された場合は、正しいコンテンツ内に各メッセージが書き出されます。

よく使用されるもう1つの機能は、message属性(setMessage()メソッド)です。 このメソッドを使用すると、ボックス全体のメイン・メッセージを設定できます。 setMessage()メソッドはMessageDataでもサポートされており、自動モードでは、<messageBox>によりMessageDataからメッセージが引き出されます。

UIX XMLの例:

<messageBox>はUIX XMLから簡単に使用できます。

 <pageLayout>
   <messages>
     <!-- A message box belongs at the top of the page's content -->
     <messageBox automatic="true"/>
   </messages>
   <contents>
     <!-- and then follows the rest of the page -->
   </contents>
 </pageLayout>

<messageBox>が特別な<messages>要素内に追加されていることに注意してください。これにより、メッセージがルック・アンド・フィールに対して正しい場所に配置されます。 <messageBox>はページの任意の場所に配置できますが、<pageLayout>を使用している場合は、ここが正しい場所になります。

<messageBox>には属性が他にもいくつかありますが、それほど使用されません。 たとえば、MessageDataの検索を行うネームスペースと名前をオーバーライドできますが、すでに説明したようにデフォルトを使用する方がより簡単です。

エラーや警告メッセージ以外を表示するため、自動モードをオフにして<messageBox>を使用することができます。 この場合は、明示的にメッセージを追加し、メッセージの種類およびタイトルを設定する必要があります。 たとえば、<messageBox>を使用して、「このファイルを本当に削除しますか?」というような確認ページを作成する場合があります。 確認専用のmessageTypeの値(JavaのUIConstants.MESSAGE_TYPE_CONFIRMATION)を使用すると、適切な概観が得られます。

インライン・エラーの表示: メッセージBean

UIXのレンダラと、それが実装するユーザー・インタフェースの仕様のエラー表示に係わる主な機能は、インライン・メッセージです。 すべてのエラーがまとめて最上部に表示されるWebページでは、ユーザーはエラーを修正するため、どのフィールドを変更する必要があるのか探し出す必要があります。 UIXでは、エラーを問題の原因となった入力コントロールとともにインラインで表示できるようにして、この使用性の問題を解決しています。

このインタフェースの中心となるコンポーネントは、<inlineMessage> XML要素(JavaのInlineMessageBean)です。 通常は、いくつかの他のコントロールの前にプロンプトが表示されます。

エラーなしのmessageTextInputの図

エラーが発生するとこのコンポーネントが自動的に拡張し、エラー・メッセージおよびアイコンが表示されます。

エラーが発生したmessageTextInputの図

この2つの図で示した<inlineMessage>のUIXは次のとおりです。

 <inlineMessage prompt="Old password:">
   <contents>
     <textInput name="pwd" secret="true"/>
   </contents>

   <boundMessage select="pwdError"/>
 </inlineMessage>

特に難しくはありません。 prompt属性で左側の「古いパスワード:」を設定し、内部の<textInput>で入力フィールドを設定します。 これらは新規の要素ですが、特に新しい概念ではありません。 本当に新しい要素は、<boundMessage>要素のみです。

<boundMessage>要素は、MessageDataのユーザー・インタフェース側をサポートするデータ・バインディングをすべて設定します。 この要素は、1つのMessageDataから値を取得して、InlineMessageBeanの属性のうち5つとデータ・バインドします。 <boundMessage>select属性がMessageDataオブジェクトに追加されたメッセージの選択キーと一致する場合、そのメッセージがコンポーネントの横に表示され、アイコンなどが表示されます。 MessageDataに一致するものがない場合は、inlineMessageが直接レンダリングされます。

Javaでは、この機能はMessageData.bindNode()メソッドで実現されます。

  InlineMessageBean imb = ....;
  // Attach this node to the "pwdError" message
  MessageData.bindNode(imb, "pwdError");

メッセージBeanのレイアウト

<inlineMessage>要素を2つ並べて配置すると、問題があることに気付きます。

 <inlineMessage prompt="Your username:">
   <contents>
     <textInput name="user"/>
   </contents>
 </inlineMessage>
 <inlineMessage prompt="Password:">
   <contents>
     <textInput name="pwd" secret="true"/>
   </contents>
 </inlineMessage>

これによって出力されたレイアウトでは2つの<textInput>要素が並びません。 並べるには、レイアウト・コンポーネント、<tableLayout>または<labeledFieldLayout>のいずれかの内部に2つの要素を入れる必要があります。 使用方法は、これらのコンポーネントのドキュメントを参照してください。

便利なメッセージBean

インライン・メッセージは、フォーム要素とともによく使用されます。 このため、<inlineMessage>の機能と別の要素の機能を結合する便利な要素がいくつか追加されています。 たとえば、<messageTextInput>要素は<inlineMessage><textInput>の組合せとして機能します。 次に示す2つのUIX XMLは、機能面では同じです。

 <inlineMessage prompt="Username:">
   <contents>
     <textInput name="user"/>
   </contents>
   <boundMessage select="userMsg"/>
 </inlineMessage>

および

 <messageTextInput name="user" prompt="Username:">
   <boundMessage select="userMsg"/>
 </messageTextInput>

両方で<boundMessage>構文がサポートされていることに特に注意してください。 その他の便利な要素として、次のものがあります。

これらはすべて簡単に使用でき、非常に便利であることに注意してください。 ここに示していないコンポーネントでインライン・メッセージが必要な場合は、<inlineMessage>でそのコンポーネントをラップしてください。 これらの便利なBeanは、MessageTextInputBeanMessageCheckBoxなどのJava APIでも使用できます。

完全な例: 新規パスワードの入力

これまでに説明した内容を元に、完全な例を説明します。 次の3つのフィールドを使用して、パスワード変更のページを定義します。

  1. 現在のパスワード
  2. 新規パスワード
  3. 確認用に再入力する新規パスワード

イベント・ハンドラでいくつかのエラー条件を調べ、エラーがあった場合はインライン・メッセージでページを再表示します。

まず、UIX XMLを示します。

<page xmlns="http://xmlns.oracle.com/uix/controller"
      xmlns:ctrl="http://xmlns.oracle.com/uix/controller"
    expressionLanguage="el">
 <content>
   <form xmlns="http://xmlns.oracle.com/uix/ui"
          name="default" method="post">
     <contents>
       <pageLayout>
         <contents>
           <messageBox automatic="true"/>
           <labeledFieldLayout>
             <contents>
               <messageTextInput prompt="Old password:"
                                  name="old" secret="true">
                 <boundMessage select="oldError"/>
               </messageTextInput>

               <messageTextInput prompt="New password:"
                                  name="new" secret="true"
                          tip="Passwords must be between 4 and 12 characters">
                 <boundMessage select="newError"/>
               </messageTextInput>

               <messageTextInput prompt="Confirm:"
                                  name="confirm" secret="true"/>
             </contents>
           </labeledFieldLayout>
         </contents>

         <contentFooter>
           <submitButton text="Submit" event="newPassword"/>
         </contentFooter>
       </pageLayout>
     </contents>
   </form>
 </content>

 <handlers>
   <event name="newPassword">
     <method class="oracle.cabo.doc.demo.PasswordDemo"
              method="handleNewPassword"/>
   </event>
 </handlers>
</page>

処理内容を簡単に推測できるコンテンツです。 入力結果を送信するための<form><submitButton>を追加し、3つの<messageTextInput>要素のうち2つをメッセージにバインドしています。 また、コンテンツの最上部には<messageBox>を配置しています。 このページ内には、発生する可能性のあるエラーの種類について記述したり、このページによって処理されるビジネス・ロジックへの参照に関する記述を行う必要がないことに注意してください。 これで正しい状態です。

次はイベント処理のソース・コードです。 実際のパスワード・チェックのコードは省略しています。古いパスワードはハードコードし、新規パスワードはどこにも保存していません。 しかし、メッセージ出力に関するコードはここに示すのがすべてです。

  static public EventResult handleNewPassword(
    BajaContext   context,
    Page          page,
    PageEvent     event) throws Throwable
  {
    MessageData messages = new MessageData();

    // Get all three parameters
    String oldPassword = event.getParameter("old");
    String newPassword = event.getParameter("new");
    // Safeguard against null values.  Always check parameters
    if (newPassword == null)
      newPassword = "";
    String confirm = event.getParameter("confirm");

    if (!"abcd".equals(oldPassword))
      messages.addError("oldError",
                        "The old password was not correct (try abcd)",
                        null,
                        "Old password",
                        null
                        );

    if (!newPassword.equals(confirm))
      messages.addError(null,
                        "The two values for the new password didn't match.",
                        null,
                        "Error",
                        null);

    if ((newPassword.length() < 4) ||
        (newPassword.length() > 12))
      messages.addError("newError",
                        "Passwords must be between 4 and 12 characters long",
                        null,
                        "Password length",
                        null);

    // Are there any error messages yet?  If not, then all is good,
    // and move on to the success page.
    if (messages.getLength() == 0)
    {
      // A real application would store the new password here!
      return new EventResult(new Page(page, "success"));
    }
    else
    {
      UIEventResult result = new UIEventResult();
      result.setDataProvider(messages.toDataProvider());
      return result;
    }
  }

このコードも処理内容を簡単に推測することが可能で、すでに説明した内容の繰返しであることがわかります。 古いパスワードが正しいこと、新規パスワードの文字数が4文字以上12文字以下であること、ユーザーがパスワードをミスタイプしていないかの確認として、新規パスワードのフィールドと確認用のフィールドが一致することを確認しています。 問題が存在すると、メッセージがリストに追加されます。メッセージがある場合は、現在のページに戻ってエラーを表示します。 ない場合はそのまま処理を進めます。 非常に単純な内容です。

メッセージBeanのその他の機能

先に進む前に、メッセージBeanのその他の機能をいくつか説明します。

メッセージBeanはすべて、tip属性をサポートします。 これによって、現在のフィールドの使用方法をユーザーに示すテキストが追加されます。 このテキストはエラーが表示されても見えるため、常に有用な情報の表示に使用してください。 一般的には、正しい形式または値を示すテキストに使用します。


    <messageDateField prompt="Date:" name="date">
      <onSubmitValidater>
        <date dateStyle="shortish"/>
      </onSubmitValidater>

      <!-- Set the tip up so it shows a sample date properly formatted
           for the correct locale -->
      <boundAttribute name="tip">
        <messageFormat format="Example: {0}">
          <format>
            <fixed javaType="java.util.Date">2006-02-17</fixed>
            <fixed javaType="oracle.cabo.ui.validate.ClientValidater">
              <date dateStyle="shortish"/>
            </fixed>
          </format>
        </messageFormat>
      </boundAttribute>
    </messageDateField>

メッセージBeanではrequired属性もサポートします。 requiredで使用できる値および値の意味については、このトピックで後述します。 メッセージBeanという観点では、これは単に、値の入力が必須であることをユーザーに示すアイコンを表示するかどうかを示します。

   <inlineMessage prompt="Username:" required="yes">
     <contents>
       <textInput name="user"/>
     </contents>
   </inlineMessage>

メッセージBeanはまた、<end>子要素(Java APIではsetEnd()メソッド)もサポートします。 この要素で囲まれた部分は、コンテンツの最後(通常は右側)に追加されます。

   <messageTextInput prompt="Username:" name="user">
     <end>
       <button text="Press Me" onClick="alert('Boo!')"/>
     </end>
   </messageTextInput>

<messagePrompt>および<messageText>

最後にもう1点説明します。 メッセージBeanのレイアウトでは全体的な形状が制限され、項目を期待どおりに並べることができない場合があります。 このような場合は、特別な2つのコンポーネント、<messagePrompt>および<messageText>、またはJavaのMessagePromptBeanおよびMessageTextBeanを使用して、必要なメッセージBeanを分割配置します。

<messagePrompt>は、プロンプト、必要なアイコン、および任意のエラー・アイコンを表示します。 <messageText>は、エラー・メッセージおよび任意のヒントを表示します。 両方とも<boundMessage>要素(およびMessageData.bindNode())をサポートします。一般的には、この2つは同じエラー・メッセージにバインドします。 次の例は、垂直方向に並べてレイアウトするために通常のインラインのメッセージBeanを分割したものです。 特に<boundMessage>要素に注意してください。

   <stackLayout>
     <contents>
       <messagePrompt prompt="Username:" required="yes">
         <boundMessage select="userError"/>
       </messagePrompt>
       <textInput name="user"/>
       <messageText tip="Your username">
         <boundMessage select="userError"/>
       </messageText>
     </contents>
   </stackLayout>

クライアント側の検証

ユーザー・エラーは、結果がサーバーに送信される前に捕捉するのが理想的です。 ユーザーにとっては、エラー・ページがロードされるまで待機する必要がなくなるため、処理が速くなります。 間違った結果を解析する必要もないため、サーバーの負荷も減ります。 UIXは、クライアント側の検証と呼ばれるこの機能をサポートしています。

クライアント側の検証を使用するにあたり、検証をクライアント側のみにしてしまわないよう注意することが必要です。 すべての層で検証するというのが複数層アプリケーションの鉄則です。 中間層のコード、たとえば、サーブレット・エンジンで実行するコードでは、クライアントで検証済であっても、常にすべての値を調べる必要があります。 データベースでは、中間層から送信されてきた値を検証します。 アプリケーションのパフォーマンスの低下につながる不要な作業に見えますが、必ずこのルールに従ってください。 悪意を持つユーザーにより、URLが変更されたり、ダミーのHTTPポストが作成される可能性があります。データベースが現在はJavaScript対応のブラウザで使用されていても、次の日には携帯情報端末の単純なブラウザで使用される可能性もあります。 このルールを守らないと、ぜい弱なアプリケーションになります。 最悪の場合にはセキュリティ・ホールが発生します。

必須フィールド

最初に説明するクライアント側の検証の種類は、required属性です。 この属性は、ユーザーのデータ入力が必要か、または値を空のままにできるかを定義します。 まずは、テキストを入力せずに次のページの「Submit」をクリックしてみてください。ページ送信の中止を求めるエラーが表示されます。

<form name="defaultForm" xmlns="http://xmlns.oracle.com/uix/ui">
 <contents>
   <messageTextInput prompt="Username:" name="user" required="yes"/>
   <submitButton text="Submit"/>
 </contents>
</form>

required属性は<choice>および<messageChoice>要素でもサポートされます。ここで使用する場合は空のオプションを追加し、selectedとマークします。

<form name="defaultForm" xmlns="http://xmlns.oracle.com/uix/ui">
 <contents>
   <messageChoice prompt="Withholding:" name="withhold" required="yes">
     <contents>
       <option selected="true"/>
       <option text="10%" value="10"/>
       <option text="20%" value="20"/>
       <option text="30%" value="30"/>
     </contents>
   </messageChoice>
   <submitButton text="Submit"/>
 </contents>
</form>

required属性には、次の4つの有効な値があります。

  1. yes(JavaではUIConstants.REQUIRED_YES): 空以外の値のみが使用できます。
  2. no(JavaではUIConstants.REQUIRED_NO): 空の値を常に使用できます。
  3. validaterOnly(JavaではUIConstants.REQUIRED_VALIDATER_ONLY): 連結されているクライアント側のValidaterで空の値が許可されている場合にのみ、空の値を使用できます。 (Validaterの連結については、次項を参照してください。)
  4. uiOnly(JavaではUIConstants.REQUIRED_UI_ONLY): ユーザーについて値は必須なように見えますが、実際にはvalidaterOnlyと同じように動作します。 この動作はあまり必要とされませんが、有用な場合もまれに存在します。

10進の検証

最も一般的な入力フィールドは数値フィールドです。 この動作は、UIXのDecimalValidaterクラスおよび<decimal>要素でサポートされています。 これは単純な要素です。次の例で、テキストを無作為に入力してフォームをサブミットしてください。

   <messageTextInput prompt="Any number:" name="number">
     <onSubmitValidater>
       <decimal/>
     </onSubmitValidater>
   </messageTextInput>

<decimal>要素は非常に単純であり、属性はサポートしません。 ここで興味深いのは、この要素をラップする<onSubmitValidater>要素です。 この要素は、使用するValidaterの種類を定義します。 <onSubmitValidate>および<onBlurValidater>の2種類があります。 Javaでは、これらはsetOnSubmitValidater()およびsetOnBlurValidater()メソッドによって設定されます。 onSubmitのValidaterは、ユーザーがフォームにすべてを入力して「Submit」ボタンをクリックすると実行され、onBlurのValidaterは、ユーザーが別のフィールドに移ると同時に実行されます。 違いを確認するために、次の例を試してください。 フィールドにテキストを不作為に入力して[Tab]キーを押します。

   <messageTextInput prompt="Any number:" name="number">
     <onBlurValidater>
       <decimal/>
     </onBlurValidater>
   </messageTextInput>

特別な理由がないかぎり、onSubmitの検証を使用してください。 正しい値を入力するまで他に何も入力できないような設定は、ユーザーにとって非効率的です。

日付の検証およびDateFieldBean

次に一般的な入力フィールドは日付フィールドです。 UIXでは、<dateField>要素およびDateFieldBeanと、これらに対応するメッセージ系の<messageDateField>要素およびMessageDateFieldBeanとともにこの機能を用意しています。 (<messageDateField>は、<inlineMessage>内の<dateField>に相当します。) <dateField>の例を次に示します。フィールドの右側にあるカレンダのアイコンをクリックしてください。

   <messageDateField prompt="Date:" name="date"/>

非常に簡単です。 しかし、デフォルト書式が望ましくない場合、または日付のみでなく時刻も編集する場合は、結果の書式を変更する必要があります。 これを実行する際は注意が必要です。<dateField>で属性を直接設定するのではなく、<onSubmitValidater>を特別な<date>要素(またはDateValidaterオブジェクト)とともに追加してください。 詳細は、<date>要素に関するドキュメントを参照してください。次の例を試すと概要がつかめるはずです。

   <tableLayout>
     <contents>
       <messageDateField prompt="Date:" name="date" columns="20">
         <onSubmitValidater>
           <date dateStyle="long"/>
         </onSubmitValidater>
       </messageDateField>

       <messageDateField prompt="Date and Time:" name="dateandtime"
                          columns="20">
         <onSubmitValidater>
           <date dateStyle="short" timeStyle="short"/>
         </onSubmitValidater>
       </messageDateField>

       <messageDateField prompt="Patterned:" name="pattern">
         <onSubmitValidater>
           <date pattern="yyyy/MM/dd"/>
         </onSubmitValidater>
       </messageDateField>
     </contents>
   </tableLayout>

正規表現

UIXでは、<regExp>要素またはRegExpValidaterクラスによる検証において、正規表現の使用もサポートしています。 JavaScriptで使用する構文をサポートしています。 このヘルプでは完全な構文を説明しませんが、必要な場合はJavaScriptのリファレンスを参照してください。社会保障番号を入力する例を次に示します(実際の番号は入力しないでください)。

   <messageTextInput prompt="SSN:" name="ssn" text="123-45-6789">
     <onSubmitValidater>
       <regExp pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
     </onSubmitValidater>
   </messageTextInput>

検証をオフにする

ページの一部のボタンでのみユーザーのデータを検証し、その他のボタンでは検証しないという場合がしばしばあります。 たとえば、「Back」ボタンでは結果は使用されないため、ユーザーに正しい結果の入力を求めることはありません。 コンポーネントごとに検証をオフにするには、unvalidated属性(setUnvalidated()メソッド)を使用します。 ここで正規表現の例を再び示します。ここでは2つのボタンが存在し、そのうちの1つでは検証は実行されないようになっています。 無効な社会保障番号を入力してください。「Submit」ボタンでは検証が実行されますが、「Back」ボタンでは実行されません。

   <messageTextInput prompt="SSN:" name="ssn" text="123-45-6789">
     <onSubmitValidater>
       <regExp pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
     </onSubmitValidater>
   </messageTextInput>
   <submitButton text="Submit"/>
   <submitButton text="Back" unvalidated="true"/>

カスタムのフォーム送信

エラー処理のための強力なシステムがUIXフレームワークで提供されていますが、追加処理の実行のため開発者がこのシステムをオーバーライドすることが必要な場合があります。 たとえば、リンクがクリックされた際に実行される特別なJavaScriptの処理をUIXの検証システムが通常実行する処理に追加する、またはそのかわりとして追加する場合などがそうです。

このため、UIXでは開発者がコールできるクライアント側のJavaScript関数をいくつか用意しています。 関数は、UIXで提供されるライブラリに実装されています。 これらのファイルにある数多くの関数はUIXで内部的に使用するもので、開発者が直接コールすることはできません。このような関数名には接頭辞としてアンダースコア(_)が付いています。 これらの関数の動作および引数は、リリースによって変更される可能性があるため、編集は避けてください。

ただし、特別なフォーム送信の動作を実装する場合に使用できる関数が1つあります。submitForm()関数です。 この関数は、UIX Componentsのレンダラで使用するものと同一です。この関数を使用すると、開発者はUIXが実装しているクライアント側の検証のフレームワークにアクセスできます。

UIXページでフォーム送信をカスタムで行う場合は、フォームのDOM要素を直接送信するかわりにこのメソッドをコールしてください。 次のパラメータを指定します。

この関数は、フォームが実際に送信された場合にtrueを返し、エラーや検証の失敗など、なんらかの理由でフォームが送信されなかった場合にfalseを返します。

UIXのデプロイ用ライブラリでは、これらの関数を含むJavaScriptファイルはパフォーマンスの向上のために縮小されています。 したがって、コメントを含むコピーおよびドキュメントを参照するには、UIXバージョンのソース・リリースを調べ、oracle.cabo.ui.jsLibsディレクトリを参照してください。