この章では、EclipseLink MOXyについて紹介および説明します。EclipseLink MOXyは、JAXB仕様で定義される標準ランタイムの1つの実装です。
JAXBプロバイダとしてEclipseLink MOXyを指定する手順は次のとおりです。
クラスパスに、(Java SE 6に含まれている)JAXB APIとeclipselink.jar
を追加します。
(ドメイン・クラスと同じパッケージにある)jaxb.properties
ファイルを使用します。
この章の内容は次のとおりです。
EclipseLink MOXyをJAXB実装として使用するには、jaxb.properties
ファイル内でEclipseLink JAXBContextFactory
を特定します。
jaxb.properties
という名前のテキスト・ファイルを作成し、新しいJAXBContexts
の構築に使用されるファクトリとして、EclipseLinkのJAXBContextFactory
を次のように指定します。
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
このファイルを、モデル・クラスが常駐しているのと同じパッケージ(ディレクトリ)にコピーします。
この標準JAXBContext.newInstance(Class... classesToBeBound)
APIを使用して、次のようなJAXBContextを作成します。
JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);
アプリケーション・コードを変更する必要がないため、異なるJAXB実装間を容易に切り替えることができます。
JAXBContext
を作成するための様々な方法の詳細は、「ブートストラップ」を参照してください。
EclipseLink MOXyには、JAXBContext
を作成する際にいくつかのオプションが用意されています。次のものからブートストラップするオプションがあります。
JAXBの注釈の付いた1つ以上のクラスのリスト
Javaクラスのマッピングが定義された1つ以上のEclipseLink XMLバインディング・ドキュメントのリスト
クラスとXMLバインディングの組合せ
コンテキスト・パスのリスト
sessions.xml
に定義されているEclipseLinkセッションを参照するセッション名のリスト
(例2-1に示されている)JAXBContext
に対するメソッドは、インスタンスの新規作成に使用されます。
例2-1 JAXBContextのメソッド
public static JAXBContext newInstance(Class... classesToBeBound) throws JAXBException public static JAXBContext newInstance(Class[] classesToBeBound, Map<String,?> properties) throws JAXBException public static JAXBContext newInstance(String contextPath) throws JAXBException public static JAXBContext newInstance(String contextPath, ClassLoader classLoader) throws JAXBException public static JAXBContext newInstance(String contextPath, ClassLoader classLoader, Map<String,?> properties) throws JAXBException
JAXBContext
は、次のオプションを受け入れます。
classesToBeBound: 新しいJAXBContext
で認識されるJavaクラスのリスト
contextPath: マップされたクラスを含むJavaパッケージ名(またはEclipseLinkセッション名)のリスト
classLoader: マップされたクラスを見つけるために使用されるクラス・ローダー
properties: 追加プロパティのマップ
例2-1にあるAPIでは、Javaパッケージ/コンテキスト・パスにjaxb.properties
ファイルがあると予想されます。詳細は、「EclipseLinkランタイムの指定」を参照してください。
JAXB注釈付きのJavaクラスのコレクションがある場合は、これらのクラスのリストを直接提供できます。
JAXBContext context = JAXBContext.newInstance(Company.class, Employee.class);
配列内のクラス(たとえば、参照クラスやスーパー・クラス)から読取り可能な他のクラスは、JAXBContext
で自動的に認識されます。サブクラスまたはクラスのうち、@
XmlTransient
とマークされたものは認識されません。
JAXBContext
をブートストラップする別の方法は、コンテキスト・パスというString
を使用することです。これは、次のような、マップされたクラスを含むパッケージ名のコロン区切りリストです。
JAXBContext context = JAXBContext.newInstance("example");
この手法を使用した、EclipseLinkがモデル・クラスを検出する方法が、次のようにいくつかあります。
jaxb.index
ファイルの使用コンテキスト・パスには、jaxb.index
というファイルを含めることができ、このファイルは単純なテキスト・ファイルで、現在のパッケージのクラス名を含み、次のJAXBContext
に入れられます。
src/example/jaxb.index:
リスト内のクラス(たとえば、参照クラスやスーパー・クラス)から読取り可能な他のクラスは、JAXBContext
で自動的に認識されます。サブクラスまたはクラスのうち、@XmlTransient
とマークされたものは認識されません。
コンテキスト・パスには、ObjectFactory
というクラスを含めることもでき、このクラスは、JAXBが探す特殊なファクトリ・クラスです。このクラスには、モデル内のタイプごとにcreate()
メソッドが含まれています。通常の場合、ObjectFactory
はJAXBコンパイラによって生成されますが、手動で作成することもできます。
src/example/ObjectFactory.java:
例2-3 ObjectFactoryのサンプル
@XmlRegistry public class ObjectFactory { private final static QName _Employee_QNAME = new QName("", "employee"); private final static QName _PhoneNumber_QNAME = new QName("", "phone-number"); public ObjectFactory() { } public EmployeeType createEmployeeType() { return new EmployeeType(); } @XmlElementDecl(namespace = "", name = "employee") public JAXBElement<EmployeeType> createEmployee(EmployeeType value) { return new JAXBElement<EmployeeType>(_Employee_QNAME, EmployeeType.class, null, value); } public PhoneNumberType createPhoneNumberType() { return new PhoneNumberType(); } @XmlElementDecl(namespace = "", name = "phone-number") public JAXBElement<PhoneNumberType> createPhoneNumber(PhoneNumberType value) { return new JAXBElement<PhoneNumberType>(_PhoneNumber_QNAME, PhoneNumberType.class, null, value); } }
EclipseLink MOXyには、EclipseLinkのMetadataSource
の実装からマッピング情報を取得する機能もあります。この手法を使用し、独自のXmlBindings
を作成する作業を担当します。
例2-4 MetadataSourceのサンプル
package org.eclipse.persistence.jaxb.metadata;
public interface MetadataSource {
/**
* @param properties – The properties passed in to create the JAXBContext
* @param classLoader – The ClassLoader passed in to create the JAXBContext
*
* @return the XmlBindings object representing the metadata
*/
XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader);
}
MetadataSourceの使用の詳細は、「MetadataSourceの使用」を参照してください。
XMLへのクラスのマッピングをさらに制御するために、EclipseLink XMLバインディング・ドキュメントからブートストラップできます。このアプローチを使用すると、EclipseLinkの堅牢なマッピング・フレームワークを活用でき、XMLの複合型をそれぞれ対応するJavaにマップする方法をカスタマイズできます。
実際のドキュメントへのリンクは、特殊キーJAXBContextProperties.OXM_METADATA_SOURCE
を次のように使用し、propertiesパラメータを介して渡されます。
例2-5 EclipseLinkバインディング・ドキュメントの使用
InputStream iStream = myClassLoader.getResourceAsStream("example/xml-bindings.xml");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
JAXBContext context = JAXBContext.newInstance(new Class[]{ Customer.class }, properties);
XMLバインディング形式の詳細は、「XMLバインディングの使用」を参照してください。
注釈付きクラスからブートストラップする際には、追加のマッピング情報にEclipseLink XMLバインディング・ドキュメントを提供できます。たとえば、モデル・クラスにJAXB-spec-onlyという注釈を付けて、EclipseLink固有のマッピング・カスタマイズをXMLバインディング・ドキュメントに入れることができます(モデル・クラスにEclipseLink注釈をインポートする必要はありません)。
たとえば、例2-6で注釈付きのEmployee
クラスを確認してください。
例2-6 Javaクラスのサンプル
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Employee { @XmlElement(name="phone-number") private PhoneNumber phoneNumber; ... }
例2-7でXMLバインディングを使用することによって、PhoneNumbers
のマーシャリング/アンマーシャリングにEclipseLink XMLAdapterを使用するようにEmployee
をカスタマイズできます。
例2-7 XMLバインディング・ドキュメントの使用
<?xml version="1.0" encoding="US-ASCII"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"> <java-types> <java-type name="example.Employee"> <java-attributes> <xml-element java-attribute="phoneNumber"> <xml-java-type-adapter value="example.util.PhoneNumberProcessor"/> </xml-element> </java-attributes> </java-type> </java-types> </xml-bindings>
最後に、例2-8に示されているように、注釈付きクラスのリストとXMLバインディングへのリンクの両方を、JAXBContext
に渡します。
例2-8 アプリケーション・コードのサンプル
InputStream iStream = myClassLoader.getResourceAsStream("example/xml-bindings.xml"); Map<String, Object> properties = new HashMap<String, Object>(); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream); Class[] classes = new Class[] { Company.class, Employee.class }; JAXBContext context = JAXBContext.newInstance(classes, properties);
EclipseLinkには、JAXBの標準注釈に加えて、EclipseLink XMLバインディング・ドキュメントという、メタデータを表現する別の方法も用意されています。XMLバインディングは、マッピング情報を実際のJavaクラスから分離できるだけでなく、次のような高度なメタデータ・タスクにも使用できます。
追加のマッピング情報による、既存の注釈の増強またはオーバーライド
Javaでの注釈をいっさい使用しない、すべてのマッピング情報の外部指定
複数のバインディング・ドキュメントにわたるマッピングの定義、Javaの具体的なフィールドに対応しない仮想マッピングの指定、その他
この項では、XMLバインディング形式について説明し、基本的なユースケースをいくつか示します。
XMLバインディング・ドキュメントとは、Javaタイプ情報、マッピング情報、コンテキスト全体のプロパティなど、JAXBシステムを定義するために必要なすべてのものを指定するXMLです。例となるバインディング・ドキュメントを例2-9に示します。
例2-9 バインディング・ドキュメントの例
<?xml version="1.0" encoding="US-ASCII"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" package-name="example" xml-accessor-type="PUBLIC_MEMBER" xml-accessor-order="ALPHABETICAL" xml-mapping-metadata-complete="false" xml-name-transformer="example.NameGenerator" supported-versions="2.4" > <xml-schema element-form-default="QUALIFIED"> <xml-ns prefix="ns1" namespace-uri="http://www.example.org/type" /> </xml-schema> <java-types> <java-type name="Employee"> <xml-type namespace="http://www.example.org/type" /> <java-attributes> <xml-attribute java-attribute="empId" xml-path="@id" /> <xml-element java-attribute="empName" name="name" /> <xml-element java-attribute="salary" /> <xml-element java-attribute="type" type="EmployeeType" /> </java-attributes> </java-type> <java-type name="Company"> <xml-root-element name="company" /> <xml-attribute java-attribute="empId" xml-path="@id" /> <xml-element java-attribute="empName" name="name" /> <java-attributes> <xml-element java-attribute="employees" name="employee" type="example.Employee" container-type="java.util.ArrayList" /> </java-attributes> </java-type> </java-types> <xml-registries> <xml-registry name="example.ObjectFactory"> <xml-element-decl java-method="createEmpleado" name="empleado" type="example.Employee" /> <xml-element-decl java-method="createCorporacion" name="corporacion" type="example.Company" /> </xml-registry> </xml-registries> <xml-enums> <xml-enum java-enum="EmployeeType" value="java.lang.String"> <xml-enum-value java-enum-value="CONTRACT">CONTRACT</xml-enum-value> <xml-enum-value java-enum-value="PART_TIME">PART_TIME</xml-enum-value> <xml-enum-value java-enum-value="FULL_TIME">FULL_TIME</xml-enum-value> </xml-enum> </xml-enums> </xml-bindings>
表2-1 バインディング・ドキュメントの属性
属性 | 説明 |
---|---|
|
XMLバインディング・ドキュメントのルート。ここでは、クラスの |
|
JAXBシステムのスキーマ・レベルに関連するプロパティを定義します。JAXBの |
|
Javaクラスごとにマッピング情報を定義します。 |
|
Javaタイプで使用できるJava列挙を定義します。 |
|
JAXBシステムで使用するための |
JAXBContext
をインスタンス化する際、バインディング・ドキュメントへのリンクは、特殊キーJAXBContextProperties.OXM_METADATA_SOURCE
を使用し、propertiesパラメータを介して渡されます。このキーの値は、次のいずれかの形式によるバインディング・ドキュメントへのハンドルとなります。
java.io.File
java.io.InputStream
java.io.Reader
java.net.URL
javax.xml.stream.XMLEventReader
javax.xml.stream.XMLStreamReader
javax.xml.transform.Source
org.w3c.dom.Node
org.xml.sax.InputSource
複数のXMLバインディング・ドキュメントからブートストラップする場合は、次のようになります。
前述の入力のマップがサポートされ、Javaパッケージ名がキーとなります。
前述の入力のリストも受け入れられます(<xml-bindings>
にはパッケージ属性が必要)。
XMLバインディング・ドキュメントの用途としては、JAXB注釈と併用されるのが最も一般的です。Javaドメイン・クラスの編集は許可されていないが、別のマッピング機能を追加したいというような状況も考えられます。または、ドメイン・モデルにEclipseLinkコードをインポートすることはやめたいが、MOXyの高度なマッピング機能は引き続き利用したいということも考えられます。コンテキストの作成中にバインディング・メタデータが提供されると、そのマッピング情報にJAXB注釈情報が組み合されます。
たとえば、例2-10に示されている単純なJAXBドメイン・クラスおよびそのデフォルトのJAXB XML表現を検討してください。
例2-10 JAXBドメイン・クラスとXMLのサンプル
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlAttribute private Integer custId; private String name; private Double salary; private byte[] picture; ... } <?xml version="1.0" encoding="UTF-8"?> <customer custId="15"> <name>Bob Dobbs</name> <salary>51727.61</salary> <picture>AgQIECBA</picture> </customer>
ここで、マッピングを次のように変更するとします。
custId
のXML要素名をcustomer-id
に変更する
このクラスのルート要素名をcustomer-info
に変更する
16進のbinary
形式でのpicture-hex
としてピクチャをXMLに書き込み、独自のカスタム・コンバータであるMyHexConverter
を使用する
例2-11に示すように、これら3つのカスタマイズをXMLバインディング・ドキュメントで指定できます。
例2-11 カスタマイズされたXMLバインディング
<?xml version="1.0" encoding="US-ASCII"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="example"> <java-types> <java-type name="Customer"> <xml-root-element name="customer-info" /> <java-attributes> <xml-attribute java-attribute="custId" name="customer-id" /> <xml-element java-attribute="picture" name="picture-hex"> <xml-schema-type name="hexBinary" /> <xml-java-type-adapter value="example.adapters.MyHexConverter" /> </xml-element> </java-attributes> </java-type> </java-types> </xml-bindings>
バインディングは、JAXBコンテキストの作成中に提供される必要があります。バインディング情報は、properties
引数を介して次のように渡されます。
例2-12 バインディングの提供
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream iStream = classLoader.getResourceAsStream("metadata/xml-bindings.xml");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Customer.class }, properties);
バインディングを提供する際には、JAXBコンテキストの作成中に、Oracle TopLinkで次の処理が行われます。
Customer.class
が分析され、JAXBマッピングが通常どおりに生成されます。
次にバインディング・ドキュメントが分析され、元のJAXBマッピングが、バインディング・ドキュメント内の情報とマージされます。
XMLバインディングを適用すると、次のように、目的のXML表現が得られます。
<?xml version="1.0" encoding="UTF-8"?> <customer-info customer-id="15"> <name>Bob Dobbs</name> <salary>51727.61</salary> <picture-hex>020408102040</picture-hex> </customer-info>
バージョン2.3以降のEclipseLinkでは、複数のXMLバインディング・ドキュメントのマッピング情報を使用できます。この手法を使用し、メタデータを好きなように分割できます。
例2-13 XMLバインディングのリストの使用
... FileReader file1 = new FileReader("base-bindings.xml"); FileReader file2 = new FileReader("override-bindings.xml"); List<Object> fileList = new ArrayList<Object>(); fileList.add(file1); fileList.add(file2); Map<String, Object> properties = new HashMap<String, Object>(); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, fileList); JAXBContext ctx = JAXBContext.newInstance(new Class[] { Customer.class }, properties); ...
バインディング・ドキュメントのリストを使用するときには、バインディングのセットごとのパッケージを示すために、それぞれのバインディング・ドキュメントで<xml-bindings>
のパッケージ属性を定義する必要があります。
例2-14 複数のパッケージでの1つのマップの使用
... FileReader fooFile1 = new FileReader("foo/base-bindings.xml"); FileReader fooFile2 = new FileReader("foo/override-bindings.xml"); List<Object> fooFileList = new ArrayList<Object>(); fooFileList.add(fooFile1); fooFileList.add(fooFile2); FileReader barFile1 = new FileReader("bar/base-bindings.xml"); FileReader barFile2 = new FileReader("bar/override-bindings.xml"); List<Object> barFileList = new ArrayList<Object>(); barFileList.add(barFile1); barFileList.add(barFile2); Map<String, List> metadataMap = new HashMap<String, List>(); metadataMap.put("foo", fooFileList); metadataMap.put("bar", barFileList); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadataMap); JAXBContext ctx = JAXBContext.newInstance(new Class[] { Customer.class }, properties); ...
同じパッケージにメタデータのソースが複数あった場合は、メタデータの完全セットをマージすることによって、マッピングの統一セットが作成されます。まずJavaクラスの注釈が処理され、次に、XMLバインディング情報があれば適用されます。バインディングの指定順序は関連性があり、後続のドキュメント内の値によって、以前のドキュメントで定義された値がオーバーライドされます。
マージに対して次のルールが使用されます。
xml-schema
namespace
、elementform
、attributeform
などの値では、後のファイルによってオーバーライドされます。
XmlNs
のネームスペース宣言のリストは、すべてのファイルのすべてのエントリを含む単一のリストにマージされます。
エントリが競合している(複数のネームスペースに同じ接頭辞がバインドされている)場合は、最後のファイルによって、以前のファイルの宣言がオーバーライドされます。
java-types
マージされたバインディングには、すべてのバインディング・ファイルの一意のjava-type
エントリがすべて含まれます。
同じjava-type
が複数のファイルに出現している場合、後のファイルで設定されている値があれば、それによって、以前のファイルの値がオーバーライドされます。
それぞれのjava-type
に対するプロパティは、統一リストにマージされます。同じプロパティが複数のファイルで参照されている場合は例外です。
クラス・レベルのXmlJavaTypeAdpater
エントリは、後のバインディング・ファイルで指定されている場合はオーバーライドされます。
クラス・レベルのXmlSchemaTypes
によって、マージされたリストが作成されます。同じタイプのエントリが、このレベルで複数のバインディング・ファイルにリストされている場合は、最後のファイルのエントリによって、以前のエントリがすべてオーバーライドされます。
xml-enums
マージされたバインディングには、すべてのバインディング・ファイルの一意のxml-enum
エントリがすべて含まれます。
java-enumが重複する場合に備えて、XmlEnumValues
のマージされたリストが作成されます。同じenumファセットのエントリが複数のファイルに出現している場合は、最後のファイルによって、そのファセットの値がオーバーライドされます。
xml-java-type-adapters
パッケージ・レベルのJavaタイプ・アダプタは、単一のリストにマージされます。複数のファイルで同じクラスに対してアダプタが指定されている場合は、最後のファイルのエントリが優先されます。
xml-registries
一意のXmlRegistry
エントリはそれぞれ、XmlRegistries
のマージされた最終的なリストに追加されます。
XmlRegistry
エントリが重複する場合に備えて、XmlElementDecls
のマージされたリストが作成されます。
同じXmlRegistry
クラスに対するXmlElementDecl
が複数のバインディング・ファイルに出現している場合は、そのXmlElementDecl
は、後のバインディングのものに置換されます。
xml-schema-types
XmlSchemaType
エントリは、統一リストにマージされます。
同じjava-typeに対するXmlSchemaType
エントリが、パッケージ・レベルで複数のバインディング・ファイルに出現している場合は、マージされたバインディングには、最後に指定されたファイルのエントリのみが含まれます。
メタデータのすべてをXMLバインディングに格納し、JavaクラスにJAXB注釈があったら無視する場合は、バインディング・ドキュメントの<xml-bindings>
要素にxml-mapping-metadata-complete
属性を含めることができます。デフォルトのJAXBマッピングは引き続き生成され(JAXBでまったく注釈が付けられていないクラスを使用している場合と同じ)、XMLバインディングに定義されているマッピング・データがあれば適用されます。
これは、まったく異なる2つのXML表現に同じJavaクラスをマップするような場合に使用できます。その場合、実際のJavaクラスに対する注釈によって最初のXML表現が定義され、次に、xml-mapping-metadata-complete="true"
によって、2番目のXML表現をXMLバインディング・ドキュメントに定義できます。これによって本質的に、Javaクラスを再マップするための「空白のキャンバス」が得られます。
JAXBで生成されるデフォルトのマッピングを無視する場合は、<java-type>
要素でxml-accessor-type="NONE"
と指定できます。この手法を使用した場合、適用されるのは、バインディング・ドキュメントで明示的に定義されたマッピングのみです。
前述のCustomerの例を使用すると、この後の例では、xml-mapping-metadata-complete
を使用したときに生成されるXML表現が示されます。
例2-15 Customerクラスのサンプル
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlAttribute private Integer custId; private String name; private Double salary; private byte[] picture; ... }
例2-16 XMLバインディング
<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="example" xml-mapping-metadata-complete="true">
<java-types>
<java-type name="Customer">
<xml-root-element />
<java-attributes>
<xml-attribute java-attribute="name" name="customer-name" />
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
例2-17 XML表現
<?xml version="1.0" encoding="UTF-8"?> <customer> <custId>15</custId> <customer-name>Bob Dobbs</customer-name> <picture>AgQIECBA</picture> <salary>51727.61</salary> </customer>
custId
にデフォルトのJAXBマッピングが生成される(custId
は、Javaフィールドに注釈が付けられていないかのようにXML要素となる)
名前要素の名前がcustomer-name
に変更される
picture
とsalary
にデフォルトのJAXBマッピングが生成される
例2-18 XMLバインディング(xml-accessor-type="NONE"と指定されている)
<?xml version="1.0" encoding="US-ASCII"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="example" xml-mapping-metadata-complete="true"> <java-types> <java-type name="Customer" xml-accessor-type="NONE"> <xml-root-element /> <java-attributes> <xml-attribute java-attribute="name" name="customer-name" /> </java-attributes> </java-type> </java-types> </xml-bindings>
例2-19 XML表現
<?xml version="1.0" encoding="UTF-8"?> <customer> <customer-name>Bob Dobbs</customer-name> </customer>
xml-accessor-type="NONE"
と指定すると、デフォルトのマッピングは生成されなくなる
XML表現には、XMLバインディング・ドキュメントに定義されているマッピングのみが含まれている
XMLバインディングは仮想マッピングを指定する際にも使用でき、仮想マッピングとは、具体的なJavaフィールドに対応しないマッピングです。たとえば、特定のマッピングのためのデータ保持の基礎となる構造として、HashMap
を使用する場合があります。仮想マッピングの使用の詳細は、「仮想アクセス・メソッドの使用」を参照してください。
EclipseLink 2.3で導入されたMetadataSource
は、EclipseLinkメタデータの提供を担当します。これにより、マッピング情報をアプリケーションの外部に保存して、アプリケーションのJAXBContext
が作成またはリフレッシュされるときにマッピング情報を取得できるようになります。
独自のMetadataSource
を実装するために、次の操作ができます。
org.eclipse.persistence.jaxb.metadata.MetadataSource
インタフェースを実装するクラスを新規作成します。
org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter
クラスを拡張するクラスを新規作成します。このメソッドを使用することのほうが望ましいと考えられます。将来、インタフェースに追加する作業が不要になるからです。
どちらの場合でも、次のメソッドの実装を担当します。
例2-20 XMlBindingsメソッドの実装
/** * Retrieve XmlBindings according to the JAXBContext bootstrapping information. * * @param properties - The properties passed in to create the JAXBContext * @param classLoader - The ClassLoader passed in to create the JAXBContext * @return the XmlBindings object representing the metadata */ XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader);
内部的には、EclipseLinkメタデータはXmlBindings
オブジェクトに格納され、このオブジェクト自体はJAXBでマップされます。つまり、JAXBアンマーシャラを実際に使用して、外部メタデータを読み取り、そこからXmlBindings
を作成できます。
例2-21 XmlBindingsオブジェクトのサンプル
package example; import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings; ... JAXBContext xmlBindingsContext = JAXBContext.newInstance("org.eclipse.persistence.jaxb.xmlmodel"); FileReader bindingsFile = new FileReader("xml-bindings.xml"); XmlBindings bindings = (XmlBindings) xmlBindingsContext.createUnmarshaller().unmarshal(bindingsFile);
JAXBContext
を作成する際にMetadataSource
を使用するには、キーJAXBContextProperties.OXM_METADATA_SOURCE
を使用してプロパティ・マップに追加します。
例2-22 プロパティ・マップへのMetadataSourceの追加
MetadataSource metadataSource = new MyMetadataSource();
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadataSource);
JAXBContext jc = JAXBContext.newInstance(new Class[] { Customer.class }, properties);
次の例では、URLからアンマーシャリングすることによって、XmlBindings
オブジェクトを作成します。
例2-23 XmlBindingsオブジェクトのサンプル
package example; import java.net.URL; import java.util.Map; import javax.xml.bind.JAXBContext; import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter; import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings; public class MyMetadataSource extends MetadataSourceAdapter { private JAXBContext bindingsContext; private URL bindingsUrl; private final String XML_BINDINGS_PACKAGE = "org.eclipse.persistence.jaxb.xmlmodel"; private final String METADATA_URL = "http://www.example.com/private/metadata/xml-bindings.xml"; public MyMetadataSource() { try { bindingsContext = JAXBContext.newInstance(XML_BINDINGS_PACKAGE); bindingsUrl = new URL(METADATA_URL); } catch (Exception e) { throw new RuntimeException(e); } } @Override public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) { try { Unmarshaller u = bindingsContext.createUnmarshaller(); XmlBindings bindings = (XmlBindings) u.unmarshal(bindingsUrl); return bindings; } catch (Exception e) { throw new RuntimeException(e); } } }
独自のXmlBindings
オブジェクトをコードでゼロから構築するというオプションもあります。次の例では、ロケール固有の名前を使用するようにAddressクラスのpCode
フィールドを変更します。
例2-24 XmlBindingsオブジェクトのサンプル
package example; import java.util.Locale; import java.util.Map; import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter; import org.eclipse.persistence.jaxb.xmlmodel.JavaType; import org.eclipse.persistence.jaxb.xmlmodel.JavaType.JavaAttributes; import org.eclipse.persistence.jaxb.xmlmodel.ObjectFactory; import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings; import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings.JavaTypes; import org.eclipse.persistence.jaxb.xmlmodel.XmlElement; public class AddressMetadataSource extends MetadataSourceAdapter { private ObjectFactory factory; private XmlBindings xmlBindings; public AddressMetadataSource() { factory = new ObjectFactory(); xmlBindings = new XmlBindings(); xmlBindings.setPackageName("example"); xmlBindings.setJavaTypes(new JavaTypes()); } @Override public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) { JavaType javaType = new JavaType(); javaType.setName("Address"); javaType.setJavaAttributes(new JavaAttributes()); XmlElement pCodeElement = new XmlElement(); pCodeElement.setJavaAttribute("pCode"); String country = Locale.getDefault().getCountry(); if (country.equals(Locale.US.getCountry())) { pCodeElement.setName("zip-code"); } else if (country.equals(Locale.UK.getCountry())) { pCodeElement.setName("post-code"); } else if (country.equals(Locale.CANADA.getCountry())) { pCodeElement.setName("postal-code"); } javaType.getJavaAttributes().getJavaAttribute().add(factory.createXmlElement(pCodeElement)); xmlBindings.getJavaTypes().getJavaType().add(javaType); return xmlBindings; } }
Javaオブジェクト・モデルからXMLスキーマを生成する手順は次のとおりです。
javax.xml.bind.SchemaOutputResolver
を拡張するクラスを作成します。
private class MySchemaOutputResolver extends SchemaOutputResolver { public Result createOutput(String uri, String suggestedFileName) throws IOException { File file = new File(suggestedFileName); StreamResult result = new StreamResult(file); result.setSystemId(file.toURI().toURL().toString()); return result; } }
このクラスのインスタンスをJAXBContext
とともに使用して、生成されたXMLスキーマを取り込みます。
Class[] classes = new Class[4]; classes[0] = org.example.customer_example.AddressType.class; classes[1] = org.example.customer_example.ContactInfo.class; classes[2] = org.example.customer_example.CustomerType.class; classes[3] = org.example.customer_example.PhoneNumber.class; JAXBContext jaxbContext = JAXBContext.newInstance(classes); SchemaOutputResolver sor = new MySchemaOutputResolver(); jaxbContext.generateSchema(sor);
マーシャリング時およびアンマーシャリング時に、XMLスキーマに対してオブジェクトを検証する場合は、JAXBのValidationEventHandler
を利用できます。
例2-25 XMLスキーマのサンプル
この例では、次のXMLスキーマに対してオブジェクトを検証します。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="customer"> <xs:complexType> <xs:sequence> <xs:element name="name" type="stringMaxSize5"/> <xs:element ref="phone-number" maxOccurs="2"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="phone-number"> <xs:complexType> <xs:sequence/> </xs:complexType> </xs:element> <xs:simpleType name="stringMaxSize5"> <xs:restriction base="xs:string"> <xs:maxLength value="5"/> </xs:restriction> </xs:simpleType> </xs:schema>
次の制約に注目してください。
顧客の名前は、5文字以内にする必要があります。
顧客に割り当てらることのできる電話番号は2つまでです。
Customerクラスを次に示します。このクラスには検証コードがないことに注目してください。
例2-26 Customerクラスのサンプル
package example; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Customer { private String name; private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); public String getName() { return name; } public void setName(String name) { this.name = name; } @XmlElement(name="phone-number") public List<PhoneNumber> getPhoneNumbers() { return phoneNumbers; } public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) { this.phoneNumbers = phoneNumbers; } }
ValidationEventHandler
の独自サブクラスを提供することによって、JAXB検証イベントを受信できます。このイベントは、ValidationEvent
のインスタンスとして表現され、問題の詳細を数多く提供します。このデータは、SAXParseException
から入手されるものに類似しています。
handleEvent
メソッドからfalseが返されると、JAXB操作が停止します。
trueが返されると、可能であればメソッドを続行できます。
例2-27では、単にイベントが受信された際のデータを出力しています。
例2-27 ValidationEventHandlerのサンプル
package example; import javax.xml.bind.ValidationEvent; import javax.xml.bind.ValidationEventHandler; public class MyValidationEventHandler implements ValidationEventHandler { public boolean handleEvent(ValidationEvent event) { System.out.println("\nEVENT"); System.out.println("SEVERITY: " + event.getSeverity()); System.out.println("MESSAGE: " + event.getMessage()); System.out.println("LINKED EXCEPTION: " + event.getLinkedException()); System.out.println("LOCATOR"); System.out.println(" LINE NUMBER: " + event.getLocator().getLineNumber()); System.out.println(" COLUMN NUMBER: " + event.getLocator().getColumnNumber()); System.out.println(" OFFSET: " + event.getLocator().getOffset()); System.out.println(" OBJECT: " + event.getLocator().getObject()); System.out.println(" NODE: " + event.getLocator().getNode()); System.out.println(" URL: " + event.getLocator().getURL()); return true; } }
ValidationEventHandler
の実装を提供することに加えて、Marshaller
またはUnmarshaller
にSchema
のインスタンスを設定する必要があります。
例2-28 Javaコードのサンプル
package example;
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
public class UnmarshalDemo {
public static void main(String[] args) throws Exception {
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("customer.xsd"));
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setSchema(schema);
unmarshaller.setEventHandler(new MyValidationEventHandler());
Customer customer = (Customer) unmarshaller.unmarshal(new File("input.xml"));
}
}
<customer> <name>Jane Doe</name> <phone-number/> <phone-number/> <phone-number/> </customer>
アンマーシャリング中に実行された検証によって、3つのイベントが提起されました。最初の2つのイベントは、長すぎるname要素のテキスト値に関連しています。3番目のイベントは、余分のphone-number要素に関連しています。
EVENT SEVERITY: 1 MESSAGE: cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'. LINKED EXCEPTION: org.xml.sax.SAXParseException: cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'. LOCATOR LINE NUMBER: 3 COLUMN NUMBER: 25 OFFSET: -1 OBJECT: null NODE: null URL: null EVENT SEVERITY: 1 MESSAGE: cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid. LINKED EXCEPTION: org.xml.sax.SAXParseException: cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid. LOCATOR LINE NUMBER: 3 COLUMN NUMBER: 25 OFFSET: -1 OBJECT: null NODE: null URL: null EVENT SEVERITY: 1 MESSAGE: cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point. LINKED EXCEPTION: org.xml.sax.SAXParseException: cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point. LOCATOR LINE NUMBER: 7 COLUMN NUMBER: 12 OFFSET: -1 OBJECT: null NODE: null URL: null
JAXBには、マーシャリング・プロセス中およびアンマーシャリング・プロセス中に、イベント・コールバックを取得するメカニズムがいくつか用意されています。マップされたオブジェクトにコールバック・メソッドを直接指定するか、または別のListener
クラスを定義し、JAXBランタイムに登録できます。
JAXBでマップしたオブジェクトのいずれに対しても、特殊なメソッドをいくつか指定して、そのオブジェクトがマーシャリングまたはアンマーシャリングされたときに、イベント通知を受信できるようにするというオプションもあります。これらのメソッドには次のシグネチャが必要です。
/** * Invoked by Marshaller after it has created an instance of this object. */ void beforeMarshal(Marshaller m); /** * Invoked by Marshaller after it has marshalled all properties of this object. */ void afterMarshal(Marshaller m); /** * This method is called immediately after the object is created and before the unmarshalling of this * object begins. The callback provides an opportunity to initialize JavaBean properties prior to unmarshalling. */ void beforeUnmarshal(Unmarshaller u, Object parent); /** * This method is called after all the properties (except IDREF) are unmarshalled for this object, * but before this object is set to the parent object. */ void afterUnmarshal(Unmarshaller u, Object parent);
次の例は、Companyオブジェクトが処理されるたびにログに書き込む方法を示しています。
例2-29 イベント・リスナーのサンプル
package example; import java.util.Date; import java.util.logging.Logger; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Company { @XmlAttribute private String id; void beforeMarshal(Marshaller m) { Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread()); } void afterMarshal(Marshaller m) { Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread()); } void beforeUnmarshal(Unmarshaller u, Object parent) { Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread()); } void afterUnmarshal(Unmarshaller u, Object parent) { Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread()); } }
JAXBのマーシャラ・インタフェースとアンマーシャラ・インタフェースは両方ともsetListener()
メソッドを定義し、それによって、独自のカスタム・リスナーを設定して、マーシャリング・イベントおよびアンマーシャリング・イベントを遮断できます。
package javax.xml.bind; public interface Marshaller { ... public static abstract class Listener { /** * Callback method invoked before marshalling from source to XML. * * This method is invoked just before marshalling process starts to marshal source. * Note that if the class of source defines its own beforeMarshal method, * the class specific callback method is invoked just before this method is invoked. * * @param source instance of JAXB mapped class prior to marshalling from it. */ public void beforeMarshal(Object source) {} /** * Callback method invoked after marshalling source to XML. * * This method is invoked after source and all its descendants have been marshalled. * Note that if the class of source defines its own afterMarshal method, * the class specific callback method is invoked just before this method is invoked. * * @param source instance of JAXB mapped class after marshalling it. */ public void afterMarshal(Object source) {} } } package javax.xml.bind; public interface Unmarshaller { ... public static abstract class Listener { /** * Callback method invoked before unmarshalling into target. * * This method is invoked immediately after target was created and * before the unmarshalling of this object begins. Note that * if the class of target defines its own beforeUnmarsha method, * the class specific callback method is invoked before this method is invoked. * * @param target non-null instance of JAXB mapped class prior to unmarshalling into it. * @param parent instance of JAXB mapped class that will eventually reference target. * null when target is root element. */ public void beforeUnmarshal(Object target, Object parent) {} /** * Callback method invoked after unmarshalling XML data into target. * * This method is invoked after all the properties (except IDREF) are unmarshalled into target, * but before target is set into its parent object. * Note that if the class of target defines its own afterUnmarshal method, * the class specific callback method is invoked before this method is invoked. * * @param target non-null instance of JAXB mapped class prior to unmarshalling into it. * @param parent instance of JAXB mapped class that will reference target. * null when target is root element. */ public void afterUnmarshal(Object target, Object parent) {} } }
この例で実行するロギングは、前述のものと同じですが、汎用的なListener
クラスを使用する点が異なります。これによって、システム内のJAXBオブジェクトをすべてログに記録しやすくなります。
例2-30 リスナー・クラスを使用したロギング
package example; import java.util.logging.Logger; private class MarshalLogger extends Marshaller.Listener { @Override public void afterMarshal(Object source) { Logger.getLogger("example").info(source + " " + Thread.currentThread()); } @Override public void beforeMarshal(Object source) { Logger.getLogger("example").info(source + " " + Thread.currentThread()); } } package example; import java.util.logging.Logger; private class UnmarshalLogger extends Unmarshaller.Listener { @Override public void afterUnmarshal(Object target, Object parent) { Logger.getLogger("example").info(target + " " + Thread.currentThread()); } @Override public void beforeUnmarshal(Object target, Object parent) { Logger.getLogger("example").info(target + " " + Thread.currentThread()); } }
次のコードによってリスナーが設定されます。
Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setListener(new MarshalLogger()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); unmarshaller.setListener(new UnmarshalLogger());
次に示すのは、典型的なマーシャリング/アンマーシャリングの例で、クラス・レベルとマーシャラ/アンマーシャラ・レベル両方のイベント出力を示しています。
Jun 2, 2011 6:31:59 PM example.Company beforeMarshal INFO: COMPANY:[id=Zoltrix] Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal INFO: example.Company@10e790c Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal INFO: example.Employee@1db7df8 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal INFO: example.Employee@1db7df8 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal INFO: example.Employee@3570b0 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal INFO: example.Employee@3570b0 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal INFO: example.Employee@79717e Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal INFO: example.Employee@79717e Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Company afterMarshal INFO: COMPANY:[id=Zoltrix] Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal INFO: example.Company@10e790c Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Company beforeUnmarshal INFO: COMPANY:[id=null] Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal INFO: example.Company@f0c0d3 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal INFO: example.Employee@4f80d6 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal INFO: example.Employee@4f80d6 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal INFO: example.Employee@1ea0252 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal INFO: example.Employee@1ea0252 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal INFO: example.Employee@3e89c3 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal INFO: example.Employee@3e89c3 Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Company afterUnmarshal INFO: COMPANY:[id=Zoltrix] Thread[main,5,main] Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal INFO: example.Company@f0c0d3 Thread[main,5,main]
EclipseLink MOXyでは、従来のJavaのアクセス方法を使用してオブジェクトの値を取得および設定する方法に加えて、XPath文を使用して値にアクセスすることもできます。EclipseLinkのJAXBContext
には、XPathで値を取得および設定できるようにするための特殊なAPIがあります。
たとえば、次のXML文書を考えてみます。
<customer id="1141"> <first-name>Jon</first-name> <last-name>Smith</last-name> <phone-number> <area-code>515</area-code> <number>2726652</number> </phone-number> </customer>
典型的なアプリケーション・コードは、次のようになる場合があります。
Customer customer = (Customer) jaxbContext.createUnmarshaller().unmarshal(instanceDoc); ... int customerId = customer.getId(); customer.setFirstName("Bob"); customer.getPhoneNumber().setAreaCode("555"); ... jaxbContext.createMarshaller().marshal(customer, System.out);
かわりに、XPathを使用してこれらの値にアクセスできます。
Customer customer = (Customer) jaxbContext.createUnmarshaller().unmarshal(instanceDoc); ... int customerId = jaxbContext.getValueByXPath(customer, "@id", null, Integer.class); jaxbContext.setValueByXPath(customer, "first-name/text()", null, "Bob"); jaxbContext.setValueByXPath(customer, "phone-number/area-code/text()", null, "555"); ... jaxbContext.createMarshaller().marshal(customer, System.out);
(JAXB 2.0で導入された)JAXBバインダ・インタフェースでは、アイテムの一部のみがマップされている場合でも、XML文書全体を保持できます。
通常の場合、アンマーシャラを使用してXML文書をオブジェクトにロードしたり、マーシャラを使用してオブジェクトを元のXMLに保存すると、マップされていないコンテンツは失われます。
JAXBContextからバインダを作成して、DOM形式のXMLと対話できます。バインダにより、Javaオブジェクトとそれに対応するDOMノードとの間の関連付けが保持されます。
例2-31 バインダの使用
import java.io.File; import javax.xml.bind.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.*; public class BinderDemo { public static void main(String[] args) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); File xml = new File("input.xml"); Document document = db.parse(xml); JAXBContext jc = JAXBContext.newInstance(Customer.class); Binder<Node> binder = jc.createBinder(); Customer customer = (Customer) binder.unmarshal(document); customer.getAddress().setStreet("2 NEW STREET"); PhoneNumber workPhone = new PhoneNumber(); workPhone.setType("work"); workPhone.setValue("555-WORK"); customer.getPhoneNumbers().add(workPhone); binder.updateXML(customer); TransformerFactory tf = TransformerFactory.newInstance(); Transformer t = tf.newTransformer(); t.transform(new DOMSource(document), new StreamResult(System.out)); } }
バインダは、XML文書を新規作成するのでなく、元のDOMに変更を適用することによって、XML情報セット全体を保持します。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <customer> <UNMAPPED_ELEMENT_1/> <name>Jane Doe</name> <!-- COMMENT #1 --> <address> <UNMAPPED_ELEMENT_2/> <street>2 NEW STREET</street> <!-- COMMENT #2 --> <UNMAPPED_ELEMENT_3/> <city>Any Town</city> </address> <!-- COMMENT #3 --> <UNMAPPED_ELEMENT_4/> <phone-number type="home">555-HOME</phone-number> <!-- COMMENT #4 --> <phone-number type="cell">555-CELL</phone-number> <phone-number type="work">555-WORK</phone-number> <UNMAPPED_ELEMENT_5/> <!-- COMMENT #5 --> </customer>