Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java b/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
index f7f6478..117a9a0 100644
--- a/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
@@ -23,6 +23,10 @@
 import org.onlab.onos.cluster.ClusterEvent;
 import org.onlab.onos.cluster.ClusterEventListener;
 import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.mastership.MastershipEvent;
+import org.onlab.onos.mastership.MastershipListener;
+import org.onlab.onos.mastership.MastershipService;
 import org.onlab.onos.net.device.DeviceEvent;
 import org.onlab.onos.net.device.DeviceListener;
 import org.onlab.onos.net.device.DeviceService;
@@ -50,15 +54,20 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentService intentService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
     private final ClusterEventListener clusterListener = new InnerClusterListener();
     private final DeviceListener deviceListener = new InnerDeviceListener();
     private final IntentListener intentListener = new InnerIntentListener();
+    private final MastershipListener mastershipListener = new InnerMastershipListener();
 
     @Activate
     public void activate() {
         clusterService.addListener(clusterListener);
         deviceService.addListener(deviceListener);
         intentService.addListener(intentListener);
+        mastershipService.addListener(mastershipListener);
         log.info("Started");
     }
 
@@ -67,6 +76,7 @@
         clusterService.removeListener(clusterListener);
         deviceService.removeListener(deviceListener);
         intentService.removeListener(intentListener);
+        mastershipService.removeListener(mastershipListener);
         log.info("Stopped");
     }
 
@@ -100,6 +110,18 @@
             log.info(message, event.subject());
         }
     }
+
+    private class InnerMastershipListener implements MastershipListener {
+        @Override
+        public void event(MastershipEvent event) {
+            final NodeId myId = clusterService.getLocalNode().id();
+            if (myId.equals(event.roleInfo().master())) {
+                log.info("I have control/I wish you luck {}", event);
+            } else {
+                log.info("you have control {}", event);
+            }
+        }
+    }
 }
 
 
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
index 7d3bfc2..13f3000 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
@@ -15,14 +15,16 @@
  */
 package org.onlab.onos.cli.net;
 
+import java.util.List;
+
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
-import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.net.HostId;
 import org.onlab.onos.net.flow.DefaultTrafficSelector;
 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Constraint;
 import org.onlab.onos.net.intent.HostToHostIntent;
 import org.onlab.onos.net.intent.IntentService;
 
@@ -31,7 +33,7 @@
  */
 @Command(scope = "onos", name = "add-host-intent",
          description = "Installs host-to-host connectivity intent")
-public class AddHostToHostIntentCommand extends AbstractShellCommand {
+public class AddHostToHostIntentCommand extends ConnectivityIntentCommand {
 
     @Argument(index = 0, name = "one", description = "One host ID",
               required = true, multiValued = false)
@@ -50,9 +52,11 @@
 
         TrafficSelector selector = DefaultTrafficSelector.builder().build();
         TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+        List<Constraint> constraints = buildConstraints();
 
         HostToHostIntent intent = new HostToHostIntent(appId(), oneId, twoId,
-                                                       selector, treatment);
+                                                       selector, treatment,
+                                                       constraints);
         service.submit(intent);
     }
 
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddMultiPointToSinglePointIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddMultiPointToSinglePointIntentCommand.java
index 9e8e2fc..1cd695e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/AddMultiPointToSinglePointIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddMultiPointToSinglePointIntentCommand.java
@@ -23,11 +23,13 @@
 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Constraint;
 import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.IntentService;
 import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import static org.onlab.onos.net.DeviceId.deviceId;
@@ -69,9 +71,11 @@
 
         TrafficSelector selector = buildTrafficSelector();
         TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+        List<Constraint> constraints = buildConstraints();
 
         Intent intent = new MultiPointToSinglePointIntent(appId(), selector, treatment,
-                                                          ingressPoints, egress);
+                                                          ingressPoints, egress,
+                                                          constraints);
         service.submit(intent);
     }
 
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
index ed98c7e..26bb1c0 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.cli.net;
 
+import java.util.List;
+
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onlab.onos.net.ConnectPoint;
@@ -22,6 +24,7 @@
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Constraint;
 import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.IntentService;
 import org.onlab.onos.net.intent.PointToPointIntent;
@@ -63,8 +66,10 @@
         TrafficSelector selector = buildTrafficSelector();
         TrafficTreatment treatment = builder().build();
 
+        List<Constraint> constraints = buildConstraints();
+
         Intent intent = new PointToPointIntent(appId(), selector, treatment,
-                                               ingress, egress);
+                                               ingress, egress, constraints);
         service.submit(intent);
     }
 
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentWithBandwidthConstraintCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentWithBandwidthConstraintCommand.java
deleted file mode 100644
index 1590ae7..0000000
--- a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentWithBandwidthConstraintCommand.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 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.flow.TrafficSelector;
-import org.onlab.onos.net.flow.TrafficTreatment;
-import org.onlab.onos.net.intent.Intent;
-import org.onlab.onos.net.intent.IntentService;
-import org.onlab.onos.net.intent.PointToPointIntent;
-
-import static org.onlab.onos.net.DeviceId.deviceId;
-import static org.onlab.onos.net.PortNumber.portNumber;
-import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
-
-/**
- * Installs point-to-point connectivity intents.
- */
-@Command(scope = "onos", name = "add-point-intent-bw",
-         description = "Installs point-to-point connectivity intent with bandwidth constraint")
-public class AddPointToPointIntentWithBandwidthConstraintCommand 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;
-
-    @Argument(index = 2, name = "bandwidth",
-            description = "Bandwidth",
-            required = true, multiValued = false)
-    String bandwidthString = 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);
-
-        long bandwidth = Long.parseLong(bandwidthString);
-
-        TrafficSelector selector = buildTrafficSelector();
-        TrafficTreatment treatment = builder().build();
-
-        // FIXME: add bandwitdh constraint
-        Intent intent = new PointToPointIntent(
-                appId(), selector, treatment,
-                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/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
index d8ec3c7..e4fc5aa 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
@@ -15,14 +15,21 @@
  */
 package org.onlab.onos.cli.net;
 
+import java.util.LinkedList;
+import java.util.List;
+
 import org.apache.karaf.shell.commands.Option;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.net.flow.DefaultTrafficSelector;
 import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.intent.Constraint;
+import org.onlab.onos.net.intent.constraint.BandwidthConstraint;
+import org.onlab.onos.net.intent.constraint.LambdaConstraint;
+import org.onlab.onos.net.resource.Bandwidth;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.MacAddress;
 
-import com.google.common.base.Strings;
+import static com.google.common.base.Strings.isNullOrEmpty;
 
 /**
  * Base class for command line operations for connectivity based intents.
@@ -41,6 +48,14 @@
             required = false, multiValued = false)
     private String ethTypeString = "";
 
+    @Option(name = "-b", aliases = "--bandwidth", description = "Bandwidth",
+            required = false, multiValued = false)
+    private String bandwidthString = "";
+
+    @Option(name = "-l", aliases = "--lambda", description = "Lambda",
+            required = false, multiValued = false)
+    private boolean lambda = false;
+
     /**
      * Constructs a traffic selector based on the command line arguments
      * presented to the command.
@@ -50,21 +65,43 @@
         TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
         Short ethType = Ethernet.TYPE_IPV4;
 
-        if (!Strings.isNullOrEmpty(ethTypeString)) {
+        if (!isNullOrEmpty(ethTypeString)) {
             EthType ethTypeParameter = EthType.valueOf(ethTypeString);
             ethType = ethTypeParameter.value();
         }
         selectorBuilder.matchEthType(ethType);
 
-        if (!Strings.isNullOrEmpty(srcMacString)) {
+        if (!isNullOrEmpty(srcMacString)) {
             selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMacString));
         }
 
-        if (!Strings.isNullOrEmpty(dstMacString)) {
+        if (!isNullOrEmpty(dstMacString)) {
             selectorBuilder.matchEthDst(MacAddress.valueOf(dstMacString));
         }
 
         return selectorBuilder.build();
     }
 
+    /**
+     * Builds the constraint list for this command based on the command line
+     * parameters.
+     *
+     * @return List of constraint objects describing the constraints requested
+     */
+    protected List<Constraint> buildConstraints() {
+        final List<Constraint> constraints = new LinkedList<>();
+
+        // Check for a bandwidth specification
+        if (!isNullOrEmpty(bandwidthString)) {
+            final double bandwidthValue = Double.parseDouble(bandwidthString);
+            constraints.add(new BandwidthConstraint(Bandwidth.valueOf(bandwidthValue)));
+        }
+
+        // Check for a lambda specification
+        if (lambda) {
+            constraints.add(new LambdaConstraint(null));
+        }
+
+        return constraints;
+    }
 }
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
index 1d32aa5..29452f7 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -25,6 +25,7 @@
 import org.onlab.onos.cli.Comparators;
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.device.DeviceService;
 
 import java.util.ArrayList;
