Author: jmsnell
Date: Wed Dec 28 23:09:16 2011
New Revision: 1225378
URL: http://svn.apache.org/viewvc?rev=1225378&view=rev
Log:
Documentation, Improvements
Modified:
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/templates/Template.java
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/text/CodepointIterator.java
abdera/abdera2/docs/Getting.Started/common.xml
Modified:
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/templates/Template.java
URL:
http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/templates/Template.java?rev=1225378&r1=1225377&r2=1225378&view=diff
==============================================================================
---
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/templates/Template.java
(original)
+++
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/templates/Template.java
Wed Dec 28 23:09:16 2011
@@ -36,6 +36,7 @@ import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
import static com.google.common.base.Preconditions.*;
@@ -235,7 +236,9 @@ public final class Template
(Context)obj :
obj instanceof Map ?
new MapContext((Map)obj, isiri) :
- new ObjectContext(obj, isiri);
+ obj instanceof Multimap ?
+ new MapContext(((Multimap)obj).asMap()) :
+ new ObjectContext(obj, isiri);
}
/**
Modified:
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/text/CodepointIterator.java
URL:
http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/text/CodepointIterator.java?rev=1225378&r1=1225377&r2=1225378&view=diff
==============================================================================
---
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/text/CodepointIterator.java
(original)
+++
abdera/abdera2/common/src/main/java/org/apache/abdera2/common/text/CodepointIterator.java
Wed Dec 28 23:09:16 2011
@@ -31,6 +31,7 @@ import java.util.NoSuchElementException;
import com.ibm.icu.text.UCharacterIterator;
+import com.ibm.icu.text.UForwardCharacterIterator;
public abstract class CodepointIterator
implements Iterator<Integer> {
@@ -154,14 +155,14 @@ public abstract class CodepointIterator
current = i.next();
}
protected int get() {
- if (current != UCharacterIterator.DONE) {
+ if (current != UForwardCharacterIterator.DONE) {
int v = current;
current = i.nextCodePoint();
return v;
} else throw new NoSuchElementException();
}
public boolean hasNext() {
- return current != UCharacterIterator.DONE;
+ return current != UForwardCharacterIterator.DONE;
}
}
Modified: abdera/abdera2/docs/Getting.Started/common.xml
URL:
http://svn.apache.org/viewvc/abdera/abdera2/docs/Getting.Started/common.xml?rev=1225378&r1=1225377&r2=1225378&view=diff
==============================================================================
--- abdera/abdera2/docs/Getting.Started/common.xml (original)
+++ abdera/abdera2/docs/Getting.Started/common.xml Wed Dec 28 23:09:16 2011
@@ -643,31 +643,309 @@ col.getItems(activityPublished(atOrBetwe
<section title="URI Templates">
- <t>TBD</t>
-
- </section>
-
- <section title="Text Utilities">
-
- <t>TBD</t>
-
- </section>
-
- <section title="Request Chain API">
-
- <t>TBD</t>
+ <t>Abdera2 includes an implementation of the current URI Template
+ <eref
target="http://tools.ietf.org/html/draft-gregorio-uritemplate-07">specification</eref>.
+ A URI Template is a specially formatted string containing tokens that
+ can be replaced to expand into a URI or IRI. For example:</t>
+
+ <figure><artwork>
+ private static final Template template =
+ new Template(
+ "http://example.org/~{user}{/categories*}{?foo,bar}");
+ </artwork></figure>
+
+ <figure><preamble>Expanding the template using a
java.util.Map:</preamble><artwork><![CDATA[
+ MapContext context = new MapContext();
+ context.put("user", "johndoe");
+ context.put("categories", of("a","b","c"));
+ context.put("foo", "xyz");
+ context.put("bar", "123");
+
+ System.out.println(template.expand(context));
+ ]]></artwork></figure>
+
+ <t>The URI generated by this template is:
+ http://example.org/~johndoe/a/b/c?foo=xyz&bar=123</t>
+
+ <figure><preamble>Expanding the template using a
com.google.common.collect.Multimap:</preamble><artwork<![CDATA[
+ Multimap<String, Object> map =
+ LinkedHashMultimap.create();
+ map.put("user", "james");
+ map.put("categories", "a");
+ map.put("categories", "b");
+ map.put("categories", "c");
+ map.put("foo", "abc");
+ map.put("bar", "xyz");
+ System.out.println(template.expand(map));
+ ]]></figure>
+
+ <figure><preamble>Expanding the template using a Java
Object:</preamble><artwork><![CDATA[
+ public static class MyObject {
+ public String user = "james";
+ public List<String> getCategories() {
+ return ImmutableList.of("a","b","c");
+ }
+ public Foo getFoo() {
+ return new Foo();
+ }
+ public String getBar() {
+ return "xyz";
+ }
+ }
+ static class Foo {
+ public String toString() {
+ return "xyz";
+ }
+ }
+
+ System.out.println(template.expand(new MyObject()));
+ ]]></artwork></figure>
</section>
-
+
<section title="Lightweight Map-Reduce API">
-
- <t>TBD</t>
-
- </section>
-
- <section title="Guava Extensions">
-
+
<t>TBD</t>
+
+ <figure><artwork><![CDATA[
+ private final static Function<
+ Iterable<Pair<Void, Activity>>,
+ Iterable<Pair<String, Iterable<Integer>>>> f1 =
+ compose(
+ new MyMapper(),
+ MapRed.<String,ASObject>countingReducer()
+ );
+
+ private final static ReducerFunction<String,Integer,Integer,String> f2 =
+ asFunction(MapRed.<String,Integer>invertReducer(),
+ Collections.<Integer>reverseOrder());
+
+ private final static Function<
+ Iterable<Pair<Void, Activity>>,
+ Iterable<Pair<Integer,Iterable<String>>>> f3 =
+ Functions.compose(f2,f1);
+
+ private final static ExecutorService exec =
+ MoreExecutors2.getExitingExecutor();
+
+ private static final Function<Collection<Activity>,Iterable<Pair<Void,
Activity>>> transform =
+ new Function<Collection<Activity>,Iterable<Pair<Void, Activity>>>() {
+ public Iterable<Pair<Void, Activity>> apply(Collection<Activity> input) {
+ return
+ Pair.<Void,Activity>make()
+ .index(MoreFunctions.<Activity>alwaysVoid(), input.getItems());
+ }
+ };
+
+ private final static Function<
+ Collection<Activity>,
+ Future<Iterable<Pair<Integer,Iterable<String>>>>> ff =
+ Functions.compose(
+ MoreFunctions.<
+ Iterable<Pair<Void, Activity>>,
+ Iterable<Pair<Integer,Iterable<String>>>>futureFunction(f3,exec),
+ transform);
+
+ private Activity getActivity(String name,int n) {
+ return Activity.makeActivity()
+ .actor(PersonObject.makePerson(name))
+ .id(String.format("urn:%s:%s",name,n))
+ .get();
+ }
+
+ @Test
+ public void testMapRed() throws Exception {
+ Collection<Activity> col =
+ Collection.<Activity>makeCollection()
+ .item(getActivity("Joe",1))
+ .item(getActivity("Joe",2))
+ .item(getActivity("Mark",3))
+ .item(getActivity("Mark",4))
+ .item(getActivity("Sally",5))
+ .get();
+
+ // This is basically MapReduce contained within a Function,
+ // Runs asynch using exiting executorservice... call to
+ // ff.apply(gen).get() hides all the magic...
+ // this particular function looks at the activity stream
+ // and counts the number of activities per actor
+
+ Iterable<Pair<Integer,Iterable<String>>> ret = ff.apply(col).get();
+ Pair<Integer,Iterable<String>> first = Iterables.get(ret,0);
+ Pair<Integer,Iterable<String>> second = Iterables.get(ret,1);
+ assertEquals(Integer.valueOf(2),first.first());
+ assertThat(first.second(),hasItems("Joe","Mark"));
+ assertEquals(Integer.valueOf(1),second.first());
+ assertThat(second.second(),hasItems("Sally"));
+ }
+
+ static class MyMapper
+ implements Mapper<Void,Activity,String,ASObject> {
+ public void map(
+ Void key,
+ Activity val,
+ Collector<String,ASObject> context) {
+ String ot = val.getActor().getDisplayName();
+ context.collect(ot!=null?ot:"", val.getActor());
+ }
+ }
+ ]]></artwork></figure>
+
+ <figure><preamble>Running this code will result in output like (the value
+ of ret is an iterable containing the following data):</preamble><artwork>
+ 2, [Joe, Mark]
+ 1, [Sally]
+ </artwork></figure>
+
+ <t>There's quite a bit there... so let's break down exactly what's going
on...</t>
+
+ <t>First of all, the Abdera2 MapReduce implementation is integrated
tightly
+ with the Guava Libraries Function interface. Essentially, all of the core
+ components of the MapReduce operations (e.g. the mapper, the reducer,
+ combiners, etc) are all implemented as Function objects. </t>
+
+ <figure><preamble>For instance, we define an initial Mapper function with
+ a basic counting reducer using this bit of
code:</preamble><artwork><![CDATA[
+ private final static Function<
+ Iterable<Pair<Void, Activity>>,
+ Iterable<Pair<String, Iterable<Integer>>>> f1 =
+ compose(
+ new MyMapper(),
+ MapRed.<String,ASObject>countingReducer()
+ );
+ ]]></artwork></figure>
+
+ <t>The MyMapper class is straightforward and should be recognizable to
+ anyone familiar with MapReduce in general...</t>
+
+ <figure><artwork><![CDATA[
+ static class MyMapper
+ implements Mapper<Void,Activity,String,ASObject> {
+ public void map(
+ Void key,
+ Activity val,
+ Collector<String,ASObject> context) {
+ String ot = val.getActor().getDisplayName();
+ context.collect(ot!=null?ot:"", val.getActor());
+ }
+ }
+ ]]></artwork></figure>
+
+ <t>If it's not clear what's going on in the mapper, we basically take an
+ Activity as input, grab the displayName of the Actor object, and collect
+ using the displayName as key and the actor object as the value. The
+ counting reducer, then, goes through and counts the number of unique
+ values we've collected. This particular implementation doesn't keep track
+ of different actors who happen to share the same name, but that's not
+ important for now.</t>
+
+ <t>Note that the mapper and the counting reducer are wrapped inside a
+ Guava Function object that takes an Iterable of
+ org.apache.abdera2.common.misc.Pair<Void,Activity> objects and
+ outputs an Iterable of
org.apache.abdera2.common.misc.Pair<String,Iterable<Integer>>
+ objects. The output is a mapping of each actor displayName to the number
of
+ activities in which that name appeared in the input collection.</t>
+
+ <t>That gives us our counts, but doesn't quite give us the output we
+ want.. so we need to define a bit more...</t>
+
+ <figure><artwork><![CDATA[
+ private final static ReducerFunction<String,Integer,Integer,String> f2 =
+ asFunction(MapRed.<String,Integer>invertReducer(),
+ Collections.<Integer>reverseOrder());
+ ]]></artwork></figure>
+
+ <t>This code creates another function that will take as input the output
+ of our initial mapper (f1) and perform an inversion mapping (swap the
+ key and the value), then reverse the order using the natural order of
+ the keys. Since the keys are integers, the highest numbers will appear
+ first.</t>
+
+ <t>So now we have two functions, f1 and f2. We could call these
separately,
+ but since the output of one becomes the output of the second, it's easier
+ if we just go ahead and compose those into a single function...</t>
+
+ <figure><artwork><![CDATA[
+ private final static Function<
+ Iterable<Pair<Void, Activity>>,
+ Iterable<Pair<Integer,Iterable<String>>>> f3 =
+ Functions.compose(f2,f1);
+ ]]></artwork></figure>
+
+ <t>So now we have a function (f3) that takes as input a bunch of Activity
+ objects and outputs a sorted listing of actor names ordered by number of
+ occurrences. But we're still not quite done yet... note that the input of
+ the function is an Iterable of Pair<Void,Activity> objects. That's
kind of
+ annoying really because what I really want to start off with is an
Activity
+ Stream. So let's create a function that converts an Activity Stream to the
+ appropriate Iterable...</t>
+
+ <figure><artwork><![CDATA[
+ private static final Function<Collection<Activity>,Iterable<Pair<Void,
Activity>>> transform =
+ new Function<Collection<Activity>,Iterable<Pair<Void, Activity>>>() {
+ public Iterable<Pair<Void, Activity>> apply(Collection<Activity> input) {
+ return
+ Pair.<Void,Activity>make()
+ .index(MoreFunctions.<Activity>alwaysVoid(), input.getItems());
+ }
+ };
+ ]]></artwork></figure>
+
+ <t>I've marked in bold the important bits.. basically, the Pair object has
+ a static method called index that takes a collection of items and an
+ Key-generating Function object (which in this case always returns void)
+ and generates an Iterable of Pair objects for each of the Activities in
+ the Stream.</t>
+
+ <t>Once we have our transform function, it's time to do a bit more
+ composition...</t>
+
+ <figure><artwork><![CDATA[
+ private final static ExecutorService exec =
+ MoreExecutors2.getExitingExecutor();
+
+ private final static Function<
+ Collection<Activity>,
+ Future<Iterable<Pair<Integer,Iterable<String>>>>> ff =
+ Functions.compose(
+ MoreFunctions.<
+ Iterable<Pair<Void, Activity>>,
+ Iterable<Pair<Integer,Iterable<String>>>>futureFunction(f3,exec),
+ transform);
+ ]]></artwork></figure>
+
+ <t>Note that here, we're creating another Function that takes as input an
+ Activity Stream Collection object and outputs a
java.util.concurrent.Future
+ whose value, when set, will be our sorted listing of actors. The new
+ function is composed of two individual functions: our transform created
+ above, and the combined MapReduce function (f3) that we created
previously.
+ However, first, we wrap f3 within another special Abdera2 construct called
+ a "futureFunction", which is essentially a Guava Function object that
+ operates asynchronously using a java.util.concurrent.ExecutorService.</t>
+
+ <t>Once all of this is defined and composed together (note that everything
+ is stored in static, final, immutable thread-safe constants) and once
we've
+ built our Activity Stream, we can call our analysis operation using a
+ single simple line of code (shown in bold below):</t>
+
+ <figure><artwork><![CDATA[
+ Collection<Activity> col =
+ Collection.<Activity>makeCollection()
+ .item(getActivity("Joe",1))
+ .item(getActivity("Joe",2))
+ .item(getActivity("Mark",3))
+ .item(getActivity("Mark",4))
+ .item(getActivity("Sally",5))
+ .get();
+
+ Iterable<Pair<Integer,Iterable<String>>> ret = ff.apply(col).get();
+ ]]></artwork></figure>
+
+ <t>The call to ff.apply(col) returns our Future object. Calling get() on
+ that blocks until the asynchronous operation is complete. Obviously this
+ is just an example; there are a variety of ways we can use that Future
+ object so that blocking the current thread isn't required (the Future
+ returned is actually a Guava ListenableFuture).</t>
</section>