You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 34 Next »

Introduction

Apache Abdera provides an implementation of the IETF Atom Syndication Format and Atom Publishing Protocol standards (RFC's 4287 and 5023).

Abdera is composed of a number of individual modules:

  • Core: Provides the core interfaces used throughout Abdera:
    • Feed Object Model - A set of Java interfaces and abstract classes modeled after the various document and element types defined by the Atom specifications.
    • Parser and Factory interfaces - Interfaces that provide the mechanisms for creating and consuming Atom documents.
    • ExtensionFactory - The primary means by which extensions to the Atom format are integrated into Abdera. Extension Factories are used by both the Parser and Factory.
    • Writer - The means by which Feed Object Model objects are serialized into XML or other formats
    • ParseFilter - Provides a means of filtering the stream of parse events
    • Converter - Base interfaces and utilities for converting objects into Feed Object Model objects
    • XPath - Base interface for navigating the Feed Object Model using XPath
  • Dependencies: Contains all of the other jars and code Abdera depends on
  • Parser: Provide an implementation of the Feed Object Model based on the Apache Axiom project
  • Protocol: Provides the base interfaces and utility classes used for the RFC 5023 (Atompub) implementation
  • Server: Provides framework code used to build Atompub servers
  • Client: Provides an Atompub client implementation that is based on the Apache Commons HTTP Client.
  • Spring: Extends the Server module by adding support for the Spring framework
  • Security: Provides support for XML Digital Signatures and XML Encryption
  • Extensions: Provides support for a number of standard and non-standard extensions to the Atom format
    • Atom Threading Extensions
    • Atom License Extension
    • Atompub Feature Discovery
    • Atom Bidi Attribute
    • Feed Paging and Archiving
    • GeoRSS
    • Simple Sharing Extensions
    • MediaRSS
    • OpenSearch
    • GData and WSSE Autbentication
  • Examples: Provides a number of examples

Downloading Abdera

Building with Ant

export ABDERA_XMLSECURITY=true
export ABDERA_SPRING=true
ant -f build/build.xml zips

Set ABDERA_XMLSECURITY to true to build the security module.
Set ABDERA_SPRING to true to build the spring module.

Building with Maven

TODO

The Classpath

Abdera uses a modular architecture that divides functionality up into a number of individual jars. Different applications will require different classpath configurations depending on the Abdera features they are utilizing.

  • Abdera Jars
    • Core API: abdera.core.0.3.0-incubating.jar (required)
    • Parser Impl: abdera.parser.0.3.0-incubating.jar (required)
    • Security Impl: abdera.security.0.3.0-incubating.jar (optional)
    • Protocol Core API: abdera.protocol.0.3.0-incubating.jar (optional)
    • Protocol Client Impl: abdera.client.0.3.0-incubating.jar (optional, requires protocol)
    • Protocol Server Impl: abdera.server.0.3.0-incubating.jar (optional, requires protocol)
    • Spring Integration: abdera.spring.0.3.0-incubating.jar (optional, requires server)
    • IRI Support: abdera.i18n.0.3.0-incubating.jar (required)
  • Abdera Extension Jars (optional)
    • Main (thread, license, paging, etc): abdera.extensions.main.0.3.0-incubating.jar (requires client)
    • GoogleLogin Auth: abdera.extensions.gdata.0.3.0-incubating.jar (requires client)
    • GeoRSS: abdera.extensions.geo.0.3.0-incubating.jar
    • JSON Writer: abdera.extensions.json.0.3.0-incubating.jar
    • MediaRSS: abdera.extensions.media.0.3.0-incubating.jar
    • OpenSearch: abdera.extensions.opensearch.0.3.0-incubating.jar
    • Simple Sharing Extensions: abdera.extensions.sharing.0.3.0-incubating.jar
    • WSSE Auth: abdera.extensions.wsse.0.3.0-incubating.jar (requires client)
  • Dependencies
    • Axiom (required by core)
      • axiom-api-1.2.5.jar
      • axiom-impl-1.2.5.jar
    • Apache Commons
      • Codec: commons-codec-1.3.jar (required by protocol)
      • HttpClient: commons-httpclient-3.1-rc1.jar (required by client)
      • Logging: commons-logging-1.0.4.jar (required)
    • Geronimo
      • geronimo-activation_1.0.2_spec-1.1.jar (any JAF impl is required)
      • geronimo-servlet_2.4_spec-1.0.jar (required by server, security and spring modules)
    • Jaxen: jaxen-1.1.1.jar (required by the core)
    • Jetty (required only by compile time for junit tests)
      • jetty-6.1.3.jar
      • jetty-util-6.1.3.jar
    • JSON: json-1.0.jar (required by json extension)
    • JUnit: junit-4.3.jar (required at compile time)
    • log4j-1.2.24.jar (required)
    • Retroweaver: retroweaver-rt-2.0.jar (required for JDK 1.4 support)
    • Stax (any Stax API impl is required at runtime)
      • stax-api-1.0.1.jar
      • wstx-asl-3.2.1.jar
    • XML Security: xmlsec-1.4.1.jar (required by security module)

Getting Started

The two most common uses for Abdera are creating and parsing Atom documents.

Parsing an Atom Document and printing entry titles
  Abdera abdera = new Abdera();
Parser parser = abdera.getParser();
  
URL url = new URL("http://www.snellspace.com/wp/wp-atom1.php");
Document<Feed> doc = parser.parse(url.openStream(),url);
Feed feed = doc.getRoot();
System.out.println(feed.getTitle());
for (Entry entry : feed.getEntries()) {
  System.out.println("\t" + entry.getTitle());
)
Creating an Atom Document and adding an entry
  Abdera abdera = new Abdera();
Feed feed = abdera.newFeed();

feed.setId("tag:example.org,2007:/foo");
feed.setTitle("Test Feed");
feed.setSubtitle("Feed subtitle");
feed.setUpdated(new Date());
feed.addAuthor("James Snell");
feed.addLink("http://example.com");
feed.addLink("http://example.com/foo","self");

Entry entry = feed.addEntry();
entry.setId("tag:example.org,2007:/foo/entries/1");
entry.setTitle("Entry title");
entry.setSummaryAsHtml("<p>This is the entry title</p>");
entry.setUpdated(new Date());
entry.setPublished(new Date());
entry.addLink("http://example.com/foo/entries/1");

As you can see in both of these examples, the first step to using Abdera is to create an instance of the Abdera object. This special thread-safe class bootstraps the Abdera configuration and provides access to all of the other components. One of the most important tasks that the Abdera object performs is the automatic discovery of extensions.

Creating a new instance of the Abdera object can be time consuming so it is recommended that only a single instance be creating per application. The Abdera object and it's direct children (Parser, Factory, XPath, etc) are threadsafe and can be stored statically.

Creating a static instance of Abdera
  public class MyApplication {
    private static Abdera abdera = null;
    
    public static synchronized Abdera getInstance() {
      if (abdera == null) abdera = new Abdera();
      return abdera;
    }
  }

Once an Abdera instance is created, you can begin parsing and creating Atom documents.

Configuring Abdera

Abdera uses a powerful and flexible configuration mechanism that, fortunately, most developers will never actually have to even think much about. Abdera uses it's configuration model to automatically discover the various implementation classes for it's Parser, Factory, Feed Object Model, ExtensionFactories, etc. The closest most developers will ever need to get to the configuration system is when implementing a new ExtensionFactory for a new Atom format extension. Implementation of Extension Factories will be covered later. For now, we can skip the details about the Abdera configuration system.

The 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

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.

An extension element in an entry
<entry xmlns="http://www.w3.org/2005/Atom">
  <mylist xmlns="http://example.org" name="listName">
    <value>one</value>
    <value>two</value>
  </mylist>
</entry>
Using the dynamic API to add the extension to an 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.

foo/MyListElement.java
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);
  }
}
foo/MyListExtensionFactory.java
package foo;
import org.apache.abdera.util.AbstractExtensionFactory;

