XMLデータへの変更を記録する
SDOに定義されたChangeSummaryメカニズムを利用して、変更を追跡し、変更の記録を注文情報と一緒に保管することができます。第二の目的を達するため、XMLスキーマファイル「po_original.xsd」を変更する必要があります(リスト3を参照)。インポートされるスキーマ「sdo.xsd」(SDO 2.1.0のもの)には、ChangeSummaryTypeなどが定義されています。このChangeSummaryTypeの要素をPurchaseOrderTypeに追加します。要素の名前は任意に決めてかまいませんが、ここでは"changes"とします。
<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を参照)に永続化されます。
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); } }
<?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に、コンソールに出力される情報を示します)。
Deleted: org.apache.tuscany.sdo.impl.DynamicDataObjectImpl@19b5217(eClass: org.apache.tuscany.sdo.impl.ClassImpl@10a2d64 (name: item)
(instanceClassName: null) (abstract: false, interface: false)) --- originally contained in : Items --- &&&BEGNNING- deleted -BEGINNING&&& &&&END- deleted -END&&& --- deleted property information --- $$$ name of the property deleted: productName $$$ ### the deleted property is a data type ### productName: Carpet $$$ name of the property deleted: price $$$ ### the deleted property is a data type ### price: 439.98 $$$ name of the property deleted: quantity $$$ ### the deleted property is a data type ### quantity: 1 $$$ name of the property deleted: comment $$$ %%%originally NOT set%%% $$$ name of the property deleted: shipDate $$$ ### the deleted property is a data type ### shipDate: 2007-12-01 $$$ name of the property deleted: partNum $$$ ### the deleted property is a data type ### partNum: 998-AA --- deleted property information --- Created: org.apache.tuscany.sdo.impl.DynamicDataObjectImpl@3b1d04
(eClass: org.apache.tuscany.sdo.impl.ClassImpl@10a2d64 (name: item)
(instanceClassName: null) (abstract: false, interface: false)) --- to be contained in : Items --- &&&BEGNNING- newly created -BEGINNING&&& productName: Armed Chair price: 299.95 quantity: 1 comment: Make sure the cover is leather. partNum: 999-AA &&&END- newly created -END&&& Updated: org.apache.tuscany.sdo.impl.DynamicDataObjectImpl@176e552
(eClass: org.apache.tuscany.sdo.impl.ClassImpl@12a0f6c
(name: USAddress) (instanceClassName: null) (abstract: false,
interface: false)) &&&BEGNNING- after update -BEGINNING&&& name: Alice Smith street: 8 Oak Avenue city: Mill Valley state: PA zip: 95819 country: US &&&END- after update -END&&& --- property update information --- $$$ name of the property updated: name $$$ ### the property is a data type ### from : Robert Smith to : Alice Smith --- property update information --- Updated: org.apache.tuscany.sdo.impl.DynamicDataObjectImpl@12ad19e
(eClass: org.apache.tuscany.sdo.impl.ClassImpl@10a2d64 (name: item)
(instanceClassName: null) (abstract: false, interface: false)) &&&BEGNNING- after update -BEGINNING&&& productName: Baby Monitor price: 39.98 quantity: 2 shipDate: 2007-11-21 partNum: 926-AA &&&END- after update -END&&& --- property update information --- $$$ name of the property updated: quantity $$$ ### the property is a data type ### from : 1 to : 2 --- property update information --- Updated: org.apache.tuscany.sdo.impl.DynamicDataObjectImpl@281d4b
(eClass: org.apache.tuscany.sdo.impl.ClassImpl@89cf1e (name: Items)
(instanceClassName: null) (abstract: false, interface: false)) &&&BEGNNING- after update -BEGINNING&&& item (item): productName: Lawnmower price: 148.95 quantity: 1 comment: Confirm this is electric partNum: 872-AA item (item): productName: Baby Monitor price: 39.98 quantity: 2 shipDate: 2007-11-21 partNum: 926-AA item (item): productName: Armed Chair price: 299.95 quantity: 1 comment: Make sure the cover is leather. partNum: 999-AA &&&END- after update -END&&& --- property update information --- $$$ name of the property updated: item $$$ ### the property is a DataObject ### ### the property is multiple valued ### ### total number of the property is : 3 ### ### and here are they with status information ### &&&BEGNNING- untouched -BEGINNING&&& productName: Lawnmower price: 148.95 quantity: 1 comment: Confirm this is electric partNum: 872-AA &&&END- untouched -END&&& &&&BEGNNING- after update -BEGINNING&&& productName: Baby Monitor price: 39.98 quantity: 2 shipDate: 2007-11-21 partNum: 926-AA &&&END- after update -END&&& &&&BEGNNING- newly created -BEGINNING&&& productName: Armed Chair price: 299.95 quantity: 1 comment: Make sure the cover is leather. partNum: 999-AA &&&END- newly created -END&&& --- property update information ---
では、ProcessPO.javaクラスのコードを追って、SDOを使って変更の詳細な記録を取得する手順を細かく検討してみましょう(リスト4を参照)。
- コメント1の下にある行で、「po.xml」をランタイムに読み込みます。
- コメント2の下の行で、ChangeSummaryオブジェクトを作成し、これをpurchaseOrderデータオブジェクトに関連付けます。
- 変更を追跡するため、コメント3の下の行で、ChangeSummaryオブジェクトのロギングをオンにします。
- ここからコメント4の下の行(ロギングをオフにする場所)までの間で、purchaseOrderとその子データオブジェクトへのすべての変更をChangeSummaryオブジェクトchngSumに記録する処理を行います。
- 変更情報の出力は、コメント5の下の行にあるUtil.javaの
printChangeSummary()
メソッドを呼び出して行います(リスト7を参照)。
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を参照)。
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アイテムは更新されたと見なされます。これは、子アイテムのデータオブジェクトが変更されたためです。
次のセクションでは、変更された注文を元に戻す方法を説明します。