[转载]认识 Atom 发布协议,第 3 部分: Apache Abdera 项目简介

认识 Atom 发布协议,第 3 部分: Apache Abdera 项目简介


本系列的前几期介绍了 Atom 发布协议(Publishing Protocol),描述了在实际应用程序中使用的各种方式。本文将举例说明如何使用一个新的开放源代码项目 Abdera 实现支持 Atom 的应用程序,该项目目前处于 Apache Software Foundation 孵化阶段。

本文假设您已经阅读过 Atom Format 规范,对连锁有一定了解(请参阅 参考资料)。所有的例子都是用 Java 编写,并提供了包含全部代码例子的 Eclipse 项目供您下载。

开始

首先要保证安装了 Apache Abdera 的当前版本。源代码可以从 Apache Subversion 资料库(http://svn.apache.org/repos/asf/incubator/abdera/java/branches/0.2.0-incubating/)下载。要检索源代码,需要安装 subversion 客户机并使用下面的命令:

> svn co http://svn.apache.org/repos/asf/incubator/abdera/java/branches/0.2.0-incubating/

下载源代码镜像之后,就可以使用 Ant 1.6.5 或更高版本构建 Abdera 了。

> cd trunk
> ant -f build/build.xml dist

构建完成之后,编译后的 jar 和依赖关系文件存放在新建的 “dist” 目录中。运行本文中的例子需要在类路径中包含下列 jar。并非所有例子都需要依赖关系列表中的全部 jar:

表 1. 运行示例需要的 jar
Abdera (dist)依赖关系(dist/lib)
  • abdera.client.0.2.0-incubating.jar
  • abdera.core.0.2.0-incubating.jar
  • abdera.parser.0.2.0-incubating.jar
  • abdera.protocol.0.2.0-incubating.jar
  • axiom-api-1.0.jar
  • axiom-impl-1.0.jar
  • commons-codec-1.3.jar
  • commons-httpclient-3.0.1.jar
  • commons-logging-1.0.4.jar
  • geronimo-activation_1.0.2_spec-1.1.jar
  • log4j-1.2.12.jar
  • stax-1.1.2-dev.jar
  • stax-api-1.0.jar
  • jaxen-1.1-beta-7.jar

起步

Abdera 项目包含一组独立的模块,下表按字母顺序列出了这些模块。当然,最重要也是最常用的模块是 core、dependencies 和 parser:

表 2. Apache Abdera 项目模块
模块说明依赖关系
build整个项目的 Ant 构建Apache Ant 1.6.5+
clientAtom Publishing Protocol Clientcore, parser, protocol, commons-codec-1.3.jar, commons-httpclient-3.0.1.jar
coreFeed Object Model 接口Java Activation Framework, commons-logging-1.0.4.jar, log4j-1.2.12.jar
dependencies所有模块共用的依赖关系
extensions提要语法和协议扩展core, protocol, json-1.0.jar
parserStAX 和基于 Axiom 的默认 Feed Object Model (FOM)实现core, axiom-api-1.0.jar, axiom-impl-1.0.jar, stax-1.1.2-dev.jar, stax-api-1.0.jar, jaxen-1.1-beta-7.jar
protocolCommon Atom Publishing Protocol 代码core, parser
securityXML Digital Signature 和 Encryption 支持core, parser, xmlsec-1.3.0.jar, Bouncy Castle JCE 实现
serverAtom Publishing Protocol 服务器实现core, parser, protocol, Servlet API

下表列出了 core 模块中的包,该模块定义了 Abdera 所称的 “Feed Object Model”,它是按照 Atom Syndication Format 规范建模的一组接口,用于解析、创建和操作 Atom 文档。

表 3. Abdera “core” 模块中的包
说明
org.apache.abdera主包,包含一个 “Abdera” 对象
org.apache.abdera.factory定义了创建新的 Feed Object Model 对象实例的 Factory 接口
org.apache.abdera.filter定义了在解析过程中筛选 ATom 文档的接口
org.apache.abdera.model定义了处理 Atom Feed 和 Entry 文档的基本接口,该模型还包括处理 Atom Publishing Protocol Service 和 Category 文档的支持。
org.apache.abdera.parser定义了从 XML 文档创建 Feed Object Model 新的对象实例的 Parser 接口
org.apache.abdera.util提供了各种工具类,主要针对希望扩展或替换 Abdera 默认解析器和工厂实现的开发人员
org.apache.abdera.writer定义了用于序列化 Feed Object Model 对象实例的 Write 接口
org.apache.abdera.xpath定义了使用 XPath 导航 Feed Object Model 的接口

下载并构建 Abdera 源代码后,如果需要的话,可以花点时间通过浏览构建过程中生成的 Javadoc 文档(位于创建的 $ABDERA_HOME/dist/docs 目录下)来熟悉这种 API。

创建记录和提要

Abdera Feed Object Model 的两个主要功能是简化 Atom 提要和记录文档的生产与消费。

创建 Atom 文档首先要获得 org.apache.abdera.factory.Factory 的实例并设置提要或记录的属性。清单 1 显示了一个简单 Atom Entry Document 的创建:


清单 1. 创建简单的 Atom Entry 文档
    
Abdera abdera = new Abdera();
Factory factory = abdera.getFactory();

Entry entry = factory.newEntry();
entry.setId("http://example.org/foo/entry");
entry.setUpdated(new java.util.Date());
entry.setTitle("This is an entry");
entry.setContentAsXhtml("This is

markup");
entry.addAuthor("James");
entry.addLink("http://www.example.org");

entry.writeTo(System.out);

这个例子说明了 Feed Object Model 的所有主要特点。

  • 首先创建一个 org.apache.abdera.Abdera 实例,它为初始化和访问各种重要的子组件如 Abdera Parser 和 Factory 提供了顶级入口。
  • 其次获得一个 org.apache.abdera.factory.Factory 实例。这是引导创建所有 Feed Object Model 对象的接口。
  • 第三步,是用 Factory 创建新的 org.apache.abdera.model.Entry 实例。Factory 接口两个最常用的方法是 newEntry()和 newFeed()。
  • 最后使用默认的 Abdera Writer 将 Atom 文档序列化到 OutputStream。

清单 1 的输出如清单 2 所示。请注意,XML 名称空间声明、日期格式和 atom:content 元素的序列化都是由 Feed Object Model 实现自动完成的。但是因为没有换行和缩进,连成一串的结果不便于阅读:


清单 2. 创建的 Atom Entry 文档
    
http://example.org/foo/entry
2006-09-04T19:27:01.068ZThis is an entrytype="xhtml">
This is markup

James

序列化不使用空白和换行是设计优化的结果,可以提高文档的传输效率。也可使用 prettyxml writer 插入换行和缩进:


清单 3. 使用 prettyxml writer 序列化
    
Writer writer = abdera.getWriterFactory().getWriter("prettyxml");
writer.writeTo(entry, System.out);

得到的结果看起来更清楚一点:


清单 4. 漂亮的 XML 输出
    
<?xml version='1.0' encoding='UTF-8'?>

http://example.org/foo/entry
2006-09-04T19:28:58.237Z
This is an entry

This is markup



James




使用 prettyxml writer 要注意,因为可能会造成一些意外的副作用。比方说,使用这种 writer 序列化包含 XML 数字签名的 Atom Feed 或 Entry 可能造成签名失效。

清单 5 示范了 Atom Feed Document 的创建。可以想象,这个过程和创建 Atom Entry Document 是一样的:


清单 5. 创建简单的 Atom Feed Document
    
Abdera abdera = new Abdera();
Factory factory = abdera.getFactory();

Feed feed = factory.newFeed();
feed.setId("http://example.org/foo");
feed.setUpdated(new java.util.Date());
feed.setTitle("This is a feed");
feed.addLink("http://www.example.org");
feed.addLink("http://www.example.org/foo", "self");

Entry entry = factory.newEntry();
entry.setId("http://example.org/foo/entry");
entry.setUpdated(new java.util.Date());
entry.setTitle("This is an entry");
entry.setContentAsXhtml("This is

markup");
entry.addAuthor("James");
entry.addLink("http://www.example.org");

feed.addEntry(entry);

Writer writer = abdera.getWriterFactory().getWriter("prettyxml");
writer.writeTo(feed, System.out);


清单 6. 创建的 Atom Feed Document
    
<?xml version='1.0' encoding='UTF-8'?>

http://example.org/foo
2006-09-04T19:31:45.286Z
This is a feed



http://example.org/foo/entry
2006-09-04T19:31:44.695Z
This is an entry

This is markup



James





Abdera 的 Feed Object Model 接口严格遵循 Atom Syndication Format 模式,因此成为熟悉 RFC 4287 规范的开发人员创建有效 Atom 文档的自然选择。但必须指出,该实现对输入没有进行任何验证。比如,RFC 4287 要求所有的 atom:entry 和 atom:feed 元素必须且只能包含一个值为规范化 IRI 的 atom:id 元素。但是如果开发人员创建和序列化没有 atom:id 或者包含多个 atom:id 的记录或提要,Abdera 也不会抛出异常。需要开发人员来保证生成的文档是有效的。使用 FeedValidator 可以检查 Atom 文档的有效性。

向文档添加扩展

Feed Object Model 允许处理 Atom 格式扩展,而不需要开发人员事先编写模块实现这些扩展。比如,清单 7 示范了如何在一个记录的 “tag:example.org,2006:/namespaces” 添加 “foo”。清单 8 给出了序列化后的结果:


清单 7. 增加扩展元素
    
QName extensionQName =
new QName("tag:example.org,2006:/namespaces", "foo", "f");
Element element = entry.addExtension(extensionQName);
element.setAttributeValue("foo", "bar");
element.setText("fooBar");


清单 8. 包含扩展元素的 Entry Document
    
<?xml version='1.0' encoding='UTF-8'?>

http://example.org/foo/entry
2006-09-04T19:34:10.322Z
This is an entry

This is markup



James


fooBar


清单 9 显示了一个更可能的例子,向提要中增加 OpenSearch version 1.1 规范的元素:


清单 9. 向 Atom Feed 中增加 OpenSearch version 1.1 元素
    
final String OSURI = "http://a9.com/-/spec/opensearch/1.1/";
final QName TOTALRESULTS = new QName(OSURI, "totalResults");
final QName STARTINDEX = new QName(OSURI, "startIndex");
final QName ITEMSPERPAGE = new QName(OSURI, "itemsPerPage");
final QName QUERY = new QName(OSURI, "Query");

Feed feed = factory.newFeed();
feed.addExtension(TOTALRESULTS).setText("4230000");
feed.addExtension(STARTINDEX).setText("21");
feed.addExtension(ITEMSPERPAGE).setText("10");
Element query = feed.addExtension(QUERY);
query.setAttributeValue("role", "request");
query.setAttributeValue("searchTerms", "New York History");
query.setAttributeValue("startPage", "1");

灵活的内容选项

Atom 格式相对于其他连锁格式一个重要的区别在于它支持各种各样的、灵活的内容类型。Atom 记录可以包含扁平文本、转义的 HTML、结构良好的 XHTML、XML、任何字符集编码的数据和 Base64 编码的内容。Atom 记录还可以通过 URI 引用内容。Feed Object Model 提供了一种简单的方法处理各种选项。清单 10 和 11 示范了如何将记录的内容设置为扁平文本:


清单 10. 将内容设置为扁平文本
    
Entry entry = factory.newEntry();
entry.setContent("This is not markup");


清单 11. 扁平文本内容元素
    
This is not markup

使用扁平文本内容(清单 10)和转义 HTML(清单 12)时,必须注意,序列化后的内容元素基本相同(清单 1113)。重要的是提要的处理程序必须注意内容类型属性的值。在提要阅读器中显示这样的内容时,清单 11 中的扁平文本必须呈现为没有格式化的文字串 “This is not markup”,而清单 13 中的转义 HTML 必须带格式:“This is markup”。


清单 12. 将内容设置为转义 HTML
    
Entry entry = factory.newEntry();
entry.setContentAsHtml("This is markup");


清单 13. HTML 内容元素
    
This is markup

对于提要发布者来说,替代转义 HTML 的首选是通过 Entry 接口的 setContentAsXhtml 方法在记录中使用结构良好的 XHTML 片段,如清单 14 所示:


清单 14. 将内容设置为 XHTML
    
Entry entry = factory.newEntry();
entry.setContentAsXhtml("This is markup");


清单 15. XHTML 内容元素
    


This is markup



Abdera 解析传递给 setContextAsXHTML 方法的 XHTML 输入保证结构良好性,但不检查是否为有效的 XHTML。类似的,也可将记录内容设置为任何结构良好的 XML,如清单 16 和 17 所示:


清单 16. 将内容设置为 XML
    
Entry entry = factory.newEntry();
entry.setContent("foo", Content.Type.XML);


清单 17. XML 内容元素
    




foo





有 时候仅仅将基于字符的内容标记为 type="text" 或 type="html" 还不够。比如,Atom 记录有可能包含完整的 HTML 文档。但是,如果 type="html",内容元素中的转义标记必须响应包含在 HTML DIV 元素中。为了包含完整的文档,type 属性必须指定 HTML媒体类型,如清单 18 和 19 所示:


清单 18. 使用基于文本的媒体内容
    
Entry entry = factory.newEntry();
entry.setContent(
"

Foo

This is new",
"text/html");


清单 19. 基于文本的媒体内容元素
    




Foo

This is new


如 果提要发布者希望连锁非基于字符的内容,必须把这些内容采用 Base64 编码,并使用内容类型属性指定编码后的二进制数据的媒体类型。为此,需要在 Abdera 中使用 Java Activation Framework DataHandler 接口。当作为记录内容传入 DataHandler 的时候,Abdera 自动使用 Base64 编码内容:


清单 20. 设置 Base64 编码的二进制内容
    
Entry entry = factory.newEntry();
URL url = new URL("file:/home/jasnell/mozilla-firefox.png");
URLDataSource ds = new URLDataSource(url);
DataHandler dh = new DataHandler(ds);
entry.setContent(dh);


清单 21. 包含 Base64 编码数据的内容元素
    
iVBORw0KGgoAAAANSUhEUgAAA...

由于 Base64 编码本身效率低,有时候直接在 Atom 记录中包含内容也不合适(比如连锁流媒体或视频的时候),可以通过 URI 引用内容,如清单 22 和 23 所示:


清单 22. 在内容元素上设置 src 属性
    
Entry entry = factory.newEntry();
URI uri = new URI("http://example.org");
entry.setContent(uri.toString(), "application/xhtml+xml");


清单 23. 使用 src 属性的内容元素
    


解析提要和记录文档

Abdera 的第二个基本功能是解析 Atom 文档。首先需要获得一个 Parser 实例,然后传递给要解析的文档的 InputStream 或 Reader:


清单 24. 解析 Atom Feed Document
    
Abdera abdera = new Abdera();
Parser parser = abdera.getParser();
URL url = Listing7.class.getResource("/example.xml");

Document feed_doc = parser.parse(url.openStream());
Feed feed = feed_doc.getRoot();

由 于 Abdera 默认的解析器实现采用拉式模型解析 XML,对 Parser 对象调用 parse 方法不会完全消费传递给该方法的 InputStream 或 Reader。而是随着请求文档中的信息逐步地消费流。可以预料,调用 writeTo、clone 之类的方法将导致把流完全消费掉。但是 Parser 接口的非默认实现不一定如此。

解析 Document 后,开发人员就可以使用 Feed Object Model API 或者 XPath 的方法访问提要内容了:


清单 25. 遍历 Feed Object Model
    
System.out.println(feed.getBaseUri());
System.out.println(feed.getLanguage());
System.out.println(feed.getId());
System.out.println(feed.getTitle());
System.out.println(feed.getTitleType());
System.out.println(feed.getUpdated());
System.out.println(feed.getAlternateLinkResolvedHref());
System.out.println(feed.getSelfLinkResolvedHref());

List entries = feed.getEntries();

for (Entry entry : entries) {

System.out.println(entry.getId());
System.out.println(entry.getTitle());
System.out.println(entry.getTitleType());
System.out.println(entry.getUpdated());
System.out.println(entry.getAlternateLinkResolvedHref());
System.out.println(entry.getEnclosureLinkResolvedHref());
System.out.println(entry.getContent());
System.out.println(entry.getContentType());

Element element = entry.getExtension(
new QName("tag:example.org,2006:/namespaces", "foo"));

System.out.println(element.getText());

}

如果不想遍历提要和记录对象并调用各种 get 方法,开发人员也可使用 XPath 表达式导航解析后的文档,如清单 26 所示:


清单 26. 使用 XPath 导航 Feed Object Model
    
Abdera abdera = new Abdera();
XPath xpath = abdera.getXPath();

System.out.println(xpath.valueOf("/a:feed/a:id", feed));
System.out.println(xpath.valueOf("/a:feed/@xml:base", feed));
System.out.println(xpath.valueOf("/a:feed/@xml:lang", feed));
System.out.println(xpath.valueOf("/a:feed/a:title", feed));
System.out.println(xpath.valueOf("/a:feed/a:title/@type", feed));
System.out.println(xpath.valueOf("/a:feed/a:updated", feed));
System.out.println(xpath.valueOf("/a:feed/a:link[not(@rel)]/@href", feed));
System.out.println(xpath.valueOf("/a:feed/a:link[@rel='self']/@href", feed));

除了选择单个节点、节点组、文本、布尔和数字值以外,还支持函数和轴这些标准 XPath 特性:


清单 27. 使用标准 XPath 函数
    
System.out.println(xpath.valueOf("count(//a:entry)", feed) + " entry");
System.out.println(xpath.valueOf("name(//a:entry/ancestor::*)", feed));

使用 XPath 选择节点返回的 Feed Object Model 对象本身可用 XPath 求值,如清单 28 所示。注意和上例不同的是,传递给 valueOf 函数的第二个参数不是提要,而是通过 selectSingleNode 方法选择的 Div 参数。XPath 根据传递给该方法的 Feed Object Model 元素求值:


清单 28. 对单个 FOM 对象计算 XPath 表达式
    
Div div = (Div) xpath.selectSingleNode(
"//a:entry/a:content/x:div", feed, namespaces);
System.out.println(xpath.valueOf("namespace-uri()", div));
System.out.println(xpath.valueOf("x:p", div, namespaces));

使 用 XPath 导航 Abdera 文档带来了一些好处,特别是对那些使用核心格式扩展的开发人员而言。比方说,使用 xpath.booleanValueOf 方法很快就能确定给定的提要或记录是否包含特定扩展元素,而且一般来说比使用各种 get 方法和迭代器遍历文档结构更有效。

Feed Object Model 的另一个重要特性是能够使用标准 Java Transformation API 对解析后的文档和元素应用 XSLT 转换。在 XPath 的支持下,可以对解析文档的任何部分应用 XSLT 转换:


清单 29. 使用 XSLT 转换 Abdera 文档
    
Abdera abdera = new Abdera();
Parser parser = abdera.getParser();

URL url = Listing8.class.getResource("/example.xml");
URL xslt = Listing8.class.getResource("/example.xslt");

Document feed_doc = parser.parse(url.openStream());
Document xslt_doc = parser.parse(xslt.openStream());

Source feed_source = new AbderaSource(feed_doc);
Source xslt_source = new AbderaSource(xslt_doc);

TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xslt_source);

Result result = new StreamResult(System.out);
transformer.transform(feed_source, result);

转换结果可以发送到 Java Transformaer API 支持的任何 Result 实现。上例中结果直接输出到 stdout。也可以将结果直接发送给 Abdera 解析器生成其他文档。

Atom Publishing 支持

Abdera 项目启动时,宣称其目标是为 Atom Syndication Format 和 Atom Publishing Protocol 提供功能完善的实现。到目前为止,本系列文章大量讨论了对 Syndication Format 的支持,并举例说明了如何生成和消费提要。下一期将讨论 Atom Publishing Protocol 客户机和正在实现之中的服务器支持。清单 30 给出了 Abdera APP 客户机的一个简单例子:


清单 30. 简单的 Atom Publishing Protocol 客户机
    
Abdera abdera = new Abdera();
Client client = new CommonsClient(abdera);
RequestOptions options = client.getDefaultRequestOptions();
options.setIfModifiedSince(new java.util.Date());
options.setNoCache(true);

ClientResponse response = client.get("http://example.org/entries/1", options);

System.out.println(response.getStatus());
System.out.println(response.getStatusText());
System.out.println(response.getEntityTag());
System.out.println(response.getLastModified());
System.out.println(response.getContentType());

response.release();

Abdera 和线程安全

结 束本文之前,有必要指出大部分核心 Abdera 组件,如 Abdera、Factory 和 Parser 对象,都是线程安全和无状态的,因此可以在多个线程之间高效地共享实例。不过它们创建的 Feed Object Model(FOM)对象不是线程安全的。由于种种原因,这些 FOM 对象都不是同步的。如果需要允许多个线程访问或修改记录和提要对象,需要自己提供同步机制:


清单 31. 非同步的并发修改 FOM 对象会造成无法预料的结果
    
final Abdera abdera = new Abdera();
final Factory factory = abdera.getFactory();
final Entry entry = factory.newEntry();

final Thread[] threads = new Thread[100];
for (int n = 0; n < threads.length; n++) {
final int i = n;
threads[n] = new Thread(
new Runnable() {
public void run() {
try {
int s = (new java.util.Random(
System.currentTimeMillis())).nextInt(10);
Thread.sleep(s);
} catch (InterruptedException e) {}
try {
IRI id = factory.newID();
id.setValue("urn:thread:" + i);
entry.setIdElement(id);
System.out.println(i + " " + entry); // unpredictable results
} catch (Exception e) {
e.printStackTrace();
}
}
}
);
}

for (Thread t : threads) t.start();


清单 32. 清单 31 的无效输出
    
85 urn:thread:85
86
urn:thread:
99
91 urn:thread:91
org.apache.axiom.om.OMException
at org.apache.axiom.om.impl.llom.OMNodeImpl.insertSiblingBefore(OMNodeImpl.java:232)
at org.apache.abdera.parser.stax.FOMElement._setChild(Unknown Source)
at org.apache.abdera.parser.stax.FOMEntry.setIdElement(Unknown Source)
at abdera.examples.atom.Listing11$1.run(Listing11.java:29)
at java.lang.Thread.run(Thread.java:788)
87 urn:thread:87
88 urn:thread:88

将记录的修改包装到一个同步块中就能解决这个问题。不过建议在一个线程中创建和使用 FOM 对象。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130303/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-130303/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值