public class MyListExtensionFactory
  extends AbstractExtensionFactory {
  public MyListExtensionFactory() {
    super(MyListElement.NS);
    addImpl(MyListElement.MYLIST,MyListElement.class);
  }
}
META-INF/services/org.apache.abdera.factory.ExtensionFactory
foo.MyListExtensionFactory
Using the static extension
MyListElement list = entry.addExtension(MyListElement.MYLIST);
list.setName("listName");
list.addValue("one");
list.addValue("two");

Customizing the Parser

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.

Using the ParserOptions
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);
  • setAutodetectCharset - Abdera will, by default, attempt to automatically detect the character set used in an XML document. It will do so by looking at the XML prolog, the Byte Order Mark, or the first few bytes of the document. The process works reasonably well for the overwhelming majority of cases but it does cause of bit of performance hit. The autodetection algorithm can be disabled by calling options.setAutodetectCharset(false). This only has an effect when parsing an InputStream.
  • setCharset - This option allows you to manually set the character set the parser should use when decoding an InputStream.
  • setCompressionCodecs - Abdera is capable of parsing InputStream's that have been compressed using the GZIP or Deflate algorithms (typically used as HTTP transfer encodings). setCompressionCodecs can be used to specify which encodings have been applied.
  • setFilterRestrictedCharacters - By default, Abdera will throw a parse exception if any characters not allowed in XML are detected. By setting setFilterRestrictedCharacters(true), the parser will automatically filter out invalid XML characters.
  • setFilterRestrictedCharacterReplacement - When setFilterRestrictedCharacters has been set to "true", Abdera will, by default, replace the character with an empty string. Alternatively, you can use setFilterRestrictedCharacterReplacement to specify a replacement character.
  • setParseFilter - See below
  • setResolveEntities - There are a number of named character entities allowed by HTML and XHTML that are not supported in XML without a DTD. However, it is not uncommon to find these entities being used without a DTD. Abdera will, by default, automatically handle these entities by replacing them with the appropriate character equivalent. To disable automatic entity resolution call setResolveEntities(false). Doing so will cause Abdera to return an error whenever a named character entity is used.
  • registerEntity - When setResolveEntities is true, registerEntity can be used to register a new custom named entity reference.

