Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
index 38366b6..cfc88b8 100644
--- a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
@@ -5,8 +5,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
 import com.google.common.collect.ImmutableList;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -14,8 +12,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
-import org.onlab.metrics.MetricsComponent;
-import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.EventMetric;
 import org.onlab.metrics.MetricsService;
 import org.onlab.onos.net.intent.IntentEvent;
 import org.onlab.onos.net.intent.IntentListener;
@@ -33,56 +30,32 @@
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentService intentService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MetricsService metricsService;
+
     private LinkedList<IntentEvent> lastEvents = new LinkedList<>();
     private static final int LAST_EVENTS_MAX_N = 100;
 
     //
     // Metrics
     //
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MetricsService metricsService;
-    //
     private static final String COMPONENT_NAME = "Intent";
     private static final String FEATURE_SUBMITTED_NAME = "Submitted";
     private static final String FEATURE_INSTALLED_NAME = "Installed";
     private static final String FEATURE_WITHDRAW_REQUESTED_NAME =
         "WithdrawRequested";
     private static final String FEATURE_WITHDRAWN_NAME = "Withdrawn";
-    private static final String GAUGE_TIMESTAMP_NAME = "Timestamp.EpochMs";
-    private static final String METER_RATE_NAME = "Rate";
     //
-    private MetricsComponent metricsComponent;
-    private MetricsFeature metricsFeatureSubmitted;
-    private MetricsFeature metricsFeatureInstalled;
-    private MetricsFeature metricsFeatureWithdrawRequested;
-    private MetricsFeature metricsFeatureWithdrawn;
+    // Event metrics:
+    //  - Intent Submitted API operation
+    //  - Intent Installed operation completion
+    //  - Intent Withdraw Requested API operation
+    //  - Intent Withdrawn operation completion
     //
-    // Timestamps:
-    //  - Intent Submitted API operation (ms from the Epoch)
-    //  - Intent Installed operation completion (ms from the Epoch)
-    //  - Intent Withdraw Requested API operation (ms from the Epoch)
-    //  - Intent Withdrawn operation completion (ms from the Epoch)
-    //
-    private volatile long intentSubmittedTimestampEpochMs = 0;
-    private volatile long intentInstalledTimestampEpochMs = 0;
-    private volatile long intentWithdrawRequestedTimestampEpochMs = 0;
-    private volatile long intentWithdrawnTimestampEpochMs = 0;
-    //
-    private Gauge<Long> intentSubmittedTimestampEpochMsGauge;
-    private Gauge<Long> intentInstalledTimestampEpochMsGauge;
-    private Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge;
-    private Gauge<Long> intentWithdrawnTimestampEpochMsGauge;
-    //
-    // Rate meters:
-    //  - Rate of the Submitted Intent API operations
-    //  - Rate of the Installed Intent operations
-    //  - Rate of the Withdrawn Requested Intent API operations
-    //  - Rate of the Withdrawn Intent operations
-    //
-    private Meter intentSubmittedRateMeter;
-    private Meter intentInstalledRateMeter;
-    private Meter intentWithdrawRequestedRateMeter;
-    private Meter intentWithdrawnRateMeter;
+    private EventMetric intentSubmittedEventMetric;
+    private EventMetric intentInstalledEventMetric;
+    private EventMetric intentWithdrawRequestedEventMetric;
+    private EventMetric intentWithdrawnEventMetric;
 
     @Activate
     protected void activate() {
@@ -108,43 +81,23 @@
     }
 
     @Override
-    public Gauge<Long> intentSubmittedTimestampEpochMsGauge() {
-        return intentSubmittedTimestampEpochMsGauge;
+    public EventMetric intentSubmittedEventMetric() {
+        return intentSubmittedEventMetric;
     }
 
     @Override
-    public Gauge<Long> intentInstalledTimestampEpochMsGauge() {
-        return intentInstalledTimestampEpochMsGauge;
+    public EventMetric intentInstalledEventMetric() {
+        return intentInstalledEventMetric;
     }
 
     @Override
-    public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge() {
-        return intentWithdrawRequestedTimestampEpochMsGauge;
+    public EventMetric intentWithdrawRequestedEventMetric() {
+        return intentWithdrawRequestedEventMetric;
     }
 
     @Override
-    public Gauge<Long> intentWithdrawnTimestampEpochMsGauge() {
-        return intentWithdrawnTimestampEpochMsGauge;
-    }
-
-    @Override
-    public Meter intentSubmittedRateMeter() {
-        return intentSubmittedRateMeter;
-    }
-
-    @Override
-    public Meter intentInstalledRateMeter() {
-        return intentInstalledRateMeter;
-    }
-
-    @Override
-    public Meter intentWithdrawRequestedRateMeter() {
-        return intentWithdrawRequestedRateMeter;
-    }
-
-    @Override
-    public Meter intentWithdrawnRateMeter() {
-        return intentWithdrawnRateMeter;
+    public EventMetric intentWithdrawnEventMetric() {
+        return intentWithdrawnEventMetric;
     }
 
     @Override
@@ -156,26 +109,21 @@
             //
             switch (event.type()) {
             case SUBMITTED:
-                intentSubmittedTimestampEpochMs = System.currentTimeMillis();
-                intentSubmittedRateMeter.mark(1);
+                intentSubmittedEventMetric.eventReceived();
                 break;
             case INSTALLED:
-                intentInstalledTimestampEpochMs = System.currentTimeMillis();
-                intentInstalledRateMeter.mark(1);
+                intentInstalledEventMetric.eventReceived();
                 break;
             case FAILED:
                 // TODO: Just ignore?
                 break;
                 /*
             case WITHDRAW_REQUESTED:
-                intentWithdrawRequestedTimestampEpochMs =
-                    System.currentTimeMillis();
-                    intentWithdrawRequestedRateMeter.mark(1);
+                intentWithdrawRequestedEventMetric.eventReceived();
                 break;
                 */
             case WITHDRAWN:
-                intentWithdrawnTimestampEpochMs = System.currentTimeMillis();
-                intentWithdrawnRateMeter.mark(1);
+                intentWithdrawnEventMetric.eventReceived();
                 break;
             default:
                 break;
@@ -199,10 +147,6 @@
      */
     private void clear() {
         synchronized (lastEvents) {
-            intentSubmittedTimestampEpochMs = 0;
-            intentInstalledTimestampEpochMs = 0;
-            intentWithdrawRequestedTimestampEpochMs = 0;
-            intentWithdrawnTimestampEpochMs = 0;
             lastEvents.clear();
         }
     }
@@ -211,109 +155,32 @@
      * Registers the metrics.
      */
     private void registerMetrics() {
-        metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
-        //
-        metricsFeatureSubmitted =
-            metricsComponent.registerFeature(FEATURE_SUBMITTED_NAME);
-        metricsFeatureInstalled =
-            metricsComponent.registerFeature(FEATURE_INSTALLED_NAME);
-        metricsFeatureWithdrawRequested =
-            metricsComponent.registerFeature(FEATURE_WITHDRAW_REQUESTED_NAME);
-        metricsFeatureWithdrawn =
-            metricsComponent.registerFeature(FEATURE_WITHDRAWN_NAME);
-        //
-        intentSubmittedTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureSubmitted,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentSubmittedTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentInstalledTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureInstalled,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentInstalledTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentWithdrawRequestedTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureWithdrawRequested,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentWithdrawRequestedTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentWithdrawnTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureWithdrawn,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentWithdrawnTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentSubmittedRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureSubmitted,
-                                       METER_RATE_NAME);
-        //
-        intentInstalledRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureInstalled,
-                                       METER_RATE_NAME);
-        //
-        intentWithdrawRequestedRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureWithdrawRequested,
-                                       METER_RATE_NAME);
-        //
-        intentWithdrawnRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureWithdrawn,
-                                       METER_RATE_NAME);
+        intentSubmittedEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_SUBMITTED_NAME);
+        intentInstalledEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_INSTALLED_NAME);
+        intentWithdrawRequestedEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_WITHDRAW_REQUESTED_NAME);
+        intentWithdrawnEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_WITHDRAWN_NAME);
+
+        intentSubmittedEventMetric.registerMetrics();
+        intentInstalledEventMetric.registerMetrics();
+        intentWithdrawRequestedEventMetric.registerMetrics();
+        intentWithdrawnEventMetric.registerMetrics();
     }
 
     /**
      * Removes the metrics.
      */
     private void removeMetrics() {
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureSubmitted,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureInstalled,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawRequested,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawn,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureSubmitted,
-                                    METER_RATE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureInstalled,
-                                    METER_RATE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawRequested,
-                                    METER_RATE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawn,
-                                    METER_RATE_NAME);
+        intentSubmittedEventMetric.removeMetrics();
+        intentInstalledEventMetric.removeMetrics();
+        intentWithdrawRequestedEventMetric.removeMetrics();
+        intentWithdrawnEventMetric.removeMetrics();
     }
 }
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
index 4acd00f..73df4bc 100644
--- a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
@@ -1,9 +1,7 @@
 package org.onlab.onos.metrics.intent;
 
 import java.util.List;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.net.intent.IntentEvent;
 
 /**
@@ -18,68 +16,32 @@
     public List<IntentEvent> getEvents();
 
     /**
-     * Gets the Metrics' Gauge for the intent SUBMITTED event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the intent SUBMITTED events.
      *
-     * @return the Metrics' Gauge for the intent SUBMITTED event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the intent SUBMITTED events.
      */
-    public Gauge<Long> intentSubmittedTimestampEpochMsGauge();
+    public EventMetric intentSubmittedEventMetric();
 
     /**
-     * Gets the Metrics' Gauge for the intent INSTALLED event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the intent INSTALLED events.
      *
-     * @return the Metrics' Gauge for the intent INSTALLED event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the intent INSTALLED events.
      */
-    public Gauge<Long> intentInstalledTimestampEpochMsGauge();
+    public EventMetric intentInstalledEventMetric();
 
     /**
-     * Gets the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
-     * timestamp (ms from the epoch).
+     * Gets the Event Metric for the intent WITHDRAW_REQUESTED events.
      *
      * TODO: This intent event is not implemented yet.
      *
-     * @return the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
-     * timestamp (ms from the epoch)
+     * @return the Event Metric for the intent WITHDRAW_REQUESTED events.
      */
-    public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge();
+    public EventMetric intentWithdrawRequestedEventMetric();
 
     /**
-     * Gets the Metrics' Gauge for the intent WITHDRAWN event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the intent WITHDRAWN events.
      *
-     * @return the Metrics' Gauge for the intent WITHDRAWN event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the intent WITHDRAWN events.
      */
-    public Gauge<Long> intentWithdrawnTimestampEpochMsGauge();
-
-    /**
-     * Gets the Metrics' Meter for the submitted intents event rate.
-     *
-     * @return the Metrics' Meter for the submitted intents event rate
-     */
-    public Meter intentSubmittedRateMeter();
-
-    /**
-     * Gets the Metrics' Meter for the installed intents event rate.
-     *
-     * @return the Metrics' Meter for the installed intent event rate
-     */
-    public Meter intentInstalledRateMeter();
-
-    /**
-     * Gets the Metrics' Meter for the withdraw requested intents event rate.
-     *
-     * @return the Metrics' Meter for the withdraw requested intents event rate
-     */
-    public Meter intentWithdrawRequestedRateMeter();
-
-    /**
-     * Gets the Metrics' Meter for the withdraw completed intents event rate.
-     *
-     * @return the Metrics' Meter for the withdraw completed intents event rate
-     */
-    public Meter intentWithdrawnRateMeter();
+    public EventMetric intentWithdrawnEventMetric();
 }
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
index 204cfd6..6f8d013 100644
--- a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
@@ -11,6 +11,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.metrics.intent.IntentMetricsService;
 
@@ -29,8 +30,6 @@
     @Override
     protected void execute() {
         IntentMetricsService service = get(IntentMetricsService.class);
-        Gauge<Long> gauge;
-        Meter meter;
 
         if (outputJson()) {
             ObjectMapper mapper = new ObjectMapper()
@@ -38,49 +37,49 @@
                                                   TimeUnit.MILLISECONDS,
                                                   false));
             ObjectNode result = mapper.createObjectNode();
-            //
-            gauge = service.intentSubmittedTimestampEpochMsGauge();
-            result.put("intentSubmittedTimestamp", json(mapper, gauge));
-            gauge = service.intentInstalledTimestampEpochMsGauge();
-            result.put("intentInstalledTimestamp", json(mapper, gauge));
-            gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
-            result.put("intentWithdrawRequestedTimestamp",
-                       json(mapper, gauge));
-            gauge = service.intentWithdrawnTimestampEpochMsGauge();
-            result.put("intentWithdrawnTimestamp", json(mapper, gauge));
-            //
-            meter = service.intentSubmittedRateMeter();
-            result.put("intentSubmittedRate", json(mapper, meter));
-            meter = service.intentInstalledRateMeter();
-            result.put("intentInstalledRate", json(mapper, meter));
-            meter = service.intentWithdrawRequestedRateMeter();
-            result.put("intentWithdrawRequestedRate", json(mapper, meter));
-            meter = service.intentWithdrawnRateMeter();
-            result.put("intentWithdrawnRate", json(mapper, meter));
-            //
+            result = json(mapper, result, "intentSubmitted",
+                          service.intentSubmittedEventMetric());
+            result = json(mapper, result, "intentInstalled",
+                          service.intentInstalledEventMetric());
+            result = json(mapper, result, "intentWithdrawRequested",
+                          service.intentWithdrawRequestedEventMetric());
+            result = json(mapper, result, "intentWithdrawn",
+                          service.intentWithdrawnEventMetric());
             print("%s", result);
         } else {
-            gauge = service.intentSubmittedTimestampEpochMsGauge();
-            printGauge("Submitted", gauge);
-            gauge = service.intentInstalledTimestampEpochMsGauge();
-            printGauge("Installed", gauge);
-            gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
-            printGauge("Withdraw Requested", gauge);
-            gauge = service.intentWithdrawnTimestampEpochMsGauge();
-            printGauge("Withdrawn", gauge);
-            //
-            meter = service.intentSubmittedRateMeter();
-            printMeter("Submitted", meter);
-            meter = service.intentInstalledRateMeter();
-            printMeter("Installed", meter);
-            meter = service.intentWithdrawRequestedRateMeter();
-            printMeter("Withdraw Requested", meter);
-            meter = service.intentWithdrawnRateMeter();
-            printMeter("Withdrawn", meter);
+            printEventMetric("Submitted",
+                             service.intentSubmittedEventMetric());
+            printEventMetric("Installed",
+                             service.intentInstalledEventMetric());
+            printEventMetric("Withdraw Requested",
+                             service.intentWithdrawRequestedEventMetric());
+            printEventMetric("Withdrawn",
+                             service.intentWithdrawnEventMetric());
         }
     }
 
     /**
+     * Produces JSON node for an Event Metric.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param objectNode the JSON object node to use
+     * @param propertyPrefix the property prefix to use
+     * @param eventMetric the Event Metric with the data
+     * @return JSON object node for the Event Metric
+     */
+    private ObjectNode json(ObjectMapper mapper, ObjectNode objectNode,
+                            String propertyPrefix, EventMetric eventMetric) {
+        String gaugeName = propertyPrefix + "Timestamp";
+        String meterName = propertyPrefix + "Rate";
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+
+        objectNode.put(gaugeName, json(mapper, gauge));
+        objectNode.put(meterName, json(mapper, meter));
+        return objectNode;
+    }
+
+    /**
      * Produces JSON node for an Object.
      *
      * @param mapper the JSON object mapper to use
@@ -94,8 +93,8 @@
         //
         try {
             final String objectJson = mapper.writeValueAsString(object);
-            JsonNode objectNode = mapper.readTree(objectJson);
-            return objectNode;
+            JsonNode jsonNode = mapper.readTree(objectJson);
+            return jsonNode;
         } catch (JsonProcessingException e) {
             log.error("Error writing value as JSON string", e);
         } catch (IOException e) {
@@ -105,28 +104,26 @@
     }
 
     /**
-     * Prints a Gauge.
+     * Prints an Event Metric.
      *
      * @param operationStr the string with the intent operation to print
-     * @param gauge the Gauge to print
+     * @param eventMetric the Event Metric to print
      */
-    private void printGauge(String operationStr, Gauge<Long> gauge) {
-        print(FORMAT_GAUGE, operationStr, gauge.getValue());
-    }
+    private void printEventMetric(String operationStr,
+                                  EventMetric eventMetric) {
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+        TimeUnit rateUnit = TimeUnit.SECONDS;
+        double rateFactor = rateUnit.toSeconds(1);
 
-    /**
-     * Prints a Meter.
-     *
-     * @param operationStr the string with the intent operation to print
-     * @param meter the Meter to print
-     */
-    private void printMeter(String operationStr, Meter meter) {
-            TimeUnit rateUnit = TimeUnit.SECONDS;
-            double rateFactor = rateUnit.toSeconds(1);
-            print(FORMAT_METER, operationStr, meter.getCount(),
-                  meter.getMeanRate() * rateFactor,
-                  meter.getOneMinuteRate() * rateFactor,
-                  meter.getFiveMinuteRate() * rateFactor,
-                  meter.getFifteenMinuteRate() * rateFactor);
+        // Print the Gauge
+        print(FORMAT_GAUGE, operationStr, gauge.getValue());
+
+        // Print the Meter
+        print(FORMAT_METER, operationStr, meter.getCount(),
+              meter.getMeanRate() * rateFactor,
+              meter.getOneMinuteRate() * rateFactor,
+              meter.getFiveMinuteRate() * rateFactor,
+              meter.getFifteenMinuteRate() * rateFactor);
     }
 }
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
index 32cf0cf..814e178 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
@@ -5,8 +5,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
 import com.google.common.collect.ImmutableList;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -14,8 +12,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
-import org.onlab.metrics.MetricsComponent;
-import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.EventMetric;
 import org.onlab.metrics.MetricsService;
 import org.onlab.onos.event.Event;
 import org.onlab.onos.net.device.DeviceEvent;
@@ -48,6 +45,8 @@
     protected LinkService linkService;
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected TopologyService topologyService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MetricsService metricsService;
 
     private LinkedList<Event> lastEvents = new LinkedList<>();
     private static final int LAST_EVENTS_MAX_N = 100;
@@ -61,22 +60,22 @@
     //
     // Metrics
     //
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MetricsService metricsService;
-    //
     private static final String COMPONENT_NAME = "Topology";
-    private static final String FEATURE_NAME = "EventNotification";
-    private static final String GAUGE_NAME = "LastEventTimestamp.EpochMs";
-    private static final String METER_NAME = "EventRate";
+    private static final String FEATURE_DEVICE_NAME = "DeviceEvent";
+    private static final String FEATURE_HOST_NAME = "HostEvent";
+    private static final String FEATURE_LINK_NAME = "LinkEvent";
+    private static final String FEATURE_GRAPH_NAME = "GraphEvent";
     //
-    private MetricsComponent metricsComponent;
-    private MetricsFeature metricsFeatureEventNotification;
+    // Event metrics:
+    //  - Device events
+    //  - Host events
+    //  - Link events
+    //  - Topology Graph events
     //
-    // Timestamp of the last Topology event (ms from the Epoch)
-    private volatile long lastEventTimestampEpochMs = 0;
-    private Gauge<Long> lastEventTimestampEpochMsGauge;
-    // Rate of the Topology events published to the Topology listeners
-    private Meter eventRateMeter;
+    private EventMetric topologyDeviceEventMetric;
+    private EventMetric topologyHostEventMetric;
+    private EventMetric topologyLinkEventMetric;
+    private EventMetric topologyGraphEventMetric;
 
     @Activate
     protected void activate() {
@@ -113,27 +112,34 @@
     }
 
     @Override