@@ -108,7 +109,7 @@
         for (Port port : service.getPorts(device.id())) {
             if (isIncluded(port)) {
                 ports.add(mapper.createObjectNode()
-                                  .put("port", port.number().toString())
+                                  .put("port", portName(port.number()))
                                   .put("isEnabled", port.isEnabled())
                                   .put("type", port.type().toString().toLowerCase())
                                   .put("portSpeed", port.portSpeed())
@@ -120,6 +121,10 @@
         return result;
     }
 
+    private String portName(PortNumber port) {
+        return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
+    }
+
     // Determines if a port should be included in output.
     private boolean isIncluded(Port port) {
         return enabled && port.isEnabled() || disabled && !port.isEnabled() ||
@@ -133,7 +138,8 @@
         Collections.sort(ports, Comparators.PORT_COMPARATOR);
         for (Port port : ports) {
             if (isIncluded(port)) {
-                print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled",
+                print(FMT, portName(port.number()),
+                      port.isEnabled() ? "enabled" : "disabled",
                       port.type().toString().toLowerCase(), port.portSpeed(),
                       annotations(port.annotations()));
             }
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 78b1dc1..2c87b18 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -116,17 +116,6 @@
             </optional-completers>
         </command>
         <command>
-            <action class="org.onlab.onos.cli.net.AddPointToPointIntentWithBandwidthConstraintCommand"/>
-            <completers>
-                <ref component-id="connectPointCompleter"/>
-                <ref component-id="connectPointCompleter"/>
-                <null/>
-            </completers>
-            <optional-completers>
-                <entry key="-t" value-ref="ethTypeCompleter"/>
-            </optional-completers>
-        </command>
-        <command>
             <action class="org.onlab.onos.cli.net.AddOpticalIntentCommand"/>
             <completers>
                 <ref component-id="connectPointCompleter"/>
diff --git a/core/api/pom.xml b/core/api/pom.xml
index 100e63f..ff94f1a 100644
--- a/core/api/pom.xml
+++ b/core/api/pom.xml
@@ -40,6 +40,10 @@
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
index 3fad93d..893270a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
@@ -105,6 +105,7 @@
                 .add("appId", appId())
                 .add("selector", selector())
                 .add("treatment", treatment())
+                .add("constraints", constraints())
                 .add("one", one)
                 .add("two", two)
                 .toString();
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
index 0c47aad..90907fb 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
@@ -22,6 +22,7 @@
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
+import java.util.List;
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -65,6 +66,38 @@
     }
 
     /**
+     * Creates a new multi-to-single point connectivity intent for the specified
+     * traffic selector and treatment.
+     *
+     * @param appId         application identifier
+     * @param selector      traffic selector
+     * @param treatment     treatment
+     * @param ingressPoints set of ports from which ingress traffic originates
+     * @param egressPoint   port to which traffic will egress
+     * @param constraints   constraints to apply to the intent
+     * @throws NullPointerException     if {@code ingressPoints} or
+     *                                  {@code egressPoint} is null.
+     * @throws IllegalArgumentException if the size of {@code ingressPoints} is
+     *                                  not more than 1
+     */
+    public MultiPointToSinglePointIntent(ApplicationId appId,
+                                         TrafficSelector selector,
+                                         TrafficTreatment treatment,
+                                         Set<ConnectPoint> ingressPoints,
+                                         ConnectPoint egressPoint,
+                                         List<Constraint> constraints) {
+        super(id(MultiPointToSinglePointIntent.class, selector, treatment,
+                ingressPoints, egressPoint), appId, null, selector, treatment,
+                constraints);
+
+        checkNotNull(ingressPoints);
+        checkArgument(!ingressPoints.isEmpty(), "Ingress point set cannot be empty");
+
+        this.ingressPoints = Sets.newHashSet(ingressPoints);
+        this.egressPoint = checkNotNull(egressPoint);
+    }
+
+    /**
      * Constructor for serializer.
      */
     protected MultiPointToSinglePointIntent() {
@@ -101,6 +134,7 @@
                 .add("treatment", treatment())
                 .add("ingress", ingressPoints())
                 .add("egress", egressPoint())
+                .add("constraints", constraints())
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
index 9189bae..9f8816d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.net.intent;
 
+import java.util.List;
+
 import com.google.common.base.MoreObjects;
 import org.onlab.onos.core.ApplicationId;
 import org.onlab.onos.net.Path;
@@ -46,6 +48,24 @@
     }
 
     /**
+     * Creates a new point-to-point intent with the supplied ingress/egress
+     * ports and using the specified explicit path.
+     *
+     * @param appId     application identifier
+     * @param selector  traffic selector
+     * @param treatment treatment
+     * @param path      traversed links
+     * @param constraints  optional list of constraints
+     * @throws NullPointerException {@code path} is null
+     */
+    public PathIntent(ApplicationId appId, TrafficSelector selector,
+                      TrafficTreatment treatment, Path path, List<Constraint> constraints) {
+        super(id(PathIntent.class, selector, treatment, path, constraints), appId,
+                resources(path.links()), selector, treatment, constraints);
+        this.path = path;
+    }
+
+    /**
      * Constructor for serializer.
      */
     protected PathIntent() {
@@ -75,6 +95,7 @@
                 .add("appId", appId())
                 .add("selector", selector())
                 .add("treatment", treatment())
+                .add("constraints", constraints())
                 .add("path", path)
                 .toString();
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/constraint/WaypointConstraint.java b/core/api/src/main/java/org/onlab/onos/net/intent/constraint/WaypointConstraint.java
new file mode 100644
index 0000000..2a1e3e3
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/constraint/WaypointConstraint.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed 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.net.intent.constraint;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onlab.onos.net.ElementId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.intent.Constraint;
+import org.onlab.onos.net.resource.LinkResourceService;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Constraint that evaluates elements passed through in order.
+ */
+public class WaypointConstraint implements Constraint {
+
+    private final List<ElementId> waypoints;
+
+    /**
+     * Creates a new waypoint constraint.
+     *
+     * @param waypoints waypoints
+     */
+    public WaypointConstraint(ElementId... waypoints) {
+        checkNotNull(waypoints, "waypoints cannot be null");
+        checkArgument(waypoints.length > 0, "length of waypoints should be more than 0");
+        this.waypoints = ImmutableList.copyOf(waypoints);
+    }
+
+    public List<ElementId> waypoints() {
+        return waypoints;
+    }
+
+    @Override
+    public double cost(Link link, LinkResourceService resourceService) {
+        // Always consider the number of hops
+        return 1;
+    }
+
+    @Override
+    public boolean validate(Path path, LinkResourceService resourceService) {
+        LinkedList<ElementId> waypoints = new LinkedList<>(this.waypoints);
+        ElementId current = waypoints.poll();
+        // This is safe because Path class ensures the number of links are more than 0
+        Link firstLink = path.links().get(0);
+        if (firstLink.src().elementId().equals(current)) {
+            current = waypoints.poll();
+        }
+
+        for (Link link : path.links()) {
+            if (link.dst().elementId().equals(current)) {
+                current = waypoints.poll();
+                // Empty waypoints means passing through all waypoints in the specified order
+                if (current == null) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(waypoints);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof WaypointConstraint)) {
+            return false;
+        }
+
+        final WaypointConstraint that = (WaypointConstraint) obj;
+        return Objects.equals(this.waypoints, that.waypoints);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("waypoints", waypoints)
+                .toString();
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/DatabaseAdminService.java b/core/api/src/main/java/org/onlab/onos/store/service/DatabaseAdminService.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/DatabaseAdminService.java
rename to core/api/src/main/java/org/onlab/onos/store/service/DatabaseAdminService.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/DatabaseException.java b/core/api/src/main/java/org/onlab/onos/store/service/DatabaseException.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/DatabaseException.java
rename to core/api/src/main/java/org/onlab/onos/store/service/DatabaseException.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/DatabaseService.java b/core/api/src/main/java/org/onlab/onos/store/service/DatabaseService.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/DatabaseService.java
rename to core/api/src/main/java/org/onlab/onos/store/service/DatabaseService.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/NoSuchTableException.java b/core/api/src/main/java/org/onlab/onos/store/service/NoSuchTableException.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/NoSuchTableException.java
rename to core/api/src/main/java/org/onlab/onos/store/service/NoSuchTableException.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/OptimisticLockException.java b/core/api/src/main/java/org/onlab/onos/store/service/OptimisticLockException.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/OptimisticLockException.java
rename to core/api/src/main/java/org/onlab/onos/store/service/OptimisticLockException.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/OptionalResult.java b/core/api/src/main/java/org/onlab/onos/store/service/OptionalResult.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/OptionalResult.java
rename to core/api/src/main/java/org/onlab/onos/store/service/OptionalResult.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/PreconditionFailedException.java b/core/api/src/main/java/org/onlab/onos/store/service/PreconditionFailedException.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/PreconditionFailedException.java
rename to core/api/src/main/java/org/onlab/onos/store/service/PreconditionFailedException.java
diff --git a/core/api/src/main/java/org/onlab/onos/store/service/ReadRequest.java b/core/api/src/main/java/org/onlab/onos/store/service/ReadRequest.java
new file mode 100644
index 0000000..9f7af4a
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/store/service/ReadRequest.java
@@ -0,0 +1,79 @@
+package org.onlab.onos.store.service;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Database read request.
+ */
+public class ReadRequest {
+
+    private final String tableName;
+    private final String key;
+
+    /**
+     * Creates a read request,
+     * which will retrieve the specified key from the table.
+     *
+     * @param tableName name of the table
+     * @param key       key in the table
+     * @return ReadRequest
+     */
+    public static ReadRequest get(String tableName, String key) {
+        return new ReadRequest(tableName, key);
+    }
+
+    public ReadRequest(String tableName, String key) {
+        this.tableName = checkNotNull(tableName);
+        this.key = checkNotNull(key);
+    }
+
+    /**
+     * Return the name of the table.
+     * @return table name.
+     */
+    public String tableName() {
+        return tableName;
+    }
+
+    /**
+     * Returns the key.
+     * @return key.
+     */
+    public String key() {
+        return key;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("tableName", tableName)
+                .add("key", key)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(key, tableName);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ReadRequest other = (ReadRequest) obj;
+        return Objects.equals(this.key, other.key) &&
+                Objects.equals(this.tableName, other.tableName);
+    }
+
+}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadResult.java b/core/api/src/main/java/org/onlab/onos/store/service/ReadResult.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/ReadResult.java
rename to core/api/src/main/java/org/onlab/onos/store/service/ReadResult.java
diff --git a/core/api/src/main/java/org/onlab/onos/store/service/VersionedValue.java b/core/api/src/main/java/org/onlab/onos/store/service/VersionedValue.java
new file mode 100644
index 0000000..ae6969c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/store/service/VersionedValue.java
@@ -0,0 +1,70 @@
+package org.onlab.onos.store.service;
+
+import java.util.Arrays;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Wrapper object that holds the object (as byte array) and its version.
+ */
+public class VersionedValue {
+
+    private final byte[] value;
+    private final long version;
+
+    /**
+     * Creates a new instance with the specified value and version.
+     * @param value value
+     * @param version version
+     */
+    public VersionedValue(byte[] value, long version) {
+        this.value = value;
+        this.version = version;
+    }
+
+    /**
+     * Returns the value.
+     * @return value.
+     */
+    public byte[] value() {
+        return value;
+    }
+
+    /**
+     * Returns the version.
+     * @return version.
+     */
+    public long version() {
+        return version;
+    }
+
+    /**
+     * Creates a copy of given VersionedValue.
+     *
+     * @param original VersionedValue to create a copy
+     * @return same as original if original or it's value is null,
+     *         otherwise creates a copy.
+     */
+    public static VersionedValue copy(VersionedValue original) {
+        if (original == null) {
+            return null;
+        }
+        if (original.value == null) {
+            // immutable, no need to copy
+            return original;
+        } else {
+            return new VersionedValue(
+                                      Arrays.copyOf(original.value,
+                                                    original.value.length),
+                                      original.version);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("version", version)
+                .add("value", Arrays.toString(value))
+                .toString();
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteAborted.java b/core/api/src/main/java/org/onlab/onos/store/service/WriteAborted.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/WriteAborted.java
rename to core/api/src/main/java/org/onlab/onos/store/service/WriteAborted.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteRequest.java b/core/api/src/main/java/org/onlab/onos/store/service/WriteRequest.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/WriteRequest.java
rename to core/api/src/main/java/org/onlab/onos/store/service/WriteRequest.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteResult.java b/core/api/src/main/java/org/onlab/onos/store/service/WriteResult.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/WriteResult.java
rename to core/api/src/main/java/org/onlab/onos/store/service/WriteResult.java
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/package-info.java b/core/api/src/main/java/org/onlab/onos/store/service/package-info.java
similarity index 100%
rename from core/store/dist/src/main/java/org/onlab/onos/store/service/package-info.java
rename to core/api/src/main/java/org/onlab/onos/store/service/package-info.java
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/constraint/WaypointConstraintTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/constraint/WaypointConstraintTest.java
new file mode 100644
index 0000000..3fbcbfe
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/constraint/WaypointConstraintTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed 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.net.intent.constraint;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.DefaultPath;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.intent.Constraint;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.resource.LinkResourceService;
+
+import java.util.Arrays;
+
+import static org.easymock.EasyMock.createMock;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.onlab.onos.net.DefaultLinkTest.cp;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Link.Type.DIRECT;
+
+/**
+ * Test for constraint of intermediate elements.
+ */
+public class WaypointConstraintTest {
+
+    public static final DeviceId DID1 = deviceId("of:1");
+    public static final DeviceId DID2 = deviceId("of:2");
+    public static final DeviceId DID3 = deviceId("of:3");
+    public static final DeviceId DID4 = deviceId("of:4");
+    public static final PortNumber PN1 = PortNumber.portNumber(1);
+    public static final PortNumber PN2 = PortNumber.portNumber(2);
+    public static final PortNumber PN3 = PortNumber.portNumber(3);
+    public static final PortNumber PN4 = PortNumber.portNumber(4);
+    public static final ProviderId PROVIDER_ID = new ProviderId("of", "foo");
+
+    private WaypointConstraint sut;
+    private LinkResourceService linkResourceService;
+
+    private Path path;
+    private DefaultLink link2;
+    private DefaultLink link1;
+
+    @Before
+    public void setUp() {
+        linkResourceService = createMock(LinkResourceService.class);
+
+        link1 = new DefaultLink(PROVIDER_ID, cp(DID1, PN1), cp(DID2, PN2), DIRECT);
+        link2 = new DefaultLink(PROVIDER_ID, cp(DID2, PN3), cp(DID3, PN4), DIRECT);
+        path = new DefaultPath(PROVIDER_ID, Arrays.asList(link1, link2), 10);
+    }
+
+    /**
+     * Tests that all of the specified waypoints are included in the specified path in order.
+     */
+    @Test
+    public void testSatisfyWaypoints() {
+        sut = new WaypointConstraint(DID1, DID2, DID3);
+
+        assertThat(sut.validate(path, linkResourceService), is(true));
+    }
+
+    /**
+     * Tests that the specified path does not includes the specified waypoint.
+     */
+    @Test
+    public void testNotSatisfyWaypoint() {
+        sut = new WaypointConstraint(DID4);
+
+        assertThat(sut.validate(path, linkResourceService), is(false));
+    }
+
+    @Test
+    public void testEquality() {
+        Constraint c1 = new WaypointConstraint(DID1, DID2);
+        Constraint c2 = new WaypointConstraint(DID1, DID2);
+
+        Constraint c3 = new WaypointConstraint(DID2);
+        Constraint c4 = new WaypointConstraint(DID3);
+
+        new EqualsTester()
+                .addEqualityGroup(c1, c2)
+                .addEqualityGroup(c3)
+                .addEqualityGroup(c4)
+                .testEquals();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/ConnectivityIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ConnectivityIntentCompiler.java
index 4cf1830..8a31900 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/ConnectivityIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ConnectivityIntentCompiler.java
@@ -118,13 +118,14 @@
 
         @Override
         public double weight(TopologyEdge edge) {
-            if (constraints == null) {
+            if (constraints == null || !constraints.iterator().hasNext()) {
                 return 1.0;
             }
 
             // iterate over all constraints in order and return the weight of
             // the first one with fast fail over the first failure
             Iterator<Constraint> it = constraints.iterator();
+
             double cost = it.next().cost(edge.link(), resourceService);
             while (it.hasNext() && cost > 0) {
                 if (it.next().cost(edge.link(), resourceService) < 0) {
@@ -132,6 +133,7 @@
                 }
             }
             return cost;
+
         }
     }
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
index 605d3c7..4d989ac 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
@@ -70,7 +70,8 @@
                                     HostToHostIntent intent) {
         TrafficSelector selector = builder(intent.selector())
                 .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build();
-        return new PathIntent(intent.appId(), selector, intent.treatment(), path);
+        return new PathIntent(intent.appId(), selector, intent.treatment(),
+                              path, intent.constraints());
     }
 
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
index 31df87a..af2af24 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
@@ -77,7 +77,7 @@
 @Service
 public class IntentManager
         implements IntentService, IntentExtensionService {
-    private final Logger log = getLogger(getClass());
+    private static final Logger log = getLogger(IntentManager.class);
 
     public static final String INTENT_NULL = "Intent cannot be null";
     public static final String INTENT_ID_NULL = "Intent ID cannot be null";
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
index c32c8ee..5c8eb94 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
@@ -77,7 +77,8 @@
     private Intent createPathIntent(Path path,
                                     PointToPointIntent intent) {
         return new PathIntent(intent.appId(),
-                              intent.selector(), intent.treatment(), path);
+                              intent.selector(), intent.treatment(), path,
+                              intent.constraints());
     }
 
 }
diff --git a/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java b/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
index ddd805f..33961e4 100644
--- a/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
@@ -28,6 +28,7 @@
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.event.impl.TestEventDispatcher;
 import org.onlab.onos.mastership.MastershipService;
+import org.onlab.onos.mastership.MastershipStore;
 import org.onlab.onos.mastership.MastershipTermService;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.store.trivial.impl.SimpleMastershipStore;
@@ -57,9 +58,9 @@
     public void setUp() {
         mgr = new MastershipManager();
         service = mgr;
-        mgr.store = new SimpleMastershipStore();
         mgr.eventDispatcher = new TestEventDispatcher();
         mgr.clusterService = new TestClusterService();
+        mgr.store = new TestSimpleMastershipStore(mgr.clusterService);
         mgr.activate();
     }
 
@@ -74,7 +75,8 @@
     @Test
     public void setRole() {
         mgr.setRole(NID_OTHER, DEV_MASTER, MASTER);
-        assertEquals("wrong local role:", STANDBY, mgr.getLocalRole(DEV_MASTER));
+        assertEquals("wrong local role:", NONE, mgr.getLocalRole(DEV_MASTER));
+        assertEquals("wrong obtained role:", STANDBY, mgr.requestRoleFor(DEV_MASTER));
 
         //set to master
         mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
@@ -182,4 +184,12 @@
         }
 
     }
+
+    private final class TestSimpleMastershipStore extends SimpleMastershipStore
+            implements MastershipStore {
+
+        public TestSimpleMastershipStore(ClusterService clusterService) {
+            super.clusterService = clusterService;
+        }
+    }
 }
diff --git a/core/store/dist/pom.xml b/core/store/dist/pom.xml
index 248e763..f0f37c7 100644
--- a/core/store/dist/pom.xml
+++ b/core/store/dist/pom.xml
@@ -64,6 +64,12 @@
 -->
 
         <dependency>
+            <groupId>org.mapdb</groupId>
+            <artifactId>mapdb</artifactId>
+            <version>1.0.6</version>
+        </dependency>
+
+        <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
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 38e1322..849ad17 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
@@ -159,7 +159,7 @@
             return messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message));
 
         } catch (IOException e) {
-            log.error("Failed interaction with remote nodeId: " + toNodeId, e);
+            log.trace("Failed interaction with remote nodeId: " + toNodeId, e);
             throw e;
         }
     }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index 42e0799..6c2ad6a 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -283,16 +283,15 @@
                 case MASTER:
                     NodeId newMaster = reelect(nodeId, deviceId, rv);
                     rv.reassign(nodeId, NONE, STANDBY);
+                    updateTerm(deviceId);
                     if (newMaster != null) {
-                        updateTerm(deviceId);
                         roleMap.put(deviceId, rv);
                         return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
                     } else {
                         // no master candidate
                         roleMap.put(deviceId, rv);
-                        // FIXME: Should there be new event type?
-                        // or should we issue null Master event?
-                        return null;
+                        // TODO: Should there be new event type for no MASTER?
+                        return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
                     }
                 case STANDBY:
                     return null;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java
deleted file mode 100644
index a22464a..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.onlab.onos.store.service;
-
-import com.google.common.base.MoreObjects;
-
-/**
- * Database read request.
- */
-public class ReadRequest {
-
-    private final String tableName;
-    private final String key;
-
-    public ReadRequest(String tableName, String key) {
-        this.tableName = tableName;
-        this.key = key;
-    }
-
-    /**
-     * Return the name of the table.
-     * @return table name.
-     */
-    public String tableName() {
-        return tableName;
-    }
-
-    /**
-     * Returns the key.
-     * @return key.
-     */
-    public String key() {
-        return key;
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("tableName", tableName)
-                .add("key", key)
-                .toString();
-    }
-}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java
deleted file mode 100644
index 852fb07..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.onlab.onos.store.service;
-
-import java.util.Arrays;
-
-import com.google.common.base.MoreObjects;
-
-/**
- * Wrapper object that holds the object (as byte array) and its version.
- */
-public class VersionedValue {
-
-    private final byte[] value;
-    private final long version;
-
-    /**
-     * Creates a new instance with the specified value and version.
-     * @param value value
-     * @param version version
-     */
-    public VersionedValue(byte[] value, long version) {
-        this.value = value;
-        this.version = version;
-    }
-
-    /**
-     * Returns the value.
-     * @return value.
-     */
-    public byte[] value() {
-        return value;
-    }
-
-    /**
-     * Returns the version.
-     * @return version.
-     */
-    public long version() {
-        return version;
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("version", version)
-                .add("value", Arrays.toString(value))
-                .toString();
-    }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
index c561221..56dba79 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
@@ -149,12 +149,12 @@
 
     @Activate
     public void activate() {
-        log.info("Started.");
+        log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
-        log.info("Stopped.");
+        log.info("Stopped");
     }
 
     @Override
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java
index 0c2aacd..582e00c 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolClient.java
@@ -132,8 +132,8 @@
             } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
                 if (message.subject().equals(ClusterMessagingProtocol.COPYCAT_SYNC) ||
                         message.subject().equals(ClusterMessagingProtocol.COPYCAT_PING)) {
-                    log.warn("Request to {} failed. Will retry "
-                            + "in {} ms", remoteNode, RETRY_INTERVAL_MILLIS);
+                    log.warn("{} Request to {} failed. Will retry in {} ms",
+                             message.subject(), remoteNode, RETRY_INTERVAL_MILLIS);
                     THREAD_POOL.schedule(
                             this,
                             RETRY_INTERVAL_MILLIS,
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolServer.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolServer.java
index 7d94847..b3eaeb4 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolServer.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocolServer.java
@@ -3,12 +3,17 @@
 import static org.slf4j.LoggerFactory.getLogger;
 
 import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
 
 import net.kuujo.copycat.protocol.PingRequest;
+import net.kuujo.copycat.protocol.PingResponse;
 import net.kuujo.copycat.protocol.PollRequest;
+import net.kuujo.copycat.protocol.PollResponse;
 import net.kuujo.copycat.protocol.RequestHandler;
 import net.kuujo.copycat.protocol.SubmitRequest;
+import net.kuujo.copycat.protocol.SubmitResponse;
 import net.kuujo.copycat.protocol.SyncRequest;
+import net.kuujo.copycat.protocol.SyncResponse;
 import net.kuujo.copycat.spi.protocol.ProtocolServer;
 
 import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
@@ -57,37 +62,37 @@
         public void handle(ClusterMessage message) {
             T request = ClusterMessagingProtocol.SERIALIZER.decode(message.payload());
             if (request.getClass().equals(PingRequest.class)) {
-                handler.ping((PingRequest) request).whenComplete((response, error) -> {
-                    try {
-                        message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response));
-                    } catch (Exception e) {
-                        log.error("Failed to respond to ping request", e);
-                    }
-                });
+                handler.ping((PingRequest) request).whenComplete(new PostExecutionTask<PingResponse>(message));
             } else if (request.getClass().equals(PollRequest.class)) {
-                handler.poll((PollRequest) request).whenComplete((response, error) -> {
-                    try {
-                        message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response));
-                    } catch (Exception e) {
-                        log.error("Failed to respond to poll request", e);
-                    }
-                });
+                handler.poll((PollRequest) request).whenComplete(new PostExecutionTask<PollResponse>(message));
             } else if (request.getClass().equals(SyncRequest.class)) {
-                handler.sync((SyncRequest) request).whenComplete((response, error) -> {
-                    try {
-                        message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response));
-                    } catch (Exception e) {
-                        log.error("Failed to respond to sync request", e);
-                    }
-                });
+                handler.sync((SyncRequest) request).whenComplete(new PostExecutionTask<SyncResponse>(message));
             } else if (request.getClass().equals(SubmitRequest.class)) {
-                handler.submit((SubmitRequest) request).whenComplete((response, error) -> {
+                handler.submit((SubmitRequest) request).whenComplete(new PostExecutionTask<SubmitResponse>(message));
+            } else {
+                throw new IllegalStateException("Unknown request type: " + request.getClass().getName());
+            }
+        }
+
+        private class PostExecutionTask<R> implements BiConsumer<R, Throwable> {
+
+            private final ClusterMessage message;
+
+            public PostExecutionTask(ClusterMessage message) {
+                this.message = message;
+            }
+
+            @Override
+            public void accept(R response, Throwable t) {
+                if (t != null) {
+                    log.error("Processing for " + message.subject() + " failed.", t);
+                } else {
                     try {
                         message.respond(ClusterMessagingProtocol.SERIALIZER.encode(response));
                     } catch (Exception e) {
-                        log.error("Failed to respond to submit request", e);
+                        log.error("Failed to respond to " + response.getClass().getName(), e);
                     }
-                });
+                }
             }
         }
     }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
