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 Mon, 14 Nov 2011 20:46:00 GMT
Work is continuing on Abdera2.... primary focus recently has been on
the Activity Streams implementation... working primarily on improving
the overall implementation, API design, performance and a number of
new features.

Several highlights..

All of the Activity Streams objects are now immutable and thread-safe.
A simple fluent factory api pattern is used to create new instances of
the various objects.. for example...

====
Activity activity =
  makeActivity()
    .actor(makePerson("James").get())
    .verb(FOLLOW)
    .object(
      makePerson("John Doe")
        .email("john.doe@example.org").get())
  .get();
====

Now let's suppose you want to modify the properties of this Activity
object... to do so, you will use it as a template to create a new
Activity object factory...

====
activity.<Activity,ActivityBuilder>template().id("foo");
====

Because of the way the Builder works, if you want to change an
existing field value, you need to filter that field out of the created
template... e.g.

====
activity.<Activity,ActivityBuilder>template(withoutFields("verb")).verb(Verb.POST);
====

Another feature that has been there for a while but has received a
significant overhaul is the ability to dynamically change the type of
object you're working with...

====
PersonObject person = activity.as(PersonObject.class);
====

This particular example obviously doesn't make a lot of sense but you
get the idea. The "as" method is more than just a cast... what you get
in response to the "as" method is a brand new object that clones all
of the property values.

One particularly interesting use of the "as" property is to merge two
objects into one... for instance..

====
PersonObject personObject = ...
PersonObject person = activity.as(PersonObject.class, personObject);
====

Calling "as" in this way will automatically replace all of the fields
from the original activity object with all of the fields from the
passed in person object and retain all the other fields from activity
that do not conflict. The returned object is a PersonObject whose
fields as effectively a union of the two.

There are several alternative ways of calling both the template and as
methods with a variety of similar effects.

Activity Objects are extensible... they can contain any combination of
arbitrary extension properties of any type. When building an object,
you can set these properties using the generic set(name,value)
method.. e.g.

====
makeActivity().set("foo", "bar").get();
====

This approach, however, is not type-safe and can be a bit cumbersome
and confusing. Likewise, when reading the property value from the
generated immutable object, you can use the generic getProperty(name)
method, which again, is generic and not entirely type-safe.

As an alternative, all objects and builders can be dynamically
extended, at runtime, with new interfaces that can either write or
read extension properties from the underlying activity object... for
example...

====

makeEvent("hangout")
  .extend( // dynamically extend the builder using the specified interface...
    AdditionalEventProperties.Builder.class)
      .host(makePerson("James").get())
      .performers(makePerson("Bob").get())
      .<EventBuilder>unwrap()
    .attending(
      makeCollection()
        .item(makePerson("Joe").get())
    .get()).get().writeTo(IO.get(),System.out,"UTF-8");
====

... what's going on here should be fairly clear...

On the builder, the "extend* method takes an Interface class as an
argument. Any setter pattern methods (methods that start with either
"set" or "Set" or methods that take a single argument) are dynamically
mapped to extension properties on the underlying object. E.g. the
"host" method maps to a "host" property. The "unwrap" method kicks us
back out of that extension interface and back to the EventBuilder so
we can continue on with our fluent api style building of the object.
Notice that the extension is type-safe, maintains the fluent api
pattern and flows rather naturally.

Extending the immutable object instances themselves is done similarly...

====
EventObject event = ....
AdditionalEventProperties aep = event.extend(AdditionalEventProperties.class);
System.out.println(aep.getHost());
====

et voici! dynamic type-safe extensibility... cool no?

Let's see.. what else...

Oh... the Abdera HTTP Client interface has been extended to support
asynchronous HTTP operations. Basically, I integrated the Apache HTTP
Client Components with the java.util.concurrent.ExecutorService and
Guava to provide a means for invoking get, put, post, delete, etc
asynchronously. You can either specify a callback listener or opt to
receive a java.util.concurrent.Future whose value will be set once the
call completes. You get to specify the ExecutorService used so you
have control over the way the calls are executed.

====
ActivitiesClient client = new ActivitiesClient();
ActivitiesSession session = client.newSession();
session.get(
  "http://www.example.org/foo",
  getExitingExecutor(),
    new SimpleActivityCollectionListener(session.getIO()) {
      protected void onResponse(ASDocument<Collection<Activity>> doc) {
        // do something with the doc...
    }});
====
There are a ton of other modifications throughout, all intended to
improve overall code quality and design. So far I'm definitely very
happy with the way this has evolved.

Oh, and as a reminder, the Activity Streams portion of Abdera2 can be
used completely independently of the Atom implementation. All you need
is the common library, generic client library and the activities
library (and of course their respective dependencies). You don't have
to touch the Atom stuff at all.

- James

Mime
View raw message