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

来源:互联网 发布:淘宝做什么最赚钱 编辑:程序博客网 时间:2024/05/12 01:41

开始

首先要保证安装了 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. 运行示例需要的 jarAbdera (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.jarcoreFeed Object Model 接口Java Activation Framework, commons-logging-1.0.4.jar, log4j-1.2.12.jardependencies所有模块共用的依赖关系 extensions提要语法和协议扩展core, protocol, json-1.0.jarparserStAX 和基于 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.jarprotocolCommon Atom Publishing Protocol 代码core, parsersecurityXML 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 ismarkup");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 文档
<entry xmlns="http://www.w3.org/2005/Atom"><id>http://example.org/foo/entry</id><updated>2006-09-04T19:27:01.068Z</updated><title type="text">This is an entry</title><contenttype="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">This is markup</div></content><author><name>James</name></author><link href="http://www.example.org"/></entry>

序列化不使用空白和换行是设计优化的结果,可以提高文档的传输效率。也可使用 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'?><entry xmlns="http://www.w3.org/2005/Atom"> <id>http://example.org/foo/entry</id> <updated>2006-09-04T19:28:58.237Z</updated> <title type="text">This is an entry</title> <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">This is markup</div> </content> <author> <name>James</name> </author> <link href="http://www.example.org"/></entry>

使用 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 ismarkup");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'?><feed xmlns="http://www.w3.org/2005/Atom"> <id>http://example.org/foo</id> <updated>2006-09-04T19:31:45.286Z</updated> <title type="text">This is a feed</title> <link href="http://www.example.org"/> <link href="http://www.example.org/foo" rel="self"/> <entry> <id>http://example.org/foo/entry</id> <updated>2006-09-04T19:31:44.695Z</updated> <title type="text">This is an entry</title> <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">This is markup</div> </content> <author> <name>James</name> </author> <link href="http://www.example.org"/> </entry></feed>

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'?><entry xmlns="http://www.w3.org/2005/Atom"> <id>http://example.org/foo/entry</id> <updated>2006-09-04T19:34:10.322Z</updated> <title type="text">This is an entry</title> <content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">This is markup</div> </content> <author> <name>James</name> </author> <link href="http://www.example.org"/> <f:foo xmlns:f="tag:example.org,2006:/namespaces" foo="bar">fooBar</f:foo></entry>

清单 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 <b>is</b> not <i>markup</i>");
清单 11. 扁平文本内容元素
<content type="text">This <b>is</b> not <i>markup</i></content>

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

清单 12. 将内容设置为转义 HTML
Entry entry = factory.newEntry();entry.setContentAsHtml("This <b>is</b> <i>markup</i>");
清单 13. HTML 内容元素
<content type="html">This <b>is</b> <i>markup</i></content>

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

清单 14. 将内容设置为 XHTML
Entry entry = factory.newEntry();entry.setContentAsXhtml("This <b>is</b> <i>markup</i>");
清单 15. XHTML 内容元素
<content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml"> This is markup </div></content>

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

清单 16. 将内容设置为 XML
Entry entry = factory.newEntry();entry.setContent("<a><b><c><d>foo</d></c></b></a>", Content.Type.XML);
清单 17. XML 内容元素
<content type="application/xml"> <a> <b> <c> <d>foo</d> </c> </b> </a></content>

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

清单 18. 使用基于文本的媒体内容
Entry entry = factory.newEntry();entry.setContent( "<html> <head> <title>Foo</title> </head> <body>This is new</body></html>",  "text/html");
清单 19. 基于文本的媒体内容元素
<content type="text/html"><html> <head> <title>Foo</title> </head> <body>This is new</body> </html> </content>

如果提要发布者希望连锁非基于字符的内容,必须把这些内容采用 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 编码数据的内容元素
<content type="image/png">iVBORw0KGgoAAAANSUhEUgAAA...</content>

由于 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 属性的内容元素
<content type="application/xhtml+xml" src="http://example.org" />

解析提要和记录文档

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> 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<Entry> 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> feed_doc = parser.parse(url.openStream());Document<Element> 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 + "\t" + entry); // unpredictable results } catch (Exception e) { e.printStackTrace(); } } } );} for (Thread t : threads) t.start();
清单 32. 清单 31 的无效输出
85 urn:thread:8586 urn:thread:9991 urn:thread:91org.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:8788 urn:thread:88

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

结束语

本文分析了 Apache Abdera 项目的核心特性,包括 Feed Object Model、XPath 和 XSLT 支持、扩展处理和递增解析模型。本系列的下一篇文章将介绍和举例说明对当前仍在开发之中的 Atom Publishing Protocol 的支持。

0 0