この章の内容は次のとおりです。
EclipseLink MOXy 2.3で導入された機能として、実行時にJAXBContext
メタデータをリフレッシュできます。これにより、ライブ・アプリケーション環境における既存のマッピングに変更を加え、それらの変更を新しいJAXBContext
を作成せずにただちに表示できます。
メタデータ・リフレッシュ機能を使用するには、メタデータ情報が次のいずれかの形式で提供されている必要があります。
javax.xml.transform.Source
org.w3c.dom.Node
org.eclipse.persistence.jaxb.metadata.MetadataSource
例8-1 メタデータのリフレッシュ
この例では、次のEclipseLink OXMファイルからブートストラップされます。
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="example"> <java-types> <java-type name="Root"> <java-attributes> <xml-element java-attribute="name" name="orig-name"/> </java-attributes> </java-type> </java-types> </xml-bindings>
JAXBContext
は次に示す標準の方法で作成されます。
... ClassLoader classLoader = ClassLoader.getSystemClassLoader(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder db = dbf.newDocumentBuilder(); InputStream metadataStream = classLoader.getResourceAsStream("example/eclipselink-oxm.xml"; Document metadataDocument = db.parse(metadataStream); metadataStream.close(); Map<String, Object> props = new HashMap<String, Object>(1); props.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadataDocument); JAXBContext context = JAXBContextFactory.createContext(new Class[] { Root.class }, props); ...
この時点でRoot
オブジェクトをXMLにマーシャリングする場合、次のようになります。
<root> <orig-name>RootName</orig-name> </root>
この例では、メタデータ・ドキュメントを直接修正して名前フィールドのXML名を変更します。その後、次のrefreshMetadata()
APIを使用してメタデータをリフレッシュできます。
... Element xmlElementElement = (Element) metadataDocument.getElementsByTagNameNS("http://www.eclipse.org/eclipselink/xsds/persistence/oxm", "xml-element").item(0); xmlElementElement.setAttribute("name", "new-name"); JAXBHelper.getJAXBContext(jc).refreshMetadata(); ...
メタデータのリフレッシュ後、同一のRoot
オブジェクトが次のようにマーシャリングされます。
<root> <new-name>RootName</new-name> </root>
JAXBは、Java名からXML名への変換のための確立したルールです。これは、注釈の使用によりオーバーライドできます。一般的なネーミング・ルール(すべてを大文字にするなど)に従うことが難しい場合もあります。EclipseLink MOXy 2.3以降では、デフォルトのネーミング・アルゴリズムをオーバーライドできます。
この例では、XMLNameTransformer
の実装を作成してMOXyにネーミング・アルゴリズムを提供します。
XMLNameTransformer
インタフェースでは、次のような名前生成のカスタマイズのための様々なメソッドを定義します。
transformElementName
: Javaフィールドまたはメソッドから要素を作成するときにコールされます。
transformAttributeName
: Javaフィールドまたはメソッドから属性を作成するときにコールされます。
transformTypeName
: Javaクラスから単純型または複合型を作成するときにコールされます。
transformRootElementName
: Javaクラスから(ルート)単純型または複合型を作成するときにコールされます。
例8-2では、次のことを行うXMLNameTransformer
を定義します。
ルート要素は非修飾Javaクラス名になります。
その他の型は(非修飾Javaクラス名) + "Type"という名前になります。
キャメル・ケース要素名は、小文字のハイフンで連結された名前に変換されます。
XML属性はすべて大文字で表示されます。
例8-2 XMLNameTransformerの使用
package example; public class NameGenerator implements org.eclipse.persistence.oxm.XMLNameTransformer { // Use the unqualified class name as our root element name. public String transformRootElementName(String name) { return name.substring(name.lastIndexOf('.') + 1); } // The same algorithm as root element name plus "Type" appended to the end. public String transformTypeName(String name) { return transformRootElementName(name) + "Type"; } // The name will be lower-case with word breaks represented by '-'. // Note: A capital letter in the original name represents the start of a new word. public String transformElementName(String name) { StringBuilder strBldr = new StringBuilder(); for (char character : name.toCharArray()) { if (Character.isUpperCase(character)) { strBldr.append('-'); strBldr.append(Character.toLowerCase(character)); } else { strBldr.append(character); } } return strBldr.toString(); } // The original name converted to upper-case. public String transformAttributeName(String name) { return name.toUpperCase(); } }
例8-3のドメイン・モデルが使用されます。スペースの都合上、アクセッサは省略します。
ネーミング・アルゴリズムの実装は、@XmlNameTransformer
注釈(パッケージ・レベルまたはタイプ・レベル)を介して、またはXMLの外部バインディング・ファイルを介して提供されます。
タイプ・レベル:
@XmlNameTransformer(example.NameGenerator.class) public class Customer
パッケージ・レベル(package-info.java):
@XmlNameTransformer(example.NameGenerator.class) package example;
外部バインディング・ファイル:
<?xml version='1.0' encoding='UTF-8'?> <xml-bindings xmlns='http://www.eclipse.org/eclipselink/xsds/persistence/oxm' xml-name-transformer='example.NameGenerator'> <xml-schema/> <java-types/> </xml-bindings>
カスタマイズされていない場合、JAXBのデフォルトのネーミング・アルゴリズムでは、次のようなXMLが生成されます。
<?xml version="1.0" encoding="UTF-8"?> <customer id="123"> <fullName>Jane Doe</fullName> <shippingAddress type="residential"> <street>1 Any Street</street> </shippingAddress> </customer>
カスタマイズ済のネーミング・アルゴリズムを利用すると、ドメイン・クラスに追加のメタデータを指定せずに、次の出力を取得できます。
<?xml version="1.0" encoding="UTF-8"?> <Customer ID="123"> <full-name>Jane Doe</full-name> <shipping-address TYPE="residential"> <street>1 Any Street</street> </shipping-address> </Customer>
(Javaフィールドおよびアクセッサ・メソッドで表される)標準JAXBプロパティに加え、EclipseLink MOXy 2.3では、特別なget()
およびset()
メソッドに依存してマッピング・データを維持する仮想プロパティおよび仮想アクセス・メソッドの概念が導入されました。たとえば、特定のマッピングのためのデータ保持の基礎となる構造として、HashMap
を使用する場合があります。仮想メソッド・アクセスを使用するマッピングが、EclipseLink OXMメタデータに定義されている必要があります。
エンティティに仮想プロパティを追加するには、次の条件を満たす必要があります。
Javaクラスに@XmlVirtualAccessMethods
注釈またはOXMの<xml-virtual-access-methods>
要素のマークが付いている必要があります。
Javaクラスに次の仮想プロパティ値にアクセスするためのgetterメソッドおよびsetterメソッドが含まれている必要があります。
public <ValueType> get(String propertyName)
public void set(String propertyName, <ValueType> value)
メソッド名は構成可能です。
<ValueType>
はObject
またはその他のJavaタイプ(メソッド・シグネチャで特定の値タイプ・クラスを使用する場合)にできます。
注意: デフォルトでは、EclipseLinkは |
マルチテナント・アーキテクチャで仮想プロパティを使用する例は、「拡張可能MOXyの使用」を参照してください。
仮想アクセス・メソッドは、Java注釈(例8-5を参照)またはEclipseLink OXMメタデータ(例8-6を参照)を通して構成できます。
例8-5 Java注釈の使用
package example; import java.util.Map; import java.util.HashMap; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods; @XmlRootElement @XmlVirtualAccessMethods @XmlAccessorType(XmlAccessType.PROPERTY) public class Customer { private int id; private String name; private Map<String, Object> extensions = new HashMap<String, Object>(); public Object get(String name) { return extensions.get(name); } public void set(String name, Object value) { extensions.put(name, value); } @XmlAttribute public int getId() { ... }
この例では、Customerクラス(例8-3)を仮想マッピングを定義するEclipseLink OXMファイルとともに使用します。このファイルに出現する、対応するJava属性を持たないプロパティは、仮想プロパティとみなされ、仮想アクセス・メソッドを介してアクセスされます。関連付けられたJavaフィールドがないため、type
情報も指定する必要があります。
例8-7 virtualprops-oxm.xmlファイル
... <java-types> <java-type name="Customer"> <java-attributes> <xml-element java-attribute="discountCode" name="discount-code" type="java.lang.String" /> </java-attributes> </java-type> </java-types> ...
JAXBContext
の作成時に、virtualprops
メタデータをCustomerクラスとともに渡します。
仮想プロパティの値を設定するには、前述のset()
メソッドを使用します。
InputStream oxm = classLoader.getResourceAsStream("virtualprops-oxm.xml"); Map<String, Object> properties = new HashMap<String, Object>(); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, oxm); Class[] classes = new Class[] { Customer.class }; JAXBContext ctx = JAXBContext.newInstance(classes, properties); Customer c = new Customer(); c.setId(7761); c.setName("Bob Smith"); c.set("discountCode", "SIUB372JS7G2IUDS7"); ctx.createMarshaller().marshal(e, System.out);
これにより、次のXMLが生成されます。
<customer id="7761"> <name>Bob Smith</name> <discount-code>SIUB372JS7G2IUDS7</discount-code> </customer>
反対に、仮想プロパティにアクセスするにはget(String)
メソッドを使用します。
... Customer c = (Customer) ctx.createUnmarshaller().unmarshal(CUSTOMER_URL); // Populate UI customerWindow.getTextField(ID).setText(String.valueOf(c.getId())); customerWindow.getTextField(NAME).setText(c.getName()); customerWindow.getTextField(DCODE).setText(c.get("discountCode")); ...
XmlAccessType.FIELD
の@XmlAccessorType
を使用している場合、Map
自身がXMLにバインドされるのを避けるため、仮想プロパティのMap
属性を@XmlTransient
とマークする必要があります。
例8-8 Map属性のマーク付け
package example;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
@XmlRootElement
@XmlVirtualAccessMethods
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
@XmlTransient
private Map<String, Object> extensions;
...
仮想メソッド・アクセッサとして異なるメソッド名を使用するには、@XmlVirtualAccessMethods
でgetMethodName
属性およびsetMethodName
属性を使用してその名前を指定します。
例8-9 代替のアクセッサ・メソッドの使用
package example; import java.util.Properties; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods; @XmlRootElement @XmlVirtualAccessMethods(getMethod = "getCustomProps", setMethod = "putCustomProps") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlAttribute private int id; private String name; @XmlTransient private Properties<String, Object> props = new Properties<String, Object>(); public Object getCustomProps(String name) { return props.getProperty(name); } public void putCustomProps(String name, Object value) { props.setProperty(name, value); } }
OXMで、次のようにします。
例8-10 xml-virtual-access-methods要素の使用
...
<java-types>
<java-type name="Customer">
<xml-virtual-access-methods get-method="getCustomProps" set-method="putCustomProps" />
<java-attributes>
<xml-attribute java-attribute="id" />
<xml-element java-attribute="name" />
<!-- virtual -->
<xml-element java-attribute="discountCode" name="discount-code"
type="java.lang.String" />
</java-attributes>
</java-type>
...
@XmlVirtualAccessMethods
でschema
属性を使用して、生成されたスキーマでの仮想プロパティの表示方法を構成できます。EclipseLinkには、2つのオプションがあります。仮想プロパティは次のことができます。
個々のノードとして記述します。
単一の<any>
要素に統合します。
これは、EclipseLinkのデフォルトの動作ですが、次のようにオーバーライドを明示的に指定できます。
例8-11 個々のノードとしてのマッピング
package example; @XmlRootElement @XmlVirtualAccessMethods(schema = XmlVirtualAccessMethodsSchema.NODES) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { ...
次に例を示します。
例8-12 元のCustomerスキーマ
<xs:schema ...> <xs:element name="customer"> <xs:complexType> <xs:sequence> <xs:element name="first-name" type="xs:string" /> <xs:element name="last-name" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
例8-13 生成されたスキーマ(middle-initialとphone-numberの追加後)
<xs:schema ...> <xs:element name="customer"> <xs:complexType> <xs:sequence> <xs:element name="first-name" type="xs:string" /> <xs:element name="last-name" type="xs:string" /> <xs:element name="middle-initial" type="xs:string" /> <xs:element name="phone-number" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
<any>
要素の仮想プロパティEclipseLinkは、<any>
要素を使用して1つのノードにすべての仮想プロパティを格納することもできます。
例8-14 <any>
要素の使用
package example; @XmlRootElement @XmlVirtualAccessMethods(schema = XmlVirtualAccessMethodsSchema.ANY) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { ...
例8-14で、このアプローチを使用して新しく生成されたスキーマは、次のようになります。
マルチテナント・アーキテクチャでは、サーバーで1つのアプリケーションが動作して、複数のクライアント組織(テナント)にサービスを提供します。優れたマルチテナント・アプリケーションでは、テナントごとのカスタマイズが可能です。これらのカスタマイズがデータに対して行われる場合は、バインディング・レイヤーでカスタマイズを処理するのが難しい場合があります。
JAXBは、実際のフィールドおよびプロパティがあるドメイン・モデルを操作するように設計されています。EclipseLink MOXy仮想プロパティは、ソースを変更しなくてもクラスを拡張できます。
クラスが拡張可能であることを指定するには、@XmlVirtualAccessMethods
注釈が使用されます。プロパティ名によって値を返すget
メソッド、およびプロパティ名によって値を格納するset
メソッドには、拡張可能クラスが必要です。これらのメソッドのデフォルト名は、get
およびset
で、@XmlVirtualAccessMethods
注釈でオーバーライドできます。
この例では複数の拡張可能クラスがあるため、拡張可能クラスが拡張できる、この動作のベース・クラスを構成します。@XmlTransient
注釈を使用することにより、ExtensibleBase
が継承関係としてマップされないようにします。モデル内で、すべてのテナントに共通な部分は、実際のプロパティで表されます。テナントごとの拡張機能は、仮想プロパティとして表されます。
例8-16 ExtensibleBaseのサンプル
package examples.virtual;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.XmlTransient;
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;
@XmlTransient
@XmlVirtualAccessMethods(setMethod="put")
public class ExtensibleBase {
private Map<String, Object> extensions = new HashMap<String, Object>();
public <T> T get(String property) {
return (T) extensions.get(property);
}
public void put(String property, Object value) {
extensions.put(property, value);
}
}
例8-17 Customer
Customerクラスは、@XmlVirtualAccessMethods
注釈が付けられたドメイン・クラスから継承するので拡張可能になります。
package examples.virtual;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Customer extends ExtensibleBase {
private String firstName;
private String lastName;
private Address billingAddress;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Address getBillingAddress() {
return billingAddress;
}
public void setBillingAddress(Address billingAddress) {
this.billingAddress = billingAddress;
}
}
例8-18 Address
モデル内のすべてのクラスを拡張可能にする必要はありません。この例のAddressクラスには、仮想プロパティはありません。
package examples.virtual;
public class Address {
private String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
例8-19 PhoneNumber
Customerと同様に、PhoneNumberは拡張可能なクラスになります。
package examples.virtual;
import javax.xml.bind.annotation.XmlValue;
public class PhoneNumber extends ExtensibleBase {
private String number;
@XmlValue
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
最初のテナントは、オンライン・スポーツ用品店で、次のようにモデルを拡張する必要があります。
顧客ID
顧客のミドル・ネーム
配送先住所
連絡先電話番号のコレクション
電話番号のタイプ(自宅、勤務先または携帯など)
仮想プロパティのメタデータは、MOXyのXMLマッピング・ファイルを介して指定します。仮想プロパティは、実際のプロパティと同様の方法でマップされます。タイプ(リフレクションでは特定できないため)や、コレクション・プロパティの場合はコンテナ・タイプなど、追加情報が必要です。
例8-20のCustomerで定義されている仮想プロパティは、middleName、shippingAddressおよびphoneNumbersです。PhoneNumberの仮想プロパティはtype
プロパティです。
例8-20 binding-tenant1.xml
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="examples.virtual"> <java-types> <java-type name="Customer"> <xml-type prop-order="firstName middleName lastName billingAddress shippingAddress phoneNumbers"/> <java-attributes> <xml-attribute java-attribute="id" type="java.lang.Integer"/> <xml-element java-attribute="middleName" type="java.lang.String"/> <xml-element java-attribute="shippingAddress" type="examples.virtual.Address"/> <xml-element java-attribute="phoneNumbers" name="phoneNumber" type="examples.virtual.PhoneNumber" container-type="java.util.List"/> </java-attributes> </java-type> <java-type name="PhoneNumber"> <java-attributes> <xml-attribute java-attribute="type" type="java.lang.String"/> </java-attributes> </java-type> </java-types> </xml-bindings>
get
/set
メソッドは、実際のプロパティを操作するためにドメイン・モデルで使用し、@XmlVirtualAccessMethods
注釈に定義されているアクセッサは、仮想プロパティを操作するために使用します。通常のJAXBメカニズムは、次に示す操作のマーシャリングおよびアンマーシャリングに使用します。
Customer customer = new Customer(); //Set Customer's real properties customer.setFirstName("Jane"); customer.setLastName("Doe"); Address billingAddress = new Address(); billingAddress.setStreet("1 Billing Street"); customer.setBillingAddress(billingAddress); //Set Customer's virtual 'middleName' property customer.put("middleName", "Anne"); //Set Customer's virtual 'shippingAddress' property Address shippingAddress = new Address(); shippingAddress.setStreet("2 Shipping Road"); customer.put("shippingAddress", shippingAddress); List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); customer.put("phoneNumbers", phoneNumbers); PhoneNumber workPhoneNumber = new PhoneNumber(); workPhoneNumber.setNumber("555-WORK"); //Set the PhoneNumber's virtual 'type' property workPhoneNumber.put("type", "WORK"); phoneNumbers.add(workPhoneNumber); PhoneNumber homePhoneNumber = new PhoneNumber(); homePhoneNumber.setNumber("555-HOME"); //Set the PhoneNumber's virtual 'type' property homePhoneNumber.put("type", "HOME"); phoneNumbers.add(homePhoneNumber); Map<String, Object> properties = new HashMap<String, Object>(); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "examples/virtual/binding-tenant1.xml"); JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class, Address.class}, properties); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out);
例8-21 出力
<?xml version="1.0" encoding="UTF-8"?> <customer> <firstName>Jane</firstName> <middleName>Anne</middleName> <lastName>Doe</lastName> <billingAddress> <street>1 Billing Street</street> </billingAddress> <shippingAddress> <street>2 Shipping Road</street> </shippingAddress> <phoneNumber type="WORK">555-WORK</phoneNumber> <phoneNumber type="HOME">555-HOME</phoneNumber> </customer>
2番目のテナントは、オンデマンドの映画や音楽を提供するストリーミング・メディア・プロバイダです。ここでは、コア・モデルに対して単一の連絡先電話番号を拡張する必要があります。
このテナントの場合、例8-22に示すように、マッピング・ファイルも利用して実際のプロパティのマッピングをカスタマイズします。
例8-22 binding-tenant2.xml
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="examples.virtual"> <xml-schema namespace="urn:tenant1" element-form-default="QUALIFIED"/> <java-types> <java-type name="Customer"> <xml-type prop-order="firstName lastName billingAddress phoneNumber"/> <java-attributes> <xml-attribute java-attribute="firstName"/> <xml-attribute java-attribute="lastName"/> <xml-element java-attribute="billingAddress" name="address"/> <xml-element java-attribute="phoneNumber" type="examples.virtual.PhoneNumber"/> </java-attributes> </java-type> </java-types> </xml-bindings>
Customer customer = new Customer(); customer.setFirstName("Jane"); customer.setLastName("Doe"); Address billingAddress = new Address(); billingAddress.setStreet("1 Billing Street"); customer.setBillingAddress(billingAddress); PhoneNumber phoneNumber = new PhoneNumber(); phoneNumber.setNumber("555-WORK"); customer.put("phoneNumber", phoneNumber); Map<String, Object> properties = new HashMap<String, Object>(); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "examples/virtual/binding-tenant2.xml"); JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class, Address.class}, properties); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out);
デフォルトでは、JAXBはJavaのフィールド名をXML要素名として使用します。
例8-24 JavaコードとXMLスキーマのサンプル
public class Customer { @XmlElement private String firstName; @XmlElement private String lastName; } <?xml version="1.0" encoding="UTF-8"?> <customer> <firstName>Bob</firstName> <lastName>Roberts</lastName> </customer>
また、XML名は次のように@XmlElement
注釈の名前属性を使用してカスタマイズできます。
例8-25 JavaコードとXMLスキーマのサンプル
public class Customer { @XmlElement(name="f-name") private String firstName; @XmlElement(name="l-name") private String lastName; } <?xml version="1.0" encoding="UTF-8"?> <customer> <f-name>Bob</f-name> <l-name>Roberts</l-name> </customer>
ただし、場合によっては、ドキュメント内の位置に基づいて、または要素の属性値に基づいて要素がマップされる必要があります。
<?xml version="1.0" encoding="UTF-8"?> <node> <name>Jane</name> <name>Doe</name> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
このような場合、EclipseLink MOXyでXPath述語を使用して、XML要素の名前を指定する式を定義できます。
XPath述語は、指定した要素に対して評価する式を表します。たとえば、XPath文は次のようになります。
node[2]
これは、2番目に出現するnode要素(DEF)と一致します。
<?xml version="1.0" encoding="UTF-8"?> <data> <node>ABC</node> <node>DEF</node> </data>
また、述語は次の属性値に基づいて照合することもできます。
node[@name='foo']
これは、属性の名前がfoo(つまりABC)のnode要素と一致します。DEFを含むノードとは一致しません。
<?xml version="1.0" encoding="UTF-8"?> <data> <node name="foo">ABC</node> <node name="bar">DEF</node> </data>
注意: XPath述語の詳細は、『XML Path Language (XPath) specification』( |
次の例では、2つのname要素がXMLに含まれています。最初の名前は顧客の名を表し、2番名の名前は姓を表しています。これをマップするには、適切なXML要素に一致する各プロパティに対してXPath式を指定します。また、@XmlType(propOrder)
も使用して、要素が常に正しい位置に配置されるようにします。
例8-26 @XmlType(propOrder)注釈の使用
package example;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
@XmlRootElement
@XmlType(propOrder={"firstName", "lastName"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
@XmlPath("name[1]/text()")
private String firstName;
@XmlPath("name[2]/text()")
private String lastName;
...
}
同一の構成は、次に示すEclipseLink XMLバインディングに関するドキュメントでも表現できます。
例8-27 prop-order属性の使用
... <java-type name="Customer"> <xml-root-element/> <xml-type prop-order="firstName lastName"/> <java-attributes> <xml-element java-attribute="firstName" xml-path="name[1]/text()"/> <xml-element java-attribute="lastName" xml-path="name[2]/text()"/> </java-attributes> </java-type> ...
これにより、目的のXML表現が生成されます。
EclipseLink MOXy 2.3以降では、属性値に基づいてXML要素にマップすることもできます。この例では、すべてのXML要素は名前付きノードですが、次の名前属性の値によって区別されています。
例8-29 XMLスキーマのサンプル
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
XPathをelement-name[@attribute-name='value']
の形式で使用して各Javaフィールドをマップできます。
例8-30 マッピングのサンプル
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath("node[@name='address']") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... } package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlAccessorType(XmlAccessType.FIELD) public class Address { @XmlPath("node[@name='street']/text()") private String street; ... } package example; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlAttribute private String type; @XmlValue private String number; ... }
EclipseLinkでは、ターゲット・オブジェクトからのデータがソース・オブジェクトのXML要素内に表示されるように1対1のマッピングを構成することもできます。前述の例を拡張して、Address情報をマッピングして、自身の要素内にラップするのではなく、customer要素の下に直接表示させることができます。これは、セルフ・マッピングと呼ばれ、ターゲット・オブジェクトのXPathを . (ドット)に設定することで実現されます。
例8-31に、注釈で宣言するセルフ・マッピングを示します。
例8-31 セルフ・マッピングの例
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath(".") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... }
EclipseLinkでセルフ・マッピングを使用すると、目的のXMLが生成されます。streetデータはルート・ノードに格納されます。
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="street">123 A Street</node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
一部のJavaクラスは、JAXBでの使用に適したものではない場合があり、一見したところマップ可能ではないように見えます。たとえば、デフォルトのno-arg
コンストラクタを持たないクラスや、XML表現が自動的に判別されないクラスなどです。JAXBのXmlAdapter
を使用すると、カスタム・コードを定義してマップ可能ではないクラスをJAXBが操作できるものに変換できます。次に、@XmlJavaTypeAdapter
注釈を使用して、マップ可能ではないクラスを操作するときはアダプタを使用するよう示すことができます。
XmlAdapter
では次の用語が使用されます。
ValueType
: JAXBがデフォルトで操作方法を認識している場合のタイプ。
BoundType
: JAXBが操作方法を認識していない場合のタイプ。このタイプがValueTypeを通してメモリー内の表現として使用できるよう、アダプタが記述されます。
XmlAdapterクラスの概要は次のとおりです。
例8-32 XmlAdapterクラスの概要
public class AdapterName extends XmlAdapter<ValueType, BoundType> { public BoundType unmarshal(ValueType value) throws Exception { ... } public ValueType marshal(BoundType value) throws Exception { ... } }
1番目の例では、次のドメイン・クラスを使用します。
例8-33 ドメイン・クラスのサンプル
package example; import java.util.Currency; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class PurchaseOrder { private Double amount; private Currency currency; ... }
ここでは、CurrencyはJAXBでは自動的にマップされません。引数なしのコンストラクタが含まれていないためです。ただし、CurrencyをJAXBで操作できる単純なString
に変換するアダプタを記述できます。ここでは、幸いにもCurrencyのtoString()
メソッドは、新しいCurrencyの作成に使用できる通貨コードを戻します。
例8-34 アダプタの使用
package example; import java.util.Currency; import javax.xml.bind.annotation.adapters.XmlAdapter; public class CurrencyAdapter extends XmlAdapter<String, Currency> { /* * Java => XML * Given the unmappable Java object, return the desired XML representation. */ public String marshal(Currency val) throws Exception { return val.toString(); } /* * XML => Java * Given an XML string, use it to build an instance of the unmappable class. */ public Currency unmarshal(String val) throws Exception { return Currency.getInstance(val); } }
アダプタをCurrencyプロパティのために使用することを示すため、@XmlJavaTypeAdapter
で注釈を付け、アダプタのクラス名を指定します。
時として、マップ可能ではないクラスを処理する最適な方法は、JAXBでマップできるかわりのクラスを記述し、XmlAdapter
内で2つのクラス間で変換を行うことです。この例では、Pointクラスを使用します。このクラスのgetLocation()
メソッド(JAXBが自動的に取得してマップする)により、マーシャリング中に無限ループが発生します。Pointクラスは変更できないため、新しいクラスのMyPointを記述してアダプタ内で使用します。
例8-36 java.awt.Pointの使用
package example; public class MyPoint { private int x, y; public MyPoint() { this(0, 0); } public MyPoint(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } ... } package example; import java.awt.Point; import javax.xml.bind.annotation.adapters.XmlAdapter; public class MyPointAdapter extends XmlAdapter<MyPoint, Point> { /* * Java => XML */ public MyPoint marshal(Point val) throws Exception { return new MyPoint((int) val.getX(), (int) val.getY()); } /* * XML => Java */ public Point unmarshal(MyPoint val) throws Exception { return new Point(val.getX(), val.getY()); } }
これで、Pointプロパティが@XmlJavaTypeAdapter
でマーク付けされました。
例8-37 @XmlJavaTypeAdapter注釈の使用
package example; import java.awt.Point; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Zone { private String name; @XmlJavaTypeAdapter(MyPointAdapter.class) private Point startCoord; @XmlJavaTypeAdapter(MyPointAdapter.class) private Point endCoord; ... }
例8-37では、両方のPointプロパティに@XmlJavaTypeAdapter
注釈で注釈付けをしています。ドメイン・クラスなどにこれらのプロパティのタイプが多数ある場合、パッケージ・レベルで@XmlJavaTypeAdaptersを指定するとより便利です。
両方のアダプタ・クラスをpackage-info.javaに定義できます。その場合、CurrencyプロパティやPointプロパティに注釈付けする必要がなくなります。
@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(value=CurrencyAdapter.class,type=Currency.class), @XmlJavaTypeAdapter(value=MyPointAdapter.class,type=Point.class) }) package example;
Javaクラスがあり、マーシャリングおよびアンマーシャリング中に常にXmlAdapter
を使用する場合、次のように@XmlJavaTypeAdapter
をクラス・レベルで直接指定します。
package example; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlJavaTypeAdapter(DataStructureAdapter.class) public class DataStructure { ... }
これで、DataStructureプロパティを持つオブジェクトが、プロパティ自身に注釈付けを行うことなく自動的にDataStructureAdapter
を使用するようになります。
多くの場合、MOXyの@XmlTransformation
注釈を使用して、オブジェクトのマーシャリングおよびアンマーシャリングをかなり制御できます。1つ以上のXMLノードがJava属性の値の作成に使用できる場合に、@XmlTransformation
を使用してカスタム・マッピングを作成できます。
マーシャリング(書込み)およびアンマーシャリング(読取り)時のカスタム要件を処理するには、@XmlTransformation
でorg.eclipse.persistence.mappings.transformers
のインスタンス(AttributeTransformer
やFieldTransformer
など)を使用します。これにより、ドメイン・オブジェクトが特別なインタフェースを実装する必要がない非侵入型ソリューションが提供されます。
たとえば、次のXMLをオブジェクトにマップしてDATE
とTIME
の値をsingle java.util.Date
オブジェクトに結合する場合、@XmlTransformation:
を使用できます。
<ELEM_B> <B_DATE>20100825</B_DATE> <B_TIME>153000</B_TIME> <NUM>123</NUM> <C_DATE>20100825</C_DATE> <C_TIME>154500</C_TIME> </ELEM_B>
注意: 通常は
このような問題があるため、MOXyのトランスフォーメーション・マッピングのほうがより実装がより容易です。 |
例8-38 マッピングの例
package example; import java.util.Date; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name="ELEM_B") public class ElemB { @XmlTransformation @XmlReadTransformer(transformerClass=DateAttributeTransformer.class) @XmlWriteTransformers({ @XmlWriteTransformer(xmlPath="B_DATE/text()", transformerClass=DateFieldTransformer.class), @XmlWriteTransformer(xmlPath="B_TIME/text()", transformerClass=TimeFieldTransformer.class), }) private Date bDate; @XmlElement(name="NUM") private int num; @XmlTransformation @XmlReadTransformer(transformerClass=DateAttributeTransformer.class) @XmlWriteTransformers({ @XmlWriteTransformer(xmlPath="C_DATE/text()", transformerClass=DateFieldTransformer.class), @XmlWriteTransformer(xmlPath="C_TIME/text()", transformerClass=TimeFieldTransformer.class), }) private Date cDate; }
AttributeTransformer
を使用してJava属性値を構成します。
例8-39 AttributeTransfomerのサンプル
package example;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping;
import org.eclipse.persistence.mappings.transformers.AttributeTransformer;
import org.eclipse.persistence.sessions.Record;
import org.eclipse.persistence.sessions.Session;
public class DateAttributeTransformer implements AttributeTransformer {
private AbstractTransformationMapping mapping;
private SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss");
public void initialize(AbstractTransformationMapping mapping) {
this.mapping = mapping;
}
public Object buildAttributeValue(Record record, Object instance, Session session) {
try {
String dateString = null;
String timeString = null;
for (DatabaseField field : mapping.getFields()) {
if (field.getName().contains("DATE")) {
dateString = (String) record.get(field);
} else {
timeString = (String) record.get(field);
}
}
return yyyyMMddHHmmss.parseObject(dateString + timeString);
} catch(ParseException e) {
throw new RuntimeException(e);
}
}
}
FieldTransformer
を使用してJavaオブジェクトからXMLフィールド値を構成します。
各トランスフォーメーション・マッピングが複数の書込みトランスフォーマを持つ場合があります。この例では、次の2つが必要です。
1番目の書込みトランスフォーマは、yyMMdd形式で年、月および日を書き込みます。
例8-40 1番目の書込みトランスフォーマ
package example; import java.text.SimpleDateFormat; import java.util.Date; import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; import org.eclipse.persistence.mappings.transformers.FieldTransformer; import org.eclipse.persistence.sessions.Session; public class DateFieldTransformer implements FieldTransformer { private AbstractTransformationMapping mapping; private SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd"); public void initialize(AbstractTransformationMapping mapping) { this.mapping = mapping; } public Object buildFieldValue(Object instance, String xPath, Session session) { Date date = (Date) mapping.getAttributeValueFromObject(instance); return yyyyMMdd.format(date); } }
2番目の書込みトランスフォーマは、HHmmss形式で時間、分および秒を書き込みます。
例8-41 2番目の書込みトランスフォーマ
package example; import java.text.SimpleDateFormat; import java.util.Date; import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping; import org.eclipse.persistence.mappings.transformers.FieldTransformer; import org.eclipse.persistence.sessions.Session; public class TimeFieldTransformer implements FieldTransformer { private AbstractTransformationMapping mapping; private SimpleDateFormat HHmmss = new SimpleDateFormat("HHmmss"); public void initialize(AbstractTransformationMapping mapping) { this.mapping = mapping; } public Object buildFieldValue(Object instance, String xPath, Session session) { Date date = (Date) mapping.getAttributeValueFromObject(instance); return HHmmss.format(date); } }
JAXBコンパイラを使用してXMLスキーマからJavaクラスを生成します。生成したクラスにはXMLバインディング・メタデータを表すJAXB注釈が含まれます。
.shファイルまたは.cmdファイルを次のように使用して、JAXBコンパイラを実行します。
<ECLIPSELINK_HOME>/eclipselink/bin/jaxb-compiler.sh <source-file.xsd> [-options]
または
<ECLIPSELINK_HOME>\eclipselink\bin\jaxb-compiler.cmd <source-file.xsd> [-options]
JAXBコンパイラは次のオプションをサポートしています。
-d <directory>
: 生成したファイルの出力ディレクトリを指定します。
-p <package>
: ターゲット・パッケージを指定します。
-classpath <arg>
: ユーザー・クラス・ファイルの検索場所を指定します。
-verbose
: コンパイラ出力を有効にします。
-quiet
: コンパイラ出力を無効にします。
-version
: コンパイラ・バージョン情報を表示します。
次に例を示します。
jaxb-compiler.sh -d jaxb-compiler-output config/Customer.xsd
コンパイラ・オプションの完全なリストを表示するには、次を使用します。
jaxb-compiler.sh -help
XMLスキーマ(sessions.xml
からのEclipseLinkプロジェクト)からのブートストラップ時に、EclipseLink OXMバインディング・ファイルを使用してEclipseLinkが生成するマッピングをカスタマイズできます。このファイルには追加のマッピングが含まれ、OXMとXSDブートストラップを組み合せることができます。つまり、EclipseLinkマッピングを使用して既存のXMLスキーマをカスタマイズできます。
この項では、スキーマに定義されたマッピングをオーバーライドする方法を示します。スキーマでは住所をカナダ式(provinceおよびpostal code)で定義していますが、米国式(stateおよびzip code)の住所を含むXMLを使用できます。
最初に、マッピング・オーバーライドを含んだeclipselink-oxm.xml
ファイルを作成する必要があります。例8-42では、province
とpostalCode
のXPathを変更します。
例8-42 eclipselink-oxm.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="Address"> <java-attributes> <xml-element java-attribute="province" xml-path="state/text()"/> <xml-element java-attribute="postalCode" xml-path="zip-code/text()"/> </java-attributes> </java-type> </java-types> </xml-bindings>
DynamicJAXBContext
の作成時に、プロパティの引数を使用してこのバインディング・ファイルを(スキーマに加えて)DynamicJAXBContextFactory
に渡します。
// Load Schema InputStream xsdStream = myClassLoader.getSystemResourceAsStream("example/resources/xsd/customer.xsd"); // Load OXM with customizations, put into Properties InputStream oxmStream = myClassLoader.getSystemResourceAsStream("example/resources/eclipselink/eclipselink-oxm.xml"); Map<String, Object> props = new HashMap<String, Object>(); props.put(JAXBContextProperties.OXM_METADATA_SOURCE, oxmStream); // Create Context DynamicJAXBContext dContext = DynamicJAXBContextFactory.createContextFromXSD(inputStream, null, myClassLoader, props);