Add server capabilities, refactor observer pattern
diff --git a/src/main/java/com/intel/jndn/utils/NDNObserver.java b/src/main/java/com/intel/jndn/utils/NDNObserver.java
new file mode 100644
index 0000000..5b9ddd9
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/NDNObserver.java
@@ -0,0 +1,179 @@
+/*
+ * File name: NDNObserver.java
+ * 
+ * Purpose: Track asynchronous events from Client and Server
+ * 
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+
+/**
+ * Track asynchronous events from Client and Server
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class NDNObserver implements Observer {
+
+  protected List<NDNEvent> events = new ArrayList<>();
+  protected long timestamp;
+  protected OnEvent then;
+  protected boolean stopThread;
+
+  /**
+   * Constructor
+   */
+  public NDNObserver() {
+    timestamp = System.currentTimeMillis();
+  }
+
+  /**
+   * Receive notifications from observables
+   *
+   * @param o
+   * @param arg
+   */
+  @Override
+  public void update(Observable o, Object arg) {
+    NDNEvent event = (NDNEvent) arg;
+    events.add(event);
+    // call onData callbacks
+    if (Data.class.isInstance(event.packet) && then != null) {
+      then.onEvent(event);
+    }
+  }
+
+  /**
+   * Register a handler for events
+   *
+   * @param callback
+   * @return
+   */
+  public NDNObserver then(OnEvent callback) {
+    then = callback;
+    return this;
+  }
+
+  /**
+   * Count the number of eventCount observed
+   *
+   * @return
+   */
+  public int eventCount() {
+    return events.size();
+  }
+
+  /**
+   * Count the number of interest packets observed (received or sent)
+   *
+   * @return
+   */
+  public int interestCount() {
+    return count(Interest.class);
+  }
+
+  /**
+   * Count the number of Data packets observed
+   *
+   * @return
+   */
+  public int dataCount() {
+    return count(Data.class);
+  }
+
+  /**
+   * Count the number of errors observed
+   *
+   * @return
+   */
+  public int errorCount() {
+    return count(Exception.class);
+  }
+
+  /**
+   * Count the number of observed packets by type
+   *
+   * @param type
+   * @return
+   */
+  public int count(Class type) {
+    int count = 0;
+    for (NDNEvent event : events) {
+      if (type.isInstance(event.packet)) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  /**
+   * Calculate time elapsed since observer started observing until this method
+   * is called
+   *
+   * @return
+   */
+  public long getTimeSinceStart() {
+    if (getLast() != null) {
+      return getLast().getTimestamp() - timestamp;
+    }
+    return -1;
+  }
+
+  /**
+   * Retrieve a list of observed events
+   *
+   * @return event or null
+   */
+  public List<NDNEvent> getEvents() {
+    return events;
+  }
+
+  /**
+   * Retrieve the first event
+   *
+   * @return event or null
+   */
+  public NDNEvent getFirst() {
+    if (events.size() > 0) {
+      return events.get(0);
+    }
+    return null;
+  }
+
+  /**
+   * Retrieve the last event
+   *
+   * @return event or null
+   */
+  public NDNEvent getLast() {
+    if (events.size() > 0) {
+      return events.get(events.size() - 1);
+    }
+    return null;
+  }
+
+  /**
+   * Stop the current Client thread; used by asynchronous Client methods to
+   * stop the request/response thread
+   */
+  public void stop() {
+    stopThread = true;
+  }
+
+  /**
+   * Check the current stop status; used by asynchronous Client methods to
+   * stop the request/response thread
+   *
+   * @return
+   */
+  public boolean mustStop() {
+    return stopThread;
+  }
+}