この記事では、C++のSDO (Service Data Object)用の基本API (Application Programming Interface)について簡単に学びます。初めにSDOの背後にある考え方の概要を学び、次にAPIの各主要領域に移ります。こうした背景情報を使用して、最初のアプリケーションに取りかかりましょう。
SDOはデータ要素のグラフの構造を記述する方法、およびデータ・ソースからそのグラフ記述に基づきデータの特定のインスタンスをロードする方法を提供します。また、SDOを使用することにより、アプリケーションによるデータのグラフの変更を追跡し、その変更がデータとともにマシン間で移動できる形式で記録することができるようになります。したがって、データベースからデータをロードし、そのデータを一時的に接続されているクライアントに渡すことができます。その後データが返されると、SDOはクライアントの切断中に行われたデータの変更を認識できます。
SDOの主な構成要素を以下に示します。
- メタデータ(つまりデータの記述)。これはタイプとプロパティーのフレームワークであり、データが従う一組の規則、およびパスを記述してデータ要素から別のデータ要素にアクセスする方法を提供します。メタデータは動的に作成するか、XSD記述からロードできます。
- データのグラフを入力し操作するAPI。データはデータ・ソースからロードするか(データ・アクセス・サービスを使用)、動的に作成できます。
- データのグラフに行われたすべての変更を記録する変更サマリーAPI。
- メタデータ、データ、変更サマリーの3要素をすべて直列化し移送する機能。
データ・アクセス・サービスはAPI記述に従うため、サード・バーティーは任意のデータ・ソースへのデータ・アクセス・サービスを独自に作成できます。SDOの基本実装は、XMLから直列化と非直列化をできるようにするサービスを備えています。
メタデータはタイプとプロパティーによって記述され、完全な記述がデータ・ファクトリーに保持されます。このファクトリーはデータ・オブジェクトの作成に使用されるため、ファクトリーによってメタデータの妥当性検査が行われた後に作成が許可されます。
タイプはデータ・オブジェクトを記述する方法の1つであり、Javaのクラスに似ています。DataTypeとDataObjectTypeの2種類の異なるタイプがあります。DataTypeはJavaのプリミティブに似ており、整数値、バイト値、ブール値などを表します。DataTypeの基本セットは、ほとんどのプログラミング言語にある通常のプリミティブを表し、データ・ファクトリーにデフォルトで用意されています。また、ユーザーが基本タイプの拡張や制限を行い、他のタイプを定義することもできます。
タイプはURI(Uniform Resource Identifier)とその名前で定義されます。たとえば、定義済みDataTypeはすべてcommonj/sdoのURIを持ち、名前はByte、Boolean、Short、Integer、Longなどになっています。
DataTypeにプロパティーはありません。
一方、DataObjectTypeにはプロパティーがあり、「包含」か「参照」と考えることができます。包含プロパティーを使用すると、データ・オブジェクトに実際のデータ値を入れることができます。参照プロパティーを使用すると、データ・オブジェクトは他の場所に保持された値を参照できます。
DataObjectTypeはプリミティブと他のDataObjectTypeのグループを表し、データ・グラフの構成要素です。DataObjectTypeには名前付きプロパティーを設定できます。名前付きプロパティー自体はDataTypeかDataObjectTypeです。グラフで簡単に表データを表現できるようにするため、各プロパティーは多値または単一値と定義することもできます。
タイプは別のタイプのサブタイプにすることができ、そのタイプのプロパティーをすべて継承できます。タイプは抽象化することができますが、その場合はデータ・ファクトリーを使用してそのタイプのインスタンスを作成することはできなくなります。抽象タイプはC++の純粋仮想クラスまたはJavaのインターフェースに似ています。
以下の例は典型的な会社の基本定義を示し、上記の点をすべて表しています。
この会社は名前と多くの部門があり、取締役が1人います。各部門には名前があり、多くの従業員がいます。各従業員には名前、シリアル番号、およびデスクの場所があります。取締役にはプライベート・アカウント番号があります。この会社の従業員の1人は組合代表者です。
サブタイプを表すため、PersonTypeのサブタイプとしてDirectorTypeとEmployeeTypeを定義しましょう。PersonTypeにはnameというプロパティーがあります。DirectorTypeにはaccountというプロパティーがあり、EmployeeTypeにはserial numberとdesk locationというプロパティーがあります。companyTypeには、従業員の1人を指すUnionRepという参照プロパティーが必要です。
会社のインスタンス化に必要なタイプとプロパティーを定義できます。
最初に、CompanyTypeを定義します。CompanyTypeはDataObjectTypeです。これはプロパティーがあるためです(name、departmentsなど)。
図1は記述するメタデータを示しています。
図1. タイプ
このメタデータはXSDスキーマからロードでき、動的に作成することもできます。XSDを調べる前に、リスト1に示すように、C++ APIのDataFactoryを使用して動的にメタデータを定義してみましょう。
リスト1.C++ APIのDataFactoryを使用する
DataFactoryPtr df = DataFactory::getDataFactory();
// df is an empty data factory, containing only definitions of the primitives. We will
// now define the types.
// Descriptions of 'open' , and 'sequenced' appear later in the document - dont worry
//about them for now.
bool isSequenced = false;
bool isOpen = false;
bool isAbstract = false;
bool isDataType = false;
df->addType("mynamespace","CompanyType", isSequenced, isOpen, isAbstract, isDataType);
df->addType("mynamespace","DepartmentType", isSequenced, isOpen, isAbstract, isDataType);
df->addType("mynamespace","DirectorType", isSequenced, isOpen, isAbstract, isDataType);
df->addType("mynamespace","EmployeeType", isSequenced, isOpen, isAbstract, isDataType);
isAbstract = true; // we will not let a person be created, only employees or directors
df->addType("mynamespace","PersonType", isSequenced, isOpen, isAbstract, isDataType);
// now we need to tell the data factory that employees and directors "are" persons.
df->setBaseType("mynamespace","DirectorType","mynamespace","PersonType");
df->setBaseType("mynamespace","EmployeeType","mynamespace","PersonType");
// now we need to add properties
bool isMany = false;
bool isReadOnly = false;
bool isContainment = false;
// add all the single valued strings...
df->addPropertyToType("mynamespace","CompanyType","Name","commonj/sdo","String",
isMany, isReadOnly, isContainment);
df->addPropertyToType("mynamespace","PersonType","Name","commonj/sdo","String",
isMany, isReadOnly, isContainment);
df->addPropertyToType("mynamespace","DepartmentType","Name","commonj/sdo","String",
isMany, isReadOnly, isContainment);
df->addPropertyToType("mynamespace","DirectorType","Account","commonj/sdo","String",
isMany, isReadOnly, isContainment);
df->addPropertyToType("mynamespace","EmployeeType","Serial Number","commonj/sdo","String",
isMany,isReadOnly,isContainment);
df->addPropertyToType("mynamespace","EmployeeType","Location","commonj/sdo","String",
isMany, isReadOnly, isContainment);
// since unionrep is a reference, isContainment" remains false...
df->addPropertyToType("mynamespace","CompanyType","UnionRep","mynamespace",
"EmployeeType",isMany,isReadOnly,isContainment);
// the director is contained, unlike the union rep ...
isContainment=true;
df->addPropertyToType("mynamespace","CompanyType","Director","mynamespace",
"DirectorType",isMany,isReadOnly,isContainment);
// departments and employees are many valued, and contained...
isMany=true;
df->addPropertyToType("mynamespace","CompanyType","Departments","mynamespace",
"DepartmentType", isMany, isReadOnly, isContainment);
df->addPropertyToType("mynamespace","DepartmentType","Employees","mynamespace",
"EmployeeType", isMany, isReadOnly, isContainment);
|
メタデータを定義したので、実際のデータ・インスタンス(現実の会社など)の作成またはロードに移りましょう。この例では、ACMEという名前を付けます。
データ・ファクトリーを使用すると、データ・オブジェクトを作成できますが、作成された各データ・オブジェクトにも、関連するデータ・オブジェクトを作成するAPIがあります。原則として、ファクトリーを使用して会社だけを作成し、会社のデータ・オブジェクトを使用して部門と従業員を作成できます。リスト2に示すコードでは、会社ACMEを設定します。
リスト2.会社を設定する
// create an object of type CompanyType
DataObjectPtr acme = df->create("mynamespace","CompanyType");
// set its Name property
acme->setCString("Name","ACME");
//Use the company to create departments. The property "Departments" tells the
// factory which type of object to create.
DataObjectPtr sales = acme->createDataObject("Departments")
sales->setCString("Name","Sales");
DataObjectPtr marketting = acme->createDataObject("Departments")
marketting->setCString("Name","Marketting");
// Now we can use the departments to create employees...
DataObjectPtr sales_emp1 = sales->createDataObject("Employees")
sales_emp1->setCString("Name","Helena B Carter");
DataObjectPtr sales_emp2 = sales->createDataObject("Employees")
sales_emp2->setCString("Name","Anthony W Thompson");
DataObjectPtr marketting_emp1 = marketting->createDataObject("Employees")
marketting_emp1->setCString("Name","Justin Marples");
DataObjectPtr marketting_emp2 = marketting->createDataObject("Employees")
marketting_emp2->setCString("Name","Stephen McTavish");
|
以下のことを疑問に思われるかもしれません。
- なぜcreateDataObjectを使用して2つのemployeesを作成でき、またこれらをどのように参照すればいいのか?
- データ・オブジェクトから別のデータ・オブジェクトにアクセスするにはどうしたらいいか?
- DataObjectPtrとは何であり、使用したDataObjectの削除はどうするのか?
createDataObjectでは、プロパティー名を使用して動作方法を区別します。Employeesの場合、プロパティーは多値と定義されているため、createDataObjectを呼び出すごとにリストに別のオブジェクトが作成されます。Directorというプロパティーに対してcreateDataObjectを2回呼び出したとすると、最初のオブジェクトは2番目のオブジェクトによって置き換えられます。
前のコードでわかるように、名前によって実際にプロパティーにアクセスし、会社オブジェクトを呼び出し、Departmentsプロパティーを使用するように指定します。Departmentsはdepartmentsオブジェクトを呼び出し、Employeesプロパティーを使用するように指定します。このアクセス方法は、XPath(XML Path Language)のサブセットにも拡張されます。つまり、オブジェクトに関連するオブジェクトのプロパティーにアクセスできます。リスト3に示すように、プロパティーを直接指定し、会社の最初の部門にアクセスし、その名前を取得できます。
リスト3.オブジェクトのプロパティーにアクセスする
cout << "Department 1=" << acme->getCString("Departments[1]/Name") << endl;
|
または、リスト4に示すように、2番目の部門の2番目の従業員にアクセスできます。
リスト4.2番目の従業員にアクセスする
cout << "Employee =" << acme->getCString("Departments[2]/Employees[2]/Name") << endl;
|
リスト5に示す構文では、データ・オブジェクトとプロパティーをスラッシュ(/)で区切っています。リストの要素は1または. 0構文で参照します。これは同じ従業員を示します。
リスト5.データ・オブジェクトとプロパティーを区別する構文
cout << "Employee =" << acme->getCString("Departments.1/Employees.1/Name") << endl; |
注: ドット構文では最初の要素は要素0です。大括弧構文では最初の要素は要素1です。
リスト6に示すように、最初にリスト自体を取得してリスト全体にアクセスし、処理を繰り返すことができます。
リスト6.リストにアクセスする
DataObjectList& emps = acme->getList("Departments[1]/Employees");
// Now we can walk the list or add or remove items.
|
ここまで、ほとんどDataObjectPtrばかり使用してきました。また、DataObjectList&(参照)を見たばかりです。リストとDataObjectPtrを正しく使用するため、ここではSDOのメモリー管理を扱う必要があります。
SDOでは、メモリーの割り振りと割り振り解除を内部的に処理し、ユーザー・コードを必要としません。DataObjectPtrは実際にはDataObjectへのポインターを保持するラッパー・クラスです。このラッパー・クラスは範囲外になるか、NULLに設定されるとDataObjectへの接続が解除されます。DataObjectPtrは削除しないでください。
ライブラリーの内部では、グラフのすべてのDataObjectはその親によって参照され、データ・グラフから分離されるまで保持されるため、DataObjectを削除する必要はありません。DataObjectへのクライアント参照をやめ、データ・グラフに接続されなくなると、自動的に削除されます。
DataObjectListは参照として返されます。この場合、値のコピーではなく、実際の値リストを参照しており、プロパティー値を変更すると、この参照も更新されます。
これまで、メタデータを作成し、メタデータに対応するデータ・グラフを設定してアクセスする方法を学びました。こうしたことは、すべてXSDHelperクラスとXMLHelperクラスを使用して行うこともできます。XSDHelperはXSDドキュメントからメタデータをロードします。XMLHelperを使用すると、XMLファイルからデータ・グラフをロードできます。
リスト7は、XSDスキーマを使用してこれらのタスクを行う方法を示しています。
リスト7.XSDスキーマを使用する
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sdo="commonj.sdo"
xmlns:sdoxml="commonj.sdo/xml"
xmlns:company="mynamespace"
targetNamespace="mynamespace">
<xsd:element name="company" type="company:CompanyType"/>
<xsd:complexType name="CompanyType">
<xsd:sequence>
<xsd:element name="Departments" type="company:DepartmentType" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="Name" type="xsd:string"/>
<xsd:attribute name="Director" type="company:DirectorType"/>
<xsd:attribute name="UnionRep" type="xsd:IDREF" doxml:propertyType="company:EmployeeType"/>
</xsd:complexType>
<xsd:complexType name="PersonType">
<xsd:attribute name="Name" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="DepartmentType">
<xsd:restriction base="company:PersonType" />
<xsd:sequence>
<xsd:element name="Employees" type="company:EmployeeType" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="EmployeeType">
<xsd:restriction base="company:PersonType" />
<xsd:attribute name="Serial Number" type="xsd:ID"/>
<xsd:attribute name="Desk Location" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
|
リスト8は、XMLとして入力されたデータを示しています。
リスト8.XMLとして入力されたデータ
<?xml version="1.0" encoding="UTF-8" ?>
<company xmlns="mynamespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="ACME" >
<Departments Name="Sales" >
<Employees>
<Name>Helena B Carter</Name>
<\Employees>
<Employees>
<Name>Anthony W Thompson</Name>
</Employees>
</Departments>
<Departments Name="Marketting" >
<Employees>
<Name>Justin Marples</Name>
</Employees>
<Employees>
<Name>Stephen McTavish</Name>
</Employees>
</Departments>
</company>
|
リスト8のすべてのコードは、リスト9に示すコードによって置き換えられます。
リスト9.置き換えコード
DataFactoryPtr df = DataFactory::getDataFactory();
XSDHelper xsdh = HelperProvider::getXSDHelper(df);
xsdh->defineFile("company.xsd");
XMLHelper xmlh = HelperProvider::getXMLHelper(df);
XMLDocumentPtr doc = xmlh->createDocument(comp,"companyNS","company");
DataObjectPtr acme = doc->getRootDataObject();
|
XSDHelperとXMLHelperの方法を使用すると、XSD/XML情報のロードと保存ができるため、メタデータとグラフのデータを直列化された形式に保存できます。
リスト10はメタデータを新しいXSDファイルに保存するXSDHelperを示しています。
リスト10.メタデータを新しいXSDファイルに保存するXSDHelper
xsdh->generateFile(df->getTypes(),
"output.xsd","companyNS");
|
リスト11はドキュメントを新しいファイルに保存するXMLHelperを示しています。
リスト11.ドキュメントを新しいファイルに保存するXMLHelper
xmlh->save(doc,"output.xml");
|
SDO APIはいろいろな例外をスローします。たとえば、無効なXPath式でデータ・オブジェクトにアクセスすると、SDOInvalidPathExceptionがスローされます。存在しないプロパティーを使おうとすると、SDOPropertyNotFoundExceptionがスローされます。これらすべての例外はSDORuntimeExceptionから継承されるため、リスト12に必要最小限のエラー処理を示します。
リスト12.最小限のエラー処理
try {
DataFactoryPtr df = DataFactory::getDataFactory();
XSDHelper xsdh = HelperProvider::getXSDHelper(df);
xsdh->defineFile("company.xsd");
XMLHelper xmlh = HelperProvider::getXMLHelper(df);
XMLDocumentPtr doc = xmh->createDocument(comp,"companyNS","company");
DataObjectPtr acme = doc->getRootDataObject();
}
catch (SDORuntimeException e)
{
cout << "Exception caught: " << e << endl;
}
|
リスト12は基本的なSDO APIを示しています。次に、シーケンス、オープン・タイプ、イントロスペクションAPI、および変更サマリーについて説明します。
DataObjectTypeはシーケンス化されていると定義されることがあります。これは、プロパティー設定用のAPIに加え、これらのDataObjectに対する別のAPIがあり、同じプロパティーを設定できることを意味します。また、値を設定する順番を覚えておく必要があります。シーケンスを使用すると、多値プロパティー間も含め、設定の順序を知ることができます。したがって、たとえば、listAの最初の要素、listBの最初の要素、listAの2番目の要素の順に設定したことがわかります。
さらに、シーケンスAPIを使用すると、プロパティー設定間にフリー・テキスト設定を追加できます。たとえば、listAの最初の要素、次に「Hello」というテキスト項目、次にlistAの2番目の要素を設定できます。シーケンスは、順序が重要な意味を持つ一連のイベントを記録するときに便利です。
オープン・タイプは特殊な形式のDataObjectTypeです。オープン・タイプはプロパティーを持ちますが、含まれていないプロパティーを設定しようとしても、通常のデータ・オブジェクトとは異なり、例外をスローしません。その代わり、データ・オブジェクトのそのインスタンスに対してプロパティーが自動的に作成されます。たとえば、プロパティーNameを持つOpenEmployeeというオープン・タイプがあるとします。リスト13に示す設定はすべて有効です。
リスト13.オープン・タイプのプロパティー
DataObjectPtr emp = df->create("myspace","OpenEmployee");
emp->setCString("Name","Alphonse Dodet");
emp->setInteger("AlfsNumber", 256);
DataObjectPtr emp2 = df->create("myspace","OpenEmployee");
emp2->setCString("Name","Bill McCawber");
emp2->setBoolean("BillsBool", true);
|
OpenEmployeeタイプのプロパティーを照会すると、Nameというプロパティーがあることがわかります。empのプロパティーを照会すると、NameとAlfsNumberというプロパティーがあることがわかります。emp2にはNameとBillsBoolプロパティーがあります。オープン・プロパティーの設定に使用されるAPIの選択によって必要なプロパティーのタイプが決まります(BillsBoolはBoolean、AlfsNumberはIntegerです)。
利用できるタイプを探しそのプロパティーを調べる機能をイントロスペクションAPIと呼びます。タイプに属するプロパティーとデータ・オブジェクトに属するプロパティーを照会できます。たとえば、リスト14に示すように、companyTypeのメタデータ全体を表示できます。
リスト14.メタデータを表示する
const Type& t = df->getType("mynamespace","Company");
cout << "==============================================================" << endl;
cout << "Type: " << t.getURI() << "#" << t.getName() << endl;
if (t.isSequenced())cout << "is Sequenced" << endl;
if (t.isOpenType())cout << " is Open" << endl;
PropertyList& pl = t.getProperties();
for (int i=0; i < pl.size() ;i++)
{
cout << " Property: " << pl[i].getName() << " ";
if (pl[i].isMany()) cout << "Many Valued ";
else cout << "Single Valued ";
if (pl[i].isDataType) cout << " DataType ";
else
{
if (pl[i].isContainment()) cout " Containment ";
else cout << " Reference ";
}
cout << pl[i].getType().getURI() << "#" << pl[i].getType().getName() << endl;
}
cout << "===============================================================" << endl;
|
変更サマリーは任意のDataObjectTypeにアタッチできます。DataObjectのインスタンスを作成するとき唯一従うべき規則は、変更サマリーを持つDataObjectは変更サマリーを持つ別のDataObjectを包含することができないという規則です。変更サマリーは、タイプにプロパティーを設定するようにアタッチできますが、設定可能なプロパティーのリストには現れません。変更サマリーは最初アクティブではないため、ユーザー・コードでアクセスし、ログを設定する必要があります。ログの設定後は、データ・オブジェクトおよびその下のツリーにあるデータ・オブジェクトのプロパティーの変更がすべて記録されます。作成、変更、および削除が記録されます。すべてのアイテムについて、更新前の古い値が記録されます(作成を除く)。同じオブジェクトが何回か変更された場合、変更の履歴は1回しか記録されないため、最初に行われた変更が一番古い値として残ることになります。削除では、削除前のDataObjectの状態全体が記録されます。
ユーザー・コードで自由にログをオフに切り替えることができます。ログをオフに切り替えた時点までの変更はすべて保存されます。もう一度ログをオンに切り替えると、以前のログの変更はすべて失われます。
変更サマリーがあるDataObjectを直列化すると、変更サマリーも保存され、データ・オブジェクトと共に復元できます。変更サマリーAPIを使用すると、グラフのすべてのオブジェクトの設定を照会し、変更されているかどうかを調べることができます。
C++ API用SDOの簡単な概要を学びました。今すぐ仕様文書を入手してください。
- 優れた関連プロジェクトであるTuscanyオープン・ソース・プロジェクトを参照してください。
- Service Data Objectに関する詳しい背景情報を入手するには、developerWorksの記事「An introduction to SDO for Java」を読んでください。
- 「An introduction to SDO for PHP」では、Service Data ObjectとPHPについて解説しています。

