IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  XML | Architecture | Java technology  >

XML の構文解析でのエラーを処理する

XML の構文解析でのエラーを SAX を使って処理する

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 中級

Brett D. McLaughlin, Sr., Author and Editor, O'Reilly Media, Inc.

2008年 7月 22日

JAXP や JAXB、JAX-WS など、Java™ 言語の API の中でも比較的新しい API を使うと XML の構文解析が容易に行えるため、XML の構文解析は Java プログラミングの基礎を成す部分となりました。しかし上位レベルの API で抽象化が行われたことに伴い、明らかにパーサーと XML データとの間でのきめ細かなやり取りの制御ができなくなりました。その結果、概してエラーが起こりやすくなり、もっと悪い場合には、ごく小さな問題が発生した場合でさえ構文解析が完全に停止してしまいます。幸い、SAX (Simple API for XML) を使うことで相変わらず容易にエラーを処理することができ、また、たとえ SAX を直接使わない場合であっても SAX の機構を利用することができます。

Java 言語の API の中でも比較的新しい API (JAXP や JAXB、JAX-WS、その他) を使うと XML の構文解析を容易に行えるため、XML の構文解析は今や Java プログラミングの基礎を成す部分となりました。しかし上位レベルの API で抽象化が行われたことが原因で、パーサーと XML データとの間のやり取りをきめ細かく制御できなくなると、問題が発生する可能性があります。この記事では、そうしたエラーを処理する上で SAX (Simple API for XML) がどれほど使いやすい機構であるか、また SAX を直接使用しない場合であっても SAX の機構を利用できることを説明します。

プログラムの強制終了はエラー処理ではありません

頻繁に使用する頭字語
  • API: Application Programming Interface
  • DOM: Document Object Model
  • JAXB: Java Architecture for XML Binding
  • JAXP: Java API for XML Processing
  • JAX-WS: Java API for XML Web Services
  • SAX: Simple API for XML
  • URI: Uniform Resource Identifier
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

すべてのアプリケーション・プログラムは、何よりもアプリケーションのユーザーのためのものです。そのアプリケーションが vi であれ emacs であれ、あるいは DreamWeaver® や Adobe® Photoshop® であれ、アプリケーションをどのように構築するかに関する決定は、他のアプリケーションでの経験がかなり参考になっています。そうしたことを考えると、最近のアプリケーション (特に Web アプリケーション) におけるエラー処理の大部分が、数字や (どんな辞書にも載っていない) 奇妙な文字で構成された、役に立たない画面を表示するだけであることは驚くにはあたりません。運が良ければ、いくらか整えられたフォントでお詫びの文字が表示される程度です。これは、アプリケーションでの問題処理の方法としては非常にお粗末です。

そしてアプリケーションではエラーが起こるものだという自明の事実と組み合わさると、状況はもっと悪くなります。例えば、皆さんが Eclipse で異なるクラスパスを使って同時にビルドを行うと、思いがけず Eclipse が強制終了されてしまうように、皆さんがプログラミングを行ったアプリケーションのユーザーは、決してクリーンアップされることのないスレッドを何とかして取り除く羽目になったり、適切なリクエスト変数にデータを入れられないサーブレットに突き当たったり、あるいは大量の接続が開かれることで MySQL® データベースに相当な負荷をかけることになったりします。

そして XML に関してユーザーがよくやる問題としては、不適切なデータをフィールドに詰め込んだり、あるいは無効なデータのままで妥当性検証を通そうとしたりすることが知られています。別の会社で作成された XML をプログラムで利用する場合には、さらにエラーの可能性が高くなります。その別の会社のプログラマーは、データ・フォーマットが詳細まですべて適切になるように、皆さんと同様遅くまで働いており、皆さんはその会社を信頼しています。こうした場合に (こうした場合と言っても非常に範囲は広く、また多様です)、よく意味のわからない例外をスローして XML の構文解析が停止してしまうのです。しかし次のようにすべてを単純に小さなブロックの中にラップしてしまう方法は、エラー処理として受け入れられるものではありません。

try {
  // some interesting and complex XML processing code
} catch (Exception e) {
  System.err.println(e.getMessage());
}

このようなコードでエラーを処理しようとしても、ユーザーを苛立たせ、皆さんの上司を怒らせるだけです。そして場合によると、CEO が最大の顧客から怒りの E メールを受信することとなり、その問題の追跡に遅くまで働かなければならない皆さんの周りの人達全員の怒りを買うかもしれません。

何よりも、実際にエラーを処理するコードを作成する必要があります。catch ブロックに System.err.println() の 1 文を含めることでエラーを処理する方法は、非常に広い意味ではエラー処理と見なせるかもしれませんが、まったくお粗末な方法です。エラー処理のコードは、単にエラーを報告する以上のことをする必要があります。きちんとしたエラー処理は次のようなものでなければなりません。

  • ユーザー・フレンドリーであること
  • (必要な場合以外) ユーザーの邪魔をしないこと
  • 有用な情報を含んでいること

エラー処理はユーザー・フレンドリーであること

エラー処理のコードは、何よりもアプリケーションのユーザーのためのものです。実際、プログラムに含まれるすべてのものは、突き詰めるとユーザーのためのものです。例えば皆さん自身がデバッグ用に挿入する文でさえも、何が起きているかを皆さんが知る上で役立ち、それによって機能を修正することができますが・・・、結局のところその機能もユーザーのためのものです。エラー処理のコードもそれと違いはありません。

もちろん、「ユーザー」という言葉は、利用者が直接使用する部分のアプリケーションのコードを作成するわけではない場合には特に、さまざまな意味を持ちます。例えばそのアプリケーションが、皆さんの会社と銀行との間で金融データを送信するためのバックエンド・システムの場合には、ユーザーはおそらく皆さんの会社または銀行の内部のグループでしょう。そのコードが、別のグループが使うための単なる基礎となるコードである場合には、その別のグループが皆さんのユーザーです。そこで最初にしなければならないことは、誰がユーザーなのかを明らかにすることです。