ParseFilters

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.

Using a ParseFilter
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:

  • WhiteListParseFilter - Only elements and attributes listed in the filter will be parsed.
  • BlackListParseFilter - Elements and attributes listed in the filter will be ignored
  • CompoundParseFilter - Allows multiple parse filters to be applied

Developers can also create their own ParseFilter instances by implementing the ParseFilter or ListParseFilter interfaces, or extending the AbstractParseFilter or AbstractListParseFilter abstract base classes:

MyCustomParseFilter.java
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:

Using a CompoundParseFilter
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:

  • Condition.ACCEPTABLE_TO_ALL: Accepts the element or attribute only if it is acceptable to all contained ParseFilters
  • Condition.ACCEPTABLE_TO_ANY: Accepts the element or attribute if it is acceptable to any of the contained ParseFilters
  • Condition.UNACCEPTABLE_TO_ALL: Accepts the element or attribute only if it is unacceptable to all contained ParseFilters
  • Condition.UNACCEPTABLE_TO_ANY: Accepts the element or attribute if it is unacceptable to any of the contained ParseFilters

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.

Serializing Atom Documents

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.

Using the default serializer
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.

The PrettyXML Writer will output formatted XML containing line-breaks and indent
Writer writer = abdera.getWriterFactory().getWriter("prettyxml");
entry.writeTo(writer,System.out);
The JSON Writer will output the Atom document converted to a JSON format
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.

Implementing a NamedWriter

Implementing a NamedWriter
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.

META-INF/services/org.apache.abdera.writer.NamedWriter
foo.MyNamedWriter
foo.MyOtherNamedWriter

Registration will allow the named writer to be accessed via the Abdera WriterFactory:

META-INF/services/org.apache.abdera.writer.NamedWriter
Writer writer = abdera.getWriterFactory().getWriter("mynamedwriter");
entry.writeTo(writer,System.out);

Text and Content Options

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.

Text Constructs

Text construct options
entry.setTitle("A text title");
    
entry.setTitleAsHtml("<p>An HTML title</p>");
    
entry.setTitleAsXhtml("<p>An XHTML title</p>");
Resulting atom:title elements
<title>A text title</title>

<title type="html">&lt;p&gt;An HTML title&lt;/p&gt;</title>

<title type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>An XHTML title</p></div></title>
Getting the text value
String title = entry.getTitle();
Text.Type titleType = entry.getTitleType();

Content

Content options
// 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");
Resulting atom:content elements
<content>A text title</content>

<content type="html">&lt;p&gt;An HTML title&lt;/p&gt;</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" />

Atom Date Constructs

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.

Using the AtomDate
// 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.

Setting date elements on an entry
entry.setUpdated(new Date());
entry.setPublished(new Date());
entry.setEdited(new Date());  // atompub app:edited element

Date Construct Extensions

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:

Creating a Date Construct Extension
QName qname = new QName("foo");
DateTime dt = abdera.getFactory().newDateTime(qname, entry);
dt.setDate(new Date());
Resulting extension element
<foo>2007-10-30T18:02:10.464Z</foo>

Static Date Constructs can be created by extending the DateTimeWrapper abstract class.

Implementing a static Date Construct Extension
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);
  }
  
  ...
}

Person Constructs

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.

Using the Person Construct
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());

Person Construct Extensions

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:

Creating a Person Construct Extension
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");
Resulting extension element
<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.

Implementing a static Person Construct Extension
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 Links

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.

