Jena RDF 包
Jena是一个为语义网应用设计的一个Java API。对应用开发者而言,主要可用的RDF包是com.hp.hpl.jena.rdf.model。因为API是以接口的方式定义的,所以应用代码可以使用不同的实现机制而不用改变代码本身。这个包包含了可以表示模型,资源,属性,文本,陈述和其他RDF关键概念的接口,还有一个用来创建模型的ModelFactory。所以如果要应用代码与实现类保持独立,最好尽可能地使用接口,而不要使用特定的实现类。(关于面向接口编程,请参考kafka0102先生的文章《消除实现继承和面向接口编程》)
com.hp.hpl.jena.Tutorial包包含了本教程所有例子中使用到的工作源代码。
com.hp.hpl.jena.impl这些包包含了许多执行时所常用的执行类。比如,它们定义了诸如ResourseImpl,PropertyImpl和LiteralImpl的类,这些类可以被不同的应用直接使用也可以被继承使用。应用程序应该尽可能少地直接使用这些类。例如,与其使用ResouceImpl来创建一个新的实例,更好的办法是使用任何正在使用的模型的createResource方法来完成。那样的话,如果模型的执行采用了一个优化的Resouce执行,那么在这两种类型中不需要有任何的转换工作。
操纵模型
到目前为止,本教程主要讲述的是如何创建,读入和输出RDF模型。现在是时候要讲述如何访问模型中的信息。
如果有了一个资源的URI,那么就可以用Model.getResource(String uri)来从模型获取这个资源对象。这个方法被定义来返回一个资源对象,如果它确实存在于模型中,否则的话就创建一个新的。例如,如何从模型中获取Adam Smith资源,这个模型是Tutorial5中从文件读入的:
// java code
// retrieve the John Smith vcard resource from the model
Resource vcard = model.getResource(johnSmithURI);
# jruby code
vcard = model.getResource JohnSmithURI
Resouce 接口定义了一系列用于访问某个资源的属性的方法。Resource.getProperty(Property p)方法访问了该资源的属性。这个方法不允许通常的Java访问的转换,因为所返回的对象是Statement,而不是你所预计的Property。返回整个陈述的好处是允许应用程序通过使用它的某个访问方法来访问该陈述的客体来访问这个属性值。例如如何获取作为vcard:N属性值的资源:
// java code
// retrieve the value of the N property
Resource name = (Resource) vcard.getProperty(VCARD.N)
.getObject();
# jruby code
# 由于jruby是无类型的,所以不需要类型转换。
name = vcard.getRequiredProperty(VCARD::N).getObject
一般而言,一个陈述的客体可以是一个资源或是一个文本。所以此应用程序代码知道这个值一定是个资源,就将类型资源映射到返回的对象上。Jena的目标之一是提供会返回值为特定类型的方法,这样,应用程序就不必再做类型转换工作,也不必再编译时做类型检查工作。以上的代码片段也可以写成更方便的形式:
// java code
// retrieve the value of the FN property
Resource name = vcard.getProperty(VCARD.N)
.getResource();
# jruby code
name = vcard.getProperty(VCARD::N).getResource
类似地, 属性的文本值也可以被获取:
// java code
// retrieve the given name property
String fullName = vcard.getProperty(VCARD.FN)
.getString();
# jruby code
fullName = vcard.getProperty(VCARD::FN).getString
在这个例子中,资源vcard只有一个vcard:FN属性和一个vcard:N属性。RDF允许资源有重复的属性,例如Adam可能有超过一个的昵称。让我们假设他有两个昵称:
// java code
// add two nickname properties to vcard
vcard.addProperty(VCARD.NICKNAME, "Smithy").addProperty(VCARD.NICKNAME, "Adman");
# jruby code
vcard.addProperty(VCARD::NICKNAME, 'Smithy').addProperty(VCARD::NICKNAME, 'Adman')
正如前面所提到的那样,Jena将RDF模型表示为一组陈述,所以在模型中新增一个与原有陈述有着相同的主体,谓词和客体的陈述并不会后什么作用。Jena没有定义会返回模型中存在的两个昵称中的哪一个。vcard.getProperty(VCARD.NICKNAME)调用的结果是不确定的。Jena会返回这些值中的某一个,但是并不保证两次连续的调用会同一个值。
一个属性很有可能会出现多次,而方法Resource.listProperty(Property p)可以用来返回一个iterator,这个iterator会列出所有的值。此方法所返回的iterator返回的对象的类型为Statement。我们可以像这样列出所有的昵称:
// java code
// set up the output
System.out.println("The nicknames of \""
+ fullName + "\" are:");
// list the nicknames
StmtIterator iter = vcard.listProperties(VCARD.NICKNAME);
while (iter.hasNext()) {
System.out.println(" " + iter.nextStatement()
.getObject()
.toString());
}
# jruby code
puts 'The nicknames of "' + full_name + '" are'
iter = vcard.listProperties VCARD::NICKNAME
while iter.hasNext
puts ' ' + iter.nextStatement.getObject.toString
end
此代码可以在Tutorial6中找到,运行后会产生如下输出:
The nicknames of "John Smith" are:
Smithy
Adman
一个资源的所有属性可以用不带参数的listStatement()方法列出。
查询模型
前一节讨论了如何通过一个有着已知URI的资源来操纵模型。本节要讨论查询模型。核心的Jena API只支持一些有限的查询原语。对于更强大查询设备RDQL的介绍不在此文档中。
列出模型所有陈述的Model.listStatements()方法也许是最原始的查询模型方式。然而并不推荐在大型的模型上使用这个方法。类似的有Model.listSubjects(),但其所返回的iterator会迭代所有含有属性的资源,例如是一些陈述的主体。
Model.listSubjectsWithProperty(Property p, RDFNode o)方法所返回的iterator跌代了所有具有属性p且p属性的值为o的资源。我们可能会预计使用rdf:type属性来搜索资源的类型属性以获得所有的vcard资源:
// retrieve all resource of type Vcard.
ResIterator iter = model.listSubjectsWithProperty(RDF.type, VCARD.Vcard);
然而,不幸的是,我们现在正在使用的vcard模式并没有为vcard定义类型。然而,如果我们假设只有类型为vcard的资源才会使用vcard:FN属性,并且在我们的数据中,所有此类资源都有这样一个属性,那么我们就可以像这样找到所有的vcard:
// list vcards
ResIterator iter = model.listSubjectsWithProperty(VCARD.FN);
while (iter.hasNext()) {
Resource r = iter.nextResource();
...
}
所有的这些查询方法不过是在原语查询方法model.listStatements(Select s)上稍做变化而已。此方法会返回一个iterator,该iterator会跌代模型中所有被s选中的陈述。这个selector接口被设计成可扩展的,但是目前,它只有一个执行类,那就是com.hp.hpl.jena.rdf.model包中的SimpleSelector类。在Jena中使用SimpleSelector 是很少见的情况,即当需要直接使用一个特定类而不是使用接口。SimpleSelector的构造函数带有三个参数:
Selector selector = new SimpleSelector(subject, predicate, object)
这个selector会选择所有主体与参数subject相配,谓词与参数predicate相配,且客体与参数object相配的陈述。
如果某个参数值为null,则它表示与任何值均匹配;否则的话,它们就会匹配一样的资源或文本。(当两个资源有相同的URI或是同一个空白结点时,这两个资源就是一样的;当两个文本是一样的当且仅当它们的所有成分都是一样的。)所以:
Selector selector = new SimpleSelector(null, null, null);
会选择模型中所有的陈述。
Selector selector = new SimpleSelector(null, VCARD.FN, null);
会选择所有谓词为VCARD.FN的陈述,而对主体和客体没有要求。作为一个特殊的缩写
listStatements(S, P, O)
等同于
listStatements(new SimpleSelector(S, P, O))
下面的代码可以在Tutorial7中找到,列举了数据库中所有vcard中的全名。
// java code
// select all the resources with a VCARD.FN property
ResIterator iter = model.listSubjectsWithProperty(VCARD.FN);
if (iter.hasNext()) {
System.out.println("The database contains vcards for:");
while (iter.hasNext()) {
System.out.println(" " + iter.nextStatement()
.getProperty(VCARD.FN)
.getString());
}
} else {
System.out.println("No vcards were found in the database");
}
# jruby code
require 'java'
module Java
include_package 'com.hp.hpl.jena.rdf.model'
include_package 'com.hp.hpl.jena.vocabulary'
include_package 'com.hp.hpl.jena.util'
include_package 'java.io'
InputFileName = 'vc-db-1.rdf'
model = Java::ModelFactory.createDefaultModel
input_stream = Java::FileManager.get.open InputFileName
if input_stream == nil
raise ArgumentError, "File:" + InputFileName + "not found"
end
model.read input_stream, ''
iter = model.listSubjectsWithProperty VCARD::FN
if iter.hasNext
puts 'The database contains vcards for:'
while iter.hasNext
puts ' ' + iter.nextResource.getRequiredProperty(VCARD::FN).getString
end
else
puts 'No vcards were found in the database'
end
end
这个会产生类似如下的输出:
The database contains vcards for:
Sarah Jones
John Smith
Matt Jones
Becky Smith
你的下一个练习是修改此代码以使用SimpleSelector而不是使用listSubjectsWithProperty来达到相同的效果。