Atom Login Admin

Above the clouds

Apache JenaでRDFの基礎を学ぶ (2)

written on Thursday,August 20,2015

今回も引き続きApache JenaのTutorialでRDFの基礎を学ぶ
(ドキュメントの翻訳です。)

Statements

それぞれのRDFモデルの弧はStatementと呼ばれる。それぞれのStatementはリソースのファクトを評価する。Statementは以下の3つの要素を持つ。

  • 主語(Subject)は有向の弧が出るところ
  • 述語(Predicate)は弧のラベルとなるプロパティ
  • 目的語(Object)は弧によって指されるリソースまたはリテラル

RDF ModelはStatementのセットとして表現される。tutorial2ではそれぞれaddPropertyを呼び出すことでModelに他のStatementを追加している。(なぜなら、ModelはStatementのセットであり、重複したStatementを追加したところで変化しない)JenaのモデルはlistStatement()メソッドをもつインターフェースを定義していて、StmtIteratorを返却する。これはJavaのIteratorのサブタイプであり、Modelの全てのStatementを舐め回すことができる。
StmtIteratorはnewxtStatement()メソッドを持っていて、次のStatementを返却する。(Statementでキャストすれば、next()と同じである)StatementインターフェースはSubject,Predicate,Objectのアクセッサーを提供している。

では、このインターフェースを使用して、Statementを列挙するようにtutorial2の拡張を行う。完全なコードはturorial3にある。

// list the statements in the Model
StmtIterator iter = model.listStatements();

// print out the predicate, subject and object of each statement
while (iter.hasNext()) {
    Statement stmt      = iter.nextStatement();  // get next statement
    Resource  subject   = stmt.getSubject();     // get the subject
    Property  predicate = stmt.getPredicate();   // get the predicate
    RDFNode   object    = stmt.getObject();      // get the object

    System.out.print(subject.toString());
    System.out.print(" " + predicate.toString() + " ");
    if (object instanceof Resource) {
       System.out.print(object.toString());
    } else {
        // object is a literal
        System.out.print(" \"" + object.toString() + "\"");
    }

    System.out.println(" .");
} 

Statementの目的語(object)はリソースかリテラルであるので、getObject()メソッドで返却されるRDFNodeはResourceとLiteralの共通のスーパークラスになっている。根底のオブジェクトは任意の型となる為、型に従った処理を行う場合はinstanceofで決定する。

上記コードの出力は以下となる。

http://somewhere/JohnSmith http://www.w3.org/2001/vcard-rdf/3.0#N anon:14df86:ecc3dee17b:-7fff .
anon:14df86:ecc3dee17b:-7fff http://www.w3.org/2001/vcard-rdf/3.0#Family  "Smith" .
anon:14df86:ecc3dee17b:-7fff http://www.w3.org/2001/vcard-rdf/3.0#Given  "John" .
http://somewhere/JohnSmith http://www.w3.org/2001/vcard-rdf/3.0#FN  "John Smith" .

これで、Modelを書くのが明解なことか分かったはずである。注意深く見てみると、それぞれのStatementが主語、述語、目的語の三つのフィールドにより構成されていることが分かる。
Modelに4つの弧をもつ場合は 4つのStatementを持っている事になる。"anon:14df86:ecc3dee17b:-7fff "はJenaによって生成される内部識別子である。
これはURIではないので混乱しないようにしたい。これは簡単に言えば、Jenaによって使用される内部ラベルである。

W3C RDFCore Working Groupは同様の単純な表記をN-Triplesと定義している。この名前はTriple Notationを意味している。次のセクションではN-Triples writerを紹介する。

Writing RDF

JenaはRDFをXMLとして書き込んだり読み込んだりするメソッドを持っている。
これはRDF Modelをファイルに保存したり、読み込んだりする事に使用することができる。

Tutorial3ではModelを作って、Tripleフォームで書き出す。Tutorial 4ではTutorial 3を変更して、ModelをRDF XMLフォームで書き、標準出力に表示する。下記コードはとても単純で、model.writeにOutputStream引数に渡している。

// now write the model in XML form to a file
model.write(System.out);

