この章の内容は次のとおりです。
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はsetおよびgetという名前のメソッドを探します。アクセッサ・メソッドの名前をカスタマイズする方法は、「代替のアクセッサ・メソッドの指定」を参照してください。 |
マルチテナント・アーキテクチャで仮想プロパティを使用する例は、「拡張可能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』(http://www.w3.org/TR/xpath)の「2.4 Predicates」を参照してください。 |
次の例では、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>
|
注意: 通常は@XmlAdapterを使用します。ただし、次のことに注意してください。
このような問題があるため、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コンパイラは次のオプションをサポートしています。
| オプション | 説明 |
|---|---|
-nv |
入力スキーマの厳密な検証を実行しません。 |
-extension |
ベンダー固有の拡張を許可し、JAXB仕様の付録E.2の互換性ルールに厳密には従いません。 |
-b <file/directory> |
処理する外部バインディング・ファイルを指定します。
注意: 各ファイルの独自の |
-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);