|
任天堂が、大画面の「ニンテンドーDSi LL」を発表。欲しいと思いますか?
|
XMLデータの変更をSDOで簡単に追跡するはじめにデータの変更を追跡することは、多くのソフトウェア、アプリケーション、およびビジネス統合シナリオで必要とされる基本機能です。一般的な変更のデルタ(差分)をモデル化し、取り扱うことは非常に込み入った作業であり、この基本機能を厳密に実装することは比較的難しい課題です。一方で、すべてのアプリケーションにこの機能を重複して実装するのは無駄です。デルタ処理モデルは同じものをさまざまな状況に応用できますし、大半のケースで求められる機能はよく似ているからです。BEA Systems社とIBM社の主導で進められているService Data Object(SDO)は、異種データアクセスのための汎用ソリューションを定義するJSRで、開発者はこのメカニズムを使用してシステムレベルのデータ履歴追跡機能を簡単に実装できます。この記事では、SDOのJava実装であるApache Tuscanyバージョン1.0を使って、XMLデータをSDOで処理する方法の例を示します。SDOは(まだ)XML処理の標準ソリューションではないため、SDOの基本XMLデータ操作についても説明して、前後関係を明らかにします。 3フェーズからなるXMLデータ処理この記事のXMLデータ処理例では、次の3つのフェーズがそれぞれ別のグループによって担当されることを前提とします。
この種の機能は、多くのアプリケーションで必要とされます。たとえば、オプティミスティック同時実行制御や、オフラインアプリケーションデータと稼動中データベースとの同期、ビジネスプロセス管理(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); } }
public static void definePOTypes() throws Exception { FileInputStream fis = new FileInputStream(PO_MODEL_ORIGINAL); XSDHelper.INSTANCE.define(fis, null); fis.close(); } (SDO仕様書の9節に、XMLスキーマエンティティからSDOのデータ型およびプロパティへの実際のマッピングが定められています。詳細については、この節を参照してください)。
DataObject shipTo = DataFactory.INSTANCE.create(CONSTANTS.PO_NAMESPACE, "USAddress"); ...... PurchaseOrder.setDataObject("shipTo", shipTo);
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用の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¥¥maindefinePOTypes()メソッドで読み込む必要があります。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>要素です。以前よりずっと複雑になっています。この要素には、このプログラムで注文に加えられた変更がすべて記録されます(<changes>要素の実際の内容は、SDO仕様書に定められています)。この要素の内容に記録された情報と変更後のデータを基に、必要であれば元のデータを復元することができます(リスト6に、コンソールに出力される情報を示します)。リスト6 Util.javaのprintChangeSummary()からの出力
Deleted: org.apache.tuscany.sdo.impl.DynamicDataObjectImpl@19b5217
リスト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つのタスクを実行できる必要があります。
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); } } 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つの大きな前進が見られました。
この記事は、SDOの一機能を紹介したに過ぎません。他にも、パワーあふれる便利な機能がたくさんあります。SDOテクノロジを丹念に調べれば、サービスドメインにおけるデータ管理に広く求められる機能を簡単に実現する方法が他にも見つかることでしょう。 謝辞
この記事の初稿とサンプルソースコードを校閲し、貴重な意見を提供していただいたLaxma Reddy氏に感謝いたします。
著者紹介Young Yang(Young Yang)
ウォール街の金融会社に勤務する上級エンタープライズアーキテクト。数学博士号を取得。IBM社のeビジネスデザイン、DB2管理、ビジネスインテリジェンス、XMLおよび関連テクノロジ、WebSphere Application Serverの認定資格、BEA社のWebLogic Application Serverの認定資格、Sun社のエンタープライズアーキテクトその他の認定資格を保有。
関連記事 最新トップニュース
|
|