Adding Atom Link elements to an entry
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.

Specifying a custom rel attribute value
entry.addLink(
  "http://example.org/foo",  // href
  "http://example.com/custom/link/rel"); // rel
Resulting link element
<link href="http://example.org/foo" rel="http://example.com/custom/link/rel" />

IRIs and URIs

Atom allows the use of Internationalized Resource Identifiers (IRIs). An IRI is a URI that has been extended to allow non-ASCII characters.

An example IRI
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.

Using the Abdera IRI implementation
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());
Output
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.

Base URIs

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.

An Atom entry using relative references
<entry xmlns="http://www.w3.org/2005/Atom" xml:base="http://example.org/foo"> 
  ...
  <link href="example" rel="alternate" />
  ...
</entry>
Resolving the absolute IRI for the link
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.

Using XPath

Abdera allows developers to use XPath statements to navigate the Feed Object Model.

Using XPath to navigate an entry
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.

Custom XPath Functions

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.

Implementing and using a custom XPath function
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);
Using custom XPath variables
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);

Using XSLT

Abdera also provides mechanisms for transforming Abdera objects using XSLT.

Source Atom Document
<?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>
XSLT Stylesheet
<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>
Using XSLT to transform Abdera objects
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"

Signatures and Encryption

Abdera supports digital signatures and encryption of Atom documents.

Signing an Atom Document

Initialize the signing key
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);
Create the entry to sign
    
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");
Prepare the digital signature options
    
AbderaSecurity absec = new AbderaSecurity(abdera);
Signature sig = absec.getSignature();
SignatureOptions options = sig.getDefaultSignatureOptions();    
options.setCertificate(cert);
options.setSigningKey(signingKey);  
Sign the entry
    
    entry = sig.sign(entry, options);      
Verify a signature
    
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

  • setSigningAlgorithm - Specify the algorithm used to sign the entry. Any of the algorithms supported by the Apache XML Security implementation can be used.
  • setSigningKey - Sets the private key used to sign the document
  • setCertificate - Sets the X.509 Certificate to be associated with the signature
  • setPublicKey - Sets the public key to be associated with the signature (alternative to setting the X.509 cert)
  • addReference - Allows a developer to add additional Href signatures to be included in the signature
  • setSignLinks - When set to true, Abdera will automatically sign resources referenced by atom:link elements and the atom:content src attribute
  • setSignLinkRels - When setSignLinks is set to true, lists the rel attribute values of link elements that should be included in the signature

Encrypting an Abdera Document

Any valid Java crypto provider can be used. In these examples, we are using the Bouncy Castle provider.

Prepare the crypto 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) {}
Generate Encryption Key
String jceAlgorithmName = "AES";
KeyGenerator keyGenerator =
    KeyGenerator.getInstance(jceAlgorithmName);
keyGenerator.init(128);
SecretKey key = keyGenerator.generateKey();
Create the entry to encrypt
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");
Prepare the encryption options
AbderaSecurity absec = new AbderaSecurity(abdera);
Encryption enc = absec.getEncryption();
EncryptionOptions options = enc.getDefaultEncryptionOptions();
options.setDataEncryptionKey(key);
Encrypt the document using the generated key
Document enc_doc = enc.encrypt(entry.getDocument(), options);

Decrypting an Atom Document

Prepare the encryption 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);

Using the Diffie-Hellman Key Exchange protocol for encryption

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.

Prepare the Diffie-Hellman Key Exchange Session
    
// 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());
Person A encrypts the document
    
Encryption enc = absec.getEncryption();
EncryptionOptions options = context_a.getEncryptionOptions(enc);
Document enc_doc = enc.encrypt(entry.getDocument(), options);
Person B decrypts the document
    
Encryption enc = absec.getEncryption();
EncryptionOptions options = context_b.getEncryptionOptions(enc);
Document<Entry> entry_doc = enc.decrypt(enc_doc, options);

Customizing EncryptionOptions

Various settings can be changed to affect the way documents are encrypted.

  • setDataEncryptionKey - The secret key used to encrypt the data
  • setKeyEncryptionKey - A secret key used to encrypt the data encryption key when the DEK is to be transmitted with the encrypted data
  • setKeyCipherAlgorithm - The algorithm used to encrypt the data encryption key
  • setDataCipherAlgorithm - The algorithm used to encrypt the data
  • setIncludeKeyInfo - True if information about the secret key used to encrypt the data should be transmitted with the encrypted data

Using Abdera Extensions

Main Extensions

Bidi