index 183a6db..2779b35 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
@@ -5,20 +5,26 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import net.kuujo.copycat.Copycat;
 import net.kuujo.copycat.StateMachine;
+import net.kuujo.copycat.cluster.ClusterConfig;
 import net.kuujo.copycat.cluster.TcpCluster;
 import net.kuujo.copycat.cluster.TcpClusterConfig;
 import net.kuujo.copycat.cluster.TcpMember;
 import net.kuujo.copycat.log.InMemoryLog;
 import net.kuujo.copycat.log.Log;
+
 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.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterEvent;
+import org.onlab.onos.cluster.ClusterEventListener;
 import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.store.service.DatabaseAdminService;
@@ -35,8 +41,6 @@
 import org.onlab.onos.store.service.WriteResult;
 import org.slf4j.Logger;
 
-import com.google.common.collect.Lists;
-
 /**
  * Strongly consistent and durable state management service based on
  * Copycat implementation of Raft consensus protocol.
@@ -58,17 +62,34 @@
     private Copycat copycat;
     private DatabaseClient client;
 
+    // guarded by synchronized block
+    private ClusterConfig<TcpMember> clusterConfig;
+
+    private CountDownLatch clusterEventLatch;
+
+    private ClusterEventListener clusterEventListener;
+
     @Activate
     public void activate() {
-        log.info("Starting.");
 
-        // TODO: Not every node can be part of the consensus ring.
+        // TODO: Not every node should be part of the consensus ring.
 
+        final ControllerNode localNode = clusterService.getLocalNode();
         TcpMember localMember =
                 new TcpMember(
-                        clusterService.getLocalNode().ip().toString(),
-                        clusterService.getLocalNode().tcpPort());
-        List<TcpMember> remoteMembers = Lists.newArrayList();
+                        localNode.ip().toString(),
+                        localNode.tcpPort());
+
+        clusterConfig = new TcpClusterConfig();
+        clusterConfig.setLocalMember(localMember);
+
+        List<TcpMember> remoteMembers = new ArrayList<>(clusterService.getNodes().size());
+
+        clusterEventLatch = new CountDownLatch(1);
+        clusterEventListener = new InternalClusterEventListener();
+        clusterService.addListener(clusterEventListener);
+
+        // note: from this point beyond, clusterConfig requires synchronization
 
         for (ControllerNode node : clusterService.getNodes()) {
             TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort());
@@ -77,20 +98,37 @@
             }
         }
 
-        // Configure the cluster.
-        TcpClusterConfig config = new TcpClusterConfig();
+        if (remoteMembers.isEmpty()) {
+            log.info("This node is the only node in the cluster.  "
+                    + "Waiting for others to show up.");
+            // FIXME: hack trying to relax cases forming multiple consensus rings.
+            // add seed node configuration to avoid this
 
-        config.setLocalMember(localMember);
-        config.setRemoteMembers(remoteMembers.toArray(new TcpMember[]{}));
+            // If the node is alone on it's own, wait some time
+            // hoping other will come up soon
+            try {
+                if (!clusterEventLatch.await(120, TimeUnit.SECONDS)) {
+                    log.info("Starting as single node cluster");
+                }
+            } catch (InterruptedException e) {
+                log.info("Interrupted waiting for others", e);
+            }
+        }
 
-        // Create the cluster.
-        TcpCluster cluster = new TcpCluster(config);
+        final TcpCluster cluster;
+        synchronized (clusterConfig) {
+            clusterConfig.addRemoteMembers(remoteMembers);
+
+            // Create the cluster.
+            cluster = new TcpCluster(clusterConfig);
+        }
+        log.info("Starting cluster: {}", cluster);
+
 
         StateMachine stateMachine = new DatabaseStateMachine();
-        ControllerNode thisNode = clusterService.getLocalNode();
         // FIXME resolve Chronicle + OSGi issue
         //Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id());
-        Log consensusLog = new InMemoryLog();
+        Log consensusLog = new KryoRegisteredInMemoryLog();
 
         copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol);
         copycat.start();
@@ -102,6 +140,7 @@
 
     @Deactivate
     public void deactivate() {
+        clusterService.removeListener(clusterEventListener);
         copycat.stop();
         log.info("Stopped.");
     }
@@ -179,6 +218,53 @@
 
     }
 
+    private final class InternalClusterEventListener
+            implements ClusterEventListener {
+
+        @Override
+        public void event(ClusterEvent event) {
+            // TODO: Not every node should be part of the consensus ring.
+
+            final ControllerNode node = event.subject();
+            final TcpMember tcpMember = new TcpMember(node.ip().toString(),
+                                                      node.tcpPort());
+
+            log.trace("{}", event);
+            switch (event.type()) {
+            case INSTANCE_ACTIVATED:
+            case INSTANCE_ADDED:
+                log.info("{} was added to the cluster", tcpMember);
+                synchronized (clusterConfig) {
+                    clusterConfig.addRemoteMember(tcpMember);
+                }
+                break;
+            case INSTANCE_DEACTIVATED:
+            case INSTANCE_REMOVED:
+                log.info("{} was removed from the cluster", tcpMember);
+                synchronized (clusterConfig) {
+                    clusterConfig.removeRemoteMember(tcpMember);
+                }
+                break;
+            default:
+                break;
+            }
+            if (copycat != null) {
+                log.debug("Current cluster: {}", copycat.cluster());
+            }
+            clusterEventLatch.countDown();
+        }
+
+    }
+
+    public static final class KryoRegisteredInMemoryLog extends InMemoryLog {
+        public KryoRegisteredInMemoryLog() {
+            super();
+            // required to deserialize object across bundles
+            super.kryo.register(TcpMember.class, new TcpMemberSerializer());
+            super.kryo.register(TcpClusterConfig.class, new TcpClusterConfigSerializer());
+        }
+    }
+
     private class DatabaseOperationResult<R, E extends DatabaseException> implements OptionalResult<R, E> {
 
         private final R result;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
index ad6773e..9662976 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
@@ -1,10 +1,10 @@
 package org.onlab.onos.store.service.impl;
 
+import static org.slf4j.LoggerFactory.getLogger;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-
 import net.kuujo.copycat.Command;
 import net.kuujo.copycat.Query;
 import net.kuujo.copycat.StateMachine;
@@ -16,7 +16,9 @@
 import org.onlab.onos.store.service.WriteRequest;
 import org.onlab.onos.store.service.WriteResult;
 import org.onlab.util.KryoNamespace;
+import org.slf4j.Logger;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 
 /**
@@ -28,6 +30,8 @@
  */
 public class DatabaseStateMachine implements StateMachine {
 
+    private final Logger log = getLogger(getClass());
+
     public static final KryoSerializer SERIALIZER = new KryoSerializer() {
         @Override
         protected void setupKryoPool() {
@@ -59,8 +63,8 @@
     }
 
     @Query
-    public Set<String> listTables() {
-        return state.getTables().keySet();
+    public List<String> listTables() {
+        return ImmutableList.copyOf(state.getTables().keySet());
     }
 
     @Query
@@ -72,7 +76,7 @@
                 results.add(new InternalReadResult(InternalReadResult.Status.NO_SUCH_TABLE, null));
                 continue;
             }
-            VersionedValue value = table.get(request.key());
+            VersionedValue value = VersionedValue.copy(table.get(request.key()));
             results.add(new InternalReadResult(
                     InternalReadResult.Status.OK,
                     new ReadResult(
@@ -85,6 +89,8 @@
 
     @Command
     public List<InternalWriteResult> write(List<WriteRequest> requests) {
+
+        // applicability check
         boolean abort = false;
         List<InternalWriteResult.Status> validationResults = new ArrayList<>(requests.size());
         for (WriteRequest request : requests) {
@@ -128,8 +134,13 @@
             return results;
         }
 
+        // apply changes
         for (WriteRequest request : requests) {
             Map<String, VersionedValue> table = state.getTables().get(request.tableName());
+            // FIXME: If this method could be called by multiple thread,
+            // synchronization scope is wrong.
+            // Whole function including applicability check needs to be protected.
+            // Confirm copycat's thread safety requirement for StateMachine
             synchronized (table) {
                 VersionedValue previousValue =
                         table.put(request.key(), new VersionedValue(request.newValue(), state.nextVersion()));
@@ -161,8 +172,8 @@
         try {
             return SERIALIZER.encode(state);
         } catch (Exception e) {
-            e.printStackTrace();
-            return null;
+            log.error("Failed to take snapshot", e);
+            throw new SnapshotException(e);
         }
     }
 
@@ -171,7 +182,8 @@
         try {
             this.state = SERIALIZER.decode(data);
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Failed to install from snapshot", e);
+            throw new SnapshotException(e);
         }
     }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/MapDBLog.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/MapDBLog.java
new file mode 100644
index 0000000..893c311
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/MapDBLog.java
@@ -0,0 +1,280 @@
+package org.onlab.onos.store.service.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentNavigableMap;
+
+import net.kuujo.copycat.log.Entry;
+import net.kuujo.copycat.log.Log;
+import net.kuujo.copycat.log.LogIndexOutOfBoundsException;
+
+import org.mapdb.Atomic;
+import org.mapdb.BTreeMap;
+import org.mapdb.DB;
+import org.mapdb.DBMaker;
+import org.mapdb.TxBlock;
+import org.mapdb.TxMaker;
+import org.onlab.onos.store.serializers.StoreSerializer;
+
+import com.google.common.collect.Lists;
+
+/**
+ * MapDB based log implementation.
+ */
+public class MapDBLog implements Log {
+
+    private final File dbFile;
+    private TxMaker txMaker;
+    private final StoreSerializer serializer;
+    private static final String LOG_NAME = "log";
+    private static final String SIZE_FIELD_NAME = "size";
+
+    public MapDBLog(File dbFile, StoreSerializer serializer) {
+        this.dbFile = dbFile;
+        this.serializer = serializer;
+    }
+
+    @Override
+    public void open() throws IOException {
+        txMaker = DBMaker
+                .newFileDB(dbFile)
+                .makeTxMaker();
+    }
+
+    @Override
+    public void close() throws IOException {
+        assertIsOpen();
+        txMaker.close();
+        txMaker = null;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return txMaker != null;
+    }
+
+    protected void assertIsOpen() {
+        checkState(isOpen(), "The log is not currently open.");
+    }
+
+    @Override
+    public long appendEntry(Entry entry) {
+        checkArgument(entry != null, "expecting non-null entry");
+        return appendEntries(entry).get(0);
+    }
+
+    @Override
+    public List<Long> appendEntries(Entry... entries) {
+        checkArgument(entries != null, "expecting non-null entries");
+        return appendEntries(Arrays.asList(entries));
+    }
+
+    @Override
+    public List<Long> appendEntries(List<Entry> entries) {
+        assertIsOpen();
+        checkArgument(entries != null, "expecting non-null entries");
+        final List<Long> indices = Lists.newArrayList();
+
+        txMaker.execute(new TxBlock() {
+            @Override
+            public void tx(DB db) {
+                BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+                Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
+                long nextIndex = log.isEmpty() ? 1 : log.lastKey() + 1;
+                for (Entry entry : entries) {
+                    byte[] entryBytes = serializer.encode(entry);
+                    log.put(nextIndex, entryBytes);
+                    size.addAndGet(entryBytes.length);
+                    indices.add(nextIndex);
+                    nextIndex++;
+                }
+            }
+        });
+
+        return indices;
+    }
+
+    @Override
+    public boolean containsEntry(long index) {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            return log.containsKey(index);
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public void delete() throws IOException {
+        assertIsOpen();
+        txMaker.execute(new TxBlock() {
+            @Override
+            public void tx(DB db) {
+                BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+                Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
+                log.clear();
+                size.set(0);
+            }
+        });
+    }
+
+    @Override
+    public <T extends Entry> T firstEntry() {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            return log.isEmpty() ? null : serializer.decode(log.firstEntry().getValue());
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public long firstIndex() {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            return log.isEmpty() ? 0 : log.firstKey();
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public <T extends Entry> List<T> getEntries(long from, long to) {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            if (log.isEmpty()) {
+                throw new LogIndexOutOfBoundsException("Log is empty");
+            } else if (from < log.firstKey()) {
+                throw new LogIndexOutOfBoundsException("From index out of bounds.");
+            } else if (to > log.lastKey()) {
+                throw new LogIndexOutOfBoundsException("To index out of bounds.");
+            }
+            List<T> entries = new ArrayList<>((int) (to - from + 1));
+            for (long i = from; i <= to; i++) {
+                T entry = serializer.decode(log.get(i));
+                entries.add(entry);
+            }
+            return entries;
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public <T extends Entry> T getEntry(long index) {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            byte[] entryBytes = log.get(index);
+            return entryBytes == null ? null : serializer.decode(entryBytes);
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public boolean isEmpty() {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            return log.isEmpty();
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public <T extends Entry> T lastEntry() {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            return log.isEmpty() ? null : serializer.decode(log.lastEntry().getValue());
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public long lastIndex() {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+            return log.isEmpty() ? 0 : log.lastKey();
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public void removeAfter(long index) {
+        assertIsOpen();
+        txMaker.execute(new TxBlock() {
+            @Override
+            public void tx(DB db) {
+                BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+                Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
+                long startIndex = index + 1;
+                long endIndex = log.lastKey();
+                for (long i = startIndex; i <= endIndex; ++i) {
+                    byte[] entryBytes = log.remove(i);
+                    size.addAndGet(-1L * entryBytes.length);
+                }
+            }
+        });
+    }
+
+    @Override
+    public long size() {
+        assertIsOpen();
+        DB db = txMaker.makeTx();
+        try {
+            Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
+            return size.get();
+        } finally {
+            db.close();
+        }
+    }
+
+    @Override
+    public void sync() throws IOException {
+        assertIsOpen();
+    }
+
+    @Override
+    public void compact(long index, Entry entry) throws IOException {
+
+        assertIsOpen();
+        txMaker.execute(new TxBlock() {
+            @Override
+            public void tx(DB db) {
+                BTreeMap<Long, byte[]> log = db.getTreeMap(LOG_NAME);
+                Atomic.Long size = db.getAtomicLong(SIZE_FIELD_NAME);
+                ConcurrentNavigableMap<Long, byte[]> headMap = log.headMap(index);
+                long deletedBytes = headMap.keySet().stream().mapToLong(i -> log.remove(i).length).sum();
+                size.addAndGet(-1 * deletedBytes);
+                byte[] entryBytes = serializer.encode(entry);
+                byte[] existingEntry = log.put(index, entryBytes);
+                size.addAndGet(entryBytes.length - existingEntry.length);
+                db.compact();
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/SnapshotException.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/SnapshotException.java
new file mode 100644
index 0000000..4cfc13b
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/SnapshotException.java
@@ -0,0 +1,13 @@
+package org.onlab.onos.store.service.impl;
+
+import org.onlab.onos.store.service.DatabaseException;
+
+/**
+ * Exception that indicates a problem with the state machine snapshotting.
+ */
+@SuppressWarnings("serial")
+public class SnapshotException extends DatabaseException {
+    public SnapshotException(Throwable t) {
+        super(t);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java
new file mode 100644
index 0000000..48887b9
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpClusterConfigSerializer.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.store.service.impl;
+
+import java.util.Collection;
+
+import net.kuujo.copycat.cluster.TcpClusterConfig;
+import net.kuujo.copycat.cluster.TcpMember;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public class TcpClusterConfigSerializer extends Serializer<TcpClusterConfig> {
+
+    @Override
+    public void write(Kryo kryo, Output output, TcpClusterConfig object) {
+        kryo.writeClassAndObject(output, object.getLocalMember());
+        kryo.writeClassAndObject(output, object.getRemoteMembers());
+    }
+
+    @Override
+    public TcpClusterConfig read(Kryo kryo, Input input,
+                                 Class<TcpClusterConfig> type) {
+        TcpMember localMember = (TcpMember) kryo.readClassAndObject(input);
+        @SuppressWarnings("unchecked")
+        Collection<TcpMember> remoteMembers = (Collection<TcpMember>) kryo.readClassAndObject(input);
+        return new TcpClusterConfig(localMember, remoteMembers);
+    }
+
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpMemberSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpMemberSerializer.java
new file mode 100644
index 0000000..e729f9b
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/TcpMemberSerializer.java
@@ -0,0 +1,24 @@
+package org.onlab.onos.store.service.impl;
+
+import net.kuujo.copycat.cluster.TcpMember;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public class TcpMemberSerializer extends Serializer<TcpMember> {
+
+    @Override
+    public void write(Kryo kryo, Output output, TcpMember object) {
+        output.writeString(object.host());
+        output.writeInt(object.port());
+    }
+
+    @Override
+    public TcpMember read(Kryo kryo, Input input, Class<TcpMember> type) {
+        String host = input.readString();
+        int port = input.readInt();
+        return new TcpMember(host, port);
+    }
+}
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/service/impl/MapDBLogTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/service/impl/MapDBLogTest.java
new file mode 100644
index 0000000..4ad98798
--- /dev/null
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/service/impl/MapDBLogTest.java
@@ -0,0 +1,195 @@
+package org.onlab.onos.store.service.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+
+import net.kuujo.copycat.internal.log.OperationEntry;
+import net.kuujo.copycat.log.Entry;
+import net.kuujo.copycat.log.Log;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.store.serializers.StoreSerializer;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Test the MapDBLog implementation.
+ */
+public class MapDBLogTest {
+
+    private static final String DB_FILE_NAME = "mapdbTest";
+    private static final StoreSerializer SERIALIZER = ClusterMessagingProtocol.SERIALIZER;
+    private static final Entry TEST_ENTRY1 = new OperationEntry(1, "test1");
+    private static final Entry TEST_ENTRY2 = new OperationEntry(2, "test12");
+    private static final Entry TEST_ENTRY3 = new OperationEntry(3, "test123");
+    private static final Entry TEST_ENTRY4 = new OperationEntry(4, "test1234");
+
+    private static final Entry TEST_SNAPSHOT_ENTRY = new OperationEntry(5, "snapshot");
+
+    private static final long TEST_ENTRY1_SIZE = SERIALIZER.encode(TEST_ENTRY1).length;
+    private static final long TEST_ENTRY2_SIZE = SERIALIZER.encode(TEST_ENTRY2).length;
+    private static final long TEST_ENTRY3_SIZE = SERIALIZER.encode(TEST_ENTRY3).length;
+    private static final long TEST_ENTRY4_SIZE = SERIALIZER.encode(TEST_ENTRY4).length;
+
+    private static final long TEST_SNAPSHOT_ENTRY_SIZE = SERIALIZER.encode(TEST_SNAPSHOT_ENTRY).length;
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Files.deleteIfExists(new File(DB_FILE_NAME).toPath());
+        Files.deleteIfExists(new File(DB_FILE_NAME + ".t").toPath());
+        Files.deleteIfExists(new File(DB_FILE_NAME + ".p").toPath());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAssertOpen() {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.size();
+    }
+
+    @Test
+    public void testAppendEntry() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntry(TEST_ENTRY1);
+        OperationEntry first = log.firstEntry();
+        OperationEntry last = log.lastEntry();
+        new EqualsTester()
+            .addEqualityGroup(first, last, TEST_ENTRY1)
+            .testEquals();
+        Assert.assertEquals(TEST_ENTRY1_SIZE, log.size());
+        Assert.assertEquals(1, log.firstIndex());
+        Assert.assertEquals(1, log.lastIndex());
+    }
+
+    @Test
+    public void testAppendEntries() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3);
+        OperationEntry first = log.firstEntry();
+        OperationEntry last = log.lastEntry();
+        new EqualsTester()
+            .addEqualityGroup(first, TEST_ENTRY1)
+            .addEqualityGroup(last, TEST_ENTRY3)
+            .testEquals();
+        Assert.assertEquals(TEST_ENTRY1_SIZE + TEST_ENTRY2_SIZE, TEST_ENTRY3_SIZE, log.size());
+        Assert.assertEquals(1, log.firstIndex());
+        Assert.assertEquals(3, log.lastIndex());
+        Assert.assertTrue(log.containsEntry(1));
+        Assert.assertTrue(log.containsEntry(2));
+    }
+
+    @Test
+    public void testDelete() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2);
+        log.delete();
+        Assert.assertEquals(0, log.size());
+        Assert.assertTrue(log.isEmpty());
+        Assert.assertEquals(0, log.firstIndex());
+        Assert.assertNull(log.firstEntry());
+        Assert.assertEquals(0, log.lastIndex());
+        Assert.assertNull(log.lastEntry());
+    }
+
+    @Test
+    public void testGetEntries() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4);
+        Assert.assertEquals(
+                TEST_ENTRY1_SIZE +
+                TEST_ENTRY2_SIZE +
+                TEST_ENTRY3_SIZE +
+                TEST_ENTRY4_SIZE, log.size());
+
+        List<Entry> entries = log.getEntries(2, 3);
+        new EqualsTester()
+            .addEqualityGroup(log.getEntry(4), TEST_ENTRY4)
+            .addEqualityGroup(entries.get(0), TEST_ENTRY2)
+            .addEqualityGroup(entries.get(1), TEST_ENTRY3)
+            .testEquals();
+    }
+
+    @Test
+    public void testRemoveAfter() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4);
+        log.removeAfter(1);
+        Assert.assertEquals(TEST_ENTRY1_SIZE, log.size());
+        new EqualsTester()
+            .addEqualityGroup(log.firstEntry(), log.lastEntry(), TEST_ENTRY1)
+            .testEquals();
+    }
+
+    @Test
+    public void testAddAfterRemove() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4);
+        log.removeAfter(1);
+        log.appendEntry(TEST_ENTRY4);
+        Assert.assertEquals(TEST_ENTRY1_SIZE + TEST_ENTRY4_SIZE, log.size());
+        new EqualsTester()
+            .addEqualityGroup(log.firstEntry(), TEST_ENTRY1)
+            .addEqualityGroup(log.lastEntry(), TEST_ENTRY4)
+            .addEqualityGroup(log.size(), TEST_ENTRY1_SIZE + TEST_ENTRY4_SIZE)
+            .testEquals();
+    }
+
+    @Test
+    public void testClose() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        Assert.assertFalse(log.isOpen());
+        log.open();
+        Assert.assertTrue(log.isOpen());
+        log.close();
+        Assert.assertFalse(log.isOpen());
+    }
+
+    @Test
+    public void testReopen() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4);
+        log.close();
+        log.open();
+
+        new EqualsTester()
+            .addEqualityGroup(log.firstEntry(), TEST_ENTRY1)
+            .addEqualityGroup(log.getEntry(2), TEST_ENTRY2)
+            .addEqualityGroup(log.lastEntry(), TEST_ENTRY4)
+            .addEqualityGroup(log.size(),
+                    TEST_ENTRY1_SIZE +
+                    TEST_ENTRY2_SIZE +
+                    TEST_ENTRY3_SIZE +
+                    TEST_ENTRY4_SIZE)
+            .testEquals();
+    }
+
+    @Test
+    public void testCompact() throws IOException {
+        Log log = new MapDBLog(new File(DB_FILE_NAME), SERIALIZER);
+        log.open();
+        log.appendEntries(TEST_ENTRY1, TEST_ENTRY2, TEST_ENTRY3, TEST_ENTRY4);
+        log.compact(3, TEST_SNAPSHOT_ENTRY);
+        new EqualsTester()
+        .addEqualityGroup(log.firstEntry(), TEST_SNAPSHOT_ENTRY)
+        .addEqualityGroup(log.lastEntry(), TEST_ENTRY4)
+        .addEqualityGroup(log.size(),
+                TEST_SNAPSHOT_ENTRY_SIZE +
+                TEST_ENTRY4_SIZE)
+        .testEquals();
+    }
+}
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 e0348ff..4cba9f0 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
@@ -103,6 +103,20 @@
 
 public final class KryoNamespaces {
 
+    public static final KryoNamespace BASIC = KryoNamespace.newBuilder()
+            .register(ImmutableMap.class, new ImmutableMapSerializer())
+            .register(ImmutableList.class, new ImmutableListSerializer())
+            .register(ImmutableSet.class, new ImmutableSetSerializer())
+            .register(
+                    ArrayList.class,
+                    Arrays.asList().getClass(),
+                    HashMap.class,
+                    HashSet.class,
+                    LinkedList.class,
+                    byte[].class
+                    )
+            .build();
+
     /**
      * KryoNamespace which can serialize ON.lab misc classes.
      */
@@ -123,19 +137,8 @@
      */
     public static final KryoNamespace API = KryoNamespace.newBuilder()
             .register(MISC)
-            .register(ImmutableMap.class, new ImmutableMapSerializer())
-            .register(ImmutableList.class, new ImmutableListSerializer())
-            .register(ImmutableSet.class, new ImmutableSetSerializer())
+            .register(BASIC)
             .register(
-                    //
-                    ArrayList.class,
-                    Arrays.asList().getClass(),
-                    HashMap.class,
-                    HashSet.class,
-                    LinkedList.class,
-                    byte[].class,
-                    //
-                    //
                     ControllerNode.State.class,
                     Device.Type.class,
                     Port.Type.class,
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
index 0f36393..62c084e 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
@@ -29,8 +29,13 @@
 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.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterEventListener;
+import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.ControllerNode;
+import org.onlab.onos.cluster.ControllerNode.State;
 import org.onlab.onos.cluster.DefaultControllerNode;
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.cluster.RoleInfo;
@@ -44,7 +49,8 @@
 import org.onlab.packet.IpAddress;
 import org.slf4j.Logger;
 
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import static org.onlab.onos.mastership.MastershipEvent.Type.*;
 
@@ -60,23 +66,65 @@
 
     private final Logger log = getLogger(getClass());
 
-    public static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
-
     private static final int NOTHING = 0;
     private static final int INIT = 1;
 
-    private ControllerNode instance =
-            new DefaultControllerNode(new NodeId("local"), LOCALHOST);
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
 
     //devices mapped to their masters, to emulate multiple nodes
     protected final Map<DeviceId, NodeId> masterMap = new HashMap<>();
     //emulate backups with pile of nodes
-    protected final Set<NodeId> backups = new HashSet<>();
+    protected final Map<DeviceId, List<NodeId>> backups = new HashMap<>();
     //terms
     protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>();
 
     @Activate
     public void activate() {
+        if (clusterService == null) {
+          // just for ease of unit test
+          final ControllerNode instance =
+                  new DefaultControllerNode(new NodeId("local"),
+                                            IpAddress.valueOf("127.0.0.1"));
+
+            clusterService = new ClusterService() {
+
+                @Override
+                public ControllerNode getLocalNode() {
+                    return instance;
+                }
+
+                @Override
+                public Set<ControllerNode> getNodes() {
+                    return ImmutableSet.of(instance);
+                }
+
+                @Override
+                public ControllerNode getNode(NodeId nodeId) {
+                    if (instance.id().equals(nodeId)) {
+                        return instance;
+                    }
+                    return null;
+                }
+
+                @Override
+                public State getState(NodeId nodeId) {
+                    if (instance.id().equals(nodeId)) {
+                        return State.ACTIVE;
+                    } else {
+                        return State.INACTIVE;
+                    }
+                }
+
+                @Override
+                public void addListener(ClusterEventListener listener) {
+                }
+
+                @Override
+                public void removeListener(ClusterEventListener listener) {
+                }
+            };
+        }
         log.info("Started");
     }
 
@@ -86,31 +134,27 @@
     }
 
     @Override
-    public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
-        MastershipRole role = getRole(nodeId, deviceId);
+    public synchronized MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
 
-        synchronized (this) {
-            switch (role) {
-                case MASTER:
-                    return null;
-                case STANDBY:
-                    masterMap.put(deviceId, nodeId);
-                    termMap.get(deviceId).incrementAndGet();
-                    backups.add(nodeId);
-                    break;
-                case NONE:
-                    masterMap.put(deviceId, nodeId);
-                    termMap.put(deviceId, new AtomicInteger(INIT));
-                    backups.add(nodeId);
-                    break;
-                default:
-                    log.warn("unknown Mastership Role {}", role);
-                    return null;
-            }
+        MastershipRole role = getRole(nodeId, deviceId);
+        switch (role) {
+        case MASTER:
+            // no-op
+            return null;
+        case STANDBY:
+        case NONE:
+            NodeId prevMaster = masterMap.put(deviceId, nodeId);
+            incrementTerm(deviceId);
+            removeFromBackups(deviceId, nodeId);
+            addToBackup(deviceId, prevMaster);
+            break;
+        default:
+            log.warn("unknown Mastership Role {}", role);
+            return null;
         }
 
         return new MastershipEvent(MASTER_CHANGED, deviceId,
-                new RoleInfo(nodeId, Lists.newLinkedList(backups)));
+                                   getNodes(deviceId));
     }
 
     @Override
@@ -118,12 +162,11 @@
         return masterMap.get(deviceId);
     }
 
+    // synchronized for atomic read
     @Override
-    public RoleInfo getNodes(DeviceId deviceId) {
-        List<NodeId> nodes = new ArrayList<>();
-        nodes.addAll(backups);
-
-        return new RoleInfo(masterMap.get(deviceId), nodes);
+    public synchronized RoleInfo getNodes(DeviceId deviceId) {
+        return new RoleInfo(masterMap.get(deviceId),
+                            backups.getOrDefault(deviceId, ImmutableList.of()));
     }
 
     @Override
@@ -134,69 +177,97 @@
                 ids.add(d.getKey());
             }
         }
-        return Collections.unmodifiableSet(ids);
+        return ids;
     }
 
     @Override
-    public MastershipRole requestRole(DeviceId deviceId) {
+    public synchronized MastershipRole requestRole(DeviceId deviceId) {
         //query+possible reelection
-        NodeId node = instance.id();
+        NodeId node = clusterService.getLocalNode().id();
         MastershipRole role = getRole(node, deviceId);
 
         switch (role) {
             case MASTER:
-                break;
+                return MastershipRole.MASTER;
             case STANDBY:
-                synchronized (this) {
-                    //try to "re-elect", since we're really not distributed
-                    NodeId rel = reelect(node);
-                    if (rel == null) {
-                        masterMap.put(deviceId, node);
-                        termMap.put(deviceId, new AtomicInteger(INIT));
-                        role = MastershipRole.MASTER;
-                    }
-                    backups.add(node);
-                }
-                break;
-            case NONE:
-                //first to get to it, say we are master
-                synchronized (this) {
+                if (getMaster(deviceId) == null) {
+                    // no master => become master
                     masterMap.put(deviceId, node);
-                    termMap.put(deviceId, new AtomicInteger(INIT));
-                    backups.add(node);
-                    role = MastershipRole.MASTER;
+                    incrementTerm(deviceId);
+                    // remove from backup list
+                    removeFromBackups(deviceId, node);
+                    notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId,
+                                                       getNodes(deviceId)));
+                    return MastershipRole.MASTER;
                 }
-                break;
+                return MastershipRole.STANDBY;
+            case NONE:
+                if (getMaster(deviceId) == null) {
+                    // no master => become master
+                    masterMap.put(deviceId, node);
+                    incrementTerm(deviceId);
+                    notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId,
+                                                       getNodes(deviceId)));
+                    return MastershipRole.MASTER;
+                }
+                // add to backup list
+                if (addToBackup(deviceId, node)) {
+                    notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId,
+                                                       getNodes(deviceId)));
+                }
+                return MastershipRole.STANDBY;
             default:
                 log.warn("unknown Mastership Role {}", role);
         }
         return role;
     }
 
+    // add to backup if not there already, silently ignores null node
+    private synchronized boolean addToBackup(DeviceId deviceId, NodeId nodeId) {
+        boolean modified = false;
+        List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+        if (nodeId != null && !stbys.contains(nodeId)) {
+            stbys.add(nodeId);
+            modified = true;
+        }
+        backups.put(deviceId, stbys);
+        return modified;
+    }
+
+    private synchronized boolean removeFromBackups(DeviceId deviceId, NodeId node) {
+        List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+        boolean modified = stbys.remove(node);
+        backups.put(deviceId, stbys);
+        return modified;
+    }
+
+    private synchronized void incrementTerm(DeviceId deviceId) {
+        AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
+        term.incrementAndGet();
+        termMap.put(deviceId, term);
+    }
+
     @Override
     public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
         //just query
         NodeId current = masterMap.get(deviceId);
         MastershipRole role;
 
-        if (current == null) {
-            if (backups.contains(nodeId)) {
-                role = MastershipRole.STANDBY;
-            } else {
-                role = MastershipRole.NONE;
-            }
+        if (current != null && current.equals(nodeId)) {
+            return MastershipRole.MASTER;
+        }
+
+        if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
+            role = MastershipRole.STANDBY;
         } else {
-            if (current.equals(nodeId)) {
-                role = MastershipRole.MASTER;
-            } else {
-                role = MastershipRole.STANDBY;
-            }
+            role = MastershipRole.NONE;
         }
         return role;
     }
 