出力結果は以下のようになる。

 <rdf:RDF
  xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
  xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
 >
  <rdf:Description rdf:about='http://somewhere/JohnSmith'>
    <vcard:FN>John Smith</vcard:FN>
    <vcard:N rdf:nodeID="A0"/>
  </rdf:Description>
  <rdf:Description rdf:nodeID="A0">
    <vcard:Given>John</vcard:Given>
    <vcard:Family>Smith</vcard:Family>
  </rdf:Description>
</rdf:RDF>

RDFの仕様ではRDFをXMLとして表現する方法について定義している。RDF XML構文は複雑である。更に詳細な説明についてはRDFCore WGが開発したRDF Primerを参照してほしい。
が、ひとまずは上記の解釈する方法の説明を行う。

RDFは通常要素に埋め込まれる。この要素は任意でXMLがRDFであることを知る方法が他にあれば必要がないものだが、通常は記述する。
RDF要素はドキュメント内で使われる二つの要素を定義する。要素はリソースのURIが "http://somewhere/JohnSmith" であることを記述している。rdf:about属性が無い場合は、この要素をブランクノードを示す事になる。
要素はリソースのプロパティを記述している。vcard名前空間でのプロパティ名はFNである。RDFはこれを名前空間のプレフィクスの為のURI参照として結合されたものと名前のローカルネームの部分を変換する。これは "http://www.w3.org/2001/vcard-rdf/3.0#FN" のURI参照を与える。プロパティの値はリテラルの"John Smith"である。

要素はリソースである。この例では相対URI参照で表されている。RDFは直近ドキュメントのベースURIと結合させて絶対URI参照に変換する。

このRDF XMLはエラーである。これでは我々が作ったModelを正確に表現していないものである。ModelのブランクノードはURI参照を持っていない。これはもはやブランクではない、RDF/XML構文はRDF Modelの全てを表現できるものではない。例えば2つのStatementを持つObjectを表現することができない。我々が使用している'dumb' Writerは正確に書くことができいるModelのセットを正しく書こうとはしない。与えられるそれぞれのブランクノードのURIはもはやブランクではなくなっている。

JenaはRDFの異なることなる言語シリアライゼーションの為のライターをプラグインできる拡張インターフェースを持っている。上記でよびだしているのは標準的な"dumb" Writerである。Jenaはさらに洗練されたRDF/XML Writerを持っていて、指定したものをwrite()メソッドの引数で渡すことができる。

// now write the model in XML form to a file
model.write(System.out, "RDF/XML-ABBREV");

このWriterはPrettyWriterとよばれるRDF/XMLの省略記法でコンパクトにModelを書く為の拡張機能である。これもまた可能な場所でブランクノードを保持する。
しかしながら、大きなModelを書くときにパフォーマンスが許容できないので、適さない。大きなファイルやブランクノードを保持する場合はN-Triplesフォーマットを使用するとよい。

// now write the model in N-TRIPLES form to a file
model.write(System.out, "N-TRIPLES");

これは、tutorial 3同様の出力でN-Triples仕様に従ったもである。

Reading RDF

Tutorial 5ではModelに記録されたRDF XMLフォームのStatementを読むデモを行う。このTutorialではRDF/XMLフォームのvcardsの小さなデータベースが提供されている。
下記コードでは読み込み書き込みをおこなっている。カレントディレクトリに入力ファイルを置いておく必要があるので注意が必要だ。

 // create an empty model
 Model model = ModelFactory.createDefaultModel();

 // use the FileManager to find the input file
 InputStream in = FileManager.get().open( inputFileName );
 if (in == null) {
    throw new IllegalArgumentException(
                                 "File: " + inputFileName + " not found");
 }

 // read the RDF/XML file
 model.read(in, null);

 // write it to standard out
 model.write(System.out);

read()メソッドの2つ目の引数はURIで相対URIを解決する為のものである。相対URIの参照が無い場合は空にすることも可能である。tutoril 5を実行すると、以下のようなXML出力を行う。

