JAXB その3

来源:互联网 发布:小公司网络需求调查表 编辑:程序博客网 时间:2024/06/01 10:33

2週に渡って,JAXBを使用したアンマーシャリング/マーシャリングを解説してきました。とはいうものの,そこで扱ったXMLドキュメントは,すべてファイルでした。

そこで,今週はファイルではない対象を扱ってみましょう。

取りあげるのはStAXとDOMです。もちろん,StAXもDOMもXMLパーサなので,単独でXMLドキュメントを解釈することが可能です。

では,なぜ複数のパースを組み合わせる必要があるのでしょうか。

たとえば,長大なXMLドキュメントの一部しか必要がない場合はどうでしょう。SAXやStAXで必要なところまで読み飛ばし,必要なところだけJAXBでアンマーシャリングします。もちろん,そのままSAXやStAXでパースしてもかまいませんが,スキーマがある場合はJAXBが簡単です。

また,DOMとXPathを組み合わせれば,必要な部分をクエリーすることが簡単にできます。必要な部分が見つかれば,後はJAXBにまかせてしまうというわけです。

このように用途によっては,パースの手段を組み合わせるということも十分意味を持ちます。

では,さっそくJAXBと他のパースを組み合わせてみましょう。

その前に,今週使用するXMLドキュメントを決めておきます。今週は,CDなどの音楽アルバムを表すXMLドキュメントを使用することにしました。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><album xmlns="http://www.javainthebox.net/music">  <title>Deja Vu</title>  <credit>Crosby, Stills, Nash & Young</credit>  <songs>    <song title="Carry On" writer="Stephen Stills" />    <song title="Tech Your Children" writer="Graham Nash" />    <song title="Almost Cut My Hair" writer="David Crosby" />    <song title="Helpless" writer="Neil Young" />    <song title="Woodstock" writer="Joni Mitchell" />    <song title="Deja Vu" writer="David Crosby" />    <song title="Our House" writer="Graham Nash" />    <song title="4 + 20" writer="Stephen Stills" />    <song title="Country Girl" writer="Neil Young" />    <song title="Everybody I Love You" writer="Stephen Stills, Neil Young" />  </songs></album>

ルートタグが<album>タグで,その中に<title>タグと<credit>タグがあります。そして,曲のデータをまとめる<songs>タグがあります。個々の曲は<song>タグで表されます。

このXMLドキュメントのスキーマをXML Schemaで表すと次のようになります。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xs:schema version="1.0"    targetNamespace="http://www.javainthebox.net/music"     xmlns:tns="http://www.javainthebox.net/music"    xmlns:xs="http://www.w3.org/2001/XMLSchema">   <xs:element name="album">    <xs:complexType>      <xs:sequence>        <xs:element ref="tns:title" minOccurs="1"/>        <xs:element ref="tns:credit" minOccurs="1"/>        <xs:element ref="tns:songs" minOccurs="1"/>      </xs:sequence>    </xs:complexType>  </xs:element>   <xs:element name="title" type="xs:string"/>  <xs:element name="credit" type="xs:string"/>   <xs:element name="songs">    <xs:complexType>      <xs:sequence>        <xs:element ref="tns:song" minOccurs="1" maxOccurs="unbounded"/>      </xs:sequence>    </xs:complexType>  </xs:element>   <xs:element name="song">    <xs:complexType>      <xs:attribute name="title" type="xs:string"/>      <xs:attribute name="writer" type="xs:string"/>    </xs:complexType>  </xs:element> </xs:schema>

このようなXMLドキュメントで,JAXBで扱うのは<song>タグとしてみましょう。

他のパースと組み合わせる前に,このスキーマからJavaのクラスを生成しておきます。

C:\jaxb>xjc album.xsdnet\javainthebox\music\Album.javanet\javainthebox\music\ObjectFactory.javanet\javainthebox\music\Song.javanet\javainthebox\music\Songs.javanet\javainthebox\music\package-info.java

これで準備は整いました。

StAXとJAXBの組み合わせ

まず,StAXとJAXBを組み合わせてみます。

サンプルのソースStAXUnmarshallerSample.java

StAXで<song>タグまでパースし,その後JAXBに引き渡します。

まず,StAXUnmarshallerSampleクラスのフィールドとコンストラクタを示します。

public class StAXUnmarshallerSample {    // StAX 用ファクトリ    private XMLInputFactory factory;     // JAXB 用ファクトリ    private JAXBContext context;    // JAXB アンマーシャラー    private Unmarshaller unmarshaller;     public StAXUnmarshallerSample() throws JAXBException {        // StAX用ファクトリの生成        factory = XMLInputFactory.newInstance();         // JAXB用ファクトリの生成        context = JAXBContext.newInstance("net.javainthebox.music");        // アンマーシャラー生成        unmarshaller = context.createUnmarshaller();    }

StAXもJAXBも,ファクトリクラスはパースやアンマーシャリング/マーシャリングのたびに生成するのではなく,なるべくまとめて生成するようにします。そのために,ここではコンストラクタで一度だけ生成するようにしました。

次にStAXでのパースをおこなうparseメソッドです。mainメソッドからは,このparseメソッドがコールされます。

    // StAX でパース    public void parse(String xmlfile) {               XMLStreamReader reader = null;        InputStream stream = null;                try {            stream = new FileInputStream(xmlfile);                    // パーサの生成            reader = factory.createXMLStreamReader(stream);                        // イベントループ            while (reader.hasNext()) {                // 次のイベントを取得                int eventType = reader.next();                if (eventType == XMLStreamReader.START_ELEMENT) {                                    // タグ名がsongかどうか調べる                    if ("song".equals(reader.getLocalName())) {                        // song タグの場合JAXBでアンマーシャル                        unmarshal(reader);                    }                }            }        } catch (FileNotFoundException ex) {            System.err.println("ファイルがオープンできません");        } catch (XMLStreamException ex) {            System.err.println("パースに失敗しました");        } finally {            if (reader != null) {                try {                    reader.close();                } catch (XMLStreamException ex) {}            }            if (stream != null) {                try {                    stream.close();                } catch (IOException ex) {}            }        }    }

ここではStAXのカーソルAPIを使用してパースをおこなっています。StAXの詳細はJava SE 6完全攻略 第69回 第三のパーサ - StAXをご参照ください。

カーソルAPIでは,XMLStreamReaderオブジェクトからイベントを取り出し,そのイベントに応じた処理を行ないます。

ここではSTART_ELEMENTイベントだけを解釈しています。この時,青字で示したように,タグ名がsongであるかを調べています。

タグ名がsongの場合はunmarshalメソッドをコールし,JAXBでアンマーシャリングします。赤字で示したように,引数はXMLStreamReaderオブジェクトです。

    // JAXB でアンマーシャリング    private void unmarshal(XMLStreamReader reader) {        try {            // アンマーシャリング            // 戻り値の型はObjectクラス            Object obj = unmarshaller.unmarshal(reader);            Song song = (Song)obj;              // song タグの内容を出力            System.out.println(song.getTitle()                              + " (" + song.getWriter() + ")");         } catch (JAXBException ex) {            // 例外処理            System.err.println("アンマーシャリングに失敗しました");        }    }

unmarshalメソッドの引数は,XMLStreamReaderオブジェクトです。アンマーシャリングの対象がストリームの場合と違うのは,この引数だけです。

ただしくアンマーシャリングできれば,unmarshalメソッドの戻り値は,XMLドキュメントに対応するオブジェクトになります。

ここでは,StAXで<song>タグまでパースしたので,JAXBでアンマーシャリングを開始するのは<song>タグからとなります。このため,unmarshalメソッドの戻り値の型はSongクラスになります。

しかし,unmarshalメソッドの戻り値の型はObjectクラスと定義されているため,Songクラスにキャストします。

もし,このキャストを排除したい場合は,以下に示すように,引数がXMLStreamReaderインタフェースと,Classクラスにオーバーロードされているunmarshalメソッドを使用します。この場合,戻り値はジェネリクスを用い,JAXBElement<T>となります。Tは第2引数で指定されたクラスとなります。

    JAXBElement<Song> element = unmarshaller.unmarshal(reader, Song.class);    Song song = element.getValue();

では,実行してみましょう。使用するXMLドキュメントは一番始めに示したXMLドキュメントを使用しました。

C:\jaxb>java  StAXUnmarshallerSample dejavu.xmlCarry On (Stephen Stills)Tech Your Children (Graham Nash)Almost Cut My Hair (David Crosby)Helpless (Neil Young)Woodstock (Joni Mitchell)Deja Vu (David Crosby)Our House (Graham Nash)4 + 20 (Stephen Stills)Country Girl (Neil Young)Everybody I Love You (Stephen Stills, Neil Young)

曲名と,カッコ内に作曲者が表示されていることがお分かりのはずです。


DOMとJAXBの組み合わせ

次に,DOMとJAXBの組み合わせです。

サンプルのソースDOMUnmarshallerSample.java

このサンプルも先ほどのサンプルと同様,<song>タグだけをJAXBでアンマーシャリングします。ここで,<song>タグを抜き出すためにXPathを使用します。

まず,DOMUnmarshallerSampleクラスのコンストラクタを示します。

    public DOMUnmarshallerSample()         throws ParserConfigurationException, JAXBException {        // DOM の初期化        factory = DocumentBuilderFactory.newInstance();        factory.setNamespaceAware(true);        builder = factory.newDocumentBuilder();         // XPath の初期化        xpathFactory = XPathFactory.newInstance();        xpath = xpathFactory.newXPath();         // JAXB の初期化        context = JAXBContext.newInstance("net.javainthebox.music");        unmarshaller = context.createUnmarshaller();    }

コンストラクタでは,DOMのDocumentBuilderオブジェクト,XPathのXPathオブジェクト,JAXBのUmarshallerオブジェクトを生成しています。

DocumentBuilderFactoryクラスのsetNamespaceAwareメソッドを引数trueでコールしているのは,DOMで名前空間を認識させるためです(赤字部分)。

つづいて,DOMによるバース処理です。

    // DOM でパース    public void parse(String xmlfile) {        try {            // パース            Document document = builder.parse(new File(xmlfile));             // 名前空間の処理            NamespaceContext context = new NamespaceContext() {                public String getNamespaceURI(String prefix) {                    if ("pre".equals(prefix)) {                        return "http://www.javainthebox.net/music";                    } else {                        return XMLConstants.NULL_NS_URI;                    }                }                                    public String getPrefix(String uri) {                    throw new UnsupportedOperationException();                }                 public Iterator getPrefixes(String uri) {                    throw new UnsupportedOperationException();                }            };            xpath.setNamespaceContext(context);             // XPath でクエリ            NodeList list =                 (NodeList)xpath.evaluate("/pre:album/pre:songs/pre:song",                                         document,                                         XPathConstants.NODESET);             // クエリ結果のノードを JAXB でアンマーシャリング            for (int i = 0; i < list.getLength(); i++) {                Node node = list.item(i);                unmarshal(node);            }        } catch (IOException ex) {            System.err.println("パースに失敗しました");        } catch (SAXException ex) {            System.err.println("パースに失敗しました");        } catch (XPathExpressionException ex) {            System.err.println("パースに失敗しました");        }    }

NamespaceContextインタフェースを実装した無名クラスを作成しているのは,XPathで名前空間を扱うためです。青字で示した,getNamespaceURIメソッドでプリフィックスと名前空間の対応を決めます。

そして,XPathでクエリーを行なっているのがXPathクラスのevaluateメソッドです(赤字部分)。ここで,getNamespaceURIメソッドで対応付けしたプリフィックスを使用して,ノードのクエリを行ないます。

evaluateメソッドの第3引数でNODESETを指定しているので,戻り値はNodeListオブジェクトになります。後はNodeListオブジェクトが保持するNodeオブジェクトをJAXBでアンマーシャリングします。

    // JAXB でアンマーシャリング    private void unmarshal(Node node) {        try {            // アンマーシャリング            Object obj = unmarshaller.unmarshal(node);            Song song = (Song)obj;             // song タグの内容を出力            System.out.println(song.getTitle()                              + " (" + song.getWriter() + ")");         } catch (JAXBException ex) {            // 例外処理            System.err.prntln("アンマーシャリングに失敗しました");        }    }

Nodeオブジェクトを使用した場合,unmarshalメソッドの引数が変化するだけで,後は何も変りません。他の場合と,同じように処理を行なうことができます。

実行した結果はStAXの場合とまったく一緒なので,ここでは省略します。

ここで示したように,JAXBは他のパースと組み合わせて使うことも簡単にできます。XMLのパースにはそれぞれ得手,不得手があります。特性に応じた使い分けをすることで効率的にXMLドキュメントを扱うことができるはずです。

さて,今までの解説でははじめにスキーマがあることを前提に考えていました。来週はそれとは逆に,JavaのクラスからXMLのスキーマを生成することを考えていきます。お楽しみに。