+    // synchronized for atomic read
     @Override
-    public MastershipTerm getTermFor(DeviceId deviceId) {
+    public synchronized MastershipTerm getTermFor(DeviceId deviceId) {
         if ((termMap.get(deviceId) == null)) {
             return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
         }
@@ -205,72 +276,71 @@
     }
 
     @Override
-    public MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) {
+    public synchronized MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) {
         MastershipRole role = getRole(nodeId, deviceId);
-        synchronized (this) {
-            switch (role) {
-                case MASTER:
-                    NodeId backup = reelect(nodeId);
-                    if (backup == null) {
-                        masterMap.remove(deviceId);
-                    } else {
-                        masterMap.put(deviceId, backup);
-                        termMap.get(deviceId).incrementAndGet();
-                        return new MastershipEvent(MASTER_CHANGED, deviceId,
-                                new RoleInfo(backup, Lists.newLinkedList(backups)));
-                    }
-                case STANDBY:
-                case NONE:
-                    if (!termMap.containsKey(deviceId)) {
-                        termMap.put(deviceId, new AtomicInteger(INIT));
-                    }
-                    backups.add(nodeId);
-                    break;
-                default:
-                    log.warn("unknown Mastership Role {}", role);
+        switch (role) {
+        case MASTER:
+            NodeId backup = reelect(deviceId, nodeId);
+            if (backup == null) {
+                // no master alternative
+                masterMap.remove(deviceId);
+                // TODO: Should there be new event type for no MASTER?
+                return new MastershipEvent(MASTER_CHANGED, deviceId,
+                                           getNodes(deviceId));
+            } else {
+                NodeId prevMaster = masterMap.put(deviceId, backup);
+                incrementTerm(deviceId);
+                addToBackup(deviceId, prevMaster);
+                return new MastershipEvent(MASTER_CHANGED, deviceId,
+                                           getNodes(deviceId));
             }
+        case STANDBY:
+        case NONE:
+            boolean modified = addToBackup(deviceId, nodeId);
+            if (modified) {
+                return new MastershipEvent(BACKUPS_CHANGED, deviceId,
+                                           getNodes(deviceId));
+            }
+        default:
+            log.warn("unknown Mastership Role {}", role);
         }
         return null;
     }
 
     //dumbly selects next-available node that's not the current one
     //emulate leader election
-    private NodeId reelect(NodeId nodeId) {
+    private synchronized NodeId reelect(DeviceId did, NodeId nodeId) {
+        List<NodeId> stbys = backups.getOrDefault(did, Collections.emptyList());
         NodeId backup = null;
-        for (NodeId n : backups) {
+        for (NodeId n : stbys) {
             if (!n.equals(nodeId)) {
                 backup = n;
                 break;
             }
         }
-        backups.remove(backup);
+        stbys.remove(backup);
         return backup;
     }
 
     @Override
-    public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
+    public synchronized MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
         MastershipRole role = getRole(nodeId, deviceId);
-        synchronized (this) {
-            switch (role) {
-                case MASTER:
-                    NodeId backup = reelect(nodeId);
-                    backups.remove(nodeId);
-                    if (backup == null) {
-                        masterMap.remove(deviceId);
-                    } else {
-                        masterMap.put(deviceId, backup);
-                        termMap.get(deviceId).incrementAndGet();
-                        return new MastershipEvent(MASTER_CHANGED, deviceId,
-                                new RoleInfo(backup, Lists.newLinkedList(backups)));
-                    }
-                case STANDBY:
-                    backups.remove(nodeId);
-                case NONE:
-                default:
-                    log.warn("unknown Mastership Role {}", role);
+        switch (role) {
+        case MASTER:
+            NodeId backup = reelect(deviceId, nodeId);
+            masterMap.put(deviceId, backup);
+            incrementTerm(deviceId);
+            return new MastershipEvent(MASTER_CHANGED, deviceId,
+                                       getNodes(deviceId));
+        case STANDBY:
+            if (removeFromBackups(deviceId, nodeId)) {
+                return new MastershipEvent(BACKUPS_CHANGED, deviceId,
+                                           getNodes(deviceId));
             }
+        case NONE:
+        default:
+            log.warn("unknown Mastership Role {}", role);
         }
         return null;
     }
-
 }
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java
index 711e366..0998e0a 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.store.trivial.impl;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -22,6 +24,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.mastership.MastershipEvent;
 import org.onlab.onos.mastership.MastershipTerm;
 import org.onlab.onos.net.DeviceId;
 
@@ -74,6 +77,7 @@
         assertEquals("wrong role", MASTER, sms.getRole(N2, DID3));
 
         //N2 is master but N1 is only in backups set
+        put(DID4, N1, false, true);
         put(DID4, N2, true, false);
         assertEquals("wrong role", STANDBY, sms.getRole(N1, DID4));
     }
@@ -127,12 +131,12 @@
         put(DID1, N1, false, false);
         assertEquals("wrong role", MASTER, sms.requestRole(DID1));
 
-        //STANDBY without backup - become MASTER
+        //was STANDBY - become MASTER
         put(DID2, N1, false, true);
         assertEquals("wrong role", MASTER, sms.requestRole(DID2));
 
-        //STANDBY with backup - stay STANDBY
-        put(DID3, N2, false, true);
+        //other MASTER - stay STANDBY
+        put(DID3, N2, true, false);
         assertEquals("wrong role", STANDBY, sms.requestRole(DID3));
 
         //local (N1) is MASTER - stay MASTER
@@ -145,30 +149,34 @@
         //NONE - record backup but take no other action
         put(DID1, N1, false, false);
         sms.setStandby(N1, DID1);
-        assertTrue("not backed up", sms.backups.contains(N1));
-        sms.termMap.clear();
+        assertTrue("not backed up", sms.backups.get(DID1).contains(N1));
+        int prev = sms.termMap.get(DID1).get();
         sms.setStandby(N1, DID1);
-        assertTrue("term not set", sms.termMap.containsKey(DID1));
+        assertEquals("term should not change", prev, sms.termMap.get(DID1).get());
 
         //no backup, MASTER
-        put(DID1, N1, true, true);
-        assertNull("wrong event", sms.setStandby(N1, DID1));
+        put(DID1, N1, true, false);
+        assertNull("expect no MASTER event", sms.setStandby(N1, DID1).roleInfo().master());
         assertNull("wrong node", sms.masterMap.get(DID1));
 
         //backup, switch
         sms.masterMap.clear();
         put(DID1, N1, true, true);
+        put(DID1, N2, false, true);
         put(DID2, N2, true, true);
-        assertEquals("wrong event", MASTER_CHANGED, sms.setStandby(N1, DID1).type());
+        MastershipEvent event = sms.setStandby(N1, DID1);
+        assertEquals("wrong event", MASTER_CHANGED, event.type());
+        assertEquals("wrong master", N2, event.roleInfo().master());
     }
 
     //helper to populate master/backup structures
-    private void put(DeviceId dev, NodeId node, boolean store, boolean backup) {
-        if (store) {
+    private void put(DeviceId dev, NodeId node, boolean master, boolean backup) {
+        if (master) {
             sms.masterMap.put(dev, node);
-        }
-        if (backup) {
-            sms.backups.add(node);
+        } else if (backup) {
+            List<NodeId> stbys = sms.backups.getOrDefault(dev, new ArrayList<>());
+            stbys.add(node);
+            sms.backups.put(dev, stbys);
         }
         sms.termMap.put(dev, new AtomicInteger());
     }
diff --git a/features/features.xml b/features/features.xml
index 0e7dfbf..b8ef86f 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -56,6 +56,9 @@
         <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle>
 
         <bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle>
+
+        <bundle>mvn:org.mapdb/mapdb/1.0.6</bundle>
+
 <!-- FIXME: resolce Chronicle's dependency issue
         <bundle>mvn:net.openhft/lang/6.4.6</bundle>
         <bundle>mvn:net.openhft/affinity/2.1.1</bundle>
diff --git a/tools/dev/eclipse-formatter.xml b/tools/dev/eclipse-formatter.xml
index aba7791..7832d52 100644
--- a/tools/dev/eclipse-formatter.xml
+++ b/tools/dev/eclipse-formatter.xml
@@ -1,295 +1,295 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <profiles version="12">
 <profile kind="CodeFormatterProfile" name="ONOS-formatter" version="12">
-<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="82"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
-<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="18"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="80"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="8"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="32"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
-<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
-<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="32"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="18"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
-<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
-<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="18"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="32"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="0"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="32"/>
-<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="32"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="8"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="0"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="32"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="32"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="32"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
 <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
-<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
 <setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
-<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="32"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="32"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
 </profile>
 </profiles>
diff --git a/web/gui/src/main/webapp/mast2.js b/web/gui/src/main/webapp/mast2.js
index 169dd35..237c511 100644
--- a/web/gui/src/main/webapp/mast2.js
+++ b/web/gui/src/main/webapp/mast2.js
@@ -15,7 +15,7 @@
  */
 
 /*
- ONOS GUI -- Masthead
+ ONOS GUI -- Masthead script
 
  Defines the masthead for the UI. Injects logo and title, as well as providing
  the placeholder for a set of radio buttons.
@@ -53,4 +53,4 @@
             class: 'right'
         });
 
-}(ONOS));
\ No newline at end of file
+}(ONOS));
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 427a23f..375fe6b 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -407,7 +407,8 @@
                     height: this.height,
                     uid: this.uid,
                     setRadio: this.setRadio,
-                    setKeys: this.setKeys
+                    setKeys: this.setKeys,
+                    dataLoadError: this.dataLoadError
                 }
             },
 
@@ -498,6 +499,16 @@
 
             uid: function (id) {
                 return makeUid(this, id);
+            },
+
+            // TODO : implement custom dialogs (don't use alerts)
+
+            dataLoadError: function (err, url) {
+                var msg = 'Data Load Error\n\n' +
+                    err.status + ' -- ' + err.statusText + '\n\n' +
+                    'relative-url: "' + url + '"\n\n' +
+                    'complete-url: "' + err.responseURL + '"';
+                alert(msg);
             }
 
             // TODO: consider schedule, clearTimer, etc.
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 88fcd94..eee9244 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -24,3 +24,6 @@
     opacity: 0.5;
 }
 
+svg .node {
+    fill: #03c;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 7ac9adc..201c99c 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -25,7 +25,7 @@
 
     // configuration data
     var config = {
-        useLiveData: false,
+        useLiveData: true,
         debugOn: false,
         debug: {
             showNodeXY: false,
@@ -56,12 +56,24 @@
             opt: 'img/opt.png'
         },
         force: {
-            marginLR: 20,
-            marginTB: 20,
+            note: 'node.class or link.class is used to differentiate',
+            linkDistance: {
+                infra: 200,
+                host: 40
+            },
+            linkStrength: {
+                infra: 1.0,
+                host: 1.0
+            },
+            charge: {
+                device: -400,
+                host: -100
+            },
+            pad: 20,
             translate: function() {
                 return 'translate(' +
-                    config.force.marginLR + ',' +
-                    config.force.marginTB + ')';
+                    config.force.pad + ',' +
+                    config.force.pad + ')';
             }
         }
     };
@@ -94,7 +106,11 @@
     // D3 selections
     var svg,
         bgImg,
-        topoG;
+        topoG,
+        nodeG,
+        linkG,
+        node,
+        link;
 
     // ==============================
     // For Debugging / Development
@@ -175,22 +191,145 @@
     // ==============================
     // Private functions
 
-    // set the size of the given element to that of the view
-    function setSize(el, view) {
+    // set the size of the given element to that of the view (reduced if padded)
+    function setSize(el, view, pad) {
+        var padding = pad ? pad * 2 : 0;
         el.attr({
-            width: view.width(),
-            height: view.height()
+            width: view.width() - padding,
+            height: view.height() - padding
         });
     }
 
-
     function getNetworkData(view) {
         var url = getTopoUrl();
 
-        // TODO ...
-
+        console.log('Fetching JSON: ' + url);
+        d3.json(url, function(err, data) {
+            if (err) {
+                view.dataLoadError(err, url);
+            } else {
+                network.data = data;
+                drawNetwork(view);
+            }
+        });
     }
 
+    function drawNetwork(view) {
+        preprocessData(view);
+        updateLayout(view);
+    }
+
+    function preprocessData(view) {
+        var w = view.width(),
+            h = view.height(),
+            hDevice = h * 0.6,
+            hHost = h * 0.3,
+            data = network.data,
+            deviceLayout = computeInitLayout(w, hDevice, data.devices.length),
+            hostLayout = computeInitLayout(w, hHost, data.hosts.length);
+
+        network.lookup = {};
+        network.nodes = [];
+        network.links = [];
+        // we created new arrays, so need to set the refs in the force layout
+        network.force.nodes(network.nodes);
+        network.force.links(network.links);
+
+        // let's just start with the nodes
+
+        // note that both 'devices' and 'hosts' get mapped into the nodes array
+        function makeNode(d, cls, layout) {
+            var node = {
+                    id: d.id,
+                    labels: d.labels,
+                    class: cls,
+                    icon: cls,
+                    type: d.type,
+                    x: layout.x(),
+                    y: layout.y()
+                };
+            network.lookup[d.id] = node;
+            network.nodes.push(node);
+        }
+
+        // first the devices...
+        network.data.devices.forEach(function (d) {
+            makeNode(d, 'device', deviceLayout);
+        });
+
+        // then the hosts...
+        network.data.hosts.forEach(function (d) {
+            makeNode(d, 'host', hostLayout);
+        });
+
+        // TODO: process links
+    }
+
+    function computeInitLayout(w, h, n) {
+        var maxdw = 60,
+            compdw, dw, ox, layout;
+
+        if (n < 2) {
+            layout = { ox: w/2, dw: 0 }
+        } else {
+            compdw = (0.8 * w) / (n - 1);
+            dw = Math.min(maxdw, compdw);
+            ox = w/2 - ((n - 1)/2 * dw);
+            layout = { ox: ox, dw: dw }
+        }
+
+        layout.i = 0;
+
+        layout.x = function () {
+            var x = layout.ox + layout.i*layout.dw;
+            layout.i++;
+            return x;
+        };
+
+        layout.y = function () {
+            return h;
+        };
+
+        return layout;
+    }
+
+    function linkId(d) {
+        return d.source.id + '~' + d.target.id;
+    }
+
+    function nodeId(d) {
+        return d.id;
+    }
+
+    function updateLayout(view) {
+        link = link.data(network.force.links(), linkId);
+        link.enter().append('line')
+            .attr('class', 'link');
+        link.exit().remove();
+
+        node = node.data(network.force.nodes(), nodeId);
+        node.enter().append('circle')
+            .attr('id', function (d) { return 'nodeId-' + d.id; })
+            .attr('class', function (d) { return 'node'; })
+            .attr('r', 12);
+
+        network.force.start();
+    }
+
+
+    function tick() {
+        node.attr({
+            cx: function(d) { return d.x; },
+            cy: function(d) { return d.y; }
+        });
+
+        link.attr({
+            x1: function (d) { return d.source.x; },
+            y1: function (d) { return d.source.y; },
+            x2: function (d) { return d.target.x; },
+            y2: function (d) { return d.target.y; }
+        });
+    }
 
     // ==============================
     // View life-cycle callbacks
@@ -199,15 +338,15 @@
         var w = view.width(),
             h = view.height(),
             idBg = view.uid('bg'),
-            showBg = config.options.showBackground ? 'visible' : 'hidden';
+            showBg = config.options.showBackground ? 'visible' : 'hidden',
+            fcfg = config.force,
+            fpad = fcfg.pad,
+            forceDim = [w - 2*fpad, h - 2*fpad];
 
         // NOTE: view.$div is a D3 selection of the view's div
         svg = view.$div.append('svg');
         setSize(svg, view);
 
-        topoG = svg.append('g')
-            .attr('transform', config.force.translate());
-
         // load the background image
         bgImg = svg.append('svg:image')
             .attr({
@@ -219,6 +358,28 @@
             .style({
                 visibility: showBg
             });
+
+        // group for the topology
+        topoG = svg.append('g')
+            .attr('transform', fcfg.translate());
+
+        // subgroups for links and nodes
+        linkG = topoG.append('g').attr('id', 'links');
+        nodeG = topoG.append('g').attr('id', 'nodes');
+
+        // selection of nodes and links
+        link = linkG.selectAll('.link');
+        node = nodeG.selectAll('.node');
+
+        // set up the force layout
+        network.force = d3.layout.force()
+            .size(forceDim)
+            .nodes(network.nodes)
+            .links(network.links)
+            .charge(function (d) { return fcfg.charge[d.class]; })
+            .linkDistance(function (d) { return fcfg.linkDistance[d.class]; })
+            .linkStrength(function (d) { return fcfg.linkStrength[d.class]; })
+            .on('tick', tick);
     }