abdera-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From James Snell <jasn...@gmail.com>
Subject Abdera2 Update
Date Thu, 20 Oct 2011 17:14:37 GMT
Ok, for those of you who may not have seen it, I posted another major
update to the Abdera2 code yesterday. Where as the first round of
updates focused primarily on updating dependencies and the
introduction of the Activity Streams capability, this update focused
more on API Refactoring and the introduction of two new major
dependencies: the Joda-Time Library
(http://joda-time.sourceforge.net/) and the Google Guava Libraries
(http://code.google.com/p/guava-libraries/).

First up, Joda-Time... for those who aren't familiar with it,
joda-time is a comprehensive code library for working with dates,
times, durations, intervals, etc. When I wrote the first version of
the Abdera Feed Object Model API, there wasn't a good open-source
implementation of the ISO8601 DateTime format required by the Atom
specification available so I wrote a fairly limited, down and dirty
implementation in the form of the AtomDate class. It had decent
performance, fairly good coverage and got the job done. Joda-Time,
however, has emerged since as a top quality rich implementation of the
8601 standard... so even though it is a breaking change to the
existing Feed Object Model API, I have gone through an have replaced
Abdera's own implementation with Joda-Time's DateTime class, which,
when used in combination with the mechanisms provided by the Google
Guava libraries, provides for some very interesting and compelling new
capabilities.

Which, of course, brings me to Guava. This library is a collection of
extremely useful utility classes from google. This update brings
significant deep integration with Guava in a number of ways, the most
important of which is the new Selector API that I introduced as part
of the first Abdera2 checkin.

Among many other things, Guava defines a number of interfaces and
utility classes aimed at making it easier for developers to write
quality, functional, readable code that has a more natural flow to it.
It is easiest to show by example.

In Abdera 1.x, if I wanted, for instance, to extract a list of entries
from an Atom feed that had been edited after a specific date and time,
it would look something like this...

AtomDate ad = new AtomDate("2011-09-10T12:12:12Z");
List<Entries> list = feed.getEntries();
List<Entries> selected = new ArrayList<Entries>();
for (Entry entry : list) {
  if (entry.getEdited() != null) {
    if (entry.getEdited().after(ad.getDate()))
      selected.add(entry);
  }
}

Note that feed.getEntries() will return every entry from the feed
whether we want it or not. We then have to iterate back over that
list, check to make sure there's an edited date, compare those and
build up a new list.  The code is ugly and cumbersome and inefficient.
 With the code I just checked in, the same process looks like this...

import static org.abdera.abdera2.model.selector.Selectors.*;
import static org.abdera.abdera2.common.date.DateTimes.*;

List<Entries> list =
  feed.getEntries(
    edited(after(dt("2011-09-10T12:12:12Z")))
  );

The portion, edited(after(dt("..."))) constructs a Selector that
filters the list of items returned by getEntries(), keeping us from
having to iterate back over the list.

Suppose we wanted to add another condition to the mix.. say some
custom selector that checks for the presence of a particular
extension... We can implement our CustomSelector by extending the
AbstractSelector class, and merely append that in to the code above
like so...

import static org.abdera.abdera2.model.selector.Selectors.*;
import static org.abdera.abdera2.common.date.DateTimes.*;

Selector<Entry> customSel = new AbstractSelector<Entry> { ... }

List<Entries> list =
  feed.getEntries(
    edited(after(dt("2011-09-10T12:12:12Z")))
   .and(customSel)
  );

Underlying the Selector API a large chunk of the Guava API...
specifically the Predicate, Function, and Constraint interfaces. The
Selector interface actually extends from Predicate and Constraint and
provides a mechanism for being cast as a Function.

A broad range of utility methods have been provided that create
constructors for many of the most common cases, in particular DateTime
related operations. Look at the following classes for those utility
methods...

org.apache.abdera2.common.date.DateTimes
org.apache.abdera2.common.selector.Selector.Utils
org.apache.abdera2.model.selector.Selectors (for Atom specific utilities)
org.apache.abdera2.activities.extra.Extra (for Activity Streams
specific utilities)

The Selector mechanism has been baked into both the Atom and Activity
Streams APIs now. For instance, suppose we have an Activity stream but
we only want a max of 10 activities for which a given user is the
intended target (using the Activity Streams Audience Targeting
Extension.. which I can explain later .. lol).. We could get that list
of entries using...

 PersonObject person = new PersonObject();
 person.setId("acct:john.doe@example.org");

 Iterable<Activity> list = stream.getItems(isTo(person).limit(10));


Additional changes in this update include....

1. Refactoring classes into immutable thread-safe objects. This will
be an ongoing change. As much as possible, a Factory/Builder model for
most basic object types will be used as opposed to the more
traditional getter/setter model. The motivation behind this change is
simple in that it helps make more a much more scalable architecture
and significantly more readable code.

For instance, if you wish to construct a Cache-Control header, you can
use a simple fluent builder api...

    CacheControl cc =
      CacheControl
        .make()
        .isPublic(true)
        .noTransform(true)
        .maxAge(1000)
        .get();

Likewise if you wish to construct a new WebLink HTTP Header,

    WebLink link =
      WebLink
        .make()
        .iri("http://example.org")
        .rel("alternate")
        .title("Home")
        .get();

The pattern is simple and consistent throughout.

2. Added support for Guava objects in the URI Template
implementation... specifically, a URI Template Context can now include
the Guava Multimap, Supplier and Optional values. In addition, support
for java.util.concurrent.Future, java.lang.ref.Reference and
java.util.concurrent.Callable were also added. The one caveat when
using Future, however, is that the context will not wait for a value
to become available. The Context calls Future.get() and takes whatever
it gets back as the value so before you use a Future in a URI
Template, make sure it's been completed.

3. org.apache.abdera2.common.text.CharUtils has been refactored. This
class was always a bit of a hacky mess. It's been cleaned up
significantly around a new CodepointMatcher that is modeled after
Guava's CharMatcher interface. Codepoint matcher, however, is designed
to work specifically with Unicode Codepoints rather than Java Chars.
For the most part, CharUtils and CodepointMatcher are internal classes
that the majority of users won't ever have to mess with.

4. Filter/FilterChain has been refactored. This is the part of the
Server framework that allows you to plug in a chain of filters before
invoking a Publishing Protocol Provider. Previously, the Filter and
FilterChain interfaces were very specific to the Server API.. they
have been refactored into generic Chain and Task interfaces and moved
to org.apache.abdera2.common.misc.*. This allows the use of Chain
anywhere you may need a simple interceptor framework...

A trivial example,

    Task<String,Void> lower =
      new Task<String,Void>() {
        public Void apply(
          String input,
          Chain<String, Void> flow) {
          if (input == null) return null;
          return flow.next(input.toLowerCase());
        }
      };

    Function<String,Void> print =
      new Function<String,Void>() {
        public Void apply(String input) {
          System.out.println(input);
          return null;
        }
    };

    Function<String,Void> chain =
      new Chain<String,Void>(print,lower);

    chain.apply("HELLO!");

The Google Guava api does provide the means of composing multiple
Function objects together such that the output of one flows into the
input of another, but it only works in one direction.. the Chain here
allows you to intercept inputs and outputs.

There are a number of other additions here and there throughout the
code, and there will be more to come. One *MAJOR* change that I'm
currently exploring is the use of Guice as a complete replacement for
the Classpath-based configuration model we currently have. Let's face
it, the current stuff provides a significant amount of flexibility and
power, but initialization is slow. Guice is significantly faster and
much more powerful than what we currently have.

Anyway, that's it for now.

Mime
View raw message