The Atom Bidi Extension is a work in progress described by an IETF Internet-Draft. It is used to specify the base directionality of bidirection text within an Atom document.

Using the bidi attribute in an Atom document
<entry xmlns="http://www.w3.org/2005/Atom" dir="rtl">
  <title>W3C? (World Wide Web Consortium)????? ?? ?????? ????? ??????? ? - ERCIM.</title>
</entry>

When this text is displayed, the Unicode Bidirectional Algorithm needs to be used to display the text correctly. Unfortunately, Atom does not included a way of specifying the base directionality of text, which is why the bidi
attribute was created. See here for more information.

Setting the text direction using BidiHelper
Entry entry = abdera.newEntry();
BidiHelper.setDirection(BidiHelper.Direction.RTL, entry);
entry.setTitle("W3C? (World Wide Web Consortium)????? ?? ?????? ????? ??????? ? - ERCIM.");

The BidiHelper class can then be used to properly render the text:

Properly rendering the bidi text in the title
System.out.println(BidiHelper.getBidiElementText(entry.getTitleElement()));

The BidiHelper class can also be used to discover the base directionality of text

Getting the text direction
BidiHelper.Direction dir = BidiHelper.getDirection(entry);

If the dir attribute has not been set, the direction may be determined by applying a variety of guessing algorithms. Such algorithms are inherently flawed in that there is no reliable means of always guessing the right direction in every case.

Guessing the text direction
dir = BidiHelper.guessDirectionFromLanguage(entry);
dir = BidiHelper.guessDirectionFromTextProperties(entry);
dir = BidiHelper.guessDirectionFromJavaBidi(entry);
dir = BidiHelper.guessDirectionFromEncoding(entry);

When guessing the text direction based on the xml:lang value, the following rules apply:

  • xml:lang values whose primary language tag is "ar","fa","ur","ps","syr","dv","he", or "yi" will always be Right-To-Left
  • xml:lang values whose script tag is "arab","avst","hebr","hung","lydi","mand", "mani","mero","mong","nkoo","orkh","phlv", "phnx","samr","syrc","syre","syrj","syrn", "tfng", or "thaa" will always be Right-To-Left
  • All other values are considered to have unspecified direction

When guessing the text direction based on the text properties, text strings that consist primarily of Right-To-Left characters will be assumed to be Right-To-Left.

When guessing the text direction based on the character encoding, the following encodings will always be considered as Right-To-Left: "iso-8859-6", "iso-8859-6-bidi", "iso-8859-6-i", "iso-ir-127", "ecma-114", "asmo-708", "arabic", "csisolatinarabic", "windows-1256", "ibm-864", "macarabic", "macfarsi", "iso-8859-8-i", "iso-8859-8-bidi", "windows-1255", "iso-8859-8", "ibm-862", "machebrew", "asmo-449", "iso-9036", "arabic7", "iso-ir-89", "csiso89asmo449", "iso-unicode-ibm-1264", "csunicodeibm1264", "iso_8859-8:1988", "iso-ir-138", "hebrew", "csisolatinhebrew", "iso-unicode-ibm-1265", "csunicodeibm1265", "cp862", "862", "cspc862latinhebrew"

Features

The Atompub Features draft is a work-in-progress extension to the Atom Publishing Protocol Service document format that is used to describe the characteristics and behaviors implemented by a server implementation.

The API for working with the features extension is a work-in-progress.

Setting and getting the features
Service service = abdera.newService();
Workspace workspace = service.addWorkspace("test");
Collection collection = workspace.addCollection("foo", "href");

Features features = FeaturesHelper.addFeaturesElement(collection);
features.addFeatures(
  FeaturesHelper.FEATURE_SUPPORTS_DRAFTS,
  FeaturesHelper.FEATURE_SUPPORTS_BIDI,
  FeaturesHelper.FEATURE_REQUIRES_XHTML_TEXT
);

FeaturesHelper.getFeatureStatus(collection, FeaturesHelper.FEATURE_SUPPORTS_BIDI);
FeaturesHelper.getFeatureStatus(collection, FeaturesHelper.FEATURE_SUPPORTS_GEO);

Paging and Archiving

The Feed Paging and Archiving Extensions are defined by RFC 5005.

Setting feed paging links
Feed feed = abdera.newFeed();