皆さんにとってのユーザーがニュージャージー州のコンピューター・ユーザーなのか、3 階にいる Web 開発者達なのか、あるいはニューヨーク証券取引所の会長なのかがわかれば、そのユーザー (あるいはユーザー・クラス) が使いやすいコードを作成することができます。利用者に対して、プログラミング用語を含まず理解しやすいエラー・メッセージを提供する必要があります。Web 開発者に対しては、皆さんの部門またはシステム管理グループの連絡先情報を提供する必要があるかもしれません。銀行の CEO に対しては・・・、エラー処理によって彼らを妨害しないことが重要です。実際、エラー・メッセージをあれこれ考える以前に、必ずしもすべてのエラー処理コードがエラーをレポートする必要はない、と考えた方が適切です。

エラー処理は必要な場合以外にユーザーの邪魔をしてはいけません

皆さんが車で出勤する途中、この先のハイウェイが大きな建設プロジェクトのために通行できなくなっている場合に、車を路肩に寄せ、エンジンを切り、がっくりと首を垂れ、頭の中でミーティングのレジュメを練り直す、といったことはしないでしょう。そんなことをするのは馬鹿げています。そんなことをするよりも、ハイウェイの次の出口から出て、別のルートを行けないかを考えるでしょう。そうすることで会社に着くのが数分遅れるかもしれず、誰かに電話をかけ、例の午前 8 時のミーティングに遅れることを伝える必要があるかもしれません。そうしたとしても、建設工事という問題にも関わらず、会社に着くことができます。

最高のエラー処理は、皆さんが問題に直面した場合に取る行動とまったく同じような動作をします。つまり問題を回避するための方法を見つけようと試みるのです。強制終了することになったり、「申し訳ございません。あなたは不運でした。」といったメッセージを表示したり、あるいはスタック・トレースを出力したりすることは、エラー処理ではありません。それはエラー・レポートです。プログラマーとしての皆さんの仕事は、たとえ建設工事が行われていてもユーザーが作業を行えるように、全力で努力することです。

これは XML の世界では、誤っている可能性のあるデータを取り上げ、それを何とか処理しようとすることを意味します。プログラム全体が強制終了され、停止するのでなければ、実際に、ある種の軽微なエラーを無視したり、メッセージのログをファイルに記録したりできる場合があります。また、プログラムのユーザー (先ほど説明したように、ユーザーは現実の人間ではない、別のプログラムかもしれません) に対して、さらに情報を要求したり、あるいは別の情報を要求したりする必要がある場合もあるかもしれません。こういった場合には処理を続け、先に進むように試みます。

本当に壊滅的な状況でない限り、ユーザーがしている作業に全面的に割り込むべきではありません。もし、ファイルがまったく見つからない、あるいは回復できないほどデータが破損している、あるいは必須の要素が XML 文書の中にない、といった場合であれば、ユーザーの作業に割り込んでかまいません。これはちょうど、4 本または 5 本のハイウェイすべてに水があふれており、また裏道も完全に冠水している状況と同じです。言い換えれば、本当に大変な状況でない限り停止してはならないのです。

こうした場合に SAX が非常に役立ちます。SAX を利用することで、不適切である可能性のあるデータやエラー条件を、プログラムが処理を停止する前に受信することができるため、適切な修正を行うことができます。

エラー処理は有用な情報を提供する必要があります

エラーが発生した場合にどのようなことを行うにせよ、有用な情報を提供する必要があります。その情報がプログラムのユーザーが元々必要としていたもの、要求したもの、あるいは作成しようとしたものであれば最高です。スマートな形での回復ができず、処理を変更する必要がある場合には、何が起きているのかを示す必要があります。完全に処理をアボートしなければならないという稀な状況では、そうした状況においても役に立つ情報を提供する必要があります。

しかしここで重要なことは、ユーザーを知ることです。もしプログラムが、他の処理コンポーネントに呼び出される BtoB のコンポーネントである場合には、ログ・ファイルのスタック・トレースや技術的なエラー・メッセージが最適かもしれません。そうした情報によって、そのプログラムを使って処理を行っているプログラマーに対して詳細な情報を与えることができます。一方、プログラムが直接顧客の目に触れる Web アプリケーションの一部である場合には、スタック・トレースやエラー・コードを表示するわけにはいきません。そうした情報の代わりに、人間が理解できるメッセージ (「プログラマーが理解できるメッセージ」とは同じではありません) を提供する必要があり、またさらなるサポートが必要な場合の連絡先情報も同時に提供する必要があります。それが不可能な場合や、そうした方法がわからない場合には、有用な情報を含んだ例外を必ず作成し、呼び出し側のプログラムが (おそらく) それに従って賢明な判断ができるようにします。

大部分の XML 処理のベースには SAX があります

XML ベースのエラー処理を適切に行うための鍵は、SAX (Simple API for XML) API と非常に関係しています。これは元々 SAX が他の API より優れているためではなく、SAX が特にエラー処理に適しているからでもありません。なぜ SAX が鍵なのかと言えば、単にほとんどすべての XML 処理が何らかのレベルで SAX に関係しているからです。

この理由は非常に単純です。SAX は驚くほど高速であり、また長年使われています。XML は非常に扱いやすいものですが、多くの面で、直感的な言語ではありません。XML にはたくさんの構成体や変わった構文があり、構文解析は簡単ではありません。パーサーやプロセッサーのベンダーの大部分にとって、テキスト・データから要素や名前空間、実体参照に至るまでの XML を処理するためにカスタムのパーサー API を作成することなど、まったく考えたくないというほどではないにしても、気が遠くなるような作業です。そうしたベンダーは (さらには API を作成する人達も)、代わりに SAX を使います。SAX は既に十分適切に動作するからです (多くの面で SAX はそれほど優れたものではありませんが、XML の構文解析に関しては優れています)。そのため、エラーを SAX で処理する方法を知っていれば、ほとんどすべての XML 処理 API でのエラー処理の方法を知っていることになります。

では、SAX の基本部分の一部を (コード例を示しながら) 調べてみましょう。最初のステップは、皆さんが使用している何らかの API から出発して SAX にたどり着くことです。

