内容


将已有的数据置入语义 Web

使用 SquirrelRDF 向语义 Web 公开 LDAP 目录

Comments

通过采用通用的、灵活的 RDF(资源描述框架)标准,语义 Web 为数据共享与集成开辟了一个新时代。RDF 的特点使其很容易合并数据和跨不同的数据源进行查询。有大量的数据以其他形式存在,如 XML、关系数据库和 LDAP 目录。RDF 非常灵活,完全能够表达这些格式。但是,将已有的数据转化成 RDF 是一项庞大、成本高昂的任务,而且在很多情况下都是不必要的。一些工具可以将现有数据公开为可通过 SPARQL 查询的 Web 端点,SPARQL 是语义 Web 的查询语言。SquirrelRDF 就是其中之一,这种开放源码工具(链接参见 参考资料 是 Jena Semantic Web 框架的一部分。

本文的目的在于说明如何为 LDAP 目录创建可通过 SPARQL 查询的端点,同时介绍了重要的语义 Web 概念。建立端点之后,我还将说明如何使用一些 Jena Java™ 类进一步完善,最后使用 JavaScript 从基于浏览器的客户端查询它,

什么是语义 Web?

语义 Web 是一种新出现的技术,它以表示数据、查询数据和对数据应用规则的一组标准为基础。核心技术包括用于表示的 RDF、用于查询的 SPARQL、用于构造的 RDFS 以及用于构造和推理的 OWL。语义 Web 有很多突出的优点,包括数据集成更简单、搜索更精确、知识管理更方便等等,结果语义 Web 这个词的含义越来越丰富(关于语义 Web 标准的更多信息请参阅 参考资料)。

RDF 是语义 Web 建立的基础:将数据表示成有向加权图的一种标准。资源 是用全局惟一的、可解析 URI 标记的实体。图的节点是资源和文字,节点之间用有向边连接,边上用谓词标记。图可以序列化,列举图中的每一条边。每条边称为一个命题(statement),都有一个主语(subject)(边的源点)、一个谓词(边的标号)和一个宾语(object)(边的宿点)。由于每个命题都有主语、谓词和宾语,也被称为三元组。每个命题的主语必须是一个资源。谓词也是资源。命题的宾语可以是资源或者文字。

RDF 和 XML 有一些重要的区别。首先,RDF 是基于图的,而 XML 是基于树的。RDF 没有明确的顺序,所有的边组成一个集合,而 XML 元素是有顺序的。最后,RDF 是一种不含标准序列化的数据模型。RDF 可以序列化成多种形式,包括 RDF-XML、n3、Terse RDF Triple Language(Turtle,请参阅 参考资料)等。清单 1 中的例子采用 Turtle 描述了关于两个人的一些联系信息。

清单 1. RDF 的例子
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
<http://wingerz.com/who#wing> a foaf:Person ;
 foaf:name "Wing C. Yung" ;
 foaf:mbox <mailto:wing@example.com> ;
 foaf:phone "1-555-555-5555" ;
 foaf:knows <http://thefigtrees.net/lee/ldf-card#LDF> .
 
<http://thefigtrees.net/lee/ldf-card#LDF> a foaf:Person ;
 foaf:name "Lee Feigenbaum" ;
 foaf:mbox <mailto:lee@example.com> ;
 foaf:phone "1-555-555-5556" .

第一行定义了数据前缀,这样 <http://xmlns.com/foaf/0.1/name> 就能简写为 foaf:name。可以指定多个前缀。此外,Turtle 用分号(;)表示后续行中的谓词和宾语使用相同的主语。谓词 a 是 RDF 类型谓词(<http://www.w3.org/1999/02/22-rdf-syntax-ns#:type>)的缩写,可用于表示资源属于特定类型。

虽然 RDF 的格式很自由,但可用 OWL(Web 本体语言)通过定义概念的谓词词汇表和关于谓词的规则来限制数据的结构(关于 OWL 的更多信息请参阅 参考资料)。OWL 本体的一个例子是 Friend of a Friend (FOAF),用于表达联系人信息及相互关系的 RDF 数据(关于FOAF 的更多信息请参阅 参考资料)。本体可以定义数据类(比如 foaf:Person),限制主语和宾语的类型(foaf:knows 的主语和宾语必须是 foaf:Person 类型,并对谓词的基数进行限制。只要可能应尽量使用 FOAF 这类通用本体,公共的结构和词汇表有助于数据的集成。

使用 SPARQL 查询 RDF

SPARQL 是语义 Web 的标准查询语言。其语法类似于 SQL,SPARQL 查询包括一系列的三元组范型和修饰符。SPARQL 被设计成能够查询 Web 上分布的多个数据源。

试验下面的查询可使用通用的 SPARQL 处理程序(请参阅 参考资料)。将 http://wingerz.com/dw/listing1.ttl 粘贴到 Target graph URI 字段中。输入查询并单击 Get Results 之后,就会检索 Turtle 文件并进行查询。

SPARQL 有四种类型的查询:

  • SELECT:返回满足查询的一组变量绑定(类似 SQL SELECT)。非常适合产生应用程序要消费的数据。
  • CONSTRUCT:返回一个图(一组 RDF 命题)。适合检索和转换 RDF。
  • ASK:返回一个布尔值,说明是否存在查询的结果。
  • DESCRIBE:依赖于实现。接收一个资源并返回描述该资源的图。

清单 2 示范了 SELECT 查询。

清单 2. SPARQL SELECT 例子
Query:
PREFIX foaf: <http://xmlns.com/foaf/0.1/> 
SELECT ?person ?phone 
WHERE {
 ?person foaf:name "Wing C. Yung" . 
 ?person foaf:phone ?phone
}

Result:
----------------------------------------------------
| person | phone |
====================================================
| <http://wingerz.com/who#wing> | "1-555-555-5555" |
----------------------------------------------------

CONSTRUCT 非常重要,可以根据 SPARQL 查询结果构造 RDF 图。在数据合并的时候很方便,因为 CONSTRUCT 图中的谓词不一定和原图中的相同。清单中查询 foaf:phone,不过在它的位置使用不同的谓词构造了一个新的图(wingerz:officephone)。

清单 3. SPARQL CONSTRUCT 的例子
Query:
PREFIX foaf: <http://xmlns.com/foaf/0.1/> 
PREFIX wingerz: <http://wingerz.com/> 
CONSTRUCT {
 ?person foaf:name "Wing C. Yung" . 
 ?person wingerz:officephone ?phone
} WHERE {
 ?person foaf:name "Wing C. Yung" . 
 ?person foaf:phone ?phone
}

Result:
<?xml version="1.0"?>
<rdf:RDF
 xmlns:wingerz="http://wingerz.com/"
 xmlns:foaf="http://xmlns.com/foaf/0.1/"
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
 <rdf:Description rdf:about="http://wingerz.com/who#wing">
 <wingerz:officephone>1-555-555-5555</wingerz:officephone>
 <foaf:name>Wing C. Yung</foaf:name>
 </rdf:Description>
</rdf:RDF>

试验 CONSTRUCT 查询的时候,在资源页面上可以看到完整的响应。遗憾的是,结果被序列化为 RDF-XML 格式,不是很优雅的一种 RDF 序列化格式。

以 RDF 格式存储数据的好处

初看起来,RDF 数据似乎有点笨拙而且非常罗嗦。但是有一些突出的优点。

  • RDF 数据是自描述的。RDF 图包括数据和结构。资源通常是可解析的。如果看到清单 1 所示的 RDF 但不知道 foaf:phone 是如何定义的,可以在 Web 浏览器中访问 http://xmlns.com/foaf/0.1/phone

  • 数据的结构没有限制。XML 是层次化的,在关系数据库中建模(和查询)图结构非常困难。

  • SPARQL 不需要设计数据访问接口。搜索 API(比如搜索雇员)往往不是功能非常有限就是过于复杂。

  • 数据容易合并。合并数据(不同的图)是一项简单操作,只需要建立包含图中所有三元组的集合。全球唯一资源减少了歧义。而且如果必要的话,可以用 OWL 规则将不同 URI 的资源映射到同一个 URI。

  • 虽然可以增加结果(使用 RDFS 和OWL),但结构不是强制性的。向资源添加未指定的属性不会使数据无效。也不会破坏和数据交互的已有代码。

SquirrelRDF 入门

Jena Semantic Web Framework 包括 RDF 存储和查询执行组件。SquirrelRDF 是将关系数据库和 LDAP 中的数据公开以便通过 SPARQL 查询的工具。本文主要讨论 SquirrelRDF 的 LDAP 组件。(关于这些项目的更多信息请参阅 参考资料。)

LDAP 目录本身的结构很容易转化成 RDF。每个 LDAP 对象类都有一组属性。其中一些属性指向文字值(比如名称),另一些则指向其他对象[比如专用名词(DN)指定的工作地点]。

下载 SquirrelRDF 源代码。还需要 Jena(下载推荐的版本)。SquirrelRDF 主页详细介绍了需要的各种信息(请参阅 参考资料)。

安装后,第一步是寻找 LDAP 存储的模式。如果没有可供试验的 LDAP 存储,可以安装 OpenLDAP 并按照使用说明创建一个简单的地址簿(请参阅 参考资料,尽管可能没有必要费这么多事,因为本文的目标是利用已有的数据源)。模式包含所有不同的对象类及其属性。通过分析属性,可以确定目录中对象之间的关系。

该例中使用一个非常简单的 LDAP 模式。其中 Person 类包含一些基本的性质(比如姓名和电话),同时指向另一个对象类 OfficeLocationOfficeLocation 包括名称、两个街道地址字段、市和州。Person 还有一个 manager 属性,指向另一个 Person

现在来创建从 LDAP 模式到 RDF 的映射。SquirrelRDF 使用 RDF 文件表示这种映射。lmap:server 谓词用于指定 LDAP 存储的位置。映射允许分配两种类型的 RDF 谓词:文字宾语或者资源宾语。姓名和电话这类基本属性映射为文字宾语谓词。这类映射需要 LDAP 属性名和 RDF 谓词名。然后即可将其链接到资源。假设需要将 LDAP cn 映射到 RDF foaf:name。清单 4 中创建了一个资源来进行链接 (:namemapping),然后将其链接到配置资源(<>)。

清单 4. SquirrelRDF 文字映射
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lmap: <http://jena.hpl.hp.com/schemas/ldapmap#> .
<> lmap:mapsProp :namemapping .
:namemapping lmap:property foaf:name 
 ; lmap:attribute "cn" .

请注意,将这些链接到一起的 URI 无关紧要,可以改用空白节点,即没有 URI 的资源。它与空白 URI 的资源不同,后面也包括这样的资源。空白 URI 是一种相对 URI,因而解析到 Turtle 文件所在的位置。

清单 5 中用空白节点替换了资源,使用方括号表明其中的两个命题片断都使用空白节点主语。这些方括号出现在 lmap:mapsProp 谓词之后,表示空白节点是该命题的宾语。

清单 5. SquirrelRDF 文字映射到空白节点
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lmap: <http://jena.hpl.hp.com/schemas/ldapmap#> .
<> lmap:mapsProp 
 [ lmap:property foaf:name 
 ; lmap:attribute "cn" ]

要映射指向专有名词的 LDAP 属性,可将空白节点的类型指定为 lmap:ObjectProperty。这样可以保证分配的谓词指向资源(而不是文字)。清单 6 显示了完整的配置文件,包括两个 lmap:ObjectProperty 属性。LDAP 属性默认分配文字资源谓词。要注意,可能的话最好使用已有的谓词,这里使用了 FOAF 本体的一些词汇表。对于地点也有一个词汇表,不过这里没有用到。

清单 6. SquirrelRDF (dw.ttl) 映射
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lmap: <http://jena.hpl.hp.com/schemas/ldapmap#> .
@prefix people: <http://wingerz.com/people#> .
@prefix ol: <http://wingerz.com/officelocation/rdf#> .

<> a lmap:Map ;
 lmap:server <ldap://wingerz.com:389/ou=people,o=wingerz.com> ;

 # Person properties
 lmap:mapsProp 
 [ lmap:property foaf:name 
 ; lmap:attribute "cn" ; ] ;
 lmap:mapsProp 
 [ lmap:property foaf:phone 
 ; lmap:attribute "telephoneNumber" ; ] ;
 lmap:mapsProp 
 [ lmap:property people:ol 
 ; lmap:attribute "officeLocation" 
 ; a lmap:ObjectProperty ; ] ;
 lmap:mapsProp 
 [ lmap:property people:manager 
 ; lmap:attribute "manager" 
 ; a lmap:ObjectProperty ; ] ;
 
 # OfficeLocation properties
 lmap:mapsProp 
 [ lmap:property ol:address1 
 ; lmap:attribute "address1" ; ] ;
 lmap:mapsProp 
 [ lmap:property ol:address2 
 ; lmap:attribute "address2" ; ] ;
 lmap:mapsProp 
 [ lmap:property ol:city 
 ; lmap:attribute "city" ; ] ;
 lmap:mapsProp 
 [ lmap:property ol:state 
 ; lmap:attribute "state" ; ] ;
 lmap:mapsProp 
 [ lmap:property ol:postalCode 
 ; lmap:attribute "postalCode" ; ] ;
.

实验映射

SquirrelRDF 提供了运行 SPARQL 查询的命令行工具。进一步开发之前先行测试是一种好办法。将清单 7 中的查询保存到文件中。

清单 7. 测试查询 (test.rq)
PREFIX foaf: <http://xmlns.com/foaf/0.1/> 
SELECT ?person ?phone 
WHERE {
 ?person foaf:name "Wing C Yung" . 
 ?person foaf:phone ?phone
}

dw.ttl(映射文件名)和 test.rq(查询文件名)为参数运行 squirrelrdf.Query。应该会得到某个人的 URI 和电话号码。

如果稍微再进一步,清单 8 查询在某个州工作的所有雇员(可能非常多)。请注意,查询检索人的姓名和所在城市。

清单 8. 测试查询 (test2.rq)
PREFIX foaf: <http://xmlns.com/foaf/0.1/> 
SELECT ?person ?city ?name 
WHERE{
 ?person foaf:name ?name . 
 ?person people:ol ?officelocation .
 ?officelocation ol:state "MA" ;
 ol:city ?city
}

将 SquirrelRDF 设置成服务

我们希望将 SquirrelRDF 设置成一种 HTTP 服务,以便 Web 客户机能够使用它。SquirrelRDF 是分布式的,使用简单的 Servlet 公开 HTTP 服务端点(squirrelrdf.Servlet)。可用喜欢的任何 Servlet 容器来建立。允许测试前需要对查询 URL 编码,以便作为 URL query 参数传递。访问 http://localhost:8080/squirrel?query=...(实际位置取决于服务器配置,请参阅 参考资料)。

缺省 Servlet 非常小,只能回答 SELECT 查询,用标准 XML 格式表示结果集。为了使 SPARQL 端点更有用,可让它支持更多的特性。

执行查询

增加更多的功能之前,我们先要找一种更好的办法测试端点,因为 URL 编码查询和手工创建 URL 都很麻烦。第一个例子使用的通用 SPARQL 处理程序无法再用,因为它取回执行查询的整个 RDF 图,我们的 SquirrelRDF 服务本身是一个能回答查询的 SPARQL 端点(通过将其转化成 LDAP 查询)。创建一个简单 HTML 表单编写和提交查询。一个例子是功能更完善的 SPARQLer(请参阅 参考资料)。单击 Graphs 选项卡来设置端点 endpoint(比如 http://localhost:8080/squirrel)。单击 Change。建立查询提交的端点。尝试本文前面用到的查询。

支持其他类型的 SPARQL 查询

如前所述,CONSTRUCT 查询很重要,尤其是对非 RDF 格式存储的数据。现在,Servlet 仅支持 SELECT 查询,但是底层的查询引擎可以执行全部四种查询类型。扩展现有的 Servlet 需要修改 doQuery() 方法,如清单 9 所示。com.hp.hpl.jena.query.Query 对象知道是什么类型的查询,因此可通过它确定 com.hp.hpl.jena.query.engine1.QueryEngine 应该怎么做。不同的查询类型返回不同类型的数据:ASK 返回布尔值,CONSTRUCTDESCRIBE 返回图(从技术上说,返回的是图的包装 Model),SELECT 返回结果集。

清单 9. 处理其他类型的 SPARQL 查询
Query q = QueryFactory.create(theQuery, ".", Syntax.syntaxSPARQL);
int queryType = q.getQueryType();
Model m = null;
switch(queryType){
case Query.QueryTypeAsk:
 boolean b = qe.execAsk();
 String str = ResultSetFormatter.asXMLString(b);
 resp.setHeader("Content-Type", "application/xml");
 resp.getOutputStream().write(str.getBytes());
 break;
case Query.QueryTypeConstruct:
 // Gets a model.
 m = qe.execConstruct();
 resp.setHeader("Content-Type", "application/rdf+xml");
 // A Model can serialize itself. 
 // The serialization format can be passed in as an argument,
 // default is to write out as RDF/XML.
 m.write(resp.getOutputStream());
 break;
case Query.QueryTypeDescribe:
 m = qe.execConstruct();
 resp.setHeader("Content-Type", "application/rdf+xml");
 m.write(resp.getOutputStream());
 break;
case Query.QueryTypeSelect:
 ResultSet results = qe.execSelect();
 resp.setHeader("Content-Type", "application/xml");
 ResultSetFormatter.outputAsXML(resp.getOutputStream(), results);
 break;
}

获得 JSON 输出

JSON 是一种数据序列化格式,可用 JavaScript 计时器 eval 来创建对象。JSON 经常被用作 Web 数据访问格式,因为基于 JavaScript 浏览器的应用程序不用解析 XML 数据就能消费结构化数据(请参阅 参考资料)。清单 10 显示了清单 23 JSON 格式的结果。

清单 10. JSON 查询结果
{
 "head": {
 "vars": [ "person" , "phone" ]
 } ,
 "results": {
 "distinct": false ,
 "ordered": false ,
 "bindings": [
 {
 "person": { "type": "uri" , "value": "http://wingerz.com/who#wing" } ,
 "phone": { "type": "literal" , "value": "1-555-555-5555" }
 }
 ]
 }
}

com.hp.hpl.jena.query.ResultSetFormatter 工具类用于输出 SPARQL 查询结果。清单 9 使用它以 XML 格式输出结果(SELECTASK 查询)。结果也可以用 RDF 格式。毫不奇怪,使用这个工具类也能将 SELECT 结果邦定输出为 JSON,如清单 11 所示。

清单 11. 为 SPARQL查询增加 JSON 支持
String output = req.getParameter("output");
if (output.equals("json")){
 resp.setHeader("Content-Type", "application/json");
 ResultSetFormatter.outputAsJSON(resp.getOutputStream(), results);
}

支持 XSLT

从 XML 形式的 SPARQL 结果集中提取数据的另一种办法是应用 XSLT。清单 12 显示了 SPARQL 结果集 XML 的例子。<head> 元素包含所有的变量,results 元素包含 result 列表,每个都包含 binding。

可以向服务器上增加 XSLT 服务,通过外部 XSLT 服务处理查询结果,或者在 XML 输出重增加样式表链接。在 XML 输出中添加样式表链接,将 com.hp.hpl.jena.query.ResultSetFormatter 作为 XSLT URI 的新增参数传递,如清单 13 所示。这样不会执行转换,只是在生成的文档中包括到 XSLT 的链接。当 XSLT 处理程序(比如现代 Web 浏览器)准备文档的时候就会执行转换了(请参阅 参考资料)。

清单 12. SPARQL 结果 XML
<?xml version="1.0"?>
<sparql
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:xs="http://www.w3.org/2001/XMLSchema#"
 xmlns="http://www.w3.org/2005/sparql-results#" >
 <head>
 <variable name="person"/>
 <variable name="phone"/>
 </head>
 <results ordered="false" distinct="false">
 <result>
 <binding name="person">
 <uri>http://wingerz.com/who#wing</uri>
 </binding>
 <binding name="phone">
 <literal>1-555-555-5555</literal>
 </binding>
 </result>
 </results>
</sparql>
清单 13. 包括 XSLT
String stylesheet = req.getParameter("xslt");
if (stylesheet != null)
 ResultSetFormatter.outputAsXML(resp.getOutputStream(), results, stylesheet);
else
 ResultSetFormatter.outputAsXML(resp.getOutputStream(), results);

从 JavaScript 查询

有时候希望建立自己的 Web 用户界面(UI)或者从 JavaScript 应用程序中运行 SPARQL 查询。参考资料 中提供了可免费下载的 SPARQL JavaScript 库。主要用于处理 SPARQL SELECT 查询返回的 JSON 格式的结果。所幸的是,可以让 SPARQL 服务支持这种功能。该库背后最突出的思想是转换器的概念——SPARQL 端点的 JSON 输出包含一般客户机可能用不到的一些数据,比如某些数据类型信息。转换器将 JSON 输出转换成更容易使用的、更自然的对象。

结束语

只需略加处理就能让 LDAP 能够通过 SPARQL 查询。让数据源模仿 RDF,可以使应用程序更容易将其与其他数据源集成起来,并提供了一种更强大的查询数据的方法。虽然本文讨论的是 LDAP,但是可以类似的方式处理其他数据源和格式。拥抱语义 Web 和享受它的优点,不需要放弃已有的数据建模和存储技术。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=228025
ArticleTitle=将已有的数据置入语义 Web
publish-date=05312007