FeedPagingHelper.setCurrent(feed, "current");
FeedPagingHelper.setNext(feed, "next");
FeedPagingHelper.setPrevious(feed, "previous");
FeedPagingHelper.setFirst(feed, "first");
FeedPagingHelper.setLast(feed, "last");
FeedPagingHelper.setNextArchive(feed, "next-archive");
FeedPagingHelper.setPreviousArchive(feed, "prev-archive"); 
Result
<feed xmlns="http://www.w3.org/2005/Atom">
  <link rel="current" href="current" />
  <link rel="next" href="next" />
  <link rel="previous" href="previous" />
  <link rel="first" href="first" />
  <link rel="last" href="last" />
  <link rel="next-archive" href="next-archive" />
  <link rel="prev-archive" href="prev-archive" />
</feed>

The FeedPagingHelper class can also be used to indicate whether a feed is a "complete" or "archive" feed

Result
FeedPagingHelper.setComplete(feed,true);
FeedPagingHelper.setArchive(feed,true);

boolean c = FeedPagingHelper.isComplete(feed);
boolean a = FeedPagingHelper.isArchive(feed);

License Extensions

The Atom License Extension is defined by RFC 4946.

Adding and getting license links from an entry
LicenseHelper.addLicense(entry, "http://creativecommons.org/licenses/by-nc/2.5/rdf");

List<Link> licenses = LicenseHelper.getLicense(entry);

Threading Extensions

The Atom Threading Extensions are defined by RFC 4685

Adding a thr:in-reply-to element to an entry
// this entry is a reply to entry "http://example.org/entries/1")
ThreadHelper.addInReplyTo(entry, "http://example.org/entries/1");

List<InReplyTo> irt = ThreadHelper.getInReplyTo(entry);

GeoRSS Extensions

The GeoRSS extension allows geospatial data to be added to Atom entries.

Adding geospatial data to an entry
Entry entry = abdera.newEntry();
Point point = new Point(36.331445,-119.64592);
GeoHelper.addPosition(entry, point);
Result
<entry xmlns="http://www.w3.org/2005/Atom" 
       xmlns:georss="http://www.georss.org/georss/10">
  <georss:point>36.331445 -119.64592</georss:point>
</entry>

Points, Lines and Polygons can be added to an entry.

Retrieving geospatial data from an entry
Position[] positions = GeoHelper.getPositions(entry);

OpenSearch Extensions

Abdera supports a subset of the OpenSearch extensions

Adding OpenSearch elements to a feed
Feed feed = abdera.newFeed();
IntegerElement ie = feed.addExtension(OpenSearchConstants.ITEMS_PER_PAGE);
ie.setValue(10);

ie = feed.addExtension(OpenSearchConstants.START_INDEX);
ie.setValue(0);

ie = feed.addExtension(OpenSearchConstants.TOTAL_RESULTS);
ie.setValue(100);
Result
<feed xmlns="http://www.w3.org/2005/Atom" 
      xmlns:os="http://a9.com/-/spec/opensearch/1.1/">
  <os:itemsPerPage>10</os:itemsPerPage>
  <os:startIndex>0</os:startIndex>
  <os:totalResults>100</os:totalResults>
</feed>

MediaRSS Extensions

Abdera supports an experimental implementation of the MediaRSS extensions that can be used to add extended media metadata to an entry.

Adding MediaRSS elements to a feed
MediaTitle mtitle = entry.addExtension(MediaConstants.TITLE);
mtitle.setText("Media title");

MediaRestriction mrestriction = entry.addExtension(MediaConstants.RESTRICTION);
mrestriction.setType(RestrictionType.COUNTRY);
mrestriction.setRelationship(Relationship.ALLOW);
mrestriction.setText("au us");
Result
<entry xmlns="http://www.w3.org/2005/Atom" 
       xmlns:media="http://search.yahoo.com/mrss/">
  <media:title>Media title</media:title>
  <media:restriction type="country" relationship="allow">au us</media:restriction>
</entry>

Auth Extenions

GoogleLogin

Abdera provides a limited implementation of the GooleLogin authentication scheme used by GData.

GData Authentication
GoogleLoginAuthScheme.register();  
AbderaClient client = new CommonsClient();
client.addCredentials(
  "http://beta.blogger.com", 
  null, "GoogleLogin", 
  new UsernamePasswordCredentials("email","password")
);

The example above has the downside of performing the GoogleLogin authentication on each request. The code below performs the authentication once and reuses it for each subsequent request.

GData Authentication
GoogleLoginAuthCredentials credentials = 
  new GoogleLoginAuthCredentials("email", "password", "blogger");
client.addCredentials(
  "http://beta.blogger.com",
  null, null, credentials
);

WSSE Login

Abdera also provides an implementation of the WSSE authentication mechanism