-    public Gauge<Long> lastEventTimestampEpochMsGauge() {
-        return lastEventTimestampEpochMsGauge;
+    public EventMetric topologyDeviceEventMetric() {
+        return topologyDeviceEventMetric;
     }
 
     @Override
-    public Meter eventRateMeter() {
-        return eventRateMeter;
+    public EventMetric topologyHostEventMetric() {
+        return topologyHostEventMetric;
+    }
+
+    @Override
+    public EventMetric topologyLinkEventMetric() {
+        return topologyLinkEventMetric;
+    }
+
+    @Override
+    public EventMetric topologyGraphEventMetric() {
+        return topologyGraphEventMetric;
     }
 
     /**
      * Records an event.
      *
      * @param event the event to record
-     * @param updateEventRateMeter if true, update the Event Rate Meter
+     * @param eventMetric the Event Metric to use
      */
-    private void recordEvent(Event event, boolean updateEventRateMeter) {
+    private void recordEvent(Event event, EventMetric eventMetric) {
         synchronized (lastEvents) {
-            lastEventTimestampEpochMs = System.currentTimeMillis();
-            if (updateEventRateMeter) {
-                eventRateMeter.mark(1);
-            }
+            eventMetric.eventReceived();
 
             //
             // Keep only the last N events, where N = LAST_EVENTS_MAX_N
@@ -151,7 +157,7 @@
     private class InnerDeviceListener implements DeviceListener {
         @Override
         public void event(DeviceEvent event) {
-            recordEvent(event, true);
+            recordEvent(event, topologyDeviceEventMetric);
             log.debug("Device Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
         }
@@ -163,7 +169,7 @@
     private class InnerHostListener implements HostListener {
         @Override
         public void event(HostEvent event) {
-            recordEvent(event, true);
+            recordEvent(event, topologyHostEventMetric);
             log.debug("Host Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
         }
@@ -175,7 +181,7 @@
     private class InnerLinkListener implements LinkListener {
         @Override
         public void event(LinkEvent event) {
-            recordEvent(event, true);
+            recordEvent(event, topologyLinkEventMetric);
             log.debug("Link Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
         }
@@ -187,11 +193,7 @@
     private class InnerTopologyListener implements TopologyListener {
         @Override
         public void event(TopologyEvent event) {
-            //
-            // NOTE: Don't update the eventRateMeter, because the real
-            // events are already captured/counted.
-            //
-            recordEvent(event, false);
+            recordEvent(event, topologyGraphEventMetric);
             log.debug("Topology Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
             for (Event reason : event.reasons()) {
@@ -206,7 +208,6 @@
      */
     private void clear() {
         synchronized (lastEvents) {
-            lastEventTimestampEpochMs = 0;
             lastEvents.clear();
         }
     }
@@ -215,35 +216,32 @@
      * Registers the metrics.
      */
     private void registerMetrics() {
-        metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
-        metricsFeatureEventNotification =
-            metricsComponent.registerFeature(FEATURE_NAME);
-        lastEventTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureEventNotification,
-                                          GAUGE_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return lastEventTimestampEpochMs;
-                                              }
-                                          });
-        eventRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureEventNotification,
-                                       METER_NAME);
+        topologyDeviceEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_DEVICE_NAME);
+        topologyHostEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_HOST_NAME);
+        topologyLinkEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_LINK_NAME);
+        topologyGraphEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_GRAPH_NAME);
 
+        topologyDeviceEventMetric.registerMetrics();
+        topologyHostEventMetric.registerMetrics();
+        topologyLinkEventMetric.registerMetrics();
+        topologyGraphEventMetric.registerMetrics();
     }
 
     /**
      * Removes the metrics.
      */
     private void removeMetrics() {
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureEventNotification,
-                                    GAUGE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureEventNotification,
-                                    METER_NAME);
+        topologyDeviceEventMetric.removeMetrics();
+        topologyHostEventMetric.removeMetrics();
+        topologyLinkEventMetric.removeMetrics();
+        topologyGraphEventMetric.removeMetrics();
     }
 }
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
index aeb2e32..9203d9b 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
@@ -1,9 +1,7 @@
 package org.onlab.onos.metrics.topology;
 
 import java.util.List;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.event.Event;
 
 /**
@@ -18,18 +16,30 @@
     public List<Event> getEvents();
 
     /**
-     * Gets the Metrics' Gauge for the last topology event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the Device Events.
      *
-     * @return the Metrics' Gauge for the last topology event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the Device Events.
      */
-    public Gauge<Long> lastEventTimestampEpochMsGauge();
+    public EventMetric topologyDeviceEventMetric();
 
     /**
-     * Gets the Metrics' Meter for the topology events rate.
+     * Gets the Event Metric for the Host Events.
      *
-     * @return the Metrics' Meter for the topology events rate
+     * @return the Event Metric for the Host Events.
      */
-    public Meter eventRateMeter();
+    public EventMetric topologyHostEventMetric();
+
+    /**
+     * Gets the Event Metric for the Link Events.
+     *
+     * @return the Event Metric for the Link Events.
+     */
+    public EventMetric topologyLinkEventMetric();
+
+    /**
+     * Gets the Event Metric for the Topology Graph Events.
+     *
+     * @return the Event Metric for the Topology Graph Events.
+     */
+    public EventMetric topologyGraphEventMetric();
 }
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
index 54d3a95..b7e0401 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
@@ -11,6 +11,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.metrics.topology.TopologyMetricsService;
 
@@ -22,15 +23,13 @@
 public class TopologyEventsMetricsCommand extends AbstractShellCommand {
 
     private static final String FORMAT_GAUGE =
-        "Last Topology Event Timestamp (ms from epoch)=%d";
+        "Topology %s Event Timestamp (ms from epoch)=%d";
     private static final String FORMAT_METER =
-        "Topology Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
+        "Topology %s Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
 
     @Override
     protected void execute() {
         TopologyMetricsService service = get(TopologyMetricsService.class);
-        Gauge<Long> gauge = service.lastEventTimestampEpochMsGauge();
-        Meter meter = service.eventRateMeter();
 
         if (outputJson()) {
             ObjectMapper mapper = new ObjectMapper()
@@ -38,32 +37,89 @@
                                                   TimeUnit.MILLISECONDS,
                                                   false));
             ObjectNode result = mapper.createObjectNode();
-            try {
-                //
-                // NOTE: The API for custom serializers is incomplete,
-                // hence we have to parse the JSON string to create JsonNode.
-                //
-                final String gaugeJson = mapper.writeValueAsString(gauge);
-                final String meterJson = mapper.writeValueAsString(meter);
-                JsonNode gaugeNode = mapper.readTree(gaugeJson);
-                JsonNode meterNode = mapper.readTree(meterJson);
-                result.put("lastTopologyEventTimestamp", gaugeNode);
-                result.put("topologyEventRate", meterNode);
-            } catch (JsonProcessingException e) {
-                log.error("Error writing value as JSON string", e);
-            } catch (IOException e) {
-                log.error("Error writing value as JSON string", e);
-            }
+            result = json(mapper, result, "topologyDeviceEvent",
+                          service.topologyDeviceEventMetric());
+            result = json(mapper, result, "topologyHostEvent",
+                          service.topologyHostEventMetric());
+            result = json(mapper, result, "topologyLinkEvent",
+                          service.topologyLinkEventMetric());
+            result = json(mapper, result, "topologyGraphEvent",
+                          service.topologyGraphEventMetric());
             print("%s", result);
         } else {
-            TimeUnit rateUnit = TimeUnit.SECONDS;
-            double rateFactor = rateUnit.toSeconds(1);
-            print(FORMAT_GAUGE, gauge.getValue());
-            print(FORMAT_METER, meter.getCount(),
-                  meter.getMeanRate() * rateFactor,
-                  meter.getOneMinuteRate() * rateFactor,
-                  meter.getFiveMinuteRate() * rateFactor,
-                  meter.getFifteenMinuteRate() * rateFactor);
+            printEventMetric("Device", service.topologyDeviceEventMetric());
+            printEventMetric("Host", service.topologyHostEventMetric());
+            printEventMetric("Link", service.topologyLinkEventMetric());
+            printEventMetric("Graph", service.topologyGraphEventMetric());
         }
     }
+
+    /**
+     * Produces JSON node for an Event Metric.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param objectNode the JSON object node to use
+     * @param propertyPrefix the property prefix to use
+     * @param eventMetric the Event Metric with the data
+     * @return JSON object node for the Event Metric
+     */
+    private ObjectNode json(ObjectMapper mapper, ObjectNode objectNode,
+                            String propertyPrefix, EventMetric eventMetric) {
+        String gaugeName = propertyPrefix + "Timestamp";
+        String meterName = propertyPrefix + "Rate";
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+
+        objectNode.put(gaugeName, json(mapper, gauge));
+        objectNode.put(meterName, json(mapper, meter));
+        return objectNode;
+    }
+
+    /**
+     * Produces JSON node for an Object.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param object the Object with the data
+     * @return JSON node for the Object
+     */
+    private JsonNode json(ObjectMapper mapper, Object object) {
+        //
+        // NOTE: The API for custom serializers is incomplete,
+        // hence we have to parse the JSON string to create JsonNode.
+        //
+        try {
+            final String objectJson = mapper.writeValueAsString(object);
+            JsonNode jsonNode = mapper.readTree(objectJson);
+            return jsonNode;
+        } catch (JsonProcessingException e) {
+            log.error("Error writing value as JSON string", e);
+        } catch (IOException e) {
+            log.error("Error writing value as JSON string", e);
+        }
+        return null;
+    }
+
+    /**
+     * Prints an Event Metric.
+     *
+     * @param operationStr the string with the intent operation to print
+     * @param eventMetric the Event Metric to print
+     */
+    private void printEventMetric(String operationStr,
+                                  EventMetric eventMetric) {
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+        TimeUnit rateUnit = TimeUnit.SECONDS;
+        double rateFactor = rateUnit.toSeconds(1);
+
+        // Print the Gauge
+        print(FORMAT_GAUGE, operationStr, gauge.getValue());
+
+        // Print the Meter
+        print(FORMAT_METER, operationStr, meter.getCount(),
+              meter.getMeanRate() * rateFactor,
+              meter.getOneMinuteRate() * rateFactor,
+              meter.getFiveMinuteRate() * rateFactor,
+              meter.getFifteenMinuteRate() * rateFactor);
+    }
 }
diff --git a/apps/optical/src/main/java/org/onlab/onos/optical/provisioner/OpticalPathProvisioner.java b/apps/optical/src/main/java/org/onlab/onos/optical/provisioner/OpticalPathProvisioner.java
new file mode 100644
index 0000000..9f77cfd
--- /dev/null
+++ b/apps/optical/src/main/java/org/onlab/onos/optical/provisioner/OpticalPathProvisioner.java
@@ -0,0 +1,226 @@
+package org.onlab.onos.optical.provisioner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.OpticalConnectivityIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.topology.LinkWeight;
+import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyEdge;
+
+import org.onlab.onos.net.topology.TopologyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * OpticalPathProvisioner listens event notifications from the Intent F/W.
+ * It generates one or more opticalConnectivityIntent(s) and submits (or withdraws) to Intent F/W
+ * for adding/releasing capacity at the packet layer.
+ *
+ */
+
+@Component(immediate = true)
+public class OpticalPathProvisioner {
+
+    protected static final Logger log = LoggerFactory
+            .getLogger(OpticalPathProvisioner.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private IntentExtensionService intentExtensionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkResourceService resourceService;
+
+    private ApplicationId appId;
+
+    //protected <IntentId> intentIdGenerator;
+
+    private final InternalOpticalPathProvisioner pathProvisioner = new InternalOpticalPathProvisioner();
+
+    @Activate
+    protected void activate() {
+        intentService.addListener(pathProvisioner);
+        appId = coreService.registerApplication("org.onlab.onos.optical");
+        log.info("Starting optical path provisoning...");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        intentService.removeListener(pathProvisioner);
+    }
+
+    public class InternalOpticalPathProvisioner implements IntentListener {
+        @Override
+        public void event(IntentEvent event) {
+            switch (event.type()) {
+                case SUBMITTED:
+                    break;
+                case INSTALLED:
+                    break;
+                case FAILED:
+                    log.info("intent {} failed, calling optical path provisioning APP.", event.subject());
+                    setuplightpath(event.subject());
+                    break;
+                case WITHDRAWN:
+                    log.info("intent {} withdrawn.", event.subject());
+                    teardownLightpath(event.subject());
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void setuplightpath(Intent intent) {
+           // TODO: considering user policies and optical reach
+           if (!intent.equals(PointToPointIntent.class)) {
+               return;
+           }
+
+           PointToPointIntent pktIntent = (PointToPointIntent) intent;
+           if (pktIntent.ingressPoint() == null || pktIntent.egressPoint() == null) {
+               return;
+           }
+
+           Topology topology = topologyService.currentTopology();
+
+           LinkWeight weight = new LinkWeight() {
+               @Override
+               public double weight(TopologyEdge edge) {
+                   boolean isOptical = false;
+                   String t = edge.link().annotations().value("linkType");
+                   if (t.equals("WDM")) {
+                      isOptical = true;
+                   }
+                   if (isOptical) {
+                       return 1000;  // optical links
+                   } else {
+                       return 10; // packet links
+                   }
+               }
+           };
+
+           Set<Path> paths = topologyService.getPaths(topology,
+                   pktIntent.ingressPoint().deviceId(),
+                   pktIntent.egressPoint().deviceId(),
+                   weight);
+
+           if (paths.isEmpty()) {
+               return;
+           }
+
+           ConnectPoint srcWdmPoint = null;
+           ConnectPoint dstWdmPoint = null;
+           Iterator<Path> itrPath = paths.iterator();
+           Path firstPath = itrPath.next();
+           log.info(firstPath.toString());
+
+           ArrayList<Map<ConnectPoint, ConnectPoint>> connectionList = new ArrayList<>();
+
+           Iterator<Link> itrLink = firstPath.links().iterator();
+           while (itrLink.hasNext()) {
+               Link link1 = itrLink.next();
+               if (!isOpticalLink(link1)) {
+                   continue;
+               } else {
+                   srcWdmPoint = link1.dst();
+                   dstWdmPoint = srcWdmPoint;
+               }
+
+               while (true) {
+
+                   if (itrLink.hasNext()) {
+                       Link link2 = itrLink.next();
+                       dstWdmPoint = link2.src();
+                   } else {
+                       break;
+                   }
+
+                   if (itrLink.hasNext()) {
+                       Link link3 = itrLink.next();
+                       if (!isOpticalLink(link3)) {
+                          break;
+                       }
+                   } else {
+                       break;
+                   }
+               }
+
+               Map<ConnectPoint, ConnectPoint> pair =
+                       new HashMap<ConnectPoint, ConnectPoint>();
+               pair.put(srcWdmPoint, dstWdmPoint);
+
+               connectionList.add(pair);
+           }
+
+           for (Map<ConnectPoint, ConnectPoint> map : connectionList) {
+               for (Entry<ConnectPoint, ConnectPoint> entry : map.entrySet()) {
+
+                   ConnectPoint src = entry.getKey();
+                   ConnectPoint dst = entry.getValue();
+
+                   Intent opticalIntent = new OpticalConnectivityIntent(appId,
+                          srcWdmPoint,
+                          dstWdmPoint);
+
+                   intentService.submit(opticalIntent);
+
+                   log.info(opticalIntent.toString());
+               }
+           }
+
+        }
+
+        private boolean isOpticalLink(Link link) {
+            boolean isOptical = false;
+            String t = link.annotations().value("linkType");
+            if (t.equals("WDM") || t.equals("PktOptLink")) {
+               isOptical = true;
+            }
+            return isOptical;
+          }
+
+        private void teardownLightpath(Intent intent) {
+          // TODO: tear down the idle lightpath if the utilization is close to zero.
+        }
+
+    }
+
+}
diff --git a/apps/optical/src/main/java/org/onlab/onos/optical/testapp/LambdaForwarding.java b/apps/optical/src/main/java/org/onlab/onos/optical/testapp/LambdaForwarding.java
new file mode 100644
index 0000000..20138c8
--- /dev/null
+++ b/apps/optical/src/main/java/org/onlab/onos/optical/testapp/LambdaForwarding.java
@@ -0,0 +1,163 @@
+package org.onlab.onos.optical.testapp;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.slf4j.Logger;
+
+/**
+ * Sample reactive forwarding application.
+ */
+//@Component(immediate = true)
+public class LambdaForwarding {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    private ApplicationId appId;
+
+    private final InternalDeviceListener listener = new InternalDeviceListener();
+
+    private final Map<DeviceId, Integer> uglyMap = new HashMap<>();
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onlab.onos.fwd");
+
+        uglyMap.put(DeviceId.deviceId("of:0000ffffffffff01"), 1);
+        uglyMap.put(DeviceId.deviceId("of:0000ffffffffff02"), 2);
+        uglyMap.put(DeviceId.deviceId("of:0000ffffffffff03"), 3);
+
+        deviceService.addListener(listener);
+
+        for (Device d : deviceService.getDevices()) {
+            pushRules(d);
+        }
+
+
+        log.info("Started with Application ID {}", appId.id());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        flowRuleService.removeFlowRulesById(appId);
+
+        log.info("Stopped");
+    }
+
+
+    private void pushRules(Device device) {
+
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        int inport;
+        int outport;
+        short lambda = 10;
+        byte sigType = 1;
+        Integer switchNumber = uglyMap.get(device.id());
+        if (switchNumber == null) {
+            return;
+        }
+
+        switch (switchNumber) {
+        case 1:
+            inport = 10;
+            outport = 20;
+            sbuilder.matchInport(PortNumber.portNumber(inport));
+            tbuilder.setOutput(PortNumber.portNumber(outport)).setLambda(lambda);
+            break;
+        case 2:
+            inport = 21;
+            outport = 11;
+            sbuilder.matchLambda(lambda).
+                    matchInport(PortNumber.portNumber(inport)); // match sigtype
+            tbuilder.setOutput(PortNumber.portNumber(outport));
+            break;
+        case 3:
+            inport = 30;
+            outport = 31;
+            sbuilder.matchLambda(lambda).
+                    matchInport(PortNumber.portNumber(inport));
+            tbuilder.setOutput(PortNumber.portNumber(outport)).setLambda(lambda);
+            break;
+        default:
+        }
+
+        TrafficTreatment treatement = tbuilder.build();
+        TrafficSelector selector = sbuilder.build();
+
+        FlowRule f = new DefaultFlowRule(device.id(), selector,
+                treatement, 100, appId, 600, false);
+
+        flowRuleService.applyFlowRules(f);
+
+
+
+    }
+
+    public class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public void event(DeviceEvent event) {
+            switch (event.type()) {
+            case DEVICE_ADDED:
+                pushRules(event.subject());
+                break;
+            case DEVICE_AVAILABILITY_CHANGED:
+                break;
+            case DEVICE_MASTERSHIP_CHANGED:
+                break;
+            case DEVICE_REMOVED:
+                break;
+            case DEVICE_SUSPENDED:
+                break;
+            case DEVICE_UPDATED:
+                break;
+            case PORT_ADDED:
+                break;
+            case PORT_REMOVED:
+                break;
+            case PORT_UPDATED:
+                break;
+            default:
+                break;
+
+            }
+
+        }
+
+    }
+
+
+}
+
+
diff --git a/apps/sdnip/src/main/resources/config-examples/sdnip.json b/apps/sdnip/src/main/resources/config-examples/sdnip.json
index b9a2d56..13f4db8 100644
--- a/apps/sdnip/src/main/resources/config-examples/sdnip.json
+++ b/apps/sdnip/src/main/resources/config-examples/sdnip.json
@@ -14,6 +14,16 @@
             "attachmentDpid" : "00:00:00:00:00:00:00:a2",
 	    "attachmentPort" : "1",
             "ipAddress" : "192.168.30.1"
+        },
+	{
+            "attachmentDpid" : "00:00:00:00:00:00:00:a6",
+	    "attachmentPort" : "1",
+            "ipAddress" : "192.168.40.1"
+        },
+	{
+            "attachmentDpid" : "00:00:00:00:00:00:00:a4",
+	    "attachmentPort" : "4",
+            "ipAddress" : "192.168.60.1"
         }
     ],
     "bgpSpeakers" : [
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddOpticalIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddOpticalIntentCommand.java
new file mode 100644
index 0000000..dc73c81
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddOpticalIntentCommand.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.cli.net;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.OpticalConnectivityIntent;
+
+/**
+ * Installs optical connectivity intents.
+ */
+@Command(scope = "onos", name = "add-optical-intent",
+         description = "Installs optical connectivity intent")
+public class AddOpticalIntentCommand extends ConnectivityIntentCommand {
+
+    @Argument(index = 0, name = "ingressDevice",
+              description = "Ingress Device/Port Description",
+              required = true, multiValued = false)
+    String ingressDeviceString = null;
+
+    @Argument(index = 1, name = "egressDevice",
+              description = "Egress Device/Port Description",
+              required = true, multiValued = false)
+    String egressDeviceString = null;
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+
+        DeviceId ingressDeviceId = deviceId(getDeviceId(ingressDeviceString));
+        PortNumber ingressPortNumber = portNumber(getPortNumber(ingressDeviceString));
+        ConnectPoint ingress = new ConnectPoint(ingressDeviceId, ingressPortNumber);
+
+        DeviceId egressDeviceId = deviceId(getDeviceId(egressDeviceString));
+        PortNumber egressPortNumber = portNumber(getPortNumber(egressDeviceString));
+        ConnectPoint egress = new ConnectPoint(egressDeviceId, egressPortNumber);
+
+        Intent intent = new OpticalConnectivityIntent(appId(), ingress, egress);
+        service.submit(intent);
+    }
+
+    /**
+     * Extracts the port number portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return port number as a string, empty string if the port is not found
+     */
+    private String getPortNumber(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(slash + 1, deviceString.length());
+    }
+
+    /**
+     * Extracts the device ID portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return device ID string
+     */
+    private String getDeviceId(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(0, slash);
+    }
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index b01d412..610646b 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -119,6 +119,14 @@
             </optional-completers>
         </command>
         <command>
+            <action class="org.onlab.onos.cli.net.AddOpticalIntentCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+                <null/>
+            </completers>
+        </command>
+        <command>
             <action class="org.onlab.onos.cli.net.GetStatistics"/>
                 <completers>
                     <ref component-id="connectPointCompleter"/>
diff --git a/core/api/src/main/java/org/onlab/onos/codec/CodecService.java b/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
new file mode 100644
index 0000000..c6dbda2
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/codec/CodecService.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.codec;
+
+import java.util.Set;
+
+/**
+ * Service for registering and retrieving JSON codecs for various entities.
+ */
+public interface CodecService {
+
+    /**
+     * Returns the set of classes with currently registered codecs.
+     *
+     * @return set of entity classes
+     */
+    Set<Class<?>> getCodecs();
+
+    /**
+     * Returns the JSON codec for the specified entity class.
+     *
+     * @param entityClass entity class
+     * @return JSON codec; null if no codec available for the class
+     */
+    JsonCodec getCodec(Class<?> entityClass);
+
+    /**
+     * Registers the specified JSON codec for the given entity class.
+     *
+     * @param entityClass entity class
+     * @param codec       JSON codec
+     */
+    void registerCodec(Class<?> entityClass, JsonCodec codec);
+
+    /**
+     * Unregisters the JSON codec for the specified entity class.
+     *
+     * @param entityClass entity class
+     */
+    void unregisterCodec(Class<?> entityClass);
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java b/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
new file mode 100644
index 0000000..4338cde
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/codec/JsonCodec.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstraction of a codec capable for encoding/decoding arbitrary objects to/from JSON.
+ */
+public abstract class JsonCodec<T> {
+
+    /**
+     * Encodes the specified entity into JSON.
+     *
+     * @param entity entity to encode
+     * @param mapper object mapper
+     * @return JSON node
+     * @throws java.lang.UnsupportedOperationException if the codec does not
+     *                                                 support encode operations
+     */
+    public abstract ObjectNode encode(T entity, ObjectMapper mapper);
+
+    /**
+     * Decodes the specified entity from JSON.
+     *
+     * @param json JSON to decode
+     * @return decoded entity
+     * @throws java.lang.UnsupportedOperationException if the codec does not
+     *                                                 support decode operations
+     */
+    public abstract T decode(ObjectNode json);
+
+    /**
+     * Encodes the collection of the specified entities.
+     *
+     * @param entities collection of entities to encode
+     * @param mapper   object mapper
+     * @return JSON array
+     * @throws java.lang.UnsupportedOperationException if the codec does not
+     *                                                 support encode operations
+     */
+    public ArrayNode encode(Iterable<T> entities, ObjectMapper mapper) {
+        ArrayNode result = mapper.createArrayNode();
+        for (T entity : entities) {
+            result.add(encode(entity, mapper));
+        }
+        return result;
+    }
+
+    /**
+     * Decodes the specified JSON array into a collection of entities.
+     *
+     * @param json JSON array to decode
+     * @return collection of decoded entities
+     * @throws java.lang.UnsupportedOperationException if the codec does not
+     *                                                 support decode operations
+     */
+    public List<T> decode(ArrayNode json) {
+        List<T> result = new ArrayList<>();
+        for (JsonNode node : json) {
+            result.add(decode((ObjectNode) node));
+        }
+        return result;
+    }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/codec/package-info.java b/core/api/src/main/java/org/onlab/onos/codec/package-info.java
new file mode 100644
index 0000000..33d6d8c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/codec/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Base JSON codec abstraction and a service for tracking various JSON codecs.
+ */
+package org.onlab.onos.codec;
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java
index 6352b53..d2db96b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java
@@ -18,7 +18,7 @@
  */
 package org.onlab.onos.net.flow;
 
-import java.util.List;
+import java.util.Set;
 
 /**
  * Interface capturing the result of a batch operation.
@@ -33,9 +33,9 @@
     boolean isSuccess();
 
     /**
-     * Obtains a list of items which failed.
-     * @return a list of failures
+     * Obtains a set of items which failed.
+     * @return a set of failures
      */
-    List<T> failedItems();
+    Set<T> failedItems();
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
index 841e948..363831c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
@@ -18,19 +18,19 @@
  */
 package org.onlab.onos.net.flow;
 
-import java.util.List;
+import java.util.Set;
 
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 public class CompletedBatchOperation implements BatchOperationResult<FlowEntry> {
 
 
     private final boolean success;
-    private final List<FlowEntry> failures;
+    private final Set<FlowEntry> failures;
 
-    public CompletedBatchOperation(boolean success, List<FlowEntry> failures) {
+    public CompletedBatchOperation(boolean success, Set<FlowEntry> failures) {
         this.success = success;
-        this.failures = ImmutableList.copyOf(failures);
+        this.failures = ImmutableSet.copyOf(failures);
     }
 
     @Override
@@ -39,7 +39,7 @@
     }
 
     @Override
-    public List<FlowEntry> failedItems() {
+    public Set<FlowEntry> failedItems() {
         return failures;
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
index abb29a6..fa8a64c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
@@ -55,6 +55,16 @@
     }
 
     @Override
+    public Criterion getCriterion(Criterion.Type type) {
+        for (Criterion c : criteria) {
+            if (c.type() == type) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    @Override
     public int hashCode() {
         return Objects.hash(criteria);
     }
@@ -176,6 +186,17 @@
         }
 
         @Override
+        public Builder matchLambda(Short lambda) {
+            return add(Criteria.matchLambda(lambda));
+        }
+
+        @Override
+        public Builder matchOpticalSignalType(Byte signalType) {
+            return add(Criteria.matchOpticalSignalType(signalType));
+
+        }
+
+        @Override
         public TrafficSelector build() {
             return new DefaultTrafficSelector(ImmutableSet.copyOf(selector.values()));
         }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
index b4d8c3e..0300079 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
@@ -137,6 +137,7 @@
                 case OUTPUT:
                     outputs.add(instruction);
                     break;
+                case L0MODIFICATION:
                 case L2MODIFICATION:
                 case L3MODIFICATION:
                     // TODO: enforce modification order if any
@@ -193,6 +194,11 @@
         }
 
         @Override
+        public Builder setLambda(short lambda) {
+            return add(Instructions.modL0Lambda(lambda));
+        }
+
+        @Override
         public TrafficTreatment build() {
 
             //If we are dropping should we just return an emptry list?
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
new file mode 100644
index 0000000..d0d1820
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
@@ -0,0 +1,67 @@
+package org.onlab.onos.net.flow;
+
+import org.onlab.onos.event.AbstractEvent;
+
+/**
+ * Describes flow rule batch event.
+ */
+public final class FlowRuleBatchEvent extends AbstractEvent<FlowRuleBatchEvent.Type, FlowRuleBatchRequest> {
+
+    /**
+     * Type of flow rule events.
+     */
+    public enum Type {
+
+        /**
+         * Signifies that a batch operation has been initiated.
+         */
+        BATCH_OPERATION_REQUESTED,
+
+        /**
+         * Signifies that a batch operation has completed.
+         */
+        BATCH_OPERATION_COMPLETED,
+    }
+
+    private final CompletedBatchOperation result;
+
+    /**
+     * Constructs a new FlowRuleBatchEvent.
+     * @param request batch operation request.
+     * @return event.
+     */
+    public static FlowRuleBatchEvent requested(FlowRuleBatchRequest request) {
+        FlowRuleBatchEvent event = new FlowRuleBatchEvent(Type.BATCH_OPERATION_REQUESTED, request, null);
+        return event;
+    }
+
+    /**
+     * Constructs a new FlowRuleBatchEvent.
+     * @param request batch operation request.
+     * @param result completed batch operation result.
+     * @return event.
+     */
+    public static FlowRuleBatchEvent completed(FlowRuleBatchRequest request, CompletedBatchOperation result) {
+        FlowRuleBatchEvent event = new FlowRuleBatchEvent(Type.BATCH_OPERATION_COMPLETED, request, result);
+        return event;
+    }
+
+    /**
+     * Returns the result of this batch operation.
+     * @return batch operation result.
+     */
+    public CompletedBatchOperation result() {
+        return result;
+    }
+
+    /**
+     * Creates an event of a given type and for the specified flow rule batch.
+     *
+     * @param type    flow rule batch event type
+     * @param batch    event flow rule batch subject
+     */
+    private FlowRuleBatchEvent(Type type, FlowRuleBatchRequest request, CompletedBatchOperation result) {
+        super(type, request);
+        this.result = result;
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
new file mode 100644
index 0000000..34e3d31
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
@@ -0,0 +1,44 @@
+package org.onlab.onos.net.flow;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+
+import com.google.common.collect.Lists;
+
+public class FlowRuleBatchRequest {
+
+    private final int batchId;
+    private final List<FlowEntry> toAdd;
+    private final List<FlowEntry> toRemove;
+
+    public FlowRuleBatchRequest(int batchId, List<FlowEntry> toAdd, List<FlowEntry> toRemove) {
+        this.batchId = batchId;
+        this.toAdd = Collections.unmodifiableList(toAdd);
+        this.toRemove = Collections.unmodifiableList(toRemove);
+    }
+
+    public List<FlowEntry> toAdd() {
+        return toAdd;
+    }
+
+    public List<FlowEntry> toRemove() {
+        return toRemove;
+    }
+
+    public FlowRuleBatchOperation asBatchOperation() {
+        List<FlowRuleBatchEntry> entries = Lists.newArrayList();
+        for (FlowEntry e : toAdd) {
+            entries.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, e));
+        }
+        for (FlowEntry e : toRemove) {
+            entries.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, e));
+        }
+        return new FlowRuleBatchOperation(entries);
+    }
+
+    public int batchId() {
+        return batchId;
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
index 0b2c3d8..ae74ac5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
@@ -18,11 +18,11 @@
  */
 package org.onlab.onos.net.flow;
 
-import java.util.concurrent.Future;
-
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.provider.Provider;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 /**
  * Abstraction of a flow rule provider.
  */
@@ -60,6 +60,6 @@
      * @param batch a batch of flow rules
      * @return a future indicating the status of this execution
      */
-    Future<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch);
+    ListenableFuture<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch);
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
index 63b7f77..11bd4ad 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
@@ -18,6 +18,8 @@
  */
 package org.onlab.onos.net.flow;
 
+import java.util.concurrent.Future;
+
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.store.Store;
@@ -25,7 +27,7 @@
 /**
  * Manages inventory of flow rules; not intended for direct use.
  */
-public interface FlowRuleStore extends Store<FlowRuleEvent, FlowRuleStoreDelegate> {
+public interface FlowRuleStore extends Store<FlowRuleBatchEvent, FlowRuleStoreDelegate> {
 
     /**
      * Returns the number of flow rule in the store.
@@ -59,12 +61,26 @@
     Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId);
 
     /**
+     // TODO: Better description of method behavior.
      * Stores a new flow rule without generating events.
      *
      * @param rule the flow rule to add
-     * @return true if the rule should be handled locally
      */
-    boolean storeFlowRule(FlowRule rule);
+    void storeFlowRule(FlowRule rule);
+
+    /**
+     * Stores a batch of flow rules.
+     * @param batchOperation batch of flow rules.
+     * @return Future response indicating success/failure of the batch operation
+     *     all the way down to the device.
+     */
+    Future<CompletedBatchOperation> storeBatch(FlowRuleBatchOperation batchOperation);
+
+    /**
+     * Invoked on the completion of a storeBatch operation.
+     * @param result
+     */
+    void batchOperationComplete(FlowRuleBatchEvent event);
 
     /**
      * Marks a flow rule for deletion. Actual deletion will occur
@@ -73,7 +89,7 @@
      * @param rule the flow rule to delete
      * @return true if the rule should be handled locally
      */
-    boolean deleteFlowRule(FlowRule rule);
+    void deleteFlowRule(FlowRule rule);
 
     /**
      * Stores a new flow rule, or updates an existing entry.
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java
index 4e5ebf6..fbd6b55 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java
@@ -23,5 +23,5 @@
 /**
  * Flow rule store delegate abstraction.
  */
-public interface FlowRuleStoreDelegate extends StoreDelegate<FlowRuleEvent> {
+public interface FlowRuleStoreDelegate extends StoreDelegate<FlowRuleBatchEvent> {
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
index b4d566c..bcaf70c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
@@ -39,6 +39,15 @@
     Set<Criterion> criteria();
 
     /**
+     * Returns the selection criterion for a particular type, if it exists in
+     * this traffic selector.
+     *
+     * @param type criterion type to look up
+     * @return the criterion of the specified type if one exists, otherwise null
+     */
+    Criterion getCriterion(Criterion.Type type);
+
+    /**
      * Builder of traffic selector entities.
      */
     public interface Builder {
@@ -130,6 +139,20 @@
         public Builder matchTcpDst(Short tcpPort);
 
         /**
+         * Matches an optical signal ID or lambda.
+         * @param lambda
+         * @return a selection builder
+         */
+        public Builder matchLambda(Short lambda);
+
+        /**
+         * Matches an optical Signal Type.
+         * @param signalType
+         * @return a selection builder
+         */
+        public Builder matchOpticalSignalType(Byte signalType);
+
+        /**
          * Builds an immutable traffic selector.
          *
          * @return traffic selector
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java
index a576138..9b135ba 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java
@@ -105,6 +105,13 @@
         public Builder setIpDst(IpPrefix addr);
 
         /**
+         * Sets the optical channel ID or lambda.
+         * @param lambda optical channel ID
+         * @return a treatment builder
+         */
+        public Builder setLambda(short lambda);
+
+        /**
          * Builds an immutable traffic treatment descriptor.
          *
          * @return traffic treatment
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
index fb5fb97..9d8ab7b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
@@ -151,10 +151,30 @@
         return new TcpPortCriterion(tcpPort, Type.TCP_DST);
     }
 
-    /*
+    /**
+     * Creates a match on lambda field using the specified value.
+     *
+     * @param lambda
+     * @return match criterion
+     */
+    public static Criterion matchLambda(Short lambda) {
+        return new LambdaCriterion(lambda, Type.OCH_SIGID);
+    }
+
+    /**
+     * Creates a match on lambda field using the specified value.
+     *
+     * @param lambda
+     * @return match criterion
+     */
+    public static Criterion matchOpticalSignalType(Byte lambda) {
+        return new OpticalSignalTypeCriterion(lambda, Type.OCH_SIGTYPE);
+    }
+
+
+    /**
      * Implementations of criteria.
      */
-
     public static final class PortCriterion implements Criterion {
         private final PortNumber port;
 
@@ -523,4 +543,93 @@
             return false;
         }
     }
+
+    public static final class LambdaCriterion implements Criterion {
+
+        private final short lambda;
+        private final Type type;
+
+        public LambdaCriterion(short lambda, Type type) {
+            this.lambda = lambda;
+            this.type = type;
+        }
+
+        @Override
+        public Type type() {
+            return this.type;
+        }
+
+        public Short lambda() {
+            return this.lambda;
+        }
+
+        @Override
+        public String toString() {
+            return toStringHelper(type().toString())
+                    .add("lambda", lambda).toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(lambda, type);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof LambdaCriterion) {
+                LambdaCriterion that = (LambdaCriterion) obj;
+                return Objects.equals(lambda, that.lambda) &&
+                        Objects.equals(type, that.type);
+            }
+            return false;
+        }
+    }
+
+    public static final class OpticalSignalTypeCriterion implements Criterion {
+
+        private final byte signalType;
+        private final Type type;
+
+        public OpticalSignalTypeCriterion(byte signalType, Type type) {
+            this.signalType = signalType;
+            this.type = type;
+        }
+
+        @Override
+        public Type type() {
+            return this.type;
+        }
+
+        public Byte signalType() {
+            return this.signalType;
+        }
+
+        @Override
+        public String toString() {
+            return toStringHelper(type().toString())
+                    .add("signalType", signalType).toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(signalType, type);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof OpticalSignalTypeCriterion) {
+                OpticalSignalTypeCriterion that = (OpticalSignalTypeCriterion) obj;
+                return Objects.equals(signalType, that.signalType) &&
+                        Objects.equals(type, that.type);
+            }
+            return false;
+        }
+    }
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java
index 5337852..6110892 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java
@@ -108,7 +108,11 @@
         /** Logical Port Metadata. */
         TUNNEL_ID,
         /** IPv6 Extension Header pseudo-field. */
-        IPV6_EXTHDR
+        IPV6_EXTHDR,
+        /** Optical channel signal ID (lambda). */
+        OCH_SIGID,
+        /** Optical channel signal type (fixed or flexible). */
+        OCH_SIGTYPE
     }
 
     /**
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java
index 084ffe4..9b578b6 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java
@@ -43,6 +43,11 @@
         GROUP,
 
         /**
+         * Signifies that the traffic should be modified in L0 way.
+         */
+        L0MODIFICATION,
+
+        /**
          * Signifies that the traffic should be modified in L2 way.
          */
         L2MODIFICATION,
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
index 988c52f..b18d7ef 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
@@ -24,6 +24,8 @@
 import java.util.Objects;
 
 import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction.L0SubType;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction.ModLambdaInstruction;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.L2SubType;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
 import org.onlab.onos.net.flow.instructions.L3ModificationInstruction.L3SubType;
@@ -62,6 +64,16 @@
     }
 
     /**
+     * Creates a l0 modification.
+     * @param lambda the lambda to modify to.
+     * @return a l0 modification
+     */
+    public static L0ModificationInstruction modL0Lambda(short lambda) {
+        checkNotNull(lambda, "L0 lambda cannot be null");
+        return new ModLambdaInstruction(L0SubType.LAMBDA, lambda);
+    }
+
+    /**
      * Creates a l2 src modification.
      * @param addr the mac address to modify to.
      * @return a l2 modification
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L0ModificationInstruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L0ModificationInstruction.java
new file mode 100644
index 0000000..23e5f2a
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L0ModificationInstruction.java
@@ -0,0 +1,75 @@
+package org.onlab.onos.net.flow.instructions;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+import java.util.Objects;
+
+public abstract class L0ModificationInstruction implements Instruction {
+
+    /**
+     * Represents the type of traffic treatment.
+     */
+    public enum L0SubType {
+        /**
+         * Lambda modification.
+         */
+        LAMBDA
+
+        //TODO: remaining types
+    }
+
+    public abstract L0SubType subtype();
+
+    @Override
+    public Type type() {
+        return Type.L0MODIFICATION;
+    }
+
+    /**
+     * Represents a L0 lambda modification instruction.
+     */
+    public static final class ModLambdaInstruction extends L0ModificationInstruction {
+
+        private final L0SubType subtype;
+        private final short lambda;
+
+        public ModLambdaInstruction(L0SubType subType, short lambda) {
+            this.subtype = subType;
+            this.lambda = lambda;
+        }
+
+        @Override
+        public L0SubType subtype() {
+            return this.subtype;
+        }
+
+        public short lambda() {
+            return this.lambda;
+        }
+
+        @Override
+        public String toString() {
+            return toStringHelper(subtype().toString())
+                    .add("lambda", lambda).toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(lambda, type(), subtype);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof ModLambdaInstruction) {
+                ModLambdaInstruction that = (ModLambdaInstruction) obj;
+                return  Objects.equals(lambda, that.lambda) &&
+                        Objects.equals(this.type(), that.type()) &&
+                        Objects.equals(subtype, that.subtype);
+            }
+            return false;
+        }
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
new file mode 100644
index 0000000..6e595c5
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
@@ -0,0 +1,55 @@
+package org.onlab.onos.net.intent;
+
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.ConnectPoint;
+
+/**
+ * An optical layer Intent for a connectivity from one Transponder port to another
+ * Transponder port. No trafficSelector as well as trafficTreament are needed.
+ *
+ */
+public class OpticalConnectivityIntent extends Intent {
+    protected ConnectPoint src;
+    protected ConnectPoint dst;
+
+    /**
+     * Constructor.
+     *
+     * @param id  ID for this new Intent object.
+     * @param src The source transponder port.
+     * @param dst The destination transponder port.
+     */
+    public OpticalConnectivityIntent(ApplicationId appId, ConnectPoint src, ConnectPoint dst) {
+        super(id(OpticalConnectivityIntent.class, src, dst),
+                appId, null);
+        this.src = src;
+        this.dst = dst;
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected OpticalConnectivityIntent() {
+        super();
+        this.src = null;
+        this.dst = null;
+    }
+
+    /**
+     * Gets source transponder port.
+     *
+     * @return The source transponder port.
+     */
+    public ConnectPoint getSrcConnectPoint() {
+        return src;
+    }
+
+    /**
+     * Gets destination transponder port.
+     *
+     * @return The source transponder port.
+     */
+    public ConnectPoint getDst() {
+        return dst;
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/OpticalPathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalPathIntent.java
new file mode 100644
index 0000000..edf3d5c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalPathIntent.java
@@ -0,0 +1,69 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Collection;
+
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.NetworkResource;
+import org.onlab.onos.net.Path;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+public class OpticalPathIntent extends Intent {
+
+    private final ConnectPoint src;
+    private final ConnectPoint dst;
+    private final Path path;
+
+
+    public OpticalPathIntent(ApplicationId appId,
+            ConnectPoint src,
+            ConnectPoint dst,
+            Path path) {
+        super(id(OpticalPathIntent.class, src, dst),
+              appId,
+              ImmutableSet.<NetworkResource>copyOf(path.links()));
+        this.src = src;
+        this.dst = dst;
+        this.path = path;
+    }
+
+    protected OpticalPathIntent() {
+        this.src = null;
+        this.dst = null;
+        this.path = null;
+    }
+
+    public ConnectPoint src() {
+        return src;
+    }
+
+    public ConnectPoint dst() {
+        return dst;
+    }
+
+    public Path path() {
+        return path;
+    }
+
+    @Override
+    public boolean isInstallable() {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("id", id())
+                .add("ingressPort", src)
+                .add("egressPort", dst)
+                .add("path", path)
+                .toString();
+    }
+
+    public Collection<Link> requiredLinks() {
+        return path.links();
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java
index b542533..5995d5f 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java
@@ -3,6 +3,21 @@
 /**
  * Representation of allocated bandwidth resource.
  */
-public interface BandwidthResourceAllocation extends BandwidthResourceRequest {
+public class BandwidthResourceAllocation extends BandwidthResourceRequest
+        implements ResourceAllocation {
 
+    @Override
+    public ResourceType type() {
+        return ResourceType.BANDWIDTH;
+    }
+
+    /**
+     * Creates a new {@link BandwidthResourceAllocation} with {@link Bandwidth}
+     * object.
+     *
+     * @param bandwidth allocated bandwidth
+     */
+    public BandwidthResourceAllocation(Bandwidth bandwidth) {
+        super(bandwidth);
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java
index 2ea24b8..5743d8e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java
@@ -3,11 +3,39 @@
 /**
  * Representation of a request for bandwidth resource.
  */
-public interface BandwidthResourceRequest {
+public class BandwidthResourceRequest implements ResourceRequest {
+    private final Bandwidth bandwidth;
+
+    /**
+     * Creates a new {@link BandwidthResourceRequest} with {@link Bandwidth}
+     * object.
+     *
+     * @param bandwidth {@link Bandwidth} object to be requested
+     */
+    public BandwidthResourceRequest(Bandwidth bandwidth) {
+        this.bandwidth = bandwidth;
+    }
+
+    /**
+     * Creates a new {@link BandwidthResourceRequest} with bandwidth value.
+     *
+     * @param bandwidth bandwidth value to be requested
+     */
+    public BandwidthResourceRequest(double bandwidth) {
+        this.bandwidth = Bandwidth.valueOf(bandwidth);
+    }
+
     /**
      * Returns the bandwidth resource.
      *
      * @return the bandwidth resource
      */
-    Bandwidth bandwidth();
+    public Bandwidth bandwidth() {
+        return bandwidth;
+    }
+
+    @Override
+    public ResourceType type() {
+        return ResourceType.BANDWIDTH;
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/DefaultLinkResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/DefaultLinkResourceRequest.java
new file mode 100644
index 0000000..b33e203
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/DefaultLinkResourceRequest.java
@@ -0,0 +1,124 @@
+package org.onlab.onos.net.resource;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Implementation of {@link LinkResourceRequest}.
+ */
+public final class DefaultLinkResourceRequest implements LinkResourceRequest {
+
+    private final IntentId intentId;
+    private final Collection<Link> links;
+    private final Set<ResourceRequest> resources;
+
+    /**
+     * Creates a new link resource request with the given ID, links, and
+     * resource requests.
+     *
+     * @param intentId intent ID related to this request
+     * @param links a set of links for the request
+     * @param resources a set of resources to be requested
+     */
+    private DefaultLinkResourceRequest(IntentId intentId,
+            Collection<Link> links,
+            Set<ResourceRequest> resources) {
+        this.intentId = intentId;
+        this.links = ImmutableSet.copyOf(links);
+        this.resources = ImmutableSet.copyOf(resources);
+    }
+
+
+    @Override
+    public ResourceType type() {
+        return null;
+    }
+
+    @Override
+    public IntentId intendId() {
+        return intentId;
+    }
+
+    @Override
+    public Collection<Link> links() {
+        return links;
+    }
+
+    @Override
+    public Set<ResourceRequest> resources() {
+        return resources;
+    }
+
+    /**
+     * Returns builder of link resource request.
+     *
+     * @param intentId intent ID related to this request
+     * @param links a set of links for the request
+     * @return builder of link resource request
+     */
+    public static LinkResourceRequest.Builder builder(
+            IntentId intentId, Collection<Link> links) {
+        return new Builder(intentId, links);
+    }
+
+    /**
+     * Builder of link resource request.
+     */
+    public static final class Builder implements LinkResourceRequest.Builder {
+        private IntentId intentId;
+        private Collection<Link> links;
+        private Set<ResourceRequest> resources;
+
+        /**
+         * Creates a new link resource request.
+         *
+         * @param intentId intent ID related to this request
+         * @param links a set of links for the request
+         */
+        private Builder(IntentId intentId, Collection<Link> links) {
+            this.intentId = intentId;
+            this.links = links;
+            this.resources = new HashSet<>();
+        }
+
+        /**
+         * Adds lambda request.
+         *
+         * @return self
+         */
+        @Override
+        public Builder addLambdaRequest() {
+            resources.add(new LambdaResourceRequest());
+            return this;
+        }
+
+        /**
+         * Adds bandwidth request with bandwidth value.
+         *
+         * @param bandwidth bandwidth value to be requested
+         * @return self
+         */
+        @Override
+        public Builder addBandwidthRequest(double bandwidth) {
+            resources.add(new BandwidthResourceRequest(bandwidth));
+            return this;
+        }
+
+        /**
+         * Returns link resource request.
+         *
+         * @return link resource request
+         */
+        @Override
+        public LinkResourceRequest build() {
+            return new DefaultLinkResourceRequest(intentId, links, resources);
+        }
+    }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java
index 1ed63b4..9095633 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java
@@ -3,11 +3,31 @@
 /**
  * Representation of allocated lambda resource.
  */
-public interface LambdaResourceAllocation extends LambdaResourceRequest {
+public class LambdaResourceAllocation extends LambdaResourceRequest
+        implements ResourceAllocation {
+    private final Lambda lambda;
+
+    @Override
+    public ResourceType type() {
+        return ResourceType.LAMBDA;
+    }
+
+    /**
+     * Creates a new {@link LambdaResourceAllocation} with {@link Lambda}
+     * object.
+     *
+     * @param lambda allocated lambda
+     */
+    public LambdaResourceAllocation(Lambda lambda) {
+        this.lambda = lambda;
+    }
+
     /**
      * Returns the lambda resource.
      *
      * @return the lambda resource
      */
-    Lambda lambda();
+    public Lambda lambda() {
+        return lambda;
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java
index dfb3b81..05bf61c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java
@@ -3,6 +3,11 @@
 /**
  * Representation of a request for lambda resource.
  */
-public interface LambdaResourceRequest {
+public class LambdaResourceRequest implements ResourceRequest {
+
+    @Override
+    public ResourceType type() {
+        return ResourceType.LAMBDA;
+    }
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceAllocations.java b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceAllocations.java
index a3d3e87..412cecd 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceAllocations.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceAllocations.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.net.resource;
 
+import java.util.Set;
+
 import org.onlab.onos.net.Link;
 
 /**
@@ -12,5 +14,5 @@
      * @param link the target link
      * @return allocated resource for the link
      */
-    ResourceAllocation getResourceAllocation(Link link);
+    Set<ResourceAllocation> getResourceAllocation(Link link);
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceRequest.java
index 3f9bf79..3f32b06 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceRequest.java
@@ -31,4 +31,31 @@
      * @return the set of resource requests
      */
     Set<ResourceRequest> resources();
+
+    /**
+     * Builder of link resource request.
+     */
+    interface Builder {
+         /**
+         * Adds lambda request.
+         *
+         * @return self
+         */
+        public Builder addLambdaRequest();
+
+        /**
+         * Adds bandwidth request with bandwidth value.
+         *
+         * @param bandwidth bandwidth value to be requested
+         * @return self
+         */
+        public Builder addBandwidthRequest(double bandwidth);
+
+        /**
+         * Returns link resource request.
+         *
+         * @return link resource request
+         */
+        public LinkResourceRequest build();
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java
index 1ea5723..5ae5187 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java
@@ -31,6 +31,14 @@
     Iterable<LinkResourceAllocations> getAllocations();
 
     /**
+     * Returns the resources allocated for an Intent.
+     *
+     * @param intentId the target Intent's id
+     * @return allocated resources for Intent
+     */
+    LinkResourceAllocations getAllocations(IntentId intentId);
+
+    /**
      * Returns all allocated resources to given link.
      *
      * @param link a target link
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/ResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/ResourceRequest.java
index bc3d904..06e3666 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/ResourceRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/ResourceRequest.java
@@ -4,5 +4,11 @@
  * Abstraction of resource request.
  */
 public interface ResourceRequest {
+    /**
+     * Returns the resource type.
+     *
+     * @return the resource type
+     */
+    ResourceType type();
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/ResourceType.java b/core/api/src/main/java/org/onlab/onos/net/resource/ResourceType.java
new file mode 100644
index 0000000..06a8174
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/ResourceType.java
@@ -0,0 +1,6 @@
+package org.onlab.onos.net.resource;
+
+public enum ResourceType {
+    LAMBDA,
+    BANDWIDTH,
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/package-info.java b/core/api/src/main/java/org/onlab/onos/net/resource/package-info.java
index 4e07a28..6b402a7 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/package-info.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Services for reserving network resources, e.g.&nbsp;bandwidth, lambdas.
  */
-package org.onlab.onos.net.resource;
\ No newline at end of file
+package org.onlab.onos.net.resource;
diff --git a/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java b/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
new file mode 100644
index 0000000..f3a10e3
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/codec/JsonCodecTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.codec;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the base JSON codec abstraction.
+ */
+public class JsonCodecTest {
+
+    private static class Foo {
+        final String name;
+
+        Foo(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(name);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final Foo other = (Foo) obj;
+            return Objects.equals(this.name, other.name);
+        }
+    }
+
+    private static class FooCodec extends JsonCodec<Foo> {
+        @Override
+        public ObjectNode encode(Foo entity, ObjectMapper mapper) {
+            return mapper.createObjectNode().put("name", entity.name);
+        }
+
+        @Override
+        public Foo decode(ObjectNode json) {
+            return new Foo(json.get("name").asText());
+        }
+    }
+
+    @Test
+    public void encode() {
+        Foo f1 = new Foo("foo");
+        Foo f2 = new Foo("bar");
+        FooCodec codec = new FooCodec();
+        ImmutableList<Foo> entities = ImmutableList.of(f1, f2);
+        ArrayNode json = codec.encode(entities, new ObjectMapper());
+        List<Foo> foos = codec.decode(json);
+        assertEquals("incorrect encode/decode", entities, foos);
+    }
+
+}
\ No newline at end of file
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index d8f89ae..60ab307 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -2,11 +2,15 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
+import static org.onlab.util.Tools.namedThreads;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -30,7 +34,9 @@
 import org.onlab.onos.net.flow.FlowRule;
 import org.onlab.onos.net.flow.FlowRuleBatchEntry;
 import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchEvent;
 import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchRequest;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleListener;
 import org.onlab.onos.net.flow.FlowRuleProvider;
@@ -47,6 +53,9 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
 
 /**
  * Provides implementation of the flow NB &amp; SB APIs.
@@ -67,6 +76,9 @@
 
     private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate();
 
+    private final ExecutorService futureListeners =
+            Executors.newCachedThreadPool(namedThreads("provider-future-listeners"));
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected FlowRuleStore store;
 
@@ -85,6 +97,8 @@
 
     @Deactivate
     public void deactivate() {
+        futureListeners.shutdownNow();
+
         store.unsetDelegate(delegate);
         eventDispatcher.removeSink(FlowRuleEvent.class);
         log.info("Stopped");
@@ -104,14 +118,7 @@
     public void applyFlowRules(FlowRule... flowRules) {
         for (int i = 0; i < flowRules.length; i++) {
             FlowRule f = flowRules[i];
-            boolean local = store.storeFlowRule(f);
-            if (local) {
-                // TODO: aggregate all local rules and push down once?
-                applyFlowRulesToProviders(f);
-                eventDispatcher.post(
-                        new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED, f));
-
-            }
+            store.storeFlowRule(f);
         }
     }
 
@@ -135,13 +142,7 @@
         FlowRule f;
         for (int i = 0; i < flowRules.length; i++) {
             f = flowRules[i];
-            boolean local = store.deleteFlowRule(f);
-            if (local) {
-                // TODO: aggregate all local rules and push down once?
-                removeFlowRulesFromProviders(f);
-                eventDispatcher.post(
-                        new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, f));
-            }
+            store.deleteFlowRule(f);
         }
     }
 
@@ -185,33 +186,21 @@
     @Override
     public Future<CompletedBatchOperation> applyBatch(
             FlowRuleBatchOperation batch) {
-        Multimap<FlowRuleProvider, FlowRuleBatchEntry> batches =
+        Multimap<DeviceId, FlowRuleBatchEntry> perDeviceBatches =
                 ArrayListMultimap.create();
         List<Future<CompletedBatchOperation>> futures = Lists.newArrayList();
         for (FlowRuleBatchEntry fbe : batch.getOperations()) {
             final FlowRule f = fbe.getTarget();
-            final Device device = deviceService.getDevice(f.deviceId());
-            final FlowRuleProvider frp = getProvider(device.providerId());
-            batches.put(frp, fbe);
-            switch (fbe.getOperator()) {
-                case ADD:
-                    store.storeFlowRule(f);
-                    break;
-                case REMOVE:
-                    store.deleteFlowRule(f);
-                    break;
-                case MODIFY:
-                default:
-                    log.error("Batch operation type {} unsupported.", fbe.getOperator());
-            }
+            perDeviceBatches.put(f.deviceId(), fbe);
         }
-        for (FlowRuleProvider provider : batches.keySet()) {
+
+        for (DeviceId deviceId : perDeviceBatches.keySet()) {
             FlowRuleBatchOperation b =
-                    new FlowRuleBatchOperation(batches.get(provider));
-            Future<CompletedBatchOperation> future = provider.executeBatch(b);
+                    new FlowRuleBatchOperation(perDeviceBatches.get(deviceId));
+            Future<CompletedBatchOperation> future = store.storeBatch(b);
             futures.add(future);
         }
-        return new FlowRuleBatchFuture(futures, batches);
+        return new FlowRuleBatchFuture(futures, perDeviceBatches);
     }
 
     @Override
@@ -324,6 +313,7 @@
                     post(event);
                 }
             } else {
+                log.info("Removing flow rules....");
                 removeFlowRules(flowEntry);
             }
 
@@ -391,21 +381,48 @@
 
     // Store delegate to re-post events emitted from the store.
     private class InternalStoreDelegate implements FlowRuleStoreDelegate {
+        // TODO: Right now we only dispatch events at individual flowEntry level.
+        // It may be more efficient for also dispatch events as a batch.
         @Override
-        public void notify(FlowRuleEvent event) {
+        public void notify(FlowRuleBatchEvent event) {
+            final FlowRuleBatchRequest request = event.subject();
             switch (event.type()) {
-            case RULE_ADD_REQUESTED:
-                applyFlowRulesToProviders(event.subject());
-                break;
-            case RULE_REMOVE_REQUESTED:
-                removeFlowRulesFromProviders(event.subject());
-                break;
+            case BATCH_OPERATION_REQUESTED:
+                for (FlowEntry entry : request.toAdd()) {
+                    eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED, entry));
+                }
+                for (FlowEntry entry : request.toRemove()) {
+                    eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, entry));
+                }
+                // FIXME: what about op.equals(FlowRuleOperation.MODIFY) ?
 
-            case RULE_ADDED:
-            case RULE_REMOVED:
-            case RULE_UPDATED:
-                // only dispatch events related to switch
-                eventDispatcher.post(event);
+                FlowRuleBatchOperation batchOperation = request.asBatchOperation();
+
+                FlowRuleProvider flowRuleProvider =
+                        getProvider(batchOperation.getOperations().get(0).getTarget().deviceId());
+                final ListenableFuture<CompletedBatchOperation> result =
+                        flowRuleProvider.executeBatch(batchOperation);
+                result.addListener(new Runnable() {
+                    @Override
+                    public void run() {
+                        store.batchOperationComplete(FlowRuleBatchEvent.completed(request,
+                                                                                  Futures.getUnchecked(result)));
+                    }
+                }, futureListeners);
+
+                break;
+            case BATCH_OPERATION_COMPLETED:
+                Set<FlowEntry> failedItems = event.result().failedItems();
+                for (FlowEntry entry : request.toAdd()) {
+                    if (!failedItems.contains(entry)) {
+                        eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, entry));
+                    }
+                }
+                for (FlowEntry entry : request.toRemove()) {
+                    if (!failedItems.contains(entry)) {
+                            eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVED, entry));
+                    }
+                }
                 break;
             default:
                 break;
@@ -413,18 +430,15 @@
         }
     }
 
-    private class FlowRuleBatchFuture
-        implements Future<CompletedBatchOperation> {
+    private class FlowRuleBatchFuture implements Future<CompletedBatchOperation> {
 
         private final List<Future<CompletedBatchOperation>> futures;
-        private final Multimap<FlowRuleProvider, FlowRuleBatchEntry> batches;
+        private final Multimap<DeviceId, FlowRuleBatchEntry> batches;
         private final AtomicReference<BatchState> state;
         private CompletedBatchOperation overall;
 
-
-
         public FlowRuleBatchFuture(List<Future<CompletedBatchOperation>> futures,
-                Multimap<FlowRuleProvider, FlowRuleBatchEntry> batches) {
+                Multimap<DeviceId, FlowRuleBatchEntry> batches) {
             this.futures = futures;
             this.batches = batches;
             state = new AtomicReference<FlowRuleManager.BatchState>();
@@ -466,7 +480,7 @@
             }
 
             boolean success = true;
-            List<FlowEntry> failed = Lists.newLinkedList();
+            Set<FlowEntry> failed = Sets.newHashSet();
             CompletedBatchOperation completed;
             for (Future<CompletedBatchOperation> future : futures) {
                 completed = future.get();
@@ -486,7 +500,7 @@
                 return overall;
             }
             boolean success = true;
-            List<FlowEntry> failed = Lists.newLinkedList();
+            Set<FlowEntry> failed = Sets.newHashSet();
             CompletedBatchOperation completed;
             long start = System.nanoTime();
             long end = start + unit.toNanos(timeout);
@@ -500,7 +514,7 @@
             return finalizeBatchOperation(success, failed);
         }
 
-        private boolean validateBatchOperation(List<FlowEntry> failed,
+        private boolean validateBatchOperation(Set<FlowEntry> failed,
                 CompletedBatchOperation completed) {
 
             if (isCancelled()) {
@@ -522,7 +536,7 @@
         }
 
         private CompletedBatchOperation finalizeBatchOperation(boolean success,
-                List<FlowEntry> failed) {
+                Set<FlowEntry> failed) {
             synchronized (this) {
                 if (!state.compareAndSet(BatchState.STARTED, BatchState.FINISHED)) {
                     if (state.get() == BatchState.FINISHED) {
@@ -545,11 +559,6 @@
                     store.storeFlowRule(fbe.getTarget());
                 }
             }
-
         }
     }
-
-
-
-
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java
new file mode 100644
index 0000000..58586dc
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java
@@ -0,0 +1,140 @@
+package org.onlab.onos.net.intent.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.OpticalConnectivityIntent;
+import org.onlab.onos.net.intent.OpticalPathIntent;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.topology.LinkWeight;
+import org.onlab.onos.net.topology.PathService;
+import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyEdge;
+import org.onlab.onos.net.topology.TopologyService;
+import org.slf4j.Logger;
+
+/**
+ * Optical compiler for OpticalConnectivityIntent.
+ * It firstly computes K-shortest paths in the optical-layer, then choose the optimal one to assign a wavelength.
+ * Finally, it generates one or more opticalPathintent(s) with opticalMatchs and opticalActions.
+ */
+@Component(immediate = true)
+public class OpticalConnectivityIntentCompiler implements IntentCompiler<OpticalConnectivityIntent> {
+
+    private final Logger log = getLogger(getClass());
+    private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkResourceService resourceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Activate
+    public void activate() {
+        intentManager.registerCompiler(OpticalConnectivityIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(OpticalConnectivityIntent.class);
+    }
+
+    @Override
+    public List<Intent> compile(OpticalConnectivityIntent intent) {
+        // TODO: compute multiple paths using the K-shortest path algorithm
+        List<Intent> retList = new ArrayList<>();
+        Path path = calculatePath(intent.getSrcConnectPoint(), intent.getDst());
+        if (path == null) {
+            return retList;
+        } else {
+            log.info("the computed lightpath is : {}.", path.toString());
+        }
+
+        List<Link> links = new ArrayList<>();
+        // links.add(DefaultEdgeLink.createEdgeLink(intent.getSrcConnectPoint(), true));
+        links.addAll(path.links());
+        //links.add(DefaultEdgeLink.createEdgeLink(intent.getDst(), false));
+
+        // create a new opticalPathIntent
+        Intent newIntent = new OpticalPathIntent(intent.appId(),
+                intent.getSrcConnectPoint(),
+                intent.getDst(),
+                path);
+
+        retList.add(newIntent);
+
+        return retList;
+    }
+
+    private Path calculatePath(ConnectPoint start, ConnectPoint end) {
+        // TODO: support user policies
+        Topology topology = topologyService.currentTopology();
+        LinkWeight weight = new LinkWeight() {
+            @Override
+            public double weight(TopologyEdge edge) {
+                boolean isOptical = false;
+
+                Link.Type lt = edge.link().type();
+
+                //String t = edge.link().annotations().value("linkType");
+                if (lt == Link.Type.OPTICAL) {
+                   isOptical = true;
+                }
+                if (isOptical) {
+                    return 1; // optical links
+                } else {
+                    return 10000; // packet links
+                }
+            }
+        };
+
+        Set<Path> paths = topologyService.getPaths(topology,
+                start.deviceId(),
+                end.deviceId(),
+                weight);
+
+        Iterator<Path> itr = paths.iterator();
+        while (itr.hasNext()) {
+            Path path = itr.next();
+            if (path.cost() >= 10000) {
+                itr.remove();
+            }
+        }
+
+        if (paths.isEmpty()) {
+            log.info("No optical path found from " + start + " to " + end);
+            return null;
+        } else {
+            return paths.iterator().next();
+        }
+
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
new file mode 100644
index 0000000..64ac3ea
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
@@ -0,0 +1,245 @@
+package org.onlab.onos.net.intent.impl;
+
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.List;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentInstaller;
+import org.onlab.onos.net.intent.OpticalPathIntent;
+import org.onlab.onos.net.resource.DefaultLinkResourceRequest;
+import org.onlab.onos.net.resource.Lambda;
+import org.onlab.onos.net.resource.LambdaResourceAllocation;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.net.resource.LinkResourceRequest;
+import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.resource.ResourceAllocation;
+import org.onlab.onos.net.resource.ResourceType;
+import org.onlab.onos.net.topology.TopologyService;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Lists;
+
+/**
+ * OpticaliIntentInstaller for optical path intents.
+ * It essentially generates optical FlowRules and
+ * call the flowRule service to execute them.
+ */
+
+@Component(immediate = true)
+public class OpticalPathIntentInstaller implements IntentInstaller<OpticalPathIntent> {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkResourceService resourceService;
+
+    private ApplicationId appId;
+
+    //final short WAVELENGTH = 80;
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onlab.onos.net.intent");
+        intentManager.registerInstaller(OpticalPathIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterInstaller(OpticalPathIntent.class);
+    }
+
+    @Override
+    public List<FlowRuleBatchOperation> install(OpticalPathIntent intent) {
+        LinkResourceAllocations allocations = assignWavelength(intent);
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        selectorBuilder.matchInport(intent.src().port());
+
+        List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+        ConnectPoint prev = intent.src();
+
+        //TODO throw exception if the lambda was not assigned successfully
+        for (Link link : intent.path().links()) {
+            Lambda la = null;
+            for (ResourceAllocation allocation : allocations.getResourceAllocation(link)) {
+                if (allocation.type() == ResourceType.LAMBDA) {
+                    la = ((LambdaResourceAllocation) allocation).lambda();
+                    break;
+                }
+            }
+
+            if (la == null) {
+                log.info("Lambda was not assigned successfully");
+                return null;
+            }
+
+            TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+            treatmentBuilder.setOutput(link.src().port());
+            treatmentBuilder.setLambda((short) la.toInt());
+
+            FlowRule rule = new DefaultFlowRule(prev.deviceId(),
+                    selectorBuilder.build(),
+                    treatmentBuilder.build(),
+                    100,
+                    appId,
+                    100,
+                    true);
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
+
+            prev = link.dst();
+            selectorBuilder.matchInport(link.dst().port());
+            selectorBuilder.matchLambda((short) la.toInt());
+        }
+
+        // build the last T port rule
+        TrafficTreatment treatmentLast = builder()
+                .setOutput(intent.dst().port()).build();
+        FlowRule rule = new DefaultFlowRule(intent.dst().deviceId(),
+                selectorBuilder.build(),
+                treatmentLast,
+                100,
+                appId,
+                100,
+                true);
+        rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
+
+        return Lists.newArrayList(new FlowRuleBatchOperation(rules));
+    }
+
+    private LinkResourceAllocations assignWavelength(OpticalPathIntent intent) {
+        LinkResourceRequest.Builder request = DefaultLinkResourceRequest.builder(intent.id(),
+                intent.path().links())
+                .addLambdaRequest();
+        LinkResourceAllocations retLambda = resourceService.requestResources(request.build());
+        return retLambda;
+    }
+
+    /*private Lambda assignWavelength(List<Link> links) {
+        // TODO More wavelength assignment algorithm
+        int wavenum = 0;
+        Iterator<Link> itrlink = links.iterator();
+        for (int i = 1; i <= WAVELENGTH; i++) {
+            wavenum = i;
+            boolean found = true;
+            while (itrlink.hasNext()) {
+                Link link = itrlink.next();
+                if (isWavelengthUsed(link, i)) {
+                    found = false;
+                    break;
+                }
+            }
+            // First-Fit wavelength assignment algorithm
+            if (found) {
+                break;
+            }
+        }
+
+        if (wavenum == 0) {
+            return null;
+        }
+
+        Lambda wave = Lambda.valueOf(wavenum);
+        return wave;
+    }
+
+    private boolean isWavelengthUsed(Link link, int i) {
+        Iterable<LinkResourceAllocations> wave = resourceService.getAllocations(link);
+        for (LinkResourceAllocations ir : wave) {
+            //if ir.resources().contains(i) {
+            //}
+        }
+        return false;
+    }*/
+
+    @Override
+    public List<FlowRuleBatchOperation> uninstall(OpticalPathIntent intent) {
+        LinkResourceAllocations allocations = resourceService.getAllocations(intent.id());
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        selectorBuilder.matchInport(intent.src().port());
+
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+
+        List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+        ConnectPoint prev = intent.src();
+
+        //TODO throw exception if the lambda was not retrieved successfully
+        for (Link link : intent.path().links()) {
+            Lambda la = null;
+            for (ResourceAllocation allocation : allocations.getResourceAllocation(link)) {
+                if (allocation.type() == ResourceType.LAMBDA) {
+                    la = ((LambdaResourceAllocation) allocation).lambda();
+                    break;
+                }
+            }
+
+            if (la == null) {
+                log.info("Lambda was not retrieved successfully");
+                return null;
+            }
+
+            treatmentBuilder.setOutput(link.src().port());
+            treatmentBuilder.setLambda((short) la.toInt());
+
+            FlowRule rule = new DefaultFlowRule(prev.deviceId(),
+                    selectorBuilder.build(),
+                    treatmentBuilder.build(),
+                    100,
+                    appId,
+                    100,
+                    true);
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule));
+
+            prev = link.dst();
+            selectorBuilder.matchInport(link.dst().port());
+            selectorBuilder.matchLambda((short) la.toInt());
+        }
+
+        // build the last T port rule
+        TrafficTreatment treatmentLast = builder()
+                .setOutput(intent.dst().port()).build();
+        FlowRule rule = new DefaultFlowRule(intent.dst().deviceId(),
+                selectorBuilder.build(),
+                treatmentLast,
+                100,
+                appId,
+                100,
+                true);
+        rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule));
+
+        return Lists.newArrayList(new FlowRuleBatchOperation(rules));
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java b/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java
new file mode 100644
index 0000000..0330821
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java
@@ -0,0 +1,64 @@
+package org.onlab.onos.net.resource.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.net.resource.LinkResourceRequest;
+import org.onlab.onos.net.resource.ResourceAllocation;
+import org.onlab.onos.net.resource.ResourceRequest;
+import org.onlab.onos.net.resource.ResourceType;
+
+/**
+ * Implementation of {@link LinkResourceAllocations}.
+ */
+public class DefaultLinkResourceAllocations implements LinkResourceAllocations {
+    private final LinkResourceRequest request;
+    private final Map<Link, Set<ResourceAllocation>> allocations;
+
+    /**
+     * Creates a new link resource allocations.
+     *
+     * @param request requested resources
+     * @param allocations allocated resources
+     */
+    protected DefaultLinkResourceAllocations(LinkResourceRequest request,
+            Map<Link, Set<ResourceAllocation>> allocations) {
+        this.request = request;
+        this.allocations = allocations;
+    }
+
+    @Override
+    public IntentId intendId() {
+        return request.intendId();
+    }
+
+    @Override
+    public Collection<Link> links() {
+        return request.links();
+    }
+
+    @Override
+    public Set<ResourceRequest> resources() {
+        return request.resources();
+    }
+
+    @Override
+    public ResourceType type() {
+        return null;
+    }
+
+    @Override
+    public Set<ResourceAllocation> getResourceAllocation(Link link) {
+        Set<ResourceAllocation> result = allocations.get(link);
+        if (result == null) {
+            result = Collections.emptySet();
+        }
+        return result;
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java b/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
new file mode 100644
index 0000000..ad15f56
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
@@ -0,0 +1,111 @@
+package org.onlab.onos.net.resource.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.resource.BandwidthResourceAllocation;
+import org.onlab.onos.net.resource.BandwidthResourceRequest;
+import org.onlab.onos.net.resource.Lambda;
+import org.onlab.onos.net.resource.LambdaResourceAllocation;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.net.resource.LinkResourceRequest;
+import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.resource.ResourceAllocation;
+import org.onlab.onos.net.resource.ResourceRequest;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Provides basic implementation of link resources allocation.
+ */
+@Component(immediate = true)
+@Service
+public class LinkResourceManager implements LinkResourceService {
+
+    private final Logger log = getLogger(getClass());
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public LinkResourceAllocations requestResources(LinkResourceRequest req) {
+        // TODO implement it using a resource data store.
+
+        ResourceAllocation alloc = null;
+        for (ResourceRequest r: req.resources()) {
+            switch (r.type()) {
+            case BANDWIDTH:
+                log.info("requestResources() always returns requested bandwidth");
+                BandwidthResourceRequest br = (BandwidthResourceRequest) r;
+                alloc = new BandwidthResourceAllocation(br.bandwidth());
+                break;
+            case LAMBDA:
+                log.info("requestResources() always returns lambda 7");
+                alloc = new LambdaResourceAllocation(Lambda.valueOf(7));
+                break;
+            default:
+                break;
+            }
+        }
+
+        Map<Link, Set<ResourceAllocation>> allocations = new HashMap<>();
+        for (Link link: req.links()) {
+            allocations.put(link, Sets.newHashSet(alloc));
+        }
+        return new DefaultLinkResourceAllocations(req, allocations);
+    }
+
+    @Override
+    public void releaseResources(LinkResourceAllocations allocations) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public Iterable<LinkResourceAllocations> getAllocations() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public LinkResourceAllocations getAllocations(IntentId intentId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Iterable<LinkResourceAllocations> getAllocations(Link link) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Iterable<IntentId> getIntents(Link link) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public ResourceRequest getAvailableResources(Link link) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/resource/impl/package-info.java b/core/net/src/main/java/org/onlab/onos/net/resource/impl/package-info.java
new file mode 100644
index 0000000..a398a80
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/resource/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Services for reserving network resources, e.g.&nbsp;bandwidth, lambdas.
+ */
+package org.onlab.onos.net.resource.impl;
diff --git a/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java b/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
index 6935148..edd7db9 100644
--- a/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
@@ -157,16 +157,12 @@
                 case RULE_UPDATED:
                     if (rule instanceof FlowEntry) {
                         statisticStore.addOrUpdateStatistic((FlowEntry) rule);
-                    } else {
-                        log.warn("IT AIN'T A FLOWENTRY");
                     }
                     break;
                 case RULE_ADD_REQUESTED:
-                    log.info("Preparing for stats");
                     statisticStore.prepareForStatistics(rule);
                     break;
                 case RULE_REMOVE_REQUESTED:
-                    log.info("Removing stats");
                     statisticStore.removeFromStatistics(rule);
                     break;
                 case RULE_REMOVED:
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index a2fbc9a..659a2c4 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -1,9 +1,15 @@
 package org.onlab.onos.net.flow.impl;
 
-
-
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.*;
-
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -12,6 +18,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -31,6 +38,7 @@
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.device.DeviceListener;
 import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.BatchOperation;
 import org.onlab.onos.net.flow.CompletedBatchOperation;
 import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.DefaultFlowRule;
@@ -50,7 +58,6 @@
 import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.net.flow.criteria.Criterion;
 import org.onlab.onos.net.flow.instructions.Instruction;
-import org.onlab.onos.net.flow.BatchOperation;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.trivial.impl.SimpleFlowRuleStore;
@@ -59,16 +66,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-
-import static java.util.Collections.EMPTY_LIST;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
+import com.google.common.util.concurrent.ListenableFuture;
 
 /**
  * Test codifying the flow rule service & flow rule provider service contracts.
@@ -182,7 +180,6 @@
 
     // TODO: If preserving iteration order is a requirement, redo FlowRuleStore.
     //backing store is sensitive to the order of additions/removals
-    @SuppressWarnings("unchecked")
     private boolean validateState(Map<FlowRule, FlowEntryState> expected) {
         Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected);
         Iterable<FlowEntry> rules = service.getFlowEntries(DID);
@@ -526,13 +523,13 @@
         }
 
         @Override
-        public Future<CompletedBatchOperation> executeBatch(
+        public ListenableFuture<CompletedBatchOperation> executeBatch(
                 BatchOperation<FlowRuleBatchEntry> batch) {
             return new TestInstallationFuture();
         }
 
         private class TestInstallationFuture
-                implements Future<CompletedBatchOperation> {
+                implements ListenableFuture<CompletedBatchOperation> {
 
             @Override
             public boolean cancel(boolean mayInterruptIfRunning) {
@@ -550,10 +547,9 @@
             }
 
             @Override
-            @SuppressWarnings("unchecked")
             public CompletedBatchOperation get()
                     throws InterruptedException, ExecutionException {
-                return new CompletedBatchOperation(true, EMPTY_LIST);
+                return new CompletedBatchOperation(true, Collections.<FlowEntry>emptySet());
             }
 
             @Override
@@ -562,6 +558,11 @@
                     ExecutionException, TimeoutException {
                 return null;
             }
+
+            @Override
+            public void addListener(Runnable task, Executor executor) {
+                // TODO: add stuff.
+            }
         }
 
     }
@@ -581,6 +582,12 @@
         }
 
         @Override
+        public Criterion getCriterion(
+                org.onlab.onos.net.flow.criteria.Criterion.Type type) {
+            return null;
+        }
+
+        @Override
         public int hashCode() {
             return testval;
         }
@@ -592,6 +599,7 @@
             }
             return false;
         }
+
     }
 
     private class TestTreatment implements TrafficTreatment {
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java b/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
index 0f020a4..e0e49ef 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.net.intent;
 
+import static org.onlab.onos.net.NetTestTools.createPath;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -12,12 +14,11 @@
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.net.flow.criteria.Criterion;
+import org.onlab.onos.net.flow.criteria.Criterion.Type;
 import org.onlab.onos.net.flow.instructions.Instruction;
 import org.onlab.onos.net.topology.LinkWeight;
 import org.onlab.onos.net.topology.PathService;
 
-import static org.onlab.onos.net.NetTestTools.createPath;
-
 /**
  * Common mocks used by the intent framework tests.
  */
@@ -30,6 +31,11 @@
         public Set<Criterion> criteria() {
             return new HashSet<>();
         }
+
+        @Override
+        public Criterion getCriterion(Type type) {
+            return null;
+        }
     }
 
     /**
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
index 4db23ce..b2f679c 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
@@ -103,7 +103,7 @@
         final ControllerNode localNode = clusterService.getLocalNode();
         for (NodeId nodeId : nodes) {
             if (!nodeId.equals(localNode.id())) {
-                ok = unicast(message, nodeId) && ok;
+                ok = unicastUnchecked(message, nodeId) && ok;
             }
         }
         return ok;
@@ -124,6 +124,14 @@
         }
     }
 
+    private boolean unicastUnchecked(ClusterMessage message, NodeId toNodeId) throws IOException {
+        try {
+            return unicast(message, toNodeId);
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
     @Override
     public ClusterMessageResponse sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException {
         ControllerNode node = clusterService.getNode(toNodeId);
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
index bde57c6..e5aa3e8 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
@@ -3,12 +3,21 @@
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
 import static org.slf4j.LoggerFactory.getLogger;
 import static org.onlab.onos.store.flow.impl.FlowStoreMessageSubjects.*;
+import static org.onlab.util.Tools.namedThreads;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -18,12 +27,20 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.CompletedBatchOperation;
 import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.FlowEntry;
 import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchEvent;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchRequest;
 import org.onlab.onos.net.flow.FlowRuleEvent;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
 import org.onlab.onos.net.flow.FlowRuleEvent.Type;
 import org.onlab.onos.net.flow.FlowRuleStore;
 import org.onlab.onos.net.flow.FlowRuleStoreDelegate;
@@ -42,7 +59,12 @@
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 
 /**
  * Manages inventory of flow rules using a distributed state management protocol.
@@ -50,7 +72,7 @@
 @Component(immediate = true)
 @Service
 public class DistributedFlowRuleStore
-        extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
+        extends AbstractStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
         implements FlowRuleStore {
 
     private final Logger log = getLogger(getClass());
@@ -63,13 +85,26 @@
             ArrayListMultimap.<Short, FlowRule>create();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    private ReplicaInfoService replicaInfoManager;
+    protected ReplicaInfoService replicaInfoManager;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    private ClusterCommunicationService clusterCommunicator;
+    protected ClusterCommunicationService clusterCommunicator;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    private ClusterService clusterService;
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+
+    // FIXME switch to expiraing map/Cache?
+    private Map<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures = Maps.newConcurrentMap();
+
+    private final ExecutorService futureListeners =
+            Executors.newCachedThreadPool(namedThreads("flowstore-peer-responders"));
+
 
     protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
         @Override
@@ -86,38 +121,44 @@
 
     @Activate
     public void activate() {
-        clusterCommunicator.addSubscriber(STORE_FLOW_RULE, new ClusterMessageHandler() {
+        clusterCommunicator.addSubscriber(APPLY_BATCH_FLOWS, new ClusterMessageHandler() {
+
+            @Override
+            public void handle(final ClusterMessage message) {
+                FlowRuleBatchOperation operation = SERIALIZER.decode(message.payload());
+                log.info("received batch request {}", operation);
+                final ListenableFuture<CompletedBatchOperation> f = storeBatchInternal(operation);
+
+                f.addListener(new Runnable() {
+
+                    @Override
+                    public void run() {
+                         CompletedBatchOperation result = Futures.getUnchecked(f);
+                        try {
+                            message.respond(SERIALIZER.encode(result));
+                        } catch (IOException e) {
+                            log.error("Failed to respond back", e);
+                        }
+                    }
+                }, futureListeners);
+            }
+        });
+
+        clusterCommunicator.addSubscriber(GET_FLOW_ENTRY, new ClusterMessageHandler() {
 
             @Override
             public void handle(ClusterMessage message) {
                 FlowRule rule = SERIALIZER.decode(message.payload());
-                log.info("received add request for {}", rule);
-                storeFlowEntryInternal(rule);
-                // FIXME what to respond.
+                log.info("received get flow entry request for {}", rule);
+                FlowEntry flowEntry = getFlowEntryInternal(rule);
                 try {
-                    message.respond(SERIALIZER.encode("ACK"));
+                    message.respond(SERIALIZER.encode(flowEntry));
                 } catch (IOException e) {
                     log.error("Failed to respond back", e);
                 }
             }
         });
 
-        clusterCommunicator.addSubscriber(DELETE_FLOW_RULE, new ClusterMessageHandler() {
-
-            @Override
-            public void handle(ClusterMessage message) {
-                FlowRule rule = SERIALIZER.decode(message.payload());
-                log.info("received delete request for {}", rule);
-                deleteFlowRuleInternal(rule);
-                // FIXME what to respond.
-                try {
-                    message.respond(SERIALIZER.encode("ACK"));
-                } catch (IOException e) {
-                    log.error("Failed to respond back", e);
-                }
-
-            }
-        });
         log.info("Started");
     }
 
@@ -127,14 +168,42 @@
     }
 
 
+    // TODO: This is not a efficient operation on a distributed sharded
+    // flow store. We need to revisit the need for this operation or at least
+    // make it device specific.
     @Override
     public int getFlowRuleCount() {
-        return flowEntries.size();
+        // implementing in-efficient operation for debugging purpose.
+        int sum = 0;
+        for (Device device : deviceService.getDevices()) {
+            final DeviceId did = device.id();
+            sum += Iterables.size(getFlowEntries(did));
+        }
+        return sum;
     }
 
     @Override
     public synchronized FlowEntry getFlowEntry(FlowRule rule) {
-        return getFlowEntryInternal(rule);
+        ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
+        if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
+            return getFlowEntryInternal(rule);
+        }
+
+        log.info("Forwarding getFlowEntry to {}, which is the primary (master) for device {}",
+                replicaInfo.master().orNull(), rule.deviceId());
+
+        ClusterMessage message = new ClusterMessage(
+                clusterService.getLocalNode().id(),
+                FlowStoreMessageSubjects.GET_FLOW_ENTRY,
+                SERIALIZER.encode(rule));
+
+        try {
+            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
+            return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+        } catch (IOException | TimeoutException e) {
+            // FIXME: throw a FlowStoreException
+            throw new RuntimeException(e);
+        }
     }
 
     private synchronized StoredFlowEntry getFlowEntryInternal(FlowRule rule) {
@@ -165,56 +234,31 @@
     }
 
     @Override
-    public boolean storeFlowRule(FlowRule rule) {
-        ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
-        if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
-            return storeFlowEntryInternal(rule);
-        }
-
-        log.info("Forwarding storeFlowRule to {}, which is the primary (master) for device {}",
-                replicaInfo.master().orNull(), rule.deviceId());
-
-        ClusterMessage message = new ClusterMessage(
-                clusterService.getLocalNode().id(),
-                FlowStoreMessageSubjects.STORE_FLOW_RULE,
-                SERIALIZER.encode(rule));
-
-        try {
-            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
-            response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (IOException | TimeoutException e) {
-            // FIXME: throw a FlowStoreException
-            throw new RuntimeException(e);
-        }
-        return false;
-    }
-
-    private synchronized boolean storeFlowEntryInternal(FlowRule flowRule) {
-        StoredFlowEntry flowEntry = new DefaultFlowEntry(flowRule);
-        DeviceId deviceId = flowRule.deviceId();
-        // write to local copy.
-        if (!flowEntries.containsEntry(deviceId, flowEntry)) {
-            flowEntries.put(deviceId, flowEntry);
-            flowEntriesById.put(flowRule.appId(), flowEntry);
-            notifyDelegate(new FlowRuleEvent(Type.RULE_ADD_REQUESTED, flowRule));
-            return true;
-        }
-        // write to backup.
-        // TODO: write to a hazelcast map.
-        return false;
+    public void storeFlowRule(FlowRule rule) {
+        storeBatch(new FlowRuleBatchOperation(Arrays.asList(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule))));
     }
 
     @Override
-    public synchronized boolean deleteFlowRule(FlowRule rule) {
-        ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
-        if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
-            return deleteFlowRuleInternal(rule);
+    public Future<CompletedBatchOperation> storeBatch(FlowRuleBatchOperation operation) {
+        if (operation.getOperations().isEmpty()) {
+            return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowEntry>emptySet()));
         }
 
+        DeviceId deviceId = operation.getOperations().get(0).getTarget().deviceId();
+
+        ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(deviceId);
+
+        if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
+            return storeBatchInternal(operation);
+        }
+
+        log.info("Forwarding storeBatch to {}, which is the primary (master) for device {}",
+                replicaInfo.master().orNull(), deviceId);
+
         ClusterMessage message = new ClusterMessage(
                 clusterService.getLocalNode().id(),
-                FlowStoreMessageSubjects.DELETE_FLOW_RULE,
-                SERIALIZER.encode(rule));
+                APPLY_BATCH_FLOWS,
+                SERIALIZER.encode(operation));
 
         try {
             ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
@@ -223,21 +267,48 @@
             // FIXME: throw a FlowStoreException
             throw new RuntimeException(e);
         }
-        return false;
+
+        return null;
     }
 
-    private synchronized boolean deleteFlowRuleInternal(FlowRule flowRule) {
-        StoredFlowEntry entry = getFlowEntryInternal(flowRule);
-        if (entry == null) {
-            return false;
+    private ListenableFuture<CompletedBatchOperation> storeBatchInternal(FlowRuleBatchOperation operation) {
+        List<FlowEntry> toRemove = new ArrayList<>();
+        List<FlowEntry> toAdd = new ArrayList<>();
+        // TODO: backup changes to hazelcast map
+        for (FlowRuleBatchEntry batchEntry : operation.getOperations()) {
+            FlowRule flowRule = batchEntry.getTarget();
+            FlowRuleOperation op = batchEntry.getOperator();
+            if (op.equals(FlowRuleOperation.REMOVE)) {
+                StoredFlowEntry entry = getFlowEntryInternal(flowRule);
+                if (entry != null) {
+                    entry.setState(FlowEntryState.PENDING_REMOVE);
+                    toRemove.add(entry);
+                }
+            } else if (op.equals(FlowRuleOperation.ADD)) {
+                StoredFlowEntry flowEntry = new DefaultFlowEntry(flowRule);
+                DeviceId deviceId = flowRule.deviceId();
+                if (!flowEntries.containsEntry(deviceId, flowEntry)) {
+                    flowEntries.put(deviceId, flowEntry);
+                    flowEntriesById.put(flowRule.appId(), flowEntry);
+                    toAdd.add(flowEntry);
+                }
+            }
         }
-        entry.setState(FlowEntryState.PENDING_REMOVE);
+        if (toAdd.isEmpty() && toRemove.isEmpty()) {
+            return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowEntry>emptySet()));
+        }
 
-        // TODO: also update backup.
+        SettableFuture<CompletedBatchOperation> r = SettableFuture.create();
+        final int batchId = localBatchIdGen.incrementAndGet();
 
-        notifyDelegate(new FlowRuleEvent(Type.RULE_REMOVE_REQUESTED, flowRule));
+        pendingFutures.put(batchId, r);
+        notifyDelegate(FlowRuleBatchEvent.requested(new FlowRuleBatchRequest(batchId, toAdd, toRemove)));
+        return r;
+    }
 
-        return true;
+    @Override
+    public void deleteFlowRule(FlowRule rule) {
+        storeBatch(new FlowRuleBatchOperation(Arrays.asList(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule))));
     }
 
     @Override
@@ -247,18 +318,9 @@
             return addOrUpdateFlowRuleInternal(rule);
         }
 
-        ClusterMessage message = new ClusterMessage(
-                clusterService.getLocalNode().id(),
-                FlowStoreMessageSubjects.ADD_OR_UPDATE_FLOW_RULE,
-                SERIALIZER.encode(rule));
-
-        try {
-            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
-            return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-        } catch (IOException | TimeoutException e) {
-            // FIXME: throw a FlowStoreException
-            throw new RuntimeException(e);
-        }
+        log.error("Tried to update FlowRule {} state,"
+                + " while the Node was not the master.", rule);
+        return null;
     }
 
     private synchronized FlowRuleEvent addOrUpdateFlowRuleInternal(FlowEntry rule) {
@@ -292,18 +354,9 @@
             return removeFlowRuleInternal(rule);
         }
 
-        ClusterMessage message = new ClusterMessage(
-                clusterService.getLocalNode().id(),
-                FlowStoreMessageSubjects.REMOVE_FLOW_RULE,
-                SERIALIZER.encode(rule));
-
-        try {
-            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
-            return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-        } catch (IOException | TimeoutException e) {
-            // FIXME: throw a FlowStoreException
-            throw new RuntimeException(e);
-        }
+        log.error("Tried to remove FlowRule {},"
+                + " while the Node was not the master.", rule);
+        return null;
     }
 
     private synchronized FlowRuleEvent removeFlowRuleInternal(FlowEntry rule) {
@@ -315,4 +368,14 @@
         }
         // TODO: also update backup.
     }
+
+    @Override
+    public void batchOperationComplete(FlowRuleBatchEvent event) {
+        SettableFuture<CompletedBatchOperation> future
+            = pendingFutures.get(event.subject().batchId());
+        if (future != null) {
+            future.set(event.result());
+        }
+        notifyDelegate(event);
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/FlowStoreMessageSubjects.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/FlowStoreMessageSubjects.java
index a43dad6..ef68b55 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/FlowStoreMessageSubjects.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/FlowStoreMessageSubjects.java
@@ -7,9 +7,10 @@
  */
 public final class FlowStoreMessageSubjects {
     private FlowStoreMessageSubjects() {}
-    public static final  MessageSubject STORE_FLOW_RULE = new MessageSubject("peer-forward-store-flow-rule");
-    public static final MessageSubject DELETE_FLOW_RULE = new MessageSubject("peer-forward-delete-flow-rule");
-    public static final MessageSubject ADD_OR_UPDATE_FLOW_RULE =
-        new MessageSubject("peer-forward-add-or-update-flow-rule");
-    public static final MessageSubject REMOVE_FLOW_RULE = new MessageSubject("peer-forward-remove-flow-rule");
+
+    public static final  MessageSubject APPLY_BATCH_FLOWS
+        = new MessageSubject("peer-forward-apply-batch");
+
+    public static final MessageSubject GET_FLOW_ENTRY
+        = new MessageSubject("peer-forward-get-flow-entry");
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
index e3d8fe0..ee3fb45 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
@@ -399,7 +399,7 @@
     }
 
     // Auxiliary extension to allow location to mutate.
-    private class StoredHost extends DefaultHost {
+    private static final class StoredHost extends DefaultHost {
         private Timestamped<HostLocation> location;
 
         /**
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index 316a3b4..f9352fe 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -58,7 +58,6 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     @Override
     @Activate
     public void activate() {
@@ -76,9 +75,9 @@
             }
         };
 
-        roleMap = new SMap(theInstance.getMap("nodeRoles"), this.serializer);
+        roleMap = new SMap<>(theInstance.<byte[], byte[]>getMap("nodeRoles"), this.serializer);
         roleMap.addEntryListener((new RemoteMasterShipEventHandler()), true);
-        terms = new SMap(theInstance.getMap("terms"), this.serializer);
+        terms = new SMap<>(theInstance.<byte[], byte[]>getMap("terms"), this.serializer);
         clusterSize = theInstance.getAtomicLong("clustersize");
 
         log.info("Started");
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
index c156143..7447161 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
@@ -1,7 +1,7 @@
 package org.onlab.onos.store.mastership.impl;
 
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.EnumMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -17,9 +17,9 @@
  * A structure that holds node mastership roles associated with a
  * {@link DeviceId}. This structure needs to be locked through IMap.
  */
-public class RoleValue {
+final class RoleValue {
 
-    protected Map<MastershipRole, List<NodeId>> value = new HashMap<>();
+    protected final Map<MastershipRole, List<NodeId>> value = new EnumMap<>(MastershipRole.class);
 
     public RoleValue() {
         value.put(MastershipRole.MASTER, new LinkedList<NodeId>());
@@ -27,7 +27,8 @@
         value.put(MastershipRole.NONE, new LinkedList<NodeId>());
     }
 
-    public Map<MastershipRole, List<NodeId>> value() {
+    // exposing internals for serialization purpose only
+    Map<MastershipRole, List<NodeId>> value() {
         return Collections.unmodifiableMap(value);
     }
 
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValueSerializer.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValueSerializer.java
index 22d1b35..4450e5b 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValueSerializer.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/RoleValueSerializer.java
@@ -35,10 +35,10 @@
 
     @Override
     public void write(Kryo kryo, Output output, RoleValue type) {
-        output.writeInt(type.value().size());
+        final Map<MastershipRole, List<NodeId>> map = type.value();
+        output.writeInt(map.size());
 
-        for (Map.Entry<MastershipRole, List<NodeId>> el :
-                type.value().entrySet()) {
+        for (Map.Entry<MastershipRole, List<NodeId>> el : map.entrySet()) {
             output.writeInt(el.getKey().ordinal());
 
             List<NodeId> nodes = el.getValue();
diff --git a/core/store/hz/common/src/main/java/org/onlab/onos/store/common/SMap.java b/core/store/hz/common/src/main/java/org/onlab/onos/store/common/SMap.java
index 93a7b0d..6dd4bfb 100644
--- a/core/store/hz/common/src/main/java/org/onlab/onos/store/common/SMap.java
+++ b/core/store/hz/common/src/main/java/org/onlab/onos/store/common/SMap.java
@@ -492,7 +492,10 @@
     }
 
     private V deserializeVal(byte[] val) {
-        return serializer.decode(val);
+        if (val == null) {
+            return null;
+        }
+        return serializer.decode(val.clone());
     }
 
     private Set<byte[]> serializeKeySet(Set<K> keys) {
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
index 9b75cea..0e9e19c 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
@@ -5,6 +5,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.cluster.DefaultControllerNode;
@@ -27,12 +28,16 @@
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.device.DefaultDeviceDescription;
 import org.onlab.onos.net.device.DefaultPortDescription;
+import org.onlab.onos.net.flow.CompletedBatchOperation;
 import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.DefaultFlowRule;
 import org.onlab.onos.net.flow.DefaultTrafficSelector;
 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
 import org.onlab.onos.net.flow.FlowEntry;
 import org.onlab.onos.net.flow.FlowId;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.StoredFlowEntry;
 import org.onlab.onos.net.flow.criteria.Criteria;
 import org.onlab.onos.net.flow.criteria.Criterion;
 import org.onlab.onos.net.flow.instructions.Instructions;
@@ -79,6 +84,7 @@
                     Arrays.asList().getClass(),
                     HashMap.class,
                     HashSet.class,
+                    LinkedList.class,
                     //
                     //
                     ControllerNode.State.class,
@@ -97,6 +103,8 @@
                     HostId.class,
                     HostDescription.class,
                     DefaultHostDescription.class,
+                    DefaultFlowEntry.class,
+                    StoredFlowEntry.class,
                     DefaultFlowRule.class,
                     DefaultFlowEntry.class,
                     FlowEntry.FlowEntryState.class,
@@ -115,7 +123,11 @@
                     DefaultTrafficTreatment.class,
                     Instructions.DropInstruction.class,
                     Instructions.OutputInstruction.class,
-                    RoleInfo.class
+                    RoleInfo.class,
+                    FlowRuleBatchOperation.class,
+                    CompletedBatchOperation.class,
+                    FlowRuleBatchEntry.class,
+                    FlowRuleBatchEntry.FlowRuleOperation.class
                     )
             .register(URI.class, new URISerializer())
             .register(NodeId.class, new NodeIdSerializer())
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
index d312af5..82ea4e2 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
@@ -1,27 +1,25 @@
 package org.onlab.onos.store.trivial.impl;
 
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
-import static org.slf4j.LoggerFactory.getLogger;
-import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.util.concurrent.Futures;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.flow.CompletedBatchOperation;
 import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.FlowEntry;
 import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowId;
 import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchEvent;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchRequest;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleEvent.Type;
 import org.onlab.onos.net.flow.FlowRuleStore;
@@ -31,8 +29,19 @@
 import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
 
-import com.google.common.base.Function;
-import com.google.common.collect.FluentIterable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Manages inventory of flow rules using trivial in-memory implementation.
@@ -40,7 +49,7 @@
 @Component(immediate = true)
 @Service
 public class SimpleFlowRuleStore
-        extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
+        extends AbstractStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
         implements FlowRuleStore {
 
     private final Logger log = getLogger(getClass());
@@ -122,15 +131,15 @@
     public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
         // flatten and make iterator unmodifiable
         return FluentIterable.from(getFlowTable(deviceId).values())
-            .transformAndConcat(
-                    new Function<List<StoredFlowEntry>, Iterable<? extends FlowEntry>>() {
+                .transformAndConcat(
+                        new Function<List<StoredFlowEntry>, Iterable<? extends FlowEntry>>() {
 
-                @Override
-                public Iterable<? extends FlowEntry> apply(
-                        List<StoredFlowEntry> input) {
-                    return Collections.unmodifiableList(input);
-                }
-            });
+                            @Override
+                            public Iterable<? extends FlowEntry> apply(
+                                    List<StoredFlowEntry> input) {
+                                return Collections.unmodifiableList(input);
+                            }
+                        });
     }
 
     @Override
@@ -148,12 +157,11 @@
     }
 
     @Override
-    public boolean storeFlowRule(FlowRule rule) {
-        final boolean added = storeFlowRuleInternal(rule);
-        return added;
+    public void storeFlowRule(FlowRule rule) {
+        storeFlowRuleInternal(rule);
     }
 
-    private boolean storeFlowRuleInternal(FlowRule rule) {
+    private void storeFlowRuleInternal(FlowRule rule) {
         StoredFlowEntry f = new DefaultFlowEntry(rule);
         final DeviceId did = f.deviceId();
         final FlowId fid = f.id();
@@ -162,19 +170,20 @@
             for (StoredFlowEntry fe : existing) {
                 if (fe.equals(rule)) {
                     // was already there? ignore
-                    return false;
+                    return;
                 }
             }
             // new flow rule added
             existing.add(f);
-            // TODO: Should we notify only if it's "remote" event?
-            //notifyDelegate(new FlowRuleEvent(Type.RULE_ADD_REQUESTED, rule));
-            return true;
+            notifyDelegate(FlowRuleBatchEvent.requested(
+                    new FlowRuleBatchRequest(1, /* FIXME generate something */
+                                             Arrays.<FlowEntry>asList(f),
+                                             Collections.<FlowEntry>emptyList())));
         }
     }
 
     @Override
-    public boolean deleteFlowRule(FlowRule rule) {
+    public void deleteFlowRule(FlowRule rule) {
 
         List<StoredFlowEntry> entries = getFlowEntries(rule.deviceId(), rule.id());
 
@@ -184,14 +193,17 @@
                     synchronized (entry) {
                         entry.setState(FlowEntryState.PENDING_REMOVE);
                         // TODO: Should we notify only if it's "remote" event?
-                        //notifyDelegate(new FlowRuleEvent(Type.RULE_REMOVE_REQUESTED, rule));
-                        return true;
+                        notifyDelegate(FlowRuleBatchEvent.requested(
+                                new FlowRuleBatchRequest(1, /* FIXME generate something */
+                                                         Collections.<FlowEntry>emptyList(),
+                                                         Arrays.<FlowEntry>asList(entry))));
                     }
                 }
             }
         }
+
+
         //log.warn("Cannot find rule {}", rule);
-        return false;
     }
 
     @Override
@@ -237,4 +249,24 @@
         }
         return null;
     }
+
+    @Override
+    public Future<CompletedBatchOperation> storeBatch(
+            FlowRuleBatchOperation batchOperation) {
+        for (FlowRuleBatchEntry entry : batchOperation.getOperations()) {
+            if (entry.getOperator().equals(FlowRuleOperation.ADD)) {
+                storeFlowRule(entry.getTarget());
+            } else if (entry.getOperator().equals(FlowRuleOperation.REMOVE)) {
+                deleteFlowRule(entry.getTarget());
+            } else {
+                throw new UnsupportedOperationException("Unsupported operation type");
+            }
+        }
+        return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowEntry>emptySet()));
+    }
+
+    @Override
+    public void batchOperationComplete(FlowRuleBatchEvent event) {
+        notifyDelegate(event);
+    }
 }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
index ef80b72..ee8570d 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
@@ -269,7 +269,7 @@
     }
 
     // Auxiliary extension to allow location to mutate.
-    private class StoredHost extends DefaultHost {
+    private static final class StoredHost extends DefaultHost {
         private HostLocation location;
 
         /**
diff --git a/docs/external.xml b/docs/external.xml
new file mode 100644
index 0000000..959b473
--- /dev/null
+++ b/docs/external.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <prerequisites>
+        <maven>3.0.0</maven>
+    </prerequisites>
+
+    <parent>
+        <groupId>org.onlab.onos</groupId>
+        <artifactId>onos</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-docs-external</artifactId>
+    <packaging>pom</packaging>
+
+    <description>ONOS Java API documentation</description>
+
+    <modules>
+        <module>..</module>
+    </modules>
+
+    <url>http://onlab.us/</url>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.10.1</version>
+                <configuration>
+                    <show>package</show>
+                    <excludePackageNames>org.onlab.thirdparty:*.impl:*.impl.*:org.onlab.onos.provider.*:org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli*:org.onlab.onos.tvue:org.onlab.onos.foo:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.optical:org.onlab.onos.config:org.onlab.onos.calendar:org.onlab.onos.sdnip*:org.onlab.onos.metrics</excludePackageNames>
+                    <docfilessubdirs>true</docfilessubdirs>
+                    <doctitle>ONOS Java API</doctitle>
+                    <groups>
+                        <group>
+                            <title>Network Model &amp; Services</title>
+                            <packages>
+                                org.onlab.onos:org.onlab.onos.*
+                            </packages>
+                        </group>
+                        <group>
+                            <title>Utilities</title>
+                            <packages>
+                                org.onlab.*
+                            </packages>
+                        </group>
+                    </groups>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/docs/pom.xml b/docs/pom.xml
new file mode 100644
index 0000000..3a38f13
--- /dev/null
+++ b/docs/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <prerequisites>
+        <maven>3.0.0</maven>
+    </prerequisites>
+
+    <parent>
+        <groupId>org.onlab.onos</groupId>
+        <artifactId>onos</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-docs</artifactId>
+    <packaging>pom</packaging>
+
+    <description>ONOS Java API documentation</description>
+
+    <modules>
+        <module>..</module>
+    </modules>
+
+    <url>http://onlab.us/</url>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.10.1</version>
+                <configuration>
+                    <show>package</show>
+                    <docfilessubdirs>true</docfilessubdirs>
+                    <doctitle>ONOS Java API</doctitle>
+                    <groups>
+                        <group>
+                            <title>Network Model &amp; Services</title>
+                            <packages>
+                                org.onlab.onos:org.onlab.onos.*
+                            </packages>
+                        </group>
+                        <group>
+                            <title>Core Subsystems</title>
+                            <packages>
+                                org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl:org.onlab.onos.json:org.onlab.onos.json.*:org.onlab.onos.provider.host.impl:org.onlab.onos.provider.lldp.impl:org.onlab.onos.net.statistic.impl
+                            </packages>
+                        </group>
+                        <group>
+                            <title>OpenFlow Providers &amp; Controller
+                            </title>
+                            <packages>
+                                org.onlab.onos.provider.of.*:org.onlab.onos.openflow.*
+                            </packages>
+                        </group>
+                        <group>
+                            <title>Utilities</title>
+                            <packages>
+                                org.onlab.*
+                            </packages>
+                        </group>
+                        <group>
+                            <title>GUI, REST &amp; Command-Line</title>
+                            <packages>
+                                org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli:org.onlab.onos.gui.*:org.onlab.onos.rest.*:org.onlab.onos.cli.*
+                            </packages>
+                        </group>
+                        <group>
+                            <title>Sample Applications</title>
+                            <packages>
+                                org.onlab.onos.tvue:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.foo:org.onlab.onos.calendar:org.onlab.onos.sdnip:org.onlab.onos.sdnip.*:org.onlab.onos.optical:org.onlab.onos.optical.*:org.onlab.onos.metrics.*:org.onlab.onos.config
+                            </packages>
+                        </group>
+                    </groups>
+                    <excludePackageNames>org.onlab.thirdparty
+                    </excludePackageNames>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/src/main/javadoc/doc-files/onos-subsystem.png b/docs/src/main/javadoc/doc-files/onos-subsystem.png
similarity index 100%
rename from src/main/javadoc/doc-files/onos-subsystem.png
rename to docs/src/main/javadoc/doc-files/onos-subsystem.png
Binary files differ
diff --git a/src/main/javadoc/doc-files/onos-tiers.png b/docs/src/main/javadoc/doc-files/onos-tiers.png
similarity index 100%
rename from src/main/javadoc/doc-files/onos-tiers.png
rename to docs/src/main/javadoc/doc-files/onos-tiers.png
Binary files differ
diff --git a/src/main/javadoc/overview.html b/docs/src/main/javadoc/overview.html
similarity index 100%
rename from src/main/javadoc/overview.html
rename to docs/src/main/javadoc/overview.html
diff --git a/features/features.xml b/features/features.xml
index e363094..be9110b 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -35,7 +35,7 @@
         <bundle>mvn:io.netty/netty-transport-native-epoll/4.0.23.Final</bundle>
         <bundle>mvn:commons-pool/commons-pool/1.6</bundle>
 
-        <bundle>mvn:com.hazelcast/hazelcast/3.3</bundle>
+        <bundle>mvn:com.hazelcast/hazelcast/3.3.2</bundle>
         <bundle>mvn:io.dropwizard.metrics/metrics-core/3.1.0</bundle>
         <bundle>mvn:io.dropwizard.metrics/metrics-json/3.1.0</bundle>
         <bundle>mvn:com.eclipsesource.minimal-json/minimal-json/0.9.1</bundle>
diff --git a/openflow/api/pom.xml b/openflow/api/pom.xml
index 59d05a2..45d863d 100644
--- a/openflow/api/pom.xml
+++ b/openflow/api/pom.xml
@@ -30,7 +30,7 @@
             <groupId>org.projectfloodlight</groupId>
             <artifactId>openflowj</artifactId>
             <!-- FIXME once experimenter gets merged to upstream -->
-            <version>0.3.8-optical_experimenter3</version>
+            <version>0.3.8-optical_experimenter4</version>
         </dependency>
         <dependency>
             <groupId>io.netty</groupId>
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/DefaultOpenFlowPacketContext.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/DefaultOpenFlowPacketContext.java
index e56a4f9..b1536fb 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/DefaultOpenFlowPacketContext.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/DefaultOpenFlowPacketContext.java
@@ -1,5 +1,9 @@
 package org.onlab.onos.openflow.controller;
 
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import org.onlab.packet.Ethernet;
 import org.projectfloodlight.openflow.protocol.OFPacketIn;
 import org.projectfloodlight.openflow.protocol.OFPacketOut;
@@ -9,9 +13,6 @@
 import org.projectfloodlight.openflow.types.OFBufferId;
 import org.projectfloodlight.openflow.types.OFPort;
 
-import java.util.Collections;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 public final class DefaultOpenFlowPacketContext implements OpenFlowPacketContext {
 
     private final AtomicBoolean free = new AtomicBoolean(true);
diff --git a/pom.xml b/pom.xml
index 8aa5ce4..ce02451 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,6 +4,10 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
+    <prerequisites>
+        <maven>3.0.0</maven>
+    </prerequisites>
+
     <groupId>org.onlab.onos</groupId>
     <artifactId>onos</artifactId>
     <packaging>pom</packaging>
@@ -28,7 +32,6 @@
 
     <licenses>
         <license>
-            <!-- TODO: Is this really our license scheme? -->
             <name>Apache License, Version 2.0</name>
             <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
         </license>
@@ -192,7 +195,7 @@
             <dependency>
                 <groupId>com.hazelcast</groupId>
                 <artifactId>hazelcast</artifactId>
-                <version>3.3</version>
+                <version>3.3.2</version>
             </dependency>
             <dependency>
                 <groupId>com.eclipsesource.minimal-json</groupId>
@@ -528,59 +531,6 @@
                     </execution>
                 </executions>
             </plugin>
-
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-javadoc-plugin</artifactId>
-                <version>2.10.1</version>
-                <configuration>
-                    <show>package</show>
-                    <docfilessubdirs>true</docfilessubdirs>
-                    <doctitle>ONOS Java API</doctitle>
-                    <groups>
-                        <group>
-                            <title>Network Model &amp; Services</title>
-                            <packages>
-                                org.onlab.onos:org.onlab.onos.*
-                            </packages>
-                        </group>
-                        <group>
-                            <title>Core Subsystems</title>
-                            <packages>
-                                org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl:org.onlab.onos.json:org.onlab.onos.json.*:org.onlab.onos.provider.host.impl:org.onlab.onos.provider.lldp.impl:org.onlab.onos.net.statistic.impl
-                            </packages>
-                        </group>
-                        <group>
-                            <title>OpenFlow Providers &amp; Controller
-                            </title>
-                            <packages>
-                                org.onlab.onos.provider.of.*:org.onlab.onos.openflow.*
-                            </packages>
-                        </group>
-                        <group>
-                            <title>Utilities</title>
-                            <packages>
-                                org.onlab.*
-                            </packages>
-                        </group>
-                        <group>
-                            <title>GUI, REST &amp; Command-Line</title>
-                            <packages>
-                                org.onlab.onos.gui:org.onlab.onos.rest:org.onlab.onos.cli:org.onlab.onos.gui.*:org.onlab.onos.rest.*:org.onlab.onos.cli.*
-                            </packages>
-                        </group>
-                        <group>
-                            <title>Sample Applications</title>
-                            <packages>
-                                org.onlab.onos.tvue:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.foo:org.onlab.onos.calendar:org.onlab.onos.sdnip:org.onlab.onos.sdnip.*:org.onlab.onos.optical:org.onlab.onos.optical.*:org.onlab.onos.metrics.*
-                            </packages>
-                        </group>
-                    </groups>
-                    <excludePackageNames>org.onlab.thirdparty
-                    </excludePackageNames>
-                </configuration>
-            </plugin>
-
         </plugins>
     </build>
 
@@ -607,10 +557,6 @@
                     </rulesets>
                 </configuration>
             </plugin>
-
         </plugins>
     </reporting>
-    <prerequisites>
-        <maven>3.0.0</maven>
-    </prerequisites>
 </project>
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
index bf4fee0..e60ed90 100644
--- a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
@@ -90,7 +90,7 @@
      * Instantiates discovery manager for the given physical switch. Creates a
      * generic LLDP packet that will be customized for the port it is sent out on.
      * Starts the the timer for the discovery process.
-     *  @param device the physical switch
+     * @param device the physical switch
      * @param masterService
      * @param useBDDP flag to also use BDDP for discovery
      */
@@ -217,7 +217,7 @@
             final PortNumber srcPort = PortNumber.portNumber(onoslldp.getPort());
             final DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
             final DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
-            this.ackProbe(srcPort.toLong());
+            this.ackProbe(dstPort.toLong());
             ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
             ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
 
@@ -245,7 +245,7 @@
      */
     @Override
     public void run(final Timeout t) {
-        this.log.debug("sending probes");
+        this.log.trace("sending probes");
         synchronized (this) {
             final Iterator<Long> fastIterator = this.fastPorts.iterator();
             Long portNumber;
@@ -256,7 +256,7 @@
                         .getAndIncrement();
 
                 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
-                    this.log.debug("sending fast probe to port");
+                    this.log.trace("sending fast probe to port");
                     sendProbes(portNumber);
                 } else {
                     // Update fast and slow ports
@@ -278,7 +278,7 @@
                 Iterator<Long> slowIterator = this.slowPorts.iterator();
                 while (slowIterator.hasNext()) {
                     portNumber = slowIterator.next();
-                    this.log.debug("sending slow probe to port {}", portNumber);
+                    this.log.trace("sending slow probe to port {}", portNumber);
 
                     sendProbes(portNumber);
 
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
index cfc3134..e04d87c 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
@@ -23,6 +23,8 @@
 import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
 import org.projectfloodlight.openflow.protocol.OFInstructionType;
 import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.action.OFActionCircuit;
+import org.projectfloodlight.openflow.protocol.action.OFActionExperimenter;
 import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetDlSrc;
@@ -34,6 +36,7 @@
 import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
 import org.projectfloodlight.openflow.protocol.match.Match;
 import org.projectfloodlight.openflow.protocol.match.MatchField;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigidBasic;
 import org.projectfloodlight.openflow.types.IPv4Address;
 import org.projectfloodlight.openflow.types.Masked;
 import org.slf4j.Logger;
@@ -166,6 +169,15 @@
                     builder.setIpSrc(IpPrefix.valueOf(si.getInt()));
                 }
                 break;
+            case EXPERIMENTER:
+                OFActionExperimenter exp = (OFActionExperimenter) act;
+                if (exp.getExperimenter() == 0x80005A06) {
+                    OFActionCircuit ct = (OFActionCircuit) exp;
+                    builder.setLambda(((OFOxmOchSigidBasic) ct.getField()).getValue().getChannelNumber());
+                } else {
+                    log.warn("Unsupported OFActionExperimenter {}", exp.getExperimenter());
+                }
+                break;
             case SET_TP_DST:
             case SET_TP_SRC:
             case POP_MPLS:
@@ -188,7 +200,7 @@
             case DEC_MPLS_TTL:
             case DEC_NW_TTL:
             case ENQUEUE:
-            case EXPERIMENTER:
+
             case GROUP:
             default:
                 log.warn("Action type {} not yet implemented.", act.getType());
@@ -268,6 +280,10 @@
             case TCP_SRC:
                 builder.matchTcpSrc((short) match.get(MatchField.TCP_SRC).getPort());
                 break;
+            case OCH_SIGID:
+                builder.matchLambda(match.get(MatchField.OCH_SIGID).getChannelNumber());
+                break;
+            case OCH_SIGTYPE_BASIC:
             case ARP_OP:
             case ARP_SHA:
             case ARP_SPA:
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
index aa50833..e1fde8a 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
@@ -14,6 +14,7 @@
 import org.onlab.onos.net.flow.criteria.Criteria.EthTypeCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.IPProtocolCriterion;
+import org.onlab.onos.net.flow.criteria.Criteria.LambdaCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.PortCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.TcpPortCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.VlanIdCriterion;
@@ -21,6 +22,8 @@
 import org.onlab.onos.net.flow.criteria.Criterion;
 import org.onlab.onos.net.flow.instructions.Instruction;
 import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction.ModLambdaInstruction;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
@@ -35,6 +38,7 @@
 import org.projectfloodlight.openflow.protocol.action.OFAction;
 import org.projectfloodlight.openflow.protocol.match.Match;
 import org.projectfloodlight.openflow.protocol.match.MatchField;
+import org.projectfloodlight.openflow.types.CircuitSignalID;
 import org.projectfloodlight.openflow.types.EthType;
 import org.projectfloodlight.openflow.types.IPv4Address;
 import org.projectfloodlight.openflow.types.IpProtocol;
@@ -137,6 +141,9 @@
             case DROP:
                 log.warn("Saw drop action; assigning drop action");
                 return new LinkedList<>();
+            case L0MODIFICATION:
+                acts.add(buildL0Modification(i));
+                break;
             case L2MODIFICATION:
                 acts.add(buildL2Modification(i));
                 break;
@@ -157,6 +164,20 @@
         return acts;
     }
 
+    private OFAction buildL0Modification(Instruction i) {
+        L0ModificationInstruction l0m = (L0ModificationInstruction) i;
+        switch (l0m.subtype()) {
+        case LAMBDA:
+            ModLambdaInstruction ml = (ModLambdaInstruction) i;
+            return factory.actions().circuit(factory.oxms().ochSigidBasic(
+                    new CircuitSignalID((byte) 1, (byte) 2, ml.lambda(), (short) 1)));
+        default:
+            log.warn("Unimplemented action type {}.", l0m.subtype());
+            break;
+        }
+        return null;
+    }
+
     private OFAction buildL3Modification(Instruction i) {
         L3ModificationInstruction l3m = (L3ModificationInstruction) i;
         ModIPInstruction ip;
@@ -261,6 +282,11 @@
                 tp = (TcpPortCriterion) c;
                 mBuilder.setExact(MatchField.TCP_SRC, TransportPort.of(tp.tcpPort()));
                 break;
+            case OCH_SIGID:
+                LambdaCriterion lc = (LambdaCriterion) c;
+                mBuilder.setExact(MatchField.OCH_SIGID,
+                        new CircuitSignalID((byte) 1, (byte) 2, lc.lambda(), (short) 1));
+                break;
             case ARP_OP:
             case ARP_SHA:
             case ARP_SPA:
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
index 6fb54e8..8d3c018 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -10,7 +10,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -69,9 +69,11 @@
 import org.slf4j.Logger;
 
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ExecutionList;
+import com.google.common.util.concurrent.ListenableFuture;
 
 /**
  * Provider which uses an OpenFlow controller to detect network
@@ -97,6 +99,8 @@
 
     private final InternalFlowProvider listener = new InternalFlowProvider();
 
+    // FIXME: This should be an expiring map to ensure futures that don't have
+    // a future eventually get garbage collected.
     private final Map<Long, InstallationFuture> pendingFutures =
             new ConcurrentHashMap<Long, InstallationFuture>();
 
@@ -169,7 +173,7 @@
     }
 
     @Override
-    public Future<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch) {
+    public ListenableFuture<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch) {
         final Set<Dpid> sws =
                 Collections.newSetFromMap(new ConcurrentHashMap<Dpid, Boolean>());
         final Map<Long, FlowRuleBatchEntry> fmXids = new HashMap<Long, FlowRuleBatchEntry>();
@@ -330,18 +334,20 @@
 
     }
 
-    private class InstallationFuture implements Future<CompletedBatchOperation> {
+    private class InstallationFuture implements ListenableFuture<CompletedBatchOperation> {
 
         private final Set<Dpid> sws;
         private final AtomicBoolean ok = new AtomicBoolean(true);
         private final Map<Long, FlowRuleBatchEntry> fms;
 
-        private final List<FlowEntry> offendingFlowMods = Lists.newLinkedList();
+        private final Set<FlowEntry> offendingFlowMods = Sets.newHashSet();
 
         private final CountDownLatch countDownLatch;
         private Long pendingXid;
         private BatchState state;
 
+        private final ExecutionList executionList = new ExecutionList();
+
         public InstallationFuture(Set<Dpid> sws, Map<Long, FlowRuleBatchEntry> fmXids) {
             this.state = BatchState.STARTED;
             this.sws = sws;
@@ -350,6 +356,7 @@
         }
 
         public void fail(OFErrorMsg msg, Dpid dpid) {
+
             ok.set(false);
             removeRequirement(dpid);
             FlowEntry fe = null;
@@ -422,6 +429,9 @@
 
         @Override
         public boolean cancel(boolean mayInterruptIfRunning) {
+            if (isDone()) {
+                return false;
+            }
             ok.set(false);
             this.state = BatchState.CANCELLED;
             cleanUp();
@@ -434,7 +444,8 @@
                 }
 
             }
-            return isCancelled();
+            invokeCallbacks();
+            return true;
         }
 
         @Override
@@ -444,14 +455,15 @@
 
         @Override
         public boolean isDone() {
-            return this.state == BatchState.FINISHED;
+            return this.state == BatchState.FINISHED || isCancelled();
         }
 
         @Override
         public CompletedBatchOperation get() throws InterruptedException, ExecutionException {
             countDownLatch.await();
             this.state = BatchState.FINISHED;
-            return new CompletedBatchOperation(ok.get(), offendingFlowMods);
+            CompletedBatchOperation result = new CompletedBatchOperation(ok.get(), offendingFlowMods);
+            return result;
         }
 
         @Override
@@ -460,7 +472,8 @@
                 TimeoutException {
             if (countDownLatch.await(timeout, unit)) {
                 this.state = BatchState.FINISHED;
-                return new CompletedBatchOperation(ok.get(), offendingFlowMods);
+                CompletedBatchOperation result = new CompletedBatchOperation(ok.get(), offendingFlowMods);
+                return result;
             }
             throw new TimeoutException();
         }
@@ -478,10 +491,21 @@
 
         private void removeRequirement(Dpid dpid) {
             countDownLatch.countDown();
+            if (countDownLatch.getCount() == 0) {
+                invokeCallbacks();
+            }
             sws.remove(dpid);
             cleanUp();
         }
 
+        @Override
+        public void addListener(Runnable runnable, Executor executor) {
+            executionList.add(runnable, executor);
+        }
+
+        private void invokeCallbacks() {
+            executionList.execute();
+        }
     }
 
 }
diff --git a/tools/build/onos-build b/tools/build/onos-build
index cf2ebe4..2f31eeb 100755
--- a/tools/build/onos-build
+++ b/tools/build/onos-build
@@ -6,5 +6,4 @@
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
 . $ONOS_ROOT/tools/build/envDefaults
 
-cd $ONOS_ROOT
-mvn clean install && mvn javadoc:aggregate
+cd $ONOS_ROOT && mvn clean install && cd docs && mvn javadoc:aggregate
diff --git a/utils/misc/src/main/java/org/onlab/metrics/EventMetric.java b/utils/misc/src/main/java/org/onlab/metrics/EventMetric.java
new file mode 100644
index 0000000..464a123
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/metrics/EventMetric.java
@@ -0,0 +1,103 @@
+package org.onlab.metrics;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+
+/**
+ * Metric measurements for events.
+ */
+public class EventMetric {
+    private static final String GAUGE_TIMESTAMP_NAME = "Timestamp.EpochMs";
+    private static final String METER_RATE_NAME = "Rate";
+
+    private final MetricsService metricsService;
+    private final String componentName;
+    private final String featureName;
+
+    private MetricsComponent metricsComponent;
+    private MetricsFeature metricsFeature;
+
+    private volatile long lastEventTimestampEpochMs = 0;
+    private Gauge<Long> lastEventTimestampGauge;
+    private Meter eventRateMeter;
+
+    /**
+     * Constructor.
+     *
+     * @param metricsService the Metrics Service to use for Metrics
+     * registration and deregistration
+     * @param componentName the Metrics Component Name to use for Metrics
+     * registration and deregistration
+     * @param featureName the Metrics Feature Name to use for Metrics
+     * registration and deregistration
+     */
+    public EventMetric(MetricsService metricsService, String componentName,
+                       String featureName) {
+        this.metricsService = metricsService;
+        this.componentName = componentName;
+        this.featureName = featureName;
+    }
+
+    /**
+     * Registers the metrics.
+     */
+    public void registerMetrics() {
+        metricsComponent = metricsService.registerComponent(componentName);
+        metricsFeature = metricsComponent.registerFeature(featureName);
+
+        lastEventTimestampEpochMs = 0;
+        lastEventTimestampGauge =
+            metricsService.registerMetric(metricsComponent,
+                                          metricsFeature,
+                                          GAUGE_TIMESTAMP_NAME,
+                                          new Gauge<Long>() {
+                                              @Override
+                                              public Long getValue() {
+                                                  return lastEventTimestampEpochMs;
+                                              }
+                                          });
+
+        eventRateMeter = metricsService.createMeter(metricsComponent,
+                                                    metricsFeature,
+                                                    METER_RATE_NAME);
+    }
+
+    /**
+     * Removes the metrics.
+     */
+    public void removeMetrics() {
+        lastEventTimestampEpochMs = 0;
+        metricsService.removeMetric(metricsComponent,
+                                    metricsFeature,
+                                    GAUGE_TIMESTAMP_NAME);
+        metricsService.removeMetric(metricsComponent,
+                                    metricsFeature,
+                                    METER_RATE_NAME);
+    }
+
+    /**
+     * Updates the metric measurements for a single event.
+     */
+    public void eventReceived() {
+        lastEventTimestampEpochMs = System.currentTimeMillis();
+        eventRateMeter.mark(1);
+    }
+
+    /**
+     * Gets the last event timestamp Gauge (ms from the Epoch).
+     *
+     * @return the last event timestamp Gauge (ms from the Epoch)
+     */
+    public Gauge<Long> lastEventTimestampGauge() {
+        return lastEventTimestampGauge;
+    }
+
+    /**
+     * Gets the event rate meter.
+     *
+     * @return the event rate meter
+     */
+    public Meter eventRateMeter() {
+        return eventRateMeter;
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
index 5b48e63..4555124 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
@@ -32,7 +32,7 @@
      * @param value the value to use.
      */
     public ChassisId(String value) {
-        this.value = Long.valueOf(value, 16);
+        this.value = Long.parseLong(value, 16);
     }
 
     /**
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
index 2a116b1..119faf9 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
@@ -379,7 +379,7 @@
         // 300
         int optionsLength = 0;
         for (final DHCPOption option : this.options) {
-            if (option.getCode() == 0 || option.getCode() == 255) {
+            if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
                 optionsLength += 1;
             } else {
                 optionsLength += 2 + (0xff & option.getLength());
diff --git a/utils/misc/src/main/java/org/onlab/packet/IPv4.java b/utils/misc/src/main/java/org/onlab/packet/IPv4.java
index 4b9fd66..634ceff 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv4.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv4.java
@@ -438,7 +438,7 @@
 
         int result = 0;
         for (int i = 0; i < 4; ++i) {
-            result |= Integer.valueOf(octets[i]) << (3 - i) * 8;
+            result |= Integer.parseInt(octets[i]) << (3 - i) * 8;
         }
         return result;
     }
@@ -471,7 +471,7 @@
         int result = 0;
         for (int i = 0; i < 4; ++i) {
             result = ipAddress >> (3 - i) * 8 & 0xff;
-        sb.append(Integer.valueOf(result).toString());
+        sb.append(result);
         if (i != 3) {
             sb.append(".");
         }
diff --git a/utils/misc/src/main/java/org/onlab/util/HexString.java b/utils/misc/src/main/java/org/onlab/util/HexString.java
index db12aa3..2b91d8e 100644
--- a/utils/misc/src/main/java/org/onlab/util/HexString.java
+++ b/utils/misc/src/main/java/org/onlab/util/HexString.java
@@ -14,7 +14,7 @@
      */
     public static String toHexString(final byte[] bytes) {
         int i;
-        StringBuilder ret = new StringBuilder();
+        StringBuilder ret = new StringBuilder(bytes.length * 3 - 1);
         String tmp;
         for (i = 0; i < bytes.length; i++) {
             if (i > 0) {
@@ -31,22 +31,22 @@
 
     public static String toHexString(final long val, final int padTo) {
         char[] arr = Long.toHexString(val).toCharArray();
-        String ret = "";
+        StringBuilder ret = new StringBuilder(padTo * 3 - 1);
         // prepend the right number of leading zeros
         int i = 0;
         for (; i < (padTo * 2 - arr.length); i++) {
-            ret += "0";
+            ret.append('0');
             if ((i % 2) != 0) {
-                ret += ":";
+                ret.append(':');
             }
         }
         for (int j = 0; j < arr.length; j++) {
-            ret += arr[j];
+            ret.append(arr[j]);
             if ((((i + j) % 2) != 0) && (j < (arr.length - 1))) {
-                ret += ":";
+                ret.append(':');
             }
         }
-        return ret;
+        return ret.toString();
     }
 
     public static String toHexString(final long val) {
diff --git a/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
index 5ef1768..26d835d 100644
--- a/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
@@ -163,6 +163,7 @@
         handlers.putIfAbsent(type, handler);
     }
 
+    @Override
     public void unregisterHandler(String type) {
         handlers.remove(type);
     }
@@ -242,7 +243,7 @@
         }
     }
 
-    private class WriteTask implements Runnable {
+    private static class WriteTask implements Runnable {
 
         private final InternalMessage message;
         private final Channel channel;
diff --git a/web/gui/src/main/webapp/img/device.png b/web/gui/src/main/webapp/img/device.png
new file mode 100644
index 0000000..d608153
--- /dev/null
+++ b/web/gui/src/main/webapp/img/device.png
Binary files differ
diff --git a/web/gui/src/main/webapp/img/host.png b/web/gui/src/main/webapp/img/host.png
new file mode 100644
index 0000000..cacde17
--- /dev/null
+++ b/web/gui/src/main/webapp/img/host.png
Binary files differ
diff --git a/web/gui/src/main/webapp/img/onos-logo.png b/web/gui/src/main/webapp/img/onos-logo.png
new file mode 100644
index 0000000..afd87e1
--- /dev/null
+++ b/web/gui/src/main/webapp/img/onos-logo.png
Binary files differ
diff --git a/web/gui/src/main/webapp/opt.png b/web/gui/src/main/webapp/img/opt.png
similarity index 100%
rename from web/gui/src/main/webapp/opt.png
rename to web/gui/src/main/webapp/img/opt.png
Binary files differ
diff --git a/web/gui/src/main/webapp/pkt.png b/web/gui/src/main/webapp/img/pkt.png
similarity index 100%
rename from web/gui/src/main/webapp/pkt.png
rename to web/gui/src/main/webapp/img/pkt.png
Binary files differ
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index ebf25c5..19c9204 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -15,19 +15,18 @@
     <link rel="stylesheet" href="onos.css">
 
     <script src="geometry.js"></script>
-    <script src="onosui.js"></script>
+    <script src="onos.js"></script>
 
 </head>
 <body>
     <div id="frame">
         <div id="mast">
-            <span class="title">
-                ONOS Web UI
-            </span>
+            <img id="logo" src="img/onos-logo.png" width="60" height="38">
+            <span class="title">Open Network Operating System</span>
             <span class="right">
-                <span class="radio">[one]</span>
-                <span class="radio">[two]</span>
-                <span class="radio">[three]</span>
+                <span class="radio">[All Layers]</span>
+                <span class="radio">[Packet Only]</span>
+                <span class="radio">[Optical Only]</span>
             </span>
         </div>
         <div id="view"></div>
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index 80d11b7..c5145ad 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -10,17 +10,32 @@
     var api = onos.api;
 
     var config = {
-            layering: false,
+            options: {
+                layering: true,
+                collisionPrevention: true
+            },
             jsonUrl: 'network.json',
             iconUrl: {
-                pkt: 'pkt.png',
-                opt: 'opt.png'
+                device: 'img/device.png',
+                host: 'img/host.png',
+                pkt: 'img/pkt.png',
+                opt: 'img/opt.png'
             },
-            mastHeight: 32,
+            mastHeight: 36,
             force: {
-                linkDistance: 240,
-                linkStrength: 0.8,
-                charge: -400,
+                note: 'node.class or link.class is used to differentiate',
+                linkDistance: {
+                    infra: 240,
+                    host: 100
+                },
+                linkStrength: {
+                    infra: 1.0,
+                    host: 0.4
+                },
+                charge: {
+                    device: -800,
+                    host: -400
+                },
                 ticksWithoutCollisions: 50,
                 marginLR: 20,
                 marginTB: 20,
@@ -31,18 +46,27 @@
                 }
             },
             labels: {
-                imgPad: 22,
+                imgPad: 16,
                 padLR: 8,
                 padTB: 6,
                 marginLR: 3,
                 marginTB: 2
             },
+            icons: {
+                w: 32,
+                h: 32,
+                xoff: -12,
+                yoff: -8
+            },
             constraints: {
                 ypos: {
-                    pkt: 0.3,
-                    opt: 0.7
+                    host: 0.15,
+                    switch: 0.3,
+                    roadm: 0.7
                 }
-            }
+            },
+            hostLinkWidth: 1.0,
+            mouseOutTimerDelayMs: 120
         },
         view = {},
         network = {},
@@ -104,14 +128,23 @@
         var nw = network.forceWidth,
             nh = network.forceHeight;
 
-        network.data.nodes.forEach(function(n) {
+        function yPosConstraintForNode(n) {
+            return config.constraints.ypos[n.type || 'host'];
+        }
+
+        // Note that both 'devices' and 'hosts' get mapped into the nodes array
+
+        // first, the devices...
+        network.data.devices.forEach(function(n) {
             var ypc = yPosConstraintForNode(n),
                 ix = Math.random() * 0.6 * nw + 0.2 * nw,
                 iy = ypc * nh,
                 node = {
                     id: n.id,
+                    labels: n.labels,
+                    class: 'device',
+                    icon: 'device',
                     type: n.type,
-                    status: n.status,
                     x: ix,
                     y: iy,
                     constraint: {
@@ -123,21 +156,61 @@
             network.nodes.push(node);
         });
 
-        function yPosConstraintForNode(n) {
-            return config.constraints.ypos[n.type] || 0.5;
-        }
+        // then, the hosts...
+        network.data.hosts.forEach(function(n) {
+            var ypc = yPosConstraintForNode(n),
+                ix = Math.random() * 0.6 * nw + 0.2 * nw,
+                iy = ypc * nh,
+                node = {
+                    id: n.id,
+                    labels: n.labels,
+                    class: 'host',
+                    icon: 'host',
+                    type: n.type,
+                    x: ix,
+                    y: iy,
+                    constraint: {
+                        weight: 0.7,
+                        y: iy
+                    }
+                };
+            network.lookup[n.id] = node;
+            network.nodes.push(node);
+        });
 
 
+        // now, process the explicit links...
         network.data.links.forEach(function(n) {
             var src = network.lookup[n.src],
                 dst = network.lookup[n.dst],
                 id = src.id + "~" + dst.id;
 
             var link = {
+                class: 'infra',
                 id: id,
+                type: n.type,
+                width: n.linkWidth,
                 source: src,
                 target: dst,
-                strength: config.force.linkStrength
+                strength: config.force.linkStrength.infra
+            };
+            network.links.push(link);
+        });
+
+        // finally, infer host links...
+        network.data.hosts.forEach(function(n) {
+            var src = network.lookup[n.id],
+                dst = network.lookup[n.cp.device],
+                id = src.id + "~" + dst.id;
+
+            var link = {
+                class: 'host',
+                id: id,
+                type: 'hostLink',
+                width: config.hostLinkWidth,
+                source: src,
+                target: dst,
+                strength: config.force.linkStrength.host
             };
             network.links.push(link);
         });
@@ -145,13 +218,15 @@
 
     function createLayout() {
 
+        var cfg = config.force;
+
         network.force = d3.layout.force()
+            .size([network.forceWidth, network.forceHeight])
             .nodes(network.nodes)
             .links(network.links)
-            .linkStrength(function(d) { return d.strength; })
-            .size([network.forceWidth, network.forceHeight])
-            .linkDistance(config.force.linkDistance)
-            .charge(config.force.charge)
+            .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
+            .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
+            .charge(function(d) { return cfg.charge[d.class]; })
             .on('tick', tick);
 
         network.svg = d3.select('#view').append('svg')
@@ -205,9 +280,10 @@
         network.link = network.svg.append('g').selectAll('.link')
             .data(network.force.links(), function(d) {return d.id})
             .enter().append('line')
-            .attr('class', 'link');
+            .attr('class', function(d) {return 'link ' + d.class});
 
-        // TODO: drag behavior
+
+        // == define node drag behavior...
         network.draggedThreshold = d3.scale.linear()
             .domain([0, 0.1])
             .range([5, 20])
@@ -258,7 +334,11 @@
             .data(network.force.nodes(), function(d) {return d.id})
             .enter().append('g')
             .attr('class', function(d) {
-                return 'node ' + d.type;
+                var cls = 'node ' + d.class;
+                if (d.type) {
+                    cls += ' ' + d.type;
+                }
+                return cls;
             })
             .attr('transform', function(d) {
                 return translate(d.x, d.y);
@@ -281,29 +361,33 @@
                     }
                     network.mouseoutTimeout = setTimeout(function() {
                         highlightObject(null);
-                    }, 160);
+                    }, config.mouseOutTimerDelayMs);
                 }
             });
 
         network.nodeRect = network.node.append('rect')
             .attr('rx', 5)
-            .attr('ry', 5)
-            .attr('width', 126)
-            .attr('height', 40);
+            .attr('ry', 5);
+        // note that width/height are adjusted to fit the label text
 
         network.node.each(function(d) {
             var node = d3.select(this),
                 rect = node.select('rect'),
-                img = node.append('svg:image')
-                    .attr('x', -16)
-                    .attr('y', -16)
-                    .attr('width', 32)
-                    .attr('height', 32)
-                    .attr('xlink:href', iconUrl(d)),
+                icon = iconUrl(d),
                 text = node.append('text')
+                    // TODO: add label cycle behavior
                     .text(d.id)
-                    .attr('dy', '1.1em'),
-                dummy;
+                    .attr('dy', '1.1em');
+
+            if (icon) {
+                var cfg = config.icons;
+                node.append('svg:image')
+                    .attr('width', cfg.w)
+                    .attr('height', cfg.h)
+                    .attr('xlink:href', icon);
+                // note, icon relative positioning (x,y) is done after we have
+                // adjusted the bounds of the rectangle...
+            }
 
         });
 
@@ -352,7 +436,8 @@
                     .attr('height', bounds.y2 - bounds.y1);
 
                 node.select('image')
-                    .attr('x', bounds.x1);
+                    .attr('x', bounds.x1 + config.icons.xoff)
+                    .attr('y', bounds.y1 + config.icons.yoff);
 
                 d.extent = {
                     left: bounds.x1 - lab.marginLR,
@@ -384,7 +469,7 @@
     }
 
     function iconUrl(d) {
-        return config.iconUrl[d.type];
+        return config.iconUrl[d.icon];
     }
 
     function translate(x, y) {
@@ -440,7 +525,7 @@
     function tick(e) {
         network.numTicks++;
 
-        if (config.layering) {
+        if (config.options.layering) {
             // adjust the y-coord of each node, based on y-pos constraints
             network.nodes.forEach(function (n) {
                 var z = e.alpha * n.constraint.weight;
@@ -450,7 +535,7 @@
             });
         }
 
-        if (network.preventCollisions) {
+        if (config.options.collisionPrevention && network.preventCollisions) {
             preventCollisions();
         }
 
diff --git a/web/gui/src/main/webapp/network.json b/web/gui/src/main/webapp/network.json
index b4f1b6e..c43e0ef 100644
--- a/web/gui/src/main/webapp/network.json
+++ b/web/gui/src/main/webapp/network.json
@@ -1,56 +1,163 @@
 {
-    "id": "network-v1",
     "meta": {
-        "__comment_1__": "This is sample data for developing the ONOS UI",
-        "foo": "bar",
-        "zoo": "goo"
+        "comments": [
+            "This is sample data for developing the ONOS UI (network view)",
+            " in a standalone mode (no server required).",
+            " Eventually, we will wire this up to live data",
+            " from the server, via a websocket.",
+            "",
+            "Note that this is just a first-draft of the data --",
+            " additional fields will be added when they are needed."
+        ],
+        "otherMetaData": "can go here..."
     },
-    "nodes": [
+    "devices": [
         {
-            "id": "sample1",
-            "type": "opt",
-            "status": "good"
+            "id": "of:0000000000000001",
+            "labels": ["00:00:00:00:00:00:00:01", "of/::01", "opt-1"],
+            "type": "roadm"
         },
         {
-            "id": "00:00:00:00:00:00:00:02",
-            "type": "opt",
-            "status": "good"
+            "id": "of:0000000000000002",
+            "labels": ["00:00:00:00:00:00:00:02", "of/::02", "opt-2"],
+            "type": "roadm"
         },
         {
-            "id": "00:00:00:00:00:00:00:03",
-            "type": "opt",
-            "status": "good"
+            "id": "of:0000000000000003",
+            "labels": ["00:00:00:00:00:00:00:03", "of/::03", "opt-3"],
+            "type": "roadm"
         },
         {
-            "id": "00:00:00:00:00:00:00:04",
-            "type": "opt",
-            "status": "good"
+            "id": "of:0000000000000004",
+            "labels": ["00:00:00:00:00:00:00:04", "of/::04", "opt-4"],
+            "type": "roadm"
         },
         {
-            "id": "00:00:00:00:00:00:00:11",
-            "type": "pkt",
-            "status": "good"
+            "id": "of:0000000000000011",
+            "labels": ["00:00:00:00:00:00:00:11", "of/::11", "pkt-11"],
+            "type": "switch"
         },
         {
-            "id": "00:00:00:00:00:00:00:12",
-            "type": "pkt",
-            "status": "good"
+            "id": "of:0000000000000012",
+            "labels": ["00:00:00:00:00:00:00:12", "of/::12", "pkt-12"],
+            "type": "switch"
         },
         {
-            "id": "00:00:00:00:00:00:00:13",
-            "type": "pkt",
-            "status": "good"
+            "id": "of:0000000000000013",
+            "labels": ["00:00:00:00:00:00:00:13", "of/::13", "pkt-13"],
+            "type": "switch"
         }
     ],
+    "linkNotes": [
+        "even though we have 'directionality' (src/dst), each link is",
+        " considered to be bi-directional. Note that links between hosts",
+        " and edge switches are inferred from host information, and not",
+        " explicitly represented here."
+    ],
     "links": [
-        { "src": "sample1", "dst": "00:00:00:00:00:00:00:02" },
-        { "src": "sample1", "dst": "00:00:00:00:00:00:00:03" },
-        { "src": "sample1", "dst": "00:00:00:00:00:00:00:04" },
-        { "src": "00:00:00:00:00:00:00:02", "dst": "00:00:00:00:00:00:00:03" },
-        { "src": "00:00:00:00:00:00:00:02", "dst": "00:00:00:00:00:00:00:04" },
-        { "src": "00:00:00:00:00:00:00:03", "dst": "00:00:00:00:00:00:00:04" },
-        { "src": "00:00:00:00:00:00:00:13", "dst": "00:00:00:00:00:00:00:03" },
-        { "src": "00:00:00:00:00:00:00:12", "dst": "00:00:00:00:00:00:00:02" },
-        { "src": "00:00:00:00:00:00:00:11", "dst": "sample1" }
+        {
+            "src": "of:0000000000000001",
+            "dst": "of:0000000000000002",
+            "type": "optical",
+            "linkWidth": 1.5
+        },
+        {
+            "src": "of:0000000000000001",
+            "dst": "of:0000000000000003",
+            "type": "optical",
+            "linkWidth": 1.5
+        },
+        {
+            "src": "of:0000000000000001",
+            "dst": "of:0000000000000004",
+            "type": "optical",
+            "linkWidth": 1.5
+        },
+        {
+            "src": "of:0000000000000002",
+            "dst": "of:0000000000000003",
+            "type": "optical",
+            "linkWidth": 1.5
+        },
+        {
+            "src": "of:0000000000000002",
+            "dst": "of:0000000000000004",
+            "type": "optical",
+            "linkWidth": 1.5
+        },
+        {
+            "src": "of:0000000000000003",
+            "dst": "of:0000000000000004",
+            "type": "optical",
+            "linkWidth": 1.5
+        },
+        {
+            "src": "of:0000000000000013",
+            "dst": "of:0000000000000003",
+            "type": "direct",
+            "linkWidth": 1.0
+        },
+        {
+            "src": "of:0000000000000012",
+            "dst": "of:0000000000000002",
+            "type": "direct",
+            "linkWidth": 1.0
+        },
+        {
+            "src": "of:0000000000000011",
+            "dst": "of:0000000000000001",
+            "type": "direct",
+            "linkWidth": 1.0
+        }
+    ],
+    "hosts": [
+        {
+            "id": "00:60:d3:00:11:01/7",
+            "labels": ["00:60:d3:00:11:01/7", "Host-11-A"],
+            "cp" : {
+                "device": "of:0000000000000011",
+                "port": 6
+            }
+        },
+        {
+            "id": "00:60:d3:00:11:02/7",
+            "labels": ["00:60:d3:00:11:02/7", "Host-11-B"],
+            "cp" : {
+                "device": "of:0000000000000011",
+                "port": 8
+            }
+        },
+        {
+            "id": "00:60:d3:00:12:01/4",
+            "labels": ["00:60:d3:00:12:01/4", "Host-12-C"],
+            "cp" : {
+                "device": "of:0000000000000012",
+                "port": 12
+            }
+        },
+        {
+            "id": "00:60:d3:00:12:02/4",
+            "labels": ["00:60:d3:00:12:02/4", "Host-12-D"],
+            "cp" : {
+                "device": "of:0000000000000012",
+                "port": 13
+            }
+        },
+        {
+            "id": "00:60:d3:00:13:01/19",
+            "labels": ["00:60:d3:00:13:01/19", "Host-13-E"],
+            "cp" : {
+                "device": "of:0000000000000013",
+                "port": 7
+            }
+        },
+        {
+            "id": "00:60:d3:00:13:02/19",
+            "labels": ["00:60:d3:00:13:02/19", "Host-13-F"],
+            "cp" : {
+                "device": "of:0000000000000013",
+                "port": 8
+            }
+        }
     ]
 }
diff --git a/web/gui/src/main/webapp/onos.css b/web/gui/src/main/webapp/onos.css
index 340ae79..7f387eb 100644
--- a/web/gui/src/main/webapp/onos.css
+++ b/web/gui/src/main/webapp/onos.css
@@ -13,13 +13,15 @@
  */
 
 span.title {
-    color: darkblue;
-    font-size: 16pt;
+    color: #37b;
+    font-size: 14pt;
     font-style: italic;
+    vertical-align: 10px;
 }
 
 span.radio {
     color: darkslateblue;
+    font-size: 10pt;
 }
 
 span.right {
@@ -38,7 +40,7 @@
  * Network Graph elements ======================================
  */
 
-.link {
+svg .link {
     fill: none;
     stroke: #666;
     stroke-width: 1.5px;
@@ -56,7 +58,7 @@
     stroke-width: 1.5px;
 }
 
-.node rect {
+svg .node rect {
     stroke-width: 1.5px;
 
     transition: opacity 250ms;
@@ -64,13 +66,15 @@
     -moz-transition: opacity 250ms;
 }
 
-/*differentiate between packet and optical nodes*/
-svg .node.pkt rect {
-    fill: #77a;
+svg .node.device.roadm rect {
+    fill: #229;
+}
+svg .node.device.switch rect {
+    fill: #55f;
 }
 
-svg .node.opt rect {
-    fill: #7a7;
+svg .node.host rect {
+    fill: #787;
 }
 
 svg .node text {
@@ -121,15 +125,13 @@
 #frame {
     width: 100%;
     height: 100%;
-    background-color: #cdf;
+    background-color: #fff;
 }
 
 #mast {
-    height: 32px;
-    background-color: #abe;
+    height: 36px;
+    padding: 4px;
+    background-color: #ccc;
     vertical-align: baseline;
 }
 
-#main {
-    background-color: #99c;
-}
diff --git a/web/gui/src/main/webapp/onosui.js b/web/gui/src/main/webapp/onos.js
similarity index 100%
rename from web/gui/src/main/webapp/onosui.js
rename to web/gui/src/main/webapp/onos.js