| Apache Abdera > Documentation > Feed Object Model |
The Feed Object Model is the set of objects you will use to interact with Atom documents. It contains classes such as "Feed", "Entry", "Person" etc, all of which are modeled closely after the various elements and documents defined by the two Atom specifications. The Javadocs for the Feed Object Model can be found here.
Extensions to the Atom format can either be dynamic or static. A dynamic extensions use a generic API for setting and getting elements and attributes of the extension.
<entry xmlns="http://www.w3.org/2005/Atom"> <mylist xmlns="http://example.org" name="listName"> <value>one</value> <value>two</value> </mylist> </entry>
ExtensibleElement e = entry.addExtension(MYLIST_QNAME); e.setAttributeValue("name", "listName"); e.addSimpleExtension(VALUE_QNAME,"one"); e.addSimpleExtension(VALUE_QNAME,"two");
Or you can implement a static extension. Implement a class that extends the ExtensibleElementWrapper class. Implement an ExtensionFactory for it. Register the ExtensionFactory with Abdera. Code below.
package foo; import javax.xml.namespace.QName; import org.apache.abdera.factory.Factory; import org.apache.abdera.model.Element; import org.apache.abdera.model.ExtensibleElementWrapper; public class MyListElement extends ExtensibleElementWrapper { public static final String NS = "http://example.org"; public static final QName MYLIST = new QName("http://example.org","mylist"); public static final QName VALUE = new QName("http://example.org","value"); public MyListElement(Element internal) { super(internal); } public MyListElement(Factory factory, QName qname) { super(factory, qname); } public void setName(String name) { setAttributeValue("listName",name); } public void addValue(String value) { addSimpleExtension(VALUE, value); } }
package foo; import org.apache.abdera.util.AbstractExtensionFactory; public class MyListExtensionFactory extends AbstractExtensionFactory { public MyListExtensionFactory() { super(MyListElement.NS); addImpl(MyListElement.MYLIST,MyListElement.class); } }
foo.MyListExtensionFactory
MyListElement list = entry.addExtension(MyListElement.MYLIST); list.setName("listName"); list.addValue("one"); list.addValue("two");
When parsing an Atom document, the parser uses a set of default configuration options that will adequately cover most application use cases. There are, however, times when the parsing options need to be adjusted. The ParserOptions class can be used to tweak the operation of the parser in a number of important ways.
Abdera abdera = new Abdera(); Parser parser = abdera.getParser(); ParserOptions options = parser.getDefaultParserOptions(); options.setAutodetectCharset(true); options.setCharset("UTF-8"); options.setCompressionCodecs(CompressionCodec.GZIP); options.setFilterRestrictedCharacterReplacement('_'); options.setFilterRestrictedCharacters(true); options.setMustPreserveWhitespace(false); options.setParseFilter(null); options.setResolveEntities(true); options.registerEntity("foo", "bar"); InputStream in = ...; String base = "http://example.org"; parser.parse(in,base,options);
A ParseFilter is used to filter the stream of parse events. In the example below, only the elements added to the ParseFilter will be parsed and added to the Feed Object Model instance. All other elements will be silently ignored. The resulting savings in CPU and memory costs is significant.
ListParseFilter filter = new WhiteListParseFilter();
filter.add(Constants.FEED);
filter.add(Constants.ENTRY);
filter.add(Constants.TITLE);
options.setParseFilter(filter);
Document<Feed> doc = parser.parse(in,base,options);
There are three basic types of ParseFilters:
Developers can also create their own ParseFilter instances by implementing the ParseFilter or ListParseFilter interfaces, or extending the AbstractParseFilter or AbstractListParseFilter abstract base classes:
public class MyParseFilter extends AbstractParseFilter() { public boolean acceptable(QName qname) { return false; } public boolean acceptable(QName qname, QName attribute) { return false; } }
Using a CompoundParseFilter, a developer can apply multple ParseFilters at once:
CompoundParseFilter cpf =
new CompoundParseFilter(
Condition.ACCEPTABLE_TO_ALL,
parseFilter1,
parseFilter2
);
By default, the CompoundParseFilter will accept an element or attribute if it is acceptable to any of the ParseFilters in it's collection. This default can be modified by explicitly setting the condition parameter:
Note that the UNACCEPTABLE_TO_* conditions will accept an element or attribute based on a negative result. This is particularly useful when building blacklist-based filters, where an item is only acceptable if it does not meet an explicitly stated condition.
Abdera uses a flexible mechanism for serializing Atom documents to a Java InputStream or Writer. A developer can use the default serializer or select an alternative Abdera writer implementation to use.
Entry entry = ...
entry.writeTo(System.out);
The default serializer will output valid, but unformatted XML; there will be no line-breaks or indents. Using the NamedWriter mechanism, it is possible to select alternative serializers. Abdera ships with two alternative serialiers: PrettyXML and JSON. Developers can implement additional serializers by implementing the NamedWriter interface.
Writer writer = abdera.getWriterFactory().getWriter("prettyxml"); entry.writeTo(writer,System.out);
Writer writer = abdera.getWriterFactory().getWriter("json"); entry.writeTo(writer,System.out);
For more on the JSON output, please see the section regarding the JSON Extension.
Writer writer = new AbstractNamedWriter("mywriter") { public Object write( Base base, WriterOptions options) throws IOException { return null; } public void writeTo( Base base, OutputStream out, WriterOptions options) throws IOException { } public void writeTo( Base base, java.io.Writer out, WriterOptions options) throws IOException { } @Override protected WriterOptions initDefaultWriterOptions() { return new AbstractWriterOptions() {} ; } }; entry.writeTo(writer,System.out);
Your custom NamedWriter can be registered with Abdera by adding a file called META-INF/services/org.apache.abdera.writer.NamedWriter to the classpath and listing the fully-qualified class name of each named writer per line.
foo.MyNamedWriter foo.MyOtherNamedWriter
Registration will allow the named writer to be accessed via the Abdera WriterFactory:
Writer writer = abdera.getWriterFactory().getWriter("mynamedwriter"); entry.writeTo(writer,System.out);
The org.apache.abdera.writer.StreamWriter interface was added to Abdera after the release of 0.3.0. It provides an alternative means of writing out Atom documents using a streaming interface that avoids the need to build up a complex, in-memory object model. It is well suited for applications that need to quickly produce potentially large Atom documents.
StreamWriter out =
abdera.newStreamWriter()
.setOutputStream(System.out,"UTF-8")
.setAutoflush(false)
.setAutoIndent(true)
.startDocument()
.startFeed()
.writeBase("http://example.org")
.writeLanguage("en-US")
.writeId("http://example.org")
.writeTitle("<Testing 123>")
.writeSubtitle("Foo")
.writeAuthor("James", null, null)
.writeUpdated(new Date())
.writeLink("http://example.org/foo")
.writeLink("http://example.org/bar","self")
.writeCategory("foo")
.writeCategory("bar")
.writeLogo("logo")
.writeIcon("icon")
.writeGenerator("1.0", "http://example.org", "foo")
.flush();
for (int n = 0; n < 100; n++) {
out.startEntry()
.writeId("http://example.org/" + n)
.writeTitle("Entry #" + n)
.writeUpdated(new Date())
.writePublished(new Date())
.writeEdited(new Date())
.writeSummary("This is text summary")
.writeAuthor("James", null, null)
.writeContributor("Joe", null, null)
.startContent("application/xml")
.startElement(new QName("a","b","c"))
.startElement(new QName("x","y","z"))
.writeElementText("This is a test")
.startElement("a")
.writeElementText("foo")
.endElement()
.startElement("b","bar")
.writeAttribute("foo", new Date())
.writeAttribute("bar", 123L)
.writeElementText(123.123)
.endElement()
.endElement()
.endElement()
.endContent()
.endEntry()
.flush();
}
out.endFeed()
.endDocument()
.flush();
Atom allows for a broad range of text and content options. The choices can often times be confusing. Text constructs such as atom:title, atom:rights, atom:subtitle and atom:summary can contain plain text, escaped HTML or XHTML markup. The atom:content element can contain plain text, escaped HTML, XHTML markup, arbitrary XML markup, any arbitrary text-based format, Base64-encoded binary data or referenced external content. Abdera provides methods for dealing with these options.
entry.setTitle("A text title"); entry.setTitleAsHtml("<p>An HTML title</p>"); entry.setTitleAsXhtml("<p>An XHTML title</p>");
<title>A text title</title> <title type="html"><p>An HTML title</p></title> <title type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>An XHTML title</p></div></title>
String title = entry.getTitle();
Text.Type titleType = entry.getTitleType();
// Plain text entry.setContent("A text title"); // Escaped HTML entry.setContentAsHtml("<p>An HTML title</p>"); // XHTML markup entry.setContentAsXhtml("<p>An XHTML title</p>"); // Arbitrary XML (parsed) entry.setContent("<a><b><c/></b></a>",Content.Type.XML); // Arbitrary XML QName qname = new QName("foo"); Element element = abdera.getFactory().newElement(qname); entry.setContent(element); // Base64-encoded binary from an inputstream InputStream in = ...; entry.setContent(in,"image/png"); // Base64-encoded from a Java Activation Framework DataHandler DataHandler dh = ...; entry.setContent(dh,"image/png"); // Content-by-reference using atom:content/@src IRI iri = new IRI("http://example.org"); entry.setContent(iri,"image/png");
<content>A text title</content> <content type="html"><p>An HTML title</p></content> <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>An XHTML title</p></div></content> <content type="application/xml"><a><b><c/></b></a></content> <content type="application/xml"><foo /></content> <content type="image/png">{base64}</content> <content type="image/png">{base64}</content> <content type="image/png" src="http://example.org" />
The Atom format requires that all dates and times be formatted to match the date-time construct from RFC 3339. The basic format is YYYY-MM-DD'T'HH:mm:ss.ms'Z' where 'Z' is either the literal value 'Z' or a timezone offset in the form +-HH:mm. Examples: 2007-10-31T12:11:12.123Z and 2007-10-31T12:11:12.123-08:00. Abdera provides the AtomDate class for working with timestamps.
// use the current date-time AtomDate ad = new AtomDate(); // using java.util.Date Date date = new Date(); AtomDate ad = new AtomDate(date); // Using java.util.Calendar Calendar calendar = Calendar.getInstance(); AtomDate ad = new AtomDate(calendar); // Miliseconds since the epoch long l = System.currentTimeMillis(); AtomDate ad = new AtomDate(l); // From formatted RFC3339 date-time AtomDate ad = new AtomDate("2007-10-31T12:11:12.123-08:00");
The AtomDate class automatically converts all dates over the UTC and automatically includes milliseconds in the output string representation.
entry.setUpdated(new Date()); entry.setPublished(new Date()); entry.setEdited(new Date()); // atompub app:edited element
The Atom format explicitly allows the Atom Date Construct to be reused by extensions. This means you can create your own extension elements that use the same syntax rules as the atom:updated, atom:published and app:edited elements. Such extensions can use the dynamic and static extension APIs:
QName qname = new QName("foo"); DateTime dt = abdera.getFactory().newDateTime(qname, entry); dt.setDate(new Date());
<foo>2007-10-30T18:02:10.464Z</foo>
Static Date Constructs can be created by extending the DateTimeWrapper abstract class.
import javax.xml.namespace.QName; import org.apache.abdera.factory.Factory; import org.apache.abdera.model.DateTimeWrapper; import org.apache.abdera.model.Element; public class MyDateElement extends DateTimeWrapper { public MyDateElement(Element internal) { super(internal); } public MyDateElement(Factory factory, QName qname) { super(factory, qname); } ... }
Atom defines the notion of a Person Construct to represent people and entities. A Person Construct consists minimally of a name, an optional email address and an optional URI.
Person person = entry.addAuthor("John Doe", "john.doe@example.org", "http://example.org/~jdoe"); System.out.println(person.getName()); System.out.println(person.getEmail()); System.out.println(person.getUri());
The Atom format explicitly allows the Atom Person Construct to be reused by extensions. This means you can create your own extension elements that use the same syntax rules as the atom:author and atom:contributor elements. Such extensions can use the dynamic and static extension APIs:
QName qname = new QName("urn:foo","foo","x"); Person person = abdera.getFactory().newPerson(qname, entry); person.setName("John Doe"); person.setEmail("john.doe@example.org"); person.setUri("http://example.org/~jdoe");
<x:foo xmlns:x="urn:foo"> <name>John Doe</name> <email>john.doe@example.org</email> <uri>http://example.org/~jdoe</uri> </x:foo>
Static Person Constructs can be created by extending the PersonWrapper abstract class.
import javax.xml.namespace.QName; import org.apache.abdera.factory.Factory; import org.apache.abdera.model.PersonWrapper; import org.apache.abdera.model.Element; public class MyPersonElement extends PersonWrapper { public MyPersonElement(Element internal) { super(internal); } public MyPersonElement(Factory factory, QName qname) { super(factory, qname); } ... }
Atom link elements are similar in design to the link tag used in HTML and XHTML. They can be added to feed, entry and source objects.
entry.addLink( "http://example.org/foo", // href Link.REL_ALTERNATE, // rel "text/html", // type "Link Title", // title "en-US", // hreflang 12345); // length
The rel attribute specifies the meaning of the link. The value of rel can either be a simple name or an IRI. Simple names MUST be registered with IANA. Note that each of the values in the IANA registry have a full IRI equivalent value, e.g., the value "http://www.iana.org/assignments/relation/alternate" is equivalent to the simple name "alternate". Any rel attribute value that is not registered MUST be an IRI.
entry.addLink( "http://example.org/foo", // href "http://example.com/custom/link/rel"); // rel
<link href="http://example.org/foo" rel="http://example.com/custom/link/rel" />
Atom allows the use of Internationalized Resource Identifiers (IRIs). An IRI is a URI that has been extended to allow non-ASCII characters.
http://examplé.com/Iñtërnâtiônàlizætiøn
IRIs allow for internationalization but can be difficult to handle due to a variety of issues involving proper Unicode normalization, conversion to URI form, etc. Abdera includes an IRI implementation that (fortunately) handles most of these details for you.
IRI iri = new IRI("http://examplé.com/Iñtërnâtiônàlizætiøn"); System.out.println(iri.toString()); System.out.println(iri.toASCIIString()); System.out.println(iri.toURL());
http://examplé.com/Iñtërnâtiônàlizætiøn http://xn--exampl-gva.com/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n http://xn--exampl-gva.com/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n
Note that the toURL() method automatically calls the ASCII conversion process to produce a valid ASCII URL.
Atom supports the use of the xml:base attribute to specify the Base URI of relative references. Abdera provides a means of automatically resolving relative references using the base URI.
<entry xmlns="http://www.w3.org/2005/Atom" xml:base="http://example.org/foo"> ... <link href="example" rel="alternate" /> ... </entry>
IRI iri = entry.getAlternateLink().getResolvedHref();
Everywhere a relative IRI reference can be used, there will be a method for retrieving the resolved absolute IRI based on the in-scope base URI.
Abdera allows developers to use XPath statements to navigate the Feed Object Model.
XPath xpath = abdera.getXPath(); Map<String,String> ns = xpath.getDefaultNamespaces(); ns.put("foo", "http://example.org"); xpath.selectSingleNode("a:title", entry, ns); xpath.selectNodes("foo:extension", entry, ns); xpath.booleanValueOf("foo:extension", entry, ns); xpath.valueOf("foo:extension", entry, ns); xpath.valueOf("a:author/a:name", entry, ns);
All XPath statements are executed relative to the type of object specified. In the example above, the path "a:title" is used to get the atom:title Text element from the entry. The statement "a:author/a:name" will return the a:name element of the first a:author element in the entry.
By default, Abdera's XPath implementation supports all of the standard functions defined by the XPath standards. With a little extra work, it is possible to extend the implementation to support custom XPath functions and variables.
FOMXPath xpath = (FOMXPath) abdera.getXPath(); Map<String,String> ns = xpath.getDefaultNamespaces(); Map<QName,Function> functions = xpath.getDefaultFunctions(); QName qname = new QName("urn:foo","myfunc"); Function myFunction = new Function() { public Object call(Context context, List list) throws FunctionCallException { return null; } }; functions.put(qname,myFunction); ns.put("x","urn:foo"); xpath.valueOf("x:myfunc(a:title)", entry, ns, functions, null);
FOMXPath xpath = (FOMXPath) abdera.getXPath(); Map<String,String> ns = xpath.getDefaultNamespaces(); Map<QName,Object> variables = xpath.getDefaultVariables(); QName qname = new QName("urn:foo","foo"); variables.put(qname, "test value"); ns.put("x","urn:foo"); xpath.valueOf("string($x:foo)", entry, ns, null, variables);
Abdera also provides mechanisms for transforming Abdera objects using XSLT.
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>Example Feed</title> <link href="http://example.org/"/> <updated>2003-12-13T18:30:02Z</updated> <author> <name>John Doe</name> </author> <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> <entry> <title>Atom-Powered Robots Run Amok</title> <link href="http://example.org/2003/12/13/atom03"/> <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id> <updated>2003-12-13T18:30:02Z</updated> <content type="application/xml"><a xmlns="http://example.org"><b><c>test</c></b></a></content> </entry> </feed>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://www.w3.org/2005/Atom" version="1.0" > <xsl:output method="text" /> <xsl:template match = "/" >This is a test <xsl:value-of select="a:feed/a:id" /></xsl:template> </xsl:stylesheet>
Parser parser = Abdera.getNewParser();
// Apply an XSLT transform to the entire Feed
TransformerFactory factory = TransformerFactory.newInstance();
// Abdera is capable of parsing any well-formed XML document, even XSLT
Document xslt = parser.parse(XsltExample.class.getResourceAsStream("/test.xslt"));
AbderaSource xsltSource = new AbderaSource(xslt);
Transformer transformer = factory.newTransformer(xsltSource);
// Now let's get the feed we're going to transform
Document<Feed> feed = parser.parse(XsltExample.class.getResourceAsStream("/simple.xml"));
AbderaSource feedSource = new AbderaSource(feed);
// Prepare the output
ByteArrayOutputStream out = new ByteArrayOutputStream();
Result result = new StreamResult(out);
transformer.transform(feedSource, result);
System.out.println(out); // "This is a test urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6"
// Apply an XSLT transform to XML in the content element
xslt = parser.parse(XsltExample.class.getResourceAsStream("/content.xslt"));
xsltSource = new AbderaSource(xslt);
transformer = factory.newTransformer(xsltSource);
feed = parser.parse(XsltExample.class.getResourceAsStream("/xmlcontent.xml"));
Entry entry = feed.getRoot().getEntries().get(0);
Content content = entry.getContentElement();
AbderaSource contentSource = new AbderaSource(content.getValueElement());
// Note that the AbderaSource is set to the value element of atom:content!!
out = new ByteArrayOutputStream();
result = new StreamResult(out);
transformer.transform(contentSource, result);
System.out.println(out); // "This is a test test"
Abdera supports digital signatures and encryption of Atom documents.
KeyStore ks = KeyStore.getInstance(keystoreType);
InputStream in = DSig.class.getResourceAsStream(keystoreFile);
ks.load(in, keystorePass.toCharArray());
PrivateKey signingKey =
(PrivateKey) ks.getKey(
privateKeyAlias,
privateKeyPass.toCharArray());
X509Certificate cert =
(X509Certificate) ks.getCertificate(
certificateAlias);
Entry entry = abdera.newEntry();
entry.setId("http://example.org/foo/entry");
entry.setUpdated(new java.util.Date());
entry.setTitle("This is an entry");
entry.setContentAsXhtml("This <b>is</b> <i>markup</i>");
entry.addAuthor("James");
entry.addLink("http://www.example.org");
AbderaSecurity absec = new AbderaSecurity(abdera);
Signature sig = absec.getSignature();
SignatureOptions options = sig.getDefaultSignatureOptions();
options.setCertificate(cert);
options.setSigningKey(signingKey);
entry = sig.sign(entry, options);
InputStream in = ...
Document<Entry> doc = abdera.getParser().parse(in);
entry = entry_doc.getRoot();
System.out.println("Valid? " + sig.verify(entry,null));
A number of options can be configured to adjust the way the document is signed
Any valid Java crypto provider can be used. In these examples, we are using the Bouncy Castle provider.
try { String jce = abdera.getConfiguration().getConfigurationOption( "jce.provider", "org.bouncycastle.jce.provider.BouncyCastleProvider"); Class provider = Class.forName(jce); Provider p = (Provider)provider.newInstance(); Security.addProvider(p); } catch (Exception e) {}
String jceAlgorithmName = "AES"; KeyGenerator keyGenerator = KeyGenerator.getInstance(jceAlgorithmName); keyGenerator.init(128); SecretKey key = keyGenerator.generateKey();
Entry entry = abdera.newEntry(); entry.setId("http://example.org/foo/entry"); entry.setUpdated(new java.util.Date()); entry.setTitle("This is an entry"); entry.setContentAsXhtml("This <b>is</b> <i>markup</i>"); entry.addAuthor("James"); entry.addLink("http://www.example.org");
AbderaSecurity absec = new AbderaSecurity(abdera);
Encryption enc = absec.getEncryption();
EncryptionOptions options = enc.getDefaultEncryptionOptions();
options.setDataEncryptionKey(key);
Document enc_doc = enc.encrypt(entry.getDocument(), options);
AbderaSecurity absec = new AbderaSecurity(abdera);
Encryption enc = absec.getEncryption();
EncryptionOptions options = enc.getDefaultEncryptionOptions();
options.setDataEncryptionKey(key);
Document<Entry> entry_doc = enc.decrypt(enc_doc, options);
The Diffie-Hellman Keyt Exchange Protocol is a popular means of establishing a shared secret for encryption. Abdera's Security Module includes utilities that make it easy to use Diffie-Hellman.
// There are two participants in the session, A and B // Each has their own DHContext. A creates their context and // sends the request key parameters to B. B uses those parameters // to create their context, the returns it's public key // back to A. DHContext context_a = new DHContext(); DHContext context_b = new DHContext(context_a.getRequestString()); context_a.setPublicKey(context_b.getResponseString());
Encryption enc = absec.getEncryption(); EncryptionOptions options = context_a.getEncryptionOptions(enc); Document enc_doc = enc.encrypt(entry.getDocument(), options);
Encryption enc = absec.getEncryption(); EncryptionOptions options = context_b.getEncryptionOptions(enc); Document<Entry> entry_doc = enc.decrypt(enc_doc, options);
Various settings can be changed to affect the way documents are encrypted.