GData Authentication
AbderaClient client = new AbderaClient();
WSSEAuthScheme.register(client,true);
client.addCredentials(
  "http://example.org", 
  null, "WSSE", 
  new UsernamePasswordCredentials("email","password")

Simple Sharing Extensions

Abdera supports an experimental implementation of the Simple Sharing Extensions. The following examples shows a minimal example of using SSE to merge entries from one feed into another.

Merging two SSE Feeds
Feed f1 = abdera.newFeed();
f1.newId();
Entry e1 = f1.addEntry();
e1.newId();
e1.setTitle("Test");
SharingHelper.getSharing(f1,true);
Sync sync = SharingHelper.getSync(e1,true);
sync.setId(e1.getId().toString());

Feed f2 = abdera.newFeed();
f2.newId();
SharingHelper.getSharing(f2,true);
Entry e2 = f2.addEntry();
e2.newId();
sync = SharingHelper.getSync(e2,true);

SharingHelper.mergeFeeds(f1, f2);

System.out.println(f2);
Result
<feed xmlns="http://www.w3.org/2005/Atom" 
      xmlns:sx="http://www.microsoft.com/schemas/sse">
  <id>urn:uuid:83BD1E87EE96454DF21193779431187</id>
  <sx:sharing />
  <entry>
    <id>urn:uuid:83BD1E87EE96454DF21193779431228</id>
    <sx:sync />
  </entry>
  <entry>
    <id>urn:uuid:83BD1E87EE96454DF21193779431115</id>
    <title type="text">Test</title>
    <sx:sync id="urn:uuid:83BD1E87EE96454DF21193779431115" />
  </entry>
</feed>

The Sharing Extensions implementation also provides a means of resolving merge conflicts.

Resolving conflicts
ConflictResolver resolver = 
  new ConflictResolver() {
    public Entry resolve(Entry entry, List<Entry> conflicts) {
      // resolve the conflicts in the entry
      return entry;
    }  
};
for (Entry entry : f2.getEntries()) {
  if (SharingHelper.hasConflicts(entry)) {
    Entry resolved = SharingHelper.resolveConflicts(entry, resolver, "jms");    
  }
}

JSON Writer

Abdera supports the ability to serialize Abdera objects out to a JSON representation. The JSON representation is documented here.

Using the JSON Writer
Entry entry = abdera.newEntry();
entry.setTitle("Title");
entry.setRightsAsHtml("<p>Copyright &copy; 2007</p>");
entry.newId();
entry.addAuthor("John Doe");
entry.setContentAsXhtml("<p>This is a <b>test</b></p>");

Writer json = abdera.getWriterFactory().getWriter("json");
entry.writeTo(json, System.out);
Result
{
 "id":"urn:uuid:5B32E821725995E5DE1193780060026",
 "title":"Title",
 "rights":{
  "attributes":{
   "type":"html"
  },
  "children":[{
    "name":"p",
    "attributes":{},
    "children":["Copyright \u00a9 2007"]
   }
  ]
 },
 "content":{
  "attributes":{
   "type":"xhtml"
  },
  "children":[{
    "name":"p",
    "attributes":{},
    "children":[
      "This is a ",
      {
        "name":"b",
        "attributes":{},
        "children":["test"]
      }
    ]
   }
  ]
 },
 "authors":[{
   "name":"John Doe"
  }
 ]
}

The Atom Publishing Protocol Client

Creating the AbderaClient instance

Create the AbderaClient
Abdera abdera = new Abdera();
AbderaClient client = new AbderaClient(abdera);

Retrieving resources

Retrieving resources using the AbderaClient is straightforward:

Retrieving resources
Abdera abdera = new Abdera();
AbderaClient client = new AbderaClient(abdera);
ClientResponse resp = client.get("http://www.snellspace.com/wp/wp-atom1.php");
if (resp.getType() == ResponseType.SUCCESS) {
  Document<Feed> doc = resp.getDocument();
} else {
  // there was an error
}

If the resource is not an XML document, the ClientResponse object can provide an InputStream

  InputStream in = resp.getInputStream();
  resp.getContentType();

The ClientResponse object provides access to all of the response headers such as ETag and Last-Modified.

System.out.println(resp.getEntityTag());
System.out.println(resp.getLastModified());
System.out.println(resp.getContentLocation());
System.out.println(resp.getSlug());

For headers that can contain encoded non-ASCII characters (like the Atompub Slug header), the ClientResponse will automatically decode the header. For instance, given the header "Slug: I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n", resp.getSlug() returns the value "Iñtërnâtiônàlizætiøn".

Posting resources

AbderaClient client = new AbderaClient(abdera);

Entry entry = abdera.newEntry();
// ...

ClientResponse resp = client.post("http://www.example.org/collection",entry);

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

Posting non-Abdera resources is also possible,

InputStream in = ...
InputStreamRequestEntity entity = new InputStreamRequestEntity(in, "image/png");

ClientResponse resp = client.post("http://www.example.org/collection",entity);

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

Putting resources

AbderaClient client = new AbderaClient(abdera);

Entry entry = abdera.newEntry();
// ...

ClientResponse resp = client.put("http://www.example.org/collection",entry);

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

Posting non-Abdera resources is also possible,

InputStream in = ...
InputStreamRequestEntity entity = new InputStreamRequestEntity(in, "image/png");

ClientResponse resp = client.put("http://www.example.org/collection",entity);

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

Deleting resources

AbderaClient client = new AbderaClient(abdera);

ClientResponse resp = client.delete("http://www.example.org/collection");

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

Using Custom HTTP Methods

Custom HTTP methods can be used by calling the client.execute method

Abdera abdera = Abdera.getInstance();
AbderaClient client = new AbderaClient(abdera);

RequestEntity entity = ...
client.execute("PATCH", "http://example.org/foo", entity, null);

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

Customizing Request Options

The RequestOptions class is used to customize the options for a client request. The RequestOptions class provides access to all HTTP Request Headers

RequestOptions options = client.getDefaultRequestOptions();
options.setIfMatch(new EntityTag("foo"));
options.setNoCache(true);

ClientResponse resp = client.get("http://example.org/foo", options);

if (resp.getType() == ResponseType.SUCCESS) {
  // success
} else {
  // there was an error
}

The RequestOptions can be modified for any type of request.

Using SSL

To use Abdera to access SSL-protected endpoints, you need to register a trust manager. Abdera ships with a default non-op Trust Manager implementation that is designed to make it possible to use SSL services without providing any level of trust verification.

Abdera abdera = new Abdera();
AbderaClient client = new AbderaClient(abdera);
    
// Default trust manager provider registered for port 443
AbderaClient.registerTrustManager();
    
client.get("https://localhost:9080/foo");

By default, the Trust Manager will be registered on the default SSL port 443. If you want to register SSL support on a different port, you need to pass the port in when calling registerTrustManager

  AbderaClient.registerTrustManager(9443);

Alternatively, you can implement your own Trust Manager

Abdera abdera = new Abdera();
AbderaClient client = new AbderaClient(abdera);
    
AbderaClient.registerTrustManager(
  new X509TrustManager() {
    public void checkClientTrusted(
      X509Certificate[] certs, 
      String arg1) 
        throws CertificateException {
      // ignore this one for now
    }

    public void checkServerTrusted(
      X509Certificate[] certs, 
      String arg1) 
        throws CertificateException {
      // logic to determine if the cert is acceptable
      // throw a CertificateException if it's not
    }

    public X509Certificate[] getAcceptedIssuers() {
      List<X509Certificate> certs = new ArrayList<X509Certificate>();
      // prepare list of accepted issuer certs
      return certs.toArray(new X509Certificate[certs.size()]);
    }
        
  }
);
    
client.get("https://localhost:9080/foo");

Abdera also makes it possible to use SSL-based Client Certificate Authentication

Abdera abdera = new Abdera();
AbderaClient client = new AbderaClient(abdera);
    
KeyStore keystore = null;
ClientAuthSSLProtocolSocketFactory factory = 
  new ClientAuthSSLProtocolSocketFactory(
    keystore,"keystorepassword");
    
AbderaClient.registerFactory(factory, 443);

// DO NOT register a trust manager after this point
    
client.get("https://localhost:9080/foo");

TODO Complete this

Topics that need to be covered:

The Abdera Parser
Using Named Parsers
Implementing a Named Parser
Registering a Named Parser

Abdera Client
Using SSL
Customizing the request options
Detecting lost updates
Use Basic Authentication
Use WSSE Authentication
Use GoogleLogin Authentication
Use Client Cert Authentication
Use Custom Authentication
Using the client cache
Handling Error conditions

Abdera Server
Implementing a Provider
Implementing a Target Resolver
Using the Regex Target Resolver
Implementing a Subject Resolver
Configuring the Abdera servlet
Integrating with Spring
ItemManagers and Object Pooling
Handling custom HTTP Operations
Handling conditional requests
Serving Service Documents
Serving Categories Documents
Serving Collection Feeds
Serving Member Entries
Serving Media Resources
Using the Slug header
Reporting Errors
Properly Handling Cache Control Options

Other
MIME Types
Entity Tags
Language Tags

  • No labels