SAX による構文解析では (当然ながら) SAX を使います

もし皆さんが既に SAX を扱った経験が豊富にあるならば、SAX を使って作業するために必要なことは何もありません。皆さんは既にこの API を使っているからです。具体的には、皆さんはおそらくリスト 1 のようなコードを作成しているはずです (このコードは SAX を使って XML 文書を構文解析するプログラムのフラグメントです)。


リスト 1. SAX を使って XML 文書を構文解析する

XMLReader reader = XMLReaderFactory.createXMLReader();
ContentHandler handler = new PrintingContentHandler();
reader.setContentHandler(handler);
reader.parse(new InputSource(new FileReader(xmlFilename)));

このようなコードを手にしているとすると、XML 処理でのエラー処理はもう手の届くところまで来ています。実際、(SAX あるいは別の API を使って) XMLReader の実装を利用する方法を既に知っているのであれば、皆さんはエラーをスムーズに処理するための最初の大きな第一歩を既に踏み出しているのです。

DOM も通常は SAX を使います

大部分の DOM パーサーは、実際には DOM ツリーを構築する SAX パーサーです。DOM API そのものは、そのベースにある SAX パーサーを公開しませんが、それはそもそも DOM API が主に DOM ツリーを扱うためのものであり、そのツリー構造自体を作成するためのものではないからです。DOM を実装する大部分のパーサーは、少なくともベースにある SAX パーサーにアクセスするためのベンダー固有の方法は提供しています。

例えば Xerces では、DOM ツリーを作成するために使われるクラスは org.apache.xerces.parsers.DOMParser というクラスです。このオブジェクトの parse() メソッドを呼び出し、そこから Document オブジェクトの形で DOM ツリーを取得することができます。このコード・フラグメントをリスト 2 に示します。


リスト 2. SAX を使って DOM ツリーを作成する

DOMParser parser = new DOMParser();
parser.parse(new InputSource(xmlFilename));
Document doc = parser.getDocument();

一見すると、このコードは SAX での構文解析プロセスに似ています。実際、InputSource クラス (このクラスは文書を DOMParser インスタンスに渡すためのいくつかの方法のうちの 1 つにすぎません) は SAX の構成体です。しかし、SAX と DOM の構文解析は見た目だけでなく、さらに似ている点があります。例えば Xerces-J API のドキュメントを調べてみたり、ソース・コードを追ってみたりすると、DOMParserXerces の別のクラス (org.apache.xerces.framework.XMLParser) を継承していることがわかります。そしてこのクラスが、DOMParser のベースになっているだけでなく、Xerces に含まれる SAX 構文解析クラス SAXParser のベースにもなっていることがわかります。

この説明は非常に混乱しやすい内容かもしれませんが、要点は次のとおりです。つまり Xerces-J の中で SAX による構文解析のベースとなっている XML 構文解析実装は、DOM による構文解析クラスのベースにもなっています。そのため、DOMParser クラスから逆にたどっても SAX の XMLReader には行き着きませんが、Xerces-J の XMLReader の実装の基礎となっているコードが DOMParser の基礎でもあるのです。そしてそのため、DOMParser にはいくつか非常に貴重なメソッドがあります。

  • setEntityResolver(): このメソッドは XML 文書の中のエンティティーを処理するために EntityResolver という SAX 構成体を引数に取ります。
  • setFeature(): これも元々は SAX のメソッドであり、このメソッドを利用することで、DOM、あるいは SAX に関連するパーサー機能を設定することができます。
  • setErrorHandler(): これはエラー処理の際にキーとなるメソッドです。このメソッドは SAX の ErrorHandler の実装を受け付けるため、エラーをインターセプトして、エラーに応答することができます。

SAX の XMLReader 実装に直接アクセスすることはできませんが、この記事で説明するエラー処理の核心である、SAX 特有のメソッドを利用することはできます。

JAXP も SAX を公開しています

ほとんどの開発者は、もはや SAX や DOM を直接操作することはなく、代わりに JAXP API (Java API for XML Processing) を使います。リスト 3 は SAX による構文解析に JAXP を使用したコード・フラグメントを示しています。


リスト 3. JAXP を使った SAX 構文解析

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(false);
SAXParser parser = factory.newSAXParser();
parser.parse(new File(args[0]), new MyHandler());

このコードはおなじみでしょう。クラス名は少し異なり、またいくつかのオプションが設定されていますが、これはリスト 1 に示した SAX による構文解析とあまり違いません。しかし、JAXP のこうした構成体を使うことで、より一層 SAX に近づくことができます。具体的には、JAXP の SAXParser クラスは getXMLReader() というメソッドを提供、このメソッドがベースとなる SAX の XMLReader 実装を返します。

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(false);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();

すると、単純な SAX アプリケーション (当然、エラー・ハンドラーの設定を含んでいます) の場合と同じように XMLReader インスタンスを扱うことができます。

DOM による構文解析に JAXP を使う場合にも同じことが言えます。この場合には、さらに使いやすい、DOM から SAX への API の階層化が行われます。リスト 4 は、DOM による通常の構文解析手順を示すコード・フラグメントです。


リスト 4. JAXP を使った DOM 構文解析

DocumentBuilderFactory factory = 
  DocumentBuilderFactory.newInstance();

factory.setValidating(true);
factory.setNamespaceAware(false);

DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(args[0]));

これは明らかに DOM ベースの構文解析ですが、とても簡単に SAX に戻ることができます。実際、DOM から SAX に変換するためのコードのことを心配するよりも、もっと基本的な事実を考えてみてください。つまり JAXP を使用しているのであれば、すでに SAX のクラスを使用しているのです (リスト 3)。そのため、JAXP を使用する場合には DOM から SAX への変換は気にする必要もありません (DOM から SAX への変換は可能であり、「参考文献」に挙げた developerWorks のヒントの中で詳細に説明されています)。DOMBuilder の代わりに SAXParser パーサーを使うための数行のコードを書き直すだけで、いつでも SAX 特有のエラー処理を追加することができます。

JAXB のような上位レベルの API も SAX を使います

SAX が広く使われていることが、そろそろわかってきたと思います。SAX は DOM の処理にも JAXP による構文解析にも使われています。そうすると、DOM を使う上位レベルの API、あるいは JAXP をモデルにする上位レベルの API も SAX を使うものと考えるのは当然です。こうした API に関してはもう少し作業が必要かもしれませんが、通常はこれらの API を使う場合にも SAX のベースの部分にアクセスすることができます。

よく使われるもう 1 つの API として、このタイプに当てはまるものが JAXB です (JAXB は Sun の Java API for XML Binding であり、バージョンによっては Java Architecture for XML Binding のこともあります)。JAXB によって、XML 文書から Java のオブジェクト・モデルに変換することができ、またそのオブジェクト・モデルを XML に逆変換することもできます。XML から Java への変換プロセスはアンマーシャリングと呼ばれ、基本的に次の 2 つのステップで構成されています。

  1. XML 文書を構文解析します (要素や属性の名前と文書中のデータの両方、また要素や属性、データの間の関係は保持します)
  2. そのデータを、Java オブジェクト・モデルのメンバー変数およびインスタンスに変換します。

構文解析が関係すると、エラーが発生する可能性があり、そしてほぼ確実に SAX が関係します。これは JAXB にも該当し、JAXB では若干間接的ですが、SAX の XMLReader を使います。リスト 5 は、アンマーシャリングに使われるコア・クラスである Unmarshaller を使って SAX ベースの構文解析プロセスをきめ細かく制御する方法を示しています。


リスト 5. JAXB から SAX の XMLReader にアクセスする

JAXBContext context = JAXBContext.newInstance("dw.ibm");
Unmarshaller unmarshaller = context.createUnmarshaller();

// Get the lower level handler from the Unmarshaller
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

// Now use JAXP to get a SAX parser
SAXParserFactory factory = SAXParserFactory.newInstance();

// Set options on the factory, using standard JAXP calls
factory.setNamespaceAware(true);
 
XMLReader reader = factory.newSAXParser().getXMLReader();

// We can use the handler from the unmarshaller as the content handler
reader.setContentHandler(unmarshallerHandler);

// Now parse, using the unmarshalling handler from JAXB
reader.parse(new InputSource(new FileReader(xmlDocument)));

MyCustomObject topObject = (MyCustomObject)unmarshallerHandler.getResult();

これは今までの例に比べると複雑ですが、それでもかなり単純です。ここでの大きな違いは、Unmarshaller.unmarshal() メソッドを介して JAXB フレームワークを直接使うのではなく、中にすべてのアンマーシャリング・コードを持つハンドラーを JAXB から取得します。次に示す呼び出しでは、この処理を行っています。

// Get the lower level handler from the Unmarshaller
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

そして返されたハンドラーをコンテンツ処理の手段として使うことで、SAX による構文解析は手動で処理されます。ここでカスタムのエラー処理コードを挿入します (次のセクションでは、これを詳細に説明します)。

いよいよ構文解析が行われ、次の最後の呼び出しによって JAXB フレームワークに戻ります。

MyCustomObject topObject = 
  (MyCustomObject)unmarshallerHandler.getResult();

この結果として、アンマーシャリングが行われる間に起こることを正確にきめ細かく制御できるようになります。これはつまり、皆さんがアンマーシャリングに手を加えることで、エラーを回避したりスムーズにエラーをレポートしたりすることができ、またアプリケーションのユーザーに対して、ずっと適切なエクスペリエンスを提供できるということです。

そして他の API も・・・

当然のことですが、すべての XML API を取り上げ、その API の最上位レベルの使い方から SAX によるベースの使い方に至るまでの詳細を説明することは不可能です。しかし皆さんは、どのようなパターンを探せばよいのかという点について、十分適切な考えを持っているはずです。つまりその API のドキュメンテーションを調べ、org.xml.sax.XMLReader を継承または実装するクラス、あるいは引数として SAX の ErrorHandler (org.xml.sax パッケージの中にあります) を取るクラスを探せばよいのです。さらには Google を使って単に "[API 名] SAX" あるいは "[API 名] ErrorHandler" のように検索する方法もあります。自分が使用している API と SAX との間の全容を明らかにすることがいかに簡単であるかを知り、きっと皆さんは驚くでしょう。

エラーを処理するために ErrorHandler インターフェースを使う

SAX でのエラー処理の中心となる org.xml.sax.ErrorHandler インターフェースは、3 つのメソッドを持つ単純なインターフェースで、これを実装することであらゆる種類のエラーを処理することができます。また簡単にエラーを SAX に登録できるため、数行のコードだけでエラー処理を行うことができます。

3 つのエラー・タイプに対する 3 つのメソッド

ErrorHandler のコードは信じられないほど単純です。リスト 6 はこのコードの全体を示しています (コメントは省略してあります)。


リスト 6. SAX の ErrorHandler インターフェース

package org.xml.sax;

public interface ErrorHandler {
  public void warning(SAXParseException exception) throws Exception {
  }

  public void error(SAXParseException exception) throws Exception {
  }

  public void fatalError(SAXParseException exception) throws Exception {
  }
}

これだけです。構文解析でのすべての問題は、これらの 3 つのメソッドのうちの 1 つにレポートされます。そして独自の ErrorHandler を実装するだけで、カスタムのエラー処理を始めることができます。

ワーニングを無視する

SAX のワーニングは、XML 1.0 勧告の定義にあるとおり、「エラーまたは致命的なエラーとは見なされないすべての問題」と定義されています。これは非常に曖昧ですが、これをもっとわかりやすく言えば、「ワーニングは、パーサーが構文解析を継続することを妨げない問題」ということです。ワーニングに対する通常のデフォルト・アクションとしては、(ワーニングは構文解析や処理を妨げるものではないため) ワーニングを完全に無視するか、あるいは有用な情報を含んだメッセージを表示して動作を継続します。

コメントに関する問題や、予想外の値であっても処理を継続できるレベルのもの、また想定すらしなかった小さな問題のほとんどが、ワーニングとして表示されます。ワーニングとは何かを気にしすぎる前に、先ほど触れた、適切なエラー処理の原則を思い出してください。

  • エラー処理はユーザー・フレンドリーであること
  • エラー処理は (必要な場合以外) ユーザーの邪魔をしないこと
  • エラー処理は有用な情報を含んでいること

これらのいずれか (あるいはすべて) をワーニングに適用してみると、ワーニングを無視するというデフォルト・アクションはおそらく最善だろう、ということがすぐにわかるはずです。デバッグ用アプリケーションやログ・ファイルにメッセージを記録したいのであれば、それは適切なことです。ただしそうした場合にも、明らかに重要ではないことのために貴重な処理時間を費やしていることになります。warning() メソッドの最高の実装は、リスト 7 のようなものです。


リスト 7. warning() メソッドが使われている ErrorHandler の実装

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class DefaultErrorHandler implements ErrorHandler {
public void warning(SAXParseException exception) 
    throws SAXException {

    // Do nothing
  }

  public void error(SAXParseException exception) throws SAXException {
    // to be filled in later
  }

  public void fatalError(SAXParseException exception) 
    throws SAXException {

    // to be filled in later
  }
}

エラーからの回復を試みる

エラーは XML の問題の中で最も処理が難しいものです。ワーニングは無視することも、単にログとして記録しておくだけにすることもできます。そして致命的なエラー (これについてはこの後すぐに学びます) では構文解析を停止して、一連の本格的なアクションを取る必要があります。エラー (致命的なエラーとは違います) は、ある種、漠然とした中間的な問題です。SAX でのエラーは XML 1.0 仕様でのエラーに対応しますが、XML 1.0 仕様でのエラーの記述は、以下に示すように非常に曖昧です。

この仕様の規則に対する違反であり、結果は定まらない。特に指定がない限り、must (しなければならない)、required (必須である)、must not (してはならない)、shall (するものとする)、shall not (しないものとする) というキーワードの 1 つによって示される、この仕様の規定を守らなかった場合はエラーである。この仕様に準拠するソフトウェアはエラーを検出してレポートしてもよく、そのエラーから回復してもよい。

残念ながら、エラーが何によって起こるのかに関して、この文章はほとんど何も言っていません。この中で唯一、特に注目に値するものは、「回復」という言葉です。言い換えると、プログラムはこの種のエラーによって構文解析や処理を停止しなければならないのではなく、この種のエラーから回復できるべきである、ということです。

実際にはエラーがレポートされるのは、内容に関するものであり、通常は予想外の、ただし XML 文書の構造や整形式性には関係しない何らかの問題が起きた場合です。そのため実際にエラーからわかることは、文書が不完全かもしれない、あるいは構文解析された文書のデータの一部が失われていたり、破損していたり、あるいは不正確であったりするもしれない、ということです。

回復に関しては、エラーがレポートされた時点までで対応できることは、ほとんどありません。SAX では先読みや後読みができないため、SAXParseException で過去のコンテキストを収集することは (できたとしても) 困難です。ただし幸いなことに、処理を停止する必要がありません。何ができるかを気にするのではなく、重要なのは「処理を停止する必要がない」ということです。ユーザー (それが顧客であれ他の開発者達であれ) は問題が起きたことを知る必要はなく、そしてほとんどの場合、彼らはプログラムから有用な結果を得ることができます。

では何をすればよいのでしょう。まず、おそらく情報をどこかにログとして記録する必要があります。問題のあるデータが得られたかもしれないとユーザーに知らせる必要はありませんが、起きたことに関する情報をどこかに記録する必要があります。System.outSystem.err は避ける必要がありますが、ファイルにログとして記録したり Log4J (「参考文献」にリンクがあります) のような API を使用したりする方法は、何が起きたのかを記録するための素晴らしい方法であり、それによってその問題の再発を防ぐことができます。

error() メッセージによってエラーを処理する際に取るとよい別のステップとして、そのアプリケーションのビジネス・ロジックに関する知識を使って賢明な判断をすることが挙げられます。例えばアプリケーションが、contact-info 要素の中にネストされた email という名前の要素の中に E メールを保存するとします。もし、error() メソッドによって問題が contact-info 要素または email 要素に関係していることがわかった場合には、その E メール・アドレスのテキスト・データが失われた可能性があります。その場合には呼び出し側のプログラムに対して、(邪魔にならない方法で) E メール・アドレスが失われた可能性があることを知らせることができます。顧客が直接使用するアプリケーションの場合で、ユーザー入力によって情報を得た場合には、簡単な Web フォームを使って E メールを再入力するようにユーザーに促すこともできます。

エラーの処理に関して説明する場合に最も困難な点は、具体的な例を挙げることです。そうした例はビジネス・ロジックとビジネス・ドメインに完全に依存します。そのため、error() の実装方法に関して皆さん自身が独自の感覚をつかむ必要があります。重要なことは、プログラムを強制終了させたり例外をスローしたりして処理を停止しないことです。エラーはその定義から、(致命的なエラーとは異なり) 処理を停止させてはならないことを忘れないでください。

致命的なエラーをレポートする

SAX での問題のタイプの最後は致命的なエラーです。致命的なエラーは XML 1.0 仕様の中で定義されており、必ず構文解析を妨害し、継続不能にする問題を言います。最も一般的な例は、整形式ではない文書です。例えば firstName 要素が開始されているのに終了されていない、あるいは開始の不等号括弧がない、といった場合です。パーサーはこうした状況から回復することはできません。なぜなら文書の構造全体に問題があるからです。SAX API のドキュメントではさらに、致命的なエラーがレポートされた場合、アプリケーションはその文書を使用不能と見なさなければならない、とまで言っています。従って致命的なエラーは非常に重大な問題なのです。

致命的なエラーは、一見、程度によっていくつかの段階に分けられるように思えるかもしれません。例えば </firstName> がない場合、経験に基づいて </firstName> に非常に近い何かを見つけられるかもしれません。もしかすると「girstName」という変な名前の終了要素があるかもしれませんが、これはおそらくタイプミスでしょう。そのため多くの致命的なエラーは、人間の目から見ると非常に簡単に修正できそうに思えます。

しかし問題は、SAX が読み取り専用のシーケンシャル・パーサーであることです。SAX は先読みすることができず、また既に読んだものを保持しません。そのため、先読みしてタイプミスらしいものを探すことや、さらにはたった今読み取ったばかりのものを見返すことすら、事実上不可能です。既に読み取ったものを保持するためにはメモリー内バッファーに相当するものを構築するハンドラーを作成する必要がありますが、そうすると先読みするためのコードも必要になります。そのためのオーバーヘッドは膨大であり、その上なお、元々の文書作成者の意図を推測しなければなりません。したがって致命的なエラーは、適切なエラー処理の 2 番目の原則「エラー処理は必要な場合以外、ユーザーの邪魔をしてはなりません」の、必要な場合に該当してしまいます。

以上のことから、致命的なエラーの処理を適切に行うために残されているのは、エラー処理の 3 番目の原則「エラー処理は有用な情報を含んでいる必要があります」です。純然たる邪魔になるのであれば、同時に有用な情報を提供する必要があります。「してはならない」例を次に示します (これはコード・リストにもなっていないので、これが適切なプラクティスだと勘違いされることもないでしょう)。

  public void fatalError(SAXParseException exception) 
    throws SAXException {

    // typical, but terrible, error handling

    // Bring things to a crashing halt
    System.out.println("**Parsing Fatal Error**" + "\n" +
                       "  Line:    " + 
                          exception.getLineNumber() + "\n" +
                       "  URI:     " + 
                          exception.getSystemId() + "\n" +
                       "  Message: " + 
                          exception.getMessage());        
    throw new SAXException("Fatal Error encountered");
  }

この内容はプログラマーにとっては有用な情報かもしれませんが、それ以外の人にとっては意味不明です。皆さんのアプリケーションはこの例とは違うので、こうした例外の処理に関して独自の方法を考えるでしょう。しかし呼び出し側のプログラムに対しては SAXException の形で情報を渡す必要があるため、やはり制約があります。例えば、おそらくユーザー・エクスペリエンスを直接制御する必要はありませんが、ユーザーにフィードバックするために使用する情報を呼び出し側のクラスに渡す必要があります。

カスタム例外クラスによって有用なエラーを送信する

意味のあるエラー情報をプログラムに提供する最も簡単な方法は、SAXException に渡すことのできる独自の例外を作成する方法です。SAXExceptionErrorHandler の 3 つのメソッドすべてがスローできる例外であり、また基本的に、呼び出し側のプログラムと通信するための唯一の方法でもあります。デフォルトでは、SAXException には以下の 3 つのメソッドしか用意されていません。

  • getException(): このメソッドは Exception を返します。Exception によって、後で例外をネストして取得することができます。この中に独自のカスタム例外を入れ、呼び出し側のプログラムに情報を渡すことができます。
  • getMessage(): 人間が理解できるメッセージをこの中に入れることができます。適切なエラー情報を送信するための方法としては非常に単純です。
  • getString(): これはスーパークラスの getString() をオーバーライドして埋め込みの例外情報を出力します。

例えば、Web プログラムで使用する XML 処理コンポーネントを作成しているとしましょう。そうした場合にはリスト 8 のようなカスタム・クラスを定義します。


リスト 8. Web アプリケーションにレポートするためのカスタム例外クラス

public class WebException extends Exception {
  private int httpStatusCode = 400;
  private String redirectURL;

  public WebException(String message, int httpStatusCode, String redirectURL) {
    super(message);
    this.httpStatusCode = httpStatusCode;
    this.redirectURL = redirectURL;
  }

  public WebException(String message, Throwable cause, 
                      int httpStatusCode, String redirectURL) {
    super(message, cause);
    this.httpStatusCode = httpStatusCode;
    this.redirectURL = redirectURL;
  }

  public int getHttpStatusCode() {
    return httpStatusCode;
  }

  public String getRedirectURL() {
    return redirectURL;
  }
}

これは非常に基本的なものですが、Web 特有の情報がいくつかあります (レポート用の HTTP ステータス・コード (404 や 401 など) と、ユーザーをリダイレクトするための URL)。そこで、これをエラー処理に使うことができます。この詳細をリスト 9 に示します。


リスト 9. ネストされたカスタム例外を使って例外をレポートする

  public void fatalError(SAXParseException exception) throws SAXException {
    // Report through a Web-specific exception
    WebException webException = new WebException("There was a problem converting your " +
      "response into a format our server could read. Please contact our customer " +
      "service team at 1-800-555-0972, and we'd love to help you in person.", 
      exception, 406, "/errorPages/xmlError.php?reason=" + exception.getMessage());
    throw new SAXException(webException);
  }

これはそれほど複雑なコードではありませんが、次のようにいくつか非常に重要なことを行います。

  1. 返されるエラー・メッセージは具体的でユーザーが理解できる内容であり、有用な情報を含んでいます。またプログラマーではない普通の人を対象にしています。
  2. ステータス・コードやエラー・ページなど、アプリケーション特有の情報が呼び出し側のアプリケーション用に提供されています。こうすることで、何をする必要があるかが不明ではなくなり、また XML 処理コンポーネントを呼び出すコードは、問題がある場合に (意味不明のスタック・トレースではなく) 有用な情報を期待することができます。
  3. リダイレクトされるページにも有用な情報が含まれています。このページに含まれるエラー・メッセージは、ログとして記録しておくこともできますし、さらには後でプログラマーが対処するためのバグ・レポートを自動生成する際に使うこともできます。

ほんの数行のカスタム例外コード、そして fatalError() の実装方法を少し考えるだけで、これらをすべて実現することができます。

アプリケーションに合わせて例外を調整する

当然ですが、WebException は Web ベースではないアプリケーションには使えません。実際のところ、皆さん独自の Web ベースのアプリケーションにも適切ではないかもしれません。そうした場合に、対象とする課題や領域に関する専門知識が必要になります。つまり皆さんのアプリケーションにとって何が必要なのかを知る必要があります。

また、現在のユーザーについて、またどのような種類の情報を彼らに提供する必要があるのかについて、知る必要があります。たとえ下位レベルの処理コンポーネントを作成する場合であっても、問題が発生した場合に呼び出し側のコードが何を必要とするかを知っている必要があります。そして、皆さんが独自に作成したそのアプリケーション専用の例外を、エラー・メソッドの中で SAXException でラップし、発生した問題に関係する情報や有用な情報をすべて、そのカスタム例外によって渡します。

それ以外に必要な唯一のステップとして、顧客が直接使用するコンポーネントを作成しているのでない場合には、そのコードを利用する開発者に (電話や E メール、場合によっては紙による書類で) 連絡します。つまりそのコードをイントラネットの Java サーブレットを開発している人達が使う場合には、SAXException の中でカスタム例外が大量の有用な情報でラップされていることを伝えます。例外のソース・コードや基本的な資料を彼らに送り、また使い方に関する助言を与えます。ちょっとしたコミュニケーションによって、彼らが getMessage() を出力したり、彼ら自身がエラー処理でミスを犯したりするのを防ぐことができます。

SAXException を継承してはなりません

よくある間違いとして、カスタム例外を SAXException クラスに組み込む代わりに実際には SAXException を継承してしまうことがあります。例えば先ほどの例 (リスト 89) では、WebException は SAXException を継承することができ、fatalError() から WebException を直接スローすることができます。この方法は有効に思えますが、望ましくない小さな問題が起こります。

第 1 の問題として、SAXException を継承することによってカスタム例外が SAX に結びつけられます。すると、XML の構文解析や処理、SAX の機能をまったく持たない他のコンポーネントでカスタム例外を使いにくくなります。例外は汎用のまま、アプリケーションのどこでも使えるようにしておいた方がずっと適切です。そうしておくからこそ、関係のない別の例外を SAXException によって組み込めるのです。第 2 の、そして第 1 の問題と同じくらい重要な問題として、SAX を扱う既存のアプリケーションの多くは、組み込まれた例外情報を自動的に抽出するように既になっています。そのため、設計された目的どおりに SAXException を使うことで、コードが他の既存のコード・コンポーネントと適切に動作する可能性が高くなります。

ErrorHandler の実装を登録する

ErrorHandler の 3 つのメソッドが実装できると、そのエラー処理を使う準備がほとんど整ったことになります。残る唯一のステップは、構文解析プロセスにエラー・ハンドラーの実装を登録することです。これは非常に単純ながら重要なステップです。エラー処理のための素晴らしいメソッド・セットを作成しても、SAX パーサーにそのことを伝えなければ意味がありません。

XMLReader の setErrorHandler() を呼び出す

XML の API または処理レイヤーから XMLReader を取得できたら、作業は簡単です。XMLReader には、引数として ErrorHandler の実装を取る setErrorHandler() メソッドが用意されています。このメソッドをリスト 10 のように呼び出します。


リスト 10. XMLReader に対してエラー・ハンドラーを設定する

JAXBContext context = JAXBContext.newInstance("dw.ibm");
Unmarshaller unmarshaller = context.createUnmarshaller();

// Get the lower level handler from the Unmarshaller
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

// Now use JAXP to get a SAX parser
SAXParserFactory factory = SAXParserFactory.newInstance();

// Set options on the factory, using standard JAXP calls
factory.setNamespaceAware(true);
 
XMLReader reader = factory.newSAXParser().getXMLReader();

// We can use the handler from the unmarshaller as the content handler
reader.setContentHandler(unmarshallerHandler);

// Register a custom ErrorHandler implementation
reader.setErrorHandler(new MyJAXBErrorHandler("/logs/logfile.txt"));

// Now parse, using the unmarshalling handler from JAXB
reader.parse(new InputSource(new FileReader(xmlDocument)));

MyCustomObject topObject = (MyCustomObject)unmarshallerHandler.getResult();

リスト 10 では、上位レベルの API は (リスト 5 で最初に見た) JAXB です。XMLReader インスタンスが取得できると、setErrorHandler() によって新しいカスタムの ErrorHandler 実装が登録されます。この例では、カスタムのハンドラーは MyJAXBErrorHandler と呼ばれます。このハンドラーは、あるファイルまで行き、そこでエラーを引数としてログに記録します。それが終わると、構文解析が (parse() によって) 開始され、すべての問題は MyJAXBErrorHandler の中のメソッドに渡されます。

現在の ErrorHandler を getErrorHandler によってチェックすることができます

XMLReader がどんな ErrorHandler の実装を使用しているかを調べることもできます。単純に getErrorHandler() を呼び出すと、現在登録されている ErrorHandler の実装を取得することができます。そして、そのクラスに対して作業することもでき、あるいは単にそのクラスに関する情報を出力することもできます。簡単な例をリスト 11 に示します。


リスト 11. 現在のエラー・ハンドラーをチェックする

JAXBContext context = JAXBContext.newInstance("dw.ibm");
Unmarshaller unmarshaller = context.createUnmarshaller();

// Get the lower level handler from the Unmarshaller
UnmarshallerHandler unmarshallerHandler = 
  unmarshaller.getUnmarshallerHandler();

// Now use JAXP to get a SAX parser
SAXParserFactory factory = SAXParserFactory.newInstance();

// Set options on the factory, using standard JAXP calls
factory.setNamespaceAware(true);
 
XMLReader reader = factory.newSAXParser().getXMLReader();

// We can use the handler from the unmarshaller as the content handler
reader.setContentHandler(unmarshallerHandler);

// See what's handling errors right now
ErrorHandler handler = reader.getErrorHandler();
System.out.println("Error handler is currently: " + 
  handler.getClass().getName());

// Now parse, using the unmarshalling handler from JAXB
reader.parse(new InputSource(new FileReader(xmlDocument)));

MyCustomObject topObject = (MyCustomObject)unmarshallerHandler.getResult();

このクラスに関して何かができるわけではないため、この情報は参考程度の価値しかありません。とは言え、お気に入りの XML API がどのようにエラーを処理するかに関心がある場合には、この方法でそれを知ることができます。

まとめ

エラー処理はアプリケーション開発の最前線部分だと考える必要があります。実際、大部分のユーザーは、アプリケーションやサイトのどのコンポーネントよりも、プログラムのエラー、そしてそうしたエラーをどのようにして処理したのかをより強力に記憶していると言います。素晴らしい機能は素晴らしく、恐ろしいエラー処理は恐ろしいものです。そう書くと単純すぎるように思えるかもしれませんが、エラーを適切に処理するために、さらにはエラーがまったく起きないように 10 パーセント余分に時間をかけるだけでも、ユーザー・エクスペリエンスを劇的に改善することができます。

XML の構文解析と処理のエラーを扱う際に重要なことは、実はある特定の SAX インターフェースではなく、どのようにして XML 処理が進められているかを理解することです。SAX が大部分の XML 処理のベースとなっていることを理解できれば、適切なエラー処理にとって SAX が鍵であることがわかるはずです。今後 5 年間に XML 構文解析用の別の API によって SAX が置き換えられた場合には、皆さんが扱う範囲でその API について学ぶ必要があります。SAX の XMLReader を利用することは簡単ですが、このインターフェースを使ってどうすればよいかを理解することは簡単ではありません。実際、それがエラー処理の本当の鍵です。つまり対象としているシステムと、そのシステムの下位レベルを理解することが重要なのです。アセンブリー言語でプッシュしたりポップしたりする必要はありませんが、今日使われている XML 構文解析の API として SAX が鍵であることは知っている必要があります。

そうするとエラー処理は実装と実行の問題になります。SAX では、XMLReaderErrorHandler というインターフェースを使ってエラーに関する情報を受信します。そうしたエラーを即座に処理したり、呼び出し側のプログラムに渡したり、追加の情報と共にカスタムのオブジェクトの中にラップしたりすることもでき、あるいは皆さんや皆さんのアプリケーションにとって意味のあることを何でもすることができます。適切なエラー処理のための魔法の公式はありませんが、重要な原則はあります。それは、ユーザーを煩わしてはならない、というものです。それを実現できれば、皆さんは同僚や競争相手よりも十分先を行っていることになります。

エラーと正面から向き合い、エラーでユーザーを苦しめるのではなくエラーを活用することです。そうすることで苦情を減らすことができ、上司もユーザーも満足させることができます。エラーに対する興味深いソリューションを皆さんが発見したら、ぜひ私に知らせてください。そしてdeveloperWorks のオンライン・フォーラム (「参考文献」にリンクがあります) でエキスパートとの議論に加わり、皆さん自身のプログラムでどのようにして壊滅的状況を回避したかを教えてください。



参考文献

学ぶために
  • JAXPのすべて 第1回」には JAXP が詳細に説明されています。

  • XML 1.0 の仕様を読み、エラーと致命的なエラー、そしてどちらでもないもの (ワーニング) について学んでください。

  • ヒント: DOMからの変換」(Brett McLaughlin 著、developerWorks、2001年4月) では、DOM 構造を SAX と JDOM に変換し、DOM を使わないアプリケーションと通信する方法について解説しています。

  • この記事で説明した API に関して学んでください。まず SAX の Web サイトで Java のための SAX 2 を学び、次に W3C の Web サイトで DOM を学んでください。

  • XML および関連技術において IBM 認定技術者になる方法については、IBM XML certification を参照してください。

  • developerWorks の XML ゾーンは XML の技術ライブラリーとして、広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBM Redbooks などを用意しています。

  • developerWorks technical events and webcasts で最新情報を入手してください。

  • technology bookstore には、この記事や他の技術的な話題に関する本が豊富に取り揃えられています。

  • developerWorks podcasts では、ソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。


製品や技術を入手するために
  • Java 5、またはもっと新しい Java 6 を入手してください。Java プログラミングが初めての人は、これらのダウンロードから完全な JDK と共に JAXP を入手してください。

  • IBM ベースの素晴らしい API、Log4J を試してみてください。Log4J は現在はオープンソースであり、テキスト・ファイルからネットワーク・ソースまで、さまざまなソースに容易にログすることができます。ロギングは適切なエラー処理アプリケーションの重要部分です。

  • Java and XML, Third Edition』(Brett McLaughlin と Justin Edelson の共著、O'Reilly Media, Inc. 刊) は XML を最初から最後まで解説しており、その中には XML や XSL など、XML 関連のいくつかの仕様の詳細な情報も含まれています。

  • developerWorks から直接ダウンロードできる IBM trial software を利用して皆さんの次期プロジェクトを構築してください。これらの試用版には DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品が含まれています。


議論するために


著者について

Photo of Brett McLaughlin

Brett McLaughlin はノンフィクション作家としてベストセラーを書き、またいくつかの賞を受賞しています。彼が執筆したコンピューター・プログラミングやホーム・シアター、分析や設計に関する本は、これまで 10 万部以上売れています。彼は 10 年近く技術的な本を執筆し、編集し、そして製作してきており、ワープロの前に座っていることが好きですが、ギターを弾いたり、家で 2 人の息子を追いかけたり、彼の妻と Arrested Development (訳注: 米国で作成、放映されていたテレビ番組) の再放送を見て笑い転げることも好きです。彼の最新の本『Head First Object Oriented Analysis and Design』は 2007年の Jolt Technical Book award を受賞しています。彼の古典作『Java & XML』は、Java 言語での XML 技術の使い方に関する決定作の地位を保っています。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


Adobe、Adobe ロゴ、PostScript、および PostScript ロゴは、Adobe Systems Incorporated の米国およびその他の国における登録商標または商標です。 IBM、IBM ロゴ、ibm.com、DB2、developerWorks、Lotus、Rational、Tivoli、WebSphere、および pureXML はInternational Business Machines Corporation の米国およびその他の国における商標です。 Java およびすべての Java 関連の商標およびロゴは、Sun Microsystems, Inc. の米国およびその他の国における商標です。 他の会社名、製品名およびサービス名等はそれぞれ各社の商標です。

    日本IBMについて プライバシー お問い合わせ