はじめに
データの変更を追跡することは、多くのソフトウェア、アプリケーション、およびビジネス統合シナリオで必要とされる基本機能です。一般的な変更のデルタ(差分)をモデル化し、取り扱うことは非常に込み入った作業であり、この基本機能を厳密に実装することは比較的難しい課題です。一方で、すべてのアプリケーションにこの機能を重複して実装するのは無駄です。デルタ処理モデルは同じものをさまざまな状況に応用できますし、大半のケースで求められる機能はよく似ているからです。BEA Systems社とIBM社の主導で進められている
Service Data Object(SDO)は、異種データアクセスのための汎用ソリューションを定義するJSRで、開発者はこのメカニズムを使用してシステムレベルのデータ履歴追跡機能を簡単に実装できます。
この記事では、SDOのJava実装である
Apache Tuscanyバージョン1.0を使って、XMLデータをSDOで処理する方法の例を示します。SDOは(まだ)XML処理の標準ソリューションではないため、SDOの基本XMLデータ操作についても説明して、前後関係を明らかにします。
3フェーズからなるXMLデータ処理
この記事のXMLデータ処理例では、次の3つのフェーズがそれぞれ別のグループによって担当されることを前提とします。
- 作成
- 処理
- 元に戻す
XMLデータは、ファイルシステムを通じてこれらの3フェーズ(および3グループ)間でやり取りされます。この例の基本シナリオはこうです。最初のグループが作成したXMLファイルに第2のグループが変更を加え、この変更を記録します。この記録は、第3のグループがXMLデータを元に戻すときに利用されます。Microsoft Wordの変更履歴機能を使用したことがあれば、この機能の値打ちがすぐに理解できるでしょう。
この種の機能は、多くのアプリケーションで必要とされます。たとえば、オプティミスティック同時実行制御や、オフラインアプリケーションデータと稼動中データベースとの同期、ビジネスプロセス管理(BPM)システムなどを実装するには不可欠です。以降のセクションでは、SDOを利用してこれらの機能を簡単に実装できることをサンプルコードを示して説明します。
サンプルのXMLデータ(基はSDO 2.1.0仕様書のサンプル)は、注文処理をモデル化したものです(スキーマ「po_original.xsd」についてはリスト1を参照)。次のセクションでは、SDOを使ってこのスキーマに基づく注文処理を作成し、ファイルシステムに永続化する手順を説明します。このサンプルでは動的APIを使用します。SDOではあらゆるデータソースを扱うための静的APIもサポートされています。
リスト1 po_original.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.example.com/PO"
targetNamespace="http://www.example.com/PO">
<xsd:import namespace="commonj.sdo/xml" schemaLocation="sdo.xsd"/>
<xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
<xsd:element name="comment" type="xsd:string"/>
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress"/>
<xsd:element name="billTo" type="USAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items"/>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
<xsd:complexType name="StatusType">
<xsd:sequence>
<xsd:element name="status" type="xsd:string"/>
<xsd:element name="contact" type="xsd:string"/>
<xsd:element name="changeDate" type="xsd:date"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="USAddress">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zip" type="xsd:decimal"/>
</xsd:sequence>
<xsd:attribute name="country" type="xsd:NMTOKEN" fixed="US"/>
</xsd:complexType>
<xsd:complexType name="Items">
<xsd:sequence>
<xsd:element name="item" minOccurs="0"
maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="productName"
type="xsd:string"/>
<xsd:element name="price"
type="xsd:decimal"/>
<xsd:element name="quantity">
<xsd:simpleType>
<xsd:restriction
base="xsd:positiveInteger">
<xsd:maxExclusive value="100"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="shipDate"
type="xsd:date" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="partNum"
type="SKU" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="SKU">
<xsd:restriction base="xsd:string">
<xsd:pattern value="¥d{3}-[A-Z]{2}"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
SDOを使ってXMLを作成、永続化する
CreatePO.javaクラス(リスト2を参照)で、このサンプルのXML作成フェーズが完結します。このクラスで特に注目する必要があるのは、コメントが付けられた7ヶ所です。
リスト2 CreatePO.java
package com.company.sdo.po;
import commonj.sdo.DataObject;
import commonj.sdo.helper.DataFactory;
public class CreatePO {
public static void main(String[] args) throws Exception {
//1. Define Types and Properties with XSD
Util.definePOTypes();
//2. Create the root DataObject
DataObject purchaseOrder =
DataFactory.INSTANCE.create(Constants.PO_NAMESPACE,
"PurchaseOrderType");
//3. Set data type Property for the root DataObject
purchaseOrder.setString("orderDate", "1999-10-20");
//4. Create child DataObjects
DataObject shipTo = purchaseOrder.createDataObject("shipTo");
//5. Set data type Property for the child DataObject
shipTo.set("country", "US");
shipTo.set("name", "Alice Smith");
shipTo.set("street", "123 Maple Street");
shipTo.set("city", "Mill Valley");
shipTo.set("state", "CA");
shipTo.setString("zip", "90952");
DataObject billTo = purchaseOrder.createDataObject("billTo");
billTo.set("country", "US");
billTo.set("name", "Robert Smith");
billTo.set("street", "8 Oak Avenue");
billTo.set("city", "Mill Valley");
billTo.set("state", "PA");
billTo.setString("zip", "95819");
purchaseOrder.set("comment", "Hurry, my lawn is going wild!");
DataObject items = purchaseOrder.createDataObject("items");
//6. Create a child DataObject for the child DataObject “items”
DataObject item1 = items.createDataObject("item");
item1.set("partNum", "872-AA");
item1.set("productName", "Lawnmower");
item1.setInt("quantity", 1);
item1.setString("price", "148.95");
item1.set("comment", "Confirm this is electric");
DataObject item2 = items.createDataObject("item");
item2.set("partNum", "926-AA");
item2.set("productName", "Baby Monitor");
item2.setInt("quantity", 1);
item2.setString("price", "39.98");
item2.setString("shipDate", "2007-11-21");
DataObject item3 = items.createDataObject("item");
item3.set("partNum", "998-AA");
item3.set("productName", "Carpet");
item3.setInt("quantity", 1);
item3.setString("price", "439.98");
item3.setString("shipDate", "2007-12-01");
//7. Persist the XML data to an XML file
//Util.storeXML(purchaseOrder,"purchaseOrder",
// Constants.PO_XML_ORIGINAL);
/*use the following line instead of the above
* one for tracking changes*/
Util.storeXML(purchaseOrder,"purchaseOrder", Constants.PO_XML);
}
}
- Define Types and Properties with XSD(XSDでデータ型とプロパティを定義する)― データはXMLスキーマによってモデル化されるため、最初に必要なことは、このスキーマに基づいてSDOのデータ型とプロパティをランタイムに定義することです。この操作を行うのは、Utilクラスの
definePOTypes()メソッドから呼び出されるXSDHelperです。
public static void definePOTypes() throws Exception {
FileInputStream fis = new FileInputStream(PO_MODEL_ORIGINAL);
XSDHelper.INSTANCE.define(fis, null);
fis.close();
}
(SDO仕様書の9節に、XMLスキーマエンティティからSDOのデータ型およびプロパティへの実際のマッピングが定められています。詳細については、この節を参照してください)。
- Create the root DataObject(ルートデータオブジェクトを作成する)― SDOの動的APIは、データオブジェクト階層(オブジェクト別にプロパティを持つ階層)またはDataGraph(データオブジェクトのグラフをメタデータごとパッケージ化したもの)によって構造化データを表します。SDOのDataFactoryインターフェイスを使って未接続のデータオブジェクトを作成できるため、ここで必要な作業は、注文処理のルートデータオブジェクトを作成することです。
- Set data type Property for the root DataObject(ルートデータオブジェクトのデータ型プロパティを設定する)― SDOの場合、注文はデータ型です。スキーマの定義に従うと、注文のデータ型プロパティは
orderDateであり、これは日付型です。コメント3の下の行で、orderDateに日付の文字列を設定します。これは、JavaのDateオブジェクトを作成し、DataObjectのsetDate()メソッドを使用することに相当します。
- Create child DataObjects(子データオブジェクトを作成する)― データオブジェクトpurchaseOrderには、複数の子データオブジェクトがあります。たとえば、コメント4の下の行では、"shipTo"と名前を指定してshipTo子オブジェクトをpurchaseOrderから直接作成します。これは、DataFactoryを使って未接続のshipToデータオブジェクトを作成し、これをDataObjectのいずれかの
setDataObject()メソッドを使ってpurchaseOrderの子として設定することに相当します。
DataObject shipTo
= DataFactory.INSTANCE.create(CONSTANTS.PO_NAMESPACE, "USAddress");
......
PurchaseOrder.setDataObject("shipTo", shipTo);
- Set data type Property for the child DataObject(子データオブジェクトのデータ型プロパティを設定する)― USAddressデータ型の定義に従い、shipToデータオブジェクトにはさまざまなデータ型プロパティが与えられます。コメント5の下の行で、これらのプロパティを作成します。
- Create a child DataObject for the child DataObject “items”(子データオブジェクト"items"の子データオブジェクトを作成する)― ここでは、XMLをモデル化した場合のSDOデータモデルの階層的な特性について説明します。ItemsはルートデータオブジェクトpurchaseOrderの子であり、これに複数のitemデータオブジェクトの子が含まれます。
- Persist the XML data to an XML file(XMLデータをXMLファイルに永続化する)― SDOの動的APIに用意されたXMLHelperインターフェイスを使って、XMLデータをXMLファイルに永続化できます。コメント7の下のコードで、Utilクラスを呼び出し、メソッドを次のように定義します。
public static void storeXML(DataObject data, String rootElementName,
String xmlFile) throws Exception {
OutputStream stream = new FileOutputStream(xmlFile);
XMLHelper.INSTANCE.save(data, PO_NAMESPACE,
rootElementName, stream);
}
永続化した注文処理用XMLファイルは、「po_original.xml」という名前です。
XML用のSDO動的APIは非常に簡単に使用できるため、DOM APIを使って同等の操作を行うよりもずっと便利です。
次のセクションでは、SDOの動的APIを使って注文処理に改良を加え、変更を追跡できるようにします。
XMLデータへの変更を記録する
SDOに定義されたChangeSummaryメカニズムを利用して、変更を追跡し、変更の記録を注文情報と一緒に保管することができます。第二の目的を達するため、XMLスキーマファイル「po_original.xsd」を変更する必要があります(リスト3を参照)。インポートされるスキーマ「sdo.xsd」(SDO 2.1.0のもの)には、ChangeSummaryTypeなどが定義されています。このChangeSummaryTypeの要素をPurchaseOrderTypeに追加します。要素の名前は任意に決めてかまいませんが、ここでは"changes"とします。
リスト3 修正後のpo_original.xsd
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.example.com/PO"
xmlns:sdo="commonj.sdo"
xmlns:sdoxml="commonj.sdo/xml"
targetNamespace="http://www.example.com/PO">
<xsd:import namespace="commonj.sdo/xml"
schemaLocation="C:¥¥eclipse¥¥workspace¥¥SDO¥¥src¥¥main
¥¥resources¥¥sdo.xsd" />
<xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
<xsd:element name="comment" type="xsd:string"/>
<xsd:element name="status" type="StatusType"/>
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress"/>
<xsd:element name="billTo" type="USAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items"/>
<xsd:element ref="status" minOccurs="1" maxOccurs="1"/>
<xsd:element name="changes" type="sdo:ChangeSummaryType"/>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
......
</xsd:schema>
この新規のスキーマを適用するには、Util.javaの
definePOTypes()メソッドで読み込む必要があります。CreatePO.javaから生成されるXMLファイルは、「po.xml」という名前で永続化されます。「po_original.xml」と異なるのは、「po.xml」に
<po:purchaseOrder>の新しいサブ要素
<changes logging="false" />が追加されることです。注文は、ProcessPO.javaクラス(リスト4を参照)で処理されます。このプログラムを実行すると、処理された注文が変更の記録と共に「po_processed.xml」(リスト5を参照)に永続化されます。
リスト4 ProcessPO.java
package com.company.sdo.po;
import java.io.FileInputStream;
import commonj.sdo.ChangeSummary;
import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLDocument;
import commonj.sdo.helper.XMLHelper;
public class ProcessPO {
public static void main(String[] args) throws Exception {
Util.definePOTypes();
FileInputStream fis = new FileInputStream(Constants.PO_XML);
//1.load the XML document
XMLDocument xmlDoc = XMLHelper.INSTANCE.load(fis);
DataObject purchaseOrder = xmlDoc.getRootObject();
DataObject items = purchaseOrder.getDataObject("items");
//2.get the ChangeSummary object
ChangeSummary chngSum = purchaseOrder.getChangeSummary();
//3.start logging changes
chngSum.beginLogging();
/*(i)send bill to Alice Smith instead of Robert Smith*/
DataObject billTo = purchaseOrder.getDataObject("billTo");
billTo.setString("name", "Alice Smith");
/*(ii)change quantity for Baby Monitor, item 2*/
DataObject item2 = items.getDataObject("item.1");
item2.setInt("quantity",2);
/*
* a potential bug in Tuscany SDO,
* this line reports exception
* when trying to print ChangeSummary in ReviewPO.java
* item2.set("comment",
* "Only consider electricity powered.");
*/
/*(iii)remove item 3 from the order*/
DataObject item3 = (DataObject) items.getDataObject("item.2");
item3.delete();
/*(iv)add an item to the order for armed chair*/
DataObject item4 = items.createDataObject("item");
item4.set("partNum", "999-AA");
item4.set("productName", "Armed Chair");
item4.setInt("quantity", 1);
item4.setString("price", "299.95");
item4.set("comment", "Make sure the cover is leather.");
//4.end logging changes
chngSum.endLogging();
//5.print in system console all the changes made above
Util.printChangeSummary(chngSum);
Util.storeXML(purchaseOrder, "purchaseOrder",
Constants.PO_PROCESSED_XML);
}
}
リスト5 po_processed.xml
<?xml version="1.0" encoding="ASCII"?>
<po:purchaseOrder xmlns:po="http://www.example.com/PO"
orderDate="1999-10-20">
<shipTo country="US">
<name>Alice Smith</name>
<street>123 Maple Street</street>
<city>Mill Valley</city>
<state>CA</state>
<zip>90952</zip>
</shipTo>
<billTo country="US">
<name>Alice Smith</name>
<street>8 Oak Avenue</street>
<city>Mill Valley</city>
<state>PA</state>
<zip>95819</zip>
</billTo>
<po:comment>Hurry, my lawn is going wild!</po:comment>
<items>
<item partNum="872-AA">
<productName>Lawnmower</productName>
<price>148.95</price>
<quantity>1</quantity>
<po:comment>Confirm this is electric</po:comment>
</item>
<item partNum="926-AA">
<productName>Baby Monitor</productName>
<price>39.98</price>
<quantity>2</quantity>
<shipDate>2007-11-21</shipDate>
</item>
<item partNum="999-AA">
<productName>Armed Chair</productName>
<price>299.95</price>
<quantity>1</quantity>
<po:comment>Make sure the cover is leather.</po:comment>
</item>
</items>
<changes create=" ##//items/item[3]" delete="
##//changes/items[1]/item[3]"
logging="false" xmlns:sdo="commonj.sdo">
<billTo sdo:ref=" ##//billTo">
<name>Robert Smith</name>
</billTo>
<item sdo:ref=" ##//items/item[2]">
<quantity>1</quantity>
</item>
<items sdo:ref=" ##//items">
<item sdo:ref=" ##//items/item[1]" />
<item sdo:ref=" ##//items/item[2]" />
<item partNum="998-AA">
<productName>Carpet</productName>
<price>439.98</price>
<quantity>1</quantity>
<shipDate>2007-12-01</shipDate></item>
</items>
</changes>
</po:purchaseOrder>
リスト5では変更がXMLファイルに永続化される過程を見ることができますが、それ以上に注目すべきは
<changes>要素です。以前よりずっと複雑になっています。この要素には、このプログラムで注文に加えられた変更がすべて記録されます(
<changes>要素の実際の内容は、SDO仕様書に定められています)。この要素の内容に記録された情報と変更後のデータを基に、必要であれば元のデータを復元することができます(リスト6に、コンソールに出力される情報を示します)。
では、ProcessPO.javaクラスのコードを追って、SDOを使って変更の詳細な記録を取得する手順を細かく検討してみましょう(リスト4を参照)。
- コメント1の下にある行で、「po.xml」をランタイムに読み込みます。
- コメント2の下の行で、ChangeSummaryオブジェクトを作成し、これをpurchaseOrderデータオブジェクトに関連付けます。
- 変更を追跡するため、コメント3の下の行で、ChangeSummaryオブジェクトのロギングをオンにします。
- ここからコメント4の下の行(ロギングをオフにする場所)までの間で、purchaseOrderとその子データオブジェクトへのすべての変更をChangeSummaryオブジェクトchngSumに記録する処理を行います。
- 変更情報の出力は、コメント5の下の行にあるUtil.javaの
printChangeSummary()メソッドを呼び出して行います(リスト7を参照)。
リスト7 Util.javaのprintChangeSummary()メソッド
public static void printChangeSummary(ChangeSummary chngSum) {
if (chngSum == null) {
System.out.println("ChangeSummary is not in existence!");
return;
}
for (Iterator it = chngSum.getChangedDataObjects().iterator();
it.hasNext();) {
DataObject changedObject = (DataObject) it.next();
System.out.println();
if (chngSum.isCreated(changedObject)) {
//is the changed object newly created
System.out.println("Created: " + changedObject);
if (changedObject.getContainer()!=null){
System.out.println("¥t--- to be contained in : "
+ changedObject.getContainer().getType().getName()
+ " ---");
}else{
System.out.println(
"¥t--- created object has no container --- ");
}
printAnnotatedDataObject("newly created",changedObject, 2);
} else if (chngSum.isDeleted(changedObject)) {
System.out.println("Deleted: " + changedObject);
if (chngSum.getOldContainer(changedObject) != null){
System.out.println(
"¥t--- originally contained in : "
+ chngSum.getOldContainer(
changedObject).getType().getName() + " ---");
}else{
System.out.println(
"¥t--- deleted object has no container ---");
}
// a potential bug in Tuscany SDO, this shows nothing
// in ProcessPO.java
printAnnotatedDataObject("deleted",changedObject, 2);
// drill down to deleted property
System.out.println(
"¥t--- deleted property information --- ");
// a potential bug in Tuscany SDO,
// this section shows nothing in ReviewPO.java
for (Iterator settingIt
= chngSum.getOldValues(changedObject).iterator();
settingIt.hasNext();) {
printDeletedProperty((ChangeSummary.Setting)
settingIt.next());
}
System.out.println(
"¥t--- deleted property information --- ");
} else if (chngSum.isModified(changedObject)) {
System.out.println("Updated: " + changedObject);
// print out the updated object
printAnnotatedDataObject("after update", changedObject, 2);
// drill down to changed property
System.out.println(
"¥t--- property update information --- ");
for (Iterator settingIt
= chngSum.getOldValues(changedObject).iterator();
settingIt.hasNext();) {
ChangeSummary.Setting changeSetting
= (ChangeSummary.Setting) settingIt.next();
printUpdatedProperty(changeSetting,
changedObject,chngSum);
}
System.out.println(
"¥t--- property update information --- ");
} else
System.out.println("Should never come here!");
}
}
この方式では、最初に
getChangedDataObjects()を使ってすべての変更されたデータオブジェクトを取得してから、カテゴリ(作成、削除、または変更)に基づいてこれらを処理します。新規に作成されたデータオブジェクトについては、オブジェクトとすべての関連プロパティに関する情報を出力します(SDO仕様書に添付されたサンプルである
printDataObject()の注釈付きバージョンを呼び出します)。また、このデータオブジェクトのコンテナがあれば、それも表示します。
削除されたデータオブジェクトについては、そのデータオブジェクトのコンテナがあればまずそれを特定し、
printDataObject()を使って出力しようとします。この場合、出力を見てわかるのはApache Tuscany実装からなにも生成されないことです。ただし、削除されたデータオブジェクトを指定してChangeSummaryの
getOldValues()メソッドを呼び出すと、そのオブジェクトのすべてのプロパティと値を取得できます。変更されたデータオブジェクトのプロパティに関するこれらの情報は、内部クラスChangeSummary.Settingに保存されます。このクラスは、プロパティに値が設定されていたかどうかと、設定されていた場合にその古い値が取得できればその値を取得します。
変更されたデータオブジェクトについては、
printDataObject()を呼び出してすべてのプロパティと現在値を確認できます。ChangeSummaryの
getOldValues()メソッドを使うと、すべてのプロパティに対応するChangeSummary.Settingオブジェクトを取得できます。このプロパティ確認作業は、Util.javaのプライベートメソッド
printUpdatedProperty()で行います(リスト8を参照)。
リスト8 util.javaのprintUpdatedProperty()メソッド
private static void printUpdatedProperty(
ChangeSummary.Setting changeSetting,
DataObject changedObject,
ChangeSummary chngSum) {
if (changeSetting == null)return;
Property property = changeSetting.getProperty();
System.out.println("¥t $$$ name of the property updated: "
+ property.getName() + " $$$");
if (!changeSetting.isSet()){
System.out.println(
"¥t ### the updated property is originally NOT set ###");
}
if (!property.getType().isDataType()) {
// the property is DataObject
System.out.println("¥t ### the property is a DataObject ###");
if (property.isMany()) { // multiple valued
System.out.println(
"¥t ### the property is multiple valued ###");
List objects = (List) changedObject.get(property);
System.out.println(
"¥t ### total number of the property is : "
+ objects.size() + " ###");
System.out.println(
"¥t ### and here are they with status information ### ");
for (Iterator objIt = objects.iterator();
objIt.hasNext();) {
DataObject itObj = (DataObject) objIt.next();
if (chngSum.isCreated(itObj)) {
printAnnotatedDataObject("newly created", itObj, 3);
} else if (chngSum.isModified(itObj)) {
printAnnotatedDataObject("after update", itObj, 3);
} else {
printAnnotatedDataObject("untouched", itObj, 3);
}
}
} else { // single valued
System.out.println(
"¥t ### the property is single valued ###");
printAnnotatedDataObject("after update",
(DataObject) changedObject.get(property), 2);
}
} else { // the property is a data type
System.out.println("¥t ### the property is a data type ###");
System.out.println("¥t from : "
+ changeSetting.getValue()
+ "¥t to : "
+ changedObject.get(property));
}
}
printUpdatedProperty()メソッドの主要部分では、最初にプロパティがデータ型であるか、データオブジェクトであるかを判断します。データオブジェクトの場合は、複数値(アイテムなど)のプロパティであるか、単一値(billToなど)のプロパティであるかをチェックします。複数値プロパティの場合は、値が作成済み、変更済み、または未操作のいずれであるかをチェックし、最後にprintDataObjectを呼び出します。DataObject型の単一値プロパティの場合は、
printDataObject()を直接呼び出します。プロパティがデータ型である場合は、
printUpdatedProperty()メソッドから
Setting.getValue()が呼び出されて古い値が取得され、DataObjectの
get(Property prop)が呼び出されて現在の値が取得されます。
Util.javaのプライベートメソッド
printDeletedProperty()の場合も、基本的なロジックはこれと同じです。違いは、削除されたデータオブジェクトのプロパティには現在の値が存在しないことです。
システムコンソールの出力においては、プログラムによって直接変更されていない場合でもこのDataObjectアイテムは更新されたと見なされます。これは、子アイテムのデータオブジェクトが変更されたためです。
次のセクションでは、変更された注文を元に戻す方法を説明します。
別のグループによる変更を元に戻す
XMLデータ処理の3番目のフェーズでは、次の2つのタスクを実行できる必要があります。
- 変更された注文に関する要約情報を出力する。
- 処理フェーズで行われたすべての変更の情報を表示する。
前の変更記録フェーズのように注文をさらに変更することもできますが、このセクションでは、ReviewPO.javaを使ってすべての変更を元に戻す方法を説明します(リスト9を参照)。コメント2「Shows the information in the modified purchase order」(変更された注文の情報を表示する)の下の行では、上記の最初のタスクを実行します。このコードの意味については、もう説明の必要はないでしょう。コメント3「Display changes made in the second phase」(2番目のフェーズで行われた変更を表示する)の下にある
printChangeSummary()呼び出しによって、ProcessPO.javaで行われた変更がシステムコンソールに再度出力されます。
リスト9 ReviewPO.java
package com.company.sdo.po;
import java.io.FileInputStream;
import java.util.List;
import commonj.sdo.ChangeSummary;
import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLDocument;
import commonj.sdo.helper.XMLHelper;
public class ReviewPO {
public static void main(String[] args) throws Exception {
Util.definePOTypes();
FileInputStream fis
= new FileInputStream(Constants.PO_PROCESSED_XML);
//1. load the processed purchase order
XMLDocument xmlDoc = XMLHelper.INSTANCE.load(fis);
DataObject purchaseOrder = xmlDoc.getRootObject();
//2. Shows the information in the modified purchase order
System.out.println();
System.out.println(
"---received purchase order information---");
System.out.println("Order date: "
+ purchaseOrder.get("orderDate"));
System.out.println("Order comment: "
+ purchaseOrder.get("comment"));
DataObject shipTo = purchaseOrder.getDataObject("shipTo");
System.out.println("Ship to name: ");
Util.printDataObject(shipTo,1);
DataObject billTo = purchaseOrder.getDataObject("billTo");
System.out.println("Bill to name: ");
Util.printDataObject(billTo,1);
DataObject items = purchaseOrder.getDataObject("items");
List itemList = items.getList("item");
System.out.println("Ordered items: ");
for (int i = 0; i < itemList.size(); i++) {
DataObject item = (DataObject) itemList.get(i);
System.out.println("¥tItem " + (i+1) + " :" );
Util.printDataObject(item,2);
}
//3. Display changes made in the second phase
System.out.println();
System.out.println("---review changes in purchase order ---");
ChangeSummary chngSum = purchaseOrder.getChangeSummary();
Util.printChangeSummary(chngSum);
//4. Undo all changes in the second phase
System.out.println();
System.out.println(
"---action taken after reviewing the changes---");
/*
* should really work on changes here,
* ChangeSummary only has a undoChanges() method;
* ideally should have methods such as:
* (1) restore() for deleted data object,
* (2) delete() for newly added object, and
* (3) revoke() for modified object
* and those methods should apply directly
* on the data objects or properties
*/
System.out.println("¥t###undo all changes###");
chngSum.undoChanges();
//5. Modify the purchase order on the original version
System.out.println();
System.out.println(
"---changes made in purchase order review---");
chngSum.beginLogging();
//this clears old change summary information
billTo.set("name", "Alice Smith");
chngSum.endLogging();
//6. print to the system console the new modifications
Util.printChangeSummary(chngSum);
//7. persist the reviewed version to an XML file
Util.storeXML(purchaseOrder,"purchaseOrder",
Constants.PO_REVIEWED_XML);
}
}
この2番目のタスクは、前のセクションのChangeSummaryの出力処理とは違います。前のセクションでは、JavaオブジェクトとしてのChangeSummaryに直接基づいて出力を実行しました。今回は、変更の要約がXMLファイルからランタイムに再度読み込まれます。この違いは、出力を比較すると明らかになります。前のセクションの出力では、削除アイテムに関して
printDataObject()からはなにも生成されませんが、
printDeletedProperty()を呼び出すと、削除アイテムのすべてのプロパティと値が取得されます。今回の出力処理では、この正反対の結果になります。この不一致は、Apache Tuscany実装のバグと思われます(第二のバグらしきものがリスト2のCreatePO.javaのコメントに見られます)。ここからも、厳密な変更追跡メカニズムを実装することがいかに難しいかがわかります。
プログラムの残りの部分を見ていきましょう。コメント4「Undo all changes in the second phase」(2番目のフェーズで行った変更をすべて元に戻す)以下ではChangeSummaryの
undoChanges()メソッドを呼び出し、コメント5「Modify the purchase order on the original version」(元のバージョンの注文を変更する)以下では元に戻したばかりの注文に簡単な変更を加えています。永続化されたXML「po_reviewed.xml」を参照して、以上の操作の結果を確認してください。
SDOの明るい未来
今後、SDOはEclipse、BEA AquaLogic Data Services Platform(ALDSP)、IBM WebSphere Process Serverなどの主要なオープンソースソフトウェアや商用ソフトウェアだけでなく、Java、C++、PHPなどのさまざまな言語にも実装される予定です。
2007年、SDO界に2つの大きな前進が見られました。
- 3月、Open SOAはSDO仕様をOASISに提出すると発表しました。
- 4月、SDOをJava EEの将来のバージョンでサポートすることを検討するとJCPが公式に発表しました。
Apache TuscanyからもSDO Java実装が最近リリースされました。以上を総合すると、SDO採用の動きはますます盛んであると言えるでしょう。
この記事は、SDOの一機能を紹介したに過ぎません。他にも、パワーあふれる便利な機能がたくさんあります。SDOテクノロジを丹念に調べれば、サービスドメインにおけるデータ管理に広く求められる機能を簡単に実現する方法が他にも見つかることでしょう。
謝辞
この記事の初稿とサンプルソースコードを校閲し、貴重な意見を提供していただいたLaxma Reddy氏に感謝いたします。