<rdf:RDF
  xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
  xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
 >
  <rdf:Description rdf:nodeID="A0">
    <vcard:Family>Smith</vcard:Family>
    <vcard:Given>John</vcard:Given>
  </rdf:Description>
  <rdf:Description rdf:about='http://somewhere/JohnSmith/'>
    <vcard:FN>John Smith</vcard:FN>
    <vcard:N rdf:nodeID="A0"/>
  </rdf:Description>
  <rdf:Description rdf:about='http://somewhere/SarahJones/'>
    <vcard:FN>Sarah Jones</vcard:FN>
    <vcard:N rdf:nodeID="A1"/>
  </rdf:Description>
  <rdf:Description rdf:about='http://somewhere/MattJones/'>
    <vcard:FN>Matt Jones</vcard:FN>
    <vcard:N rdf:nodeID="A2"/>
  </rdf:Description>
  <rdf:Description rdf:nodeID="A3">
    <vcard:Family>Smith</vcard:Family>
    <vcard:Given>Rebecca</vcard:Given>
  </rdf:Description>
  <rdf:Description rdf:nodeID="A1">
    <vcard:Family>Jones</vcard:Family>
    <vcard:Given>Sarah</vcard:Given>
  </rdf:Description>
  <rdf:Description rdf:nodeID="A2">
    <vcard:Family>Jones</vcard:Family>
    <vcard:Given>Matthew</vcard:Given>
  </rdf:Description>
  <rdf:Description rdf:about='http://somewhere/RebeccaSmith/'>
    <vcard:FN>Becky Smith</vcard:FN>
    <vcard:N rdf:nodeID="A3"/>
  </rdf:Description>
</rdf:RDF>

Controlling Prefixes

Explicit prefix definitions

前回のセクションでは省略URIのプレフィックスに使われる名前空間プレフィックスのvcardの宣言を出力XMLから読み取れたが、RDFは完全なURIのみを使い、短いものは使わない。JenaはPrefix mappingで出力に使われる名前空間を操作する方法を提供している。下記はそのサンプルである。

 Model m = ModelFactory.createDefaultModel();
 String nsA = "http://somewhere/else#";
 String nsB = "http://nowhere/else#";
 Resource root = m.createResource( nsA + "root" );
 Property P = m.createProperty( nsA + "P" );
 Property Q = m.createProperty( nsB + "Q" );
 Resource x = m.createResource( nsA + "x" );
 Resource y = m.createResource( nsA + "y" );
 Resource z = m.createResource( nsA + "z" );
 m.add( root, P, x ).add( root, P, y ).add( y, Q, z );
 System.out.println( "# -- no special prefixes defined" );
 m.write( System.out );
 System.out.println( "# -- nsA defined" );
 m.setNsPrefix( "nsA", nsA );
 m.write( System.out );
 System.out.println( "# -- nsA and cat defined" );
 m.setNsPrefix( "cat", nsB );
 m.write( System.out );

以下は異なるプレフィックスによる3つのRDF/XMLである。
最初はデフォルトとなっていてプレフィックスを持たない標準的なものとなっている。

 # -- no special prefixes defined

<rdf:RDF
    xmlns:j.0="http://nowhere/else#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:j.1="http://somewhere/else#" >
  <rdf:Description rdf:about="http://somewhere/else#root">
    <j.1:P rdf:resource="http://somewhere/else#x"/>
    <j.1:P rdf:resource="http://somewhere/else#y"/>
  </rdf:Description>
  <rdf:Description rdf:about="http://somewhere/else#y">
    <j.0:Q rdf:resource="http://somewhere/else#z"/>
  </rdf:Description>
</rdf:RDF>

見て取れるようにrdf名前空間が自動的に宣言されている。これはタグが必要になるからである。XML名前空間の宣言にはPとQのプロパティが必要になるのだが、この例ではModelにそのプレフィックスは導入されていない。ここでは、j.0とj.1という名前空間が作られた。

setNsPrefix(String prefix, String URI)メソッドはプレフィックスで省略されるであろう名前空間URIを宣言する。Jenaは無名キャラクタで終わるURIと正しいXML名前空間名のプレフィックスを必要とする。RDF/XML writerはXML名前空間にそれらプレフィックス宣言を含めるようにし、出力に使用する。

# -- nsA defined

<rdf:RDF
    xmlns:j.0="http://nowhere/else#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:nsA="http://somewhere/else#" >
  <rdf:Description rdf:about="http://somewhere/else#root">
    <nsA:P rdf:resource="http://somewhere/else#x"/>
    <nsA:P rdf:resource="http://somewhere/else#y"/>
  </rdf:Description>
  <rdf:Description rdf:about="http://somewhere/else#y">
    <j.0:Q rdf:resource="http://somewhere/else#z"/>
  </rdf:Description>
</rdf:RDF>

その他の名前空間は構築された名前を取得しているが、nsA名はプロパティダグで使われている。Jenaのコードでは値を使用してなにかするのにもプレフィクス名は要らない。

# -- nsA and cat defined

<rdf:RDF
    xmlns:cat="http://nowhere/else#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:nsA="http://somewhere/else#" >
  <rdf:Description rdf:about="http://somewhere/else#root">
    <nsA:P rdf:resource="http://somewhere/else#x"/>
    <nsA:P rdf:resource="http://somewhere/else#y"/>
  </rdf:Description>
  <rdf:Description rdf:about="http://somewhere/else#y">
    <cat:Q rdf:resource="http://somewhere/else#z"/>
  </rdf:Description>
</rdf:RDF>

どちらのプレフィクスも出力に使用され、生成されたプレフィックスが必要なくなっている。

Implicit prefix definitions

setNsPrefixを呼び出すことで提供されるプレフィックス宣言と同様に、Jenaはmodel.read()で使われた入力で使用されたプレフィックスを覚える。前回の例で出力されたものをファイルに貼付けて、file:/tmp/fragment.rdfに保存する。そして以下のコードを実行すると

Model m2 = ModelFactory.createDefaultModel();
m2.read( "file:/tmp/fragment.rdf" );
m2.write( System.out );

出力には保存された入力からのプレフィックスが見る事ができる。どこにも使われていなくても全てのプレフィックスが書かれる。もし出力に必要のないプレフィックスであればremoverNsPrefix(String prefix)で削除することができる。
NTriplesは任意の短いURIを書く方法を持っていないので、出力プレフィックスの通知を取らず、入力も提供されない。
N3 notationもJenaでサポートされていて、短いプレフィックスを持っておりそれらは入力で記録され、出力で使用される。

Jenaは既存のマッピングであるJavaのMapのようなモデルが保持しているプレフィックスマッピングのオペレーションも持っている。また、一度に全てのグループのマッピングを追加できる。PerfixMappingで詳細は説明する。

Jena RDF Packages

JenaはセマンティックWebアプリケーションの為のJava APIである。org.apache.jena.rdf.modelパッケージはアプリケーション開発者にとってキーとなるRDFパッケージである。APIは変更なしに他の実装でアプリケーションコードを動作させる為のInterfaceを定義している。このパッケージにはModel、Resource、Properties、Literals、Statementsや他のRDFのキーコンセプトをInterfaceとして含んでいる。また、Modelを生成する為のModelFactoryも含んでいる。特定の実装クラスを使用せずに、可能な限りInterfaceを使用することで、アプリケーションコードを独立させている。

org.apache.jena.tutorialパッケージはTutorial中に使用される動くコードを含んでいる。

org.apache.jena….implパッケージは共通の多くの実装クラスを含んでいる。例えば、ResourceImpl, PropertyImplとLiteralImplは直接使うか、他の実装のサブクラス化するなどの為のクラスが定義されている。特に変わった事が無ければだいたいの場合は直接使う。
例えば、使われているModelのcreateResourceメソッドを使用した方がResourceImplの新しいインスタンスを作る場合によい。
この方法では、最適化されたResourceの実装をModelの実装として使用する場合に、この二つの型の違いについて議論する必要がなくなる。

次回も引き続きApache JenaでRDFの基礎を学ぶ。

Comments

Add Comment

Login
This entry was tagged #OpenData #LinkedData #RDF