TopoRegions: created skeleton Topo2 UI view for development of the "region-aware" topology.
 - Added initial event generation (layout/region/ etc.) -- WIP

Change-Id: I2f93eea7505ff0400085d7f67491f6b61231cb86
diff --git a/core/api/src/main/java/org/onosproject/ui/RequestHandler.java b/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
index a789a74..76710a2 100644
--- a/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
@@ -89,6 +89,17 @@
 
     /**
      * Sends a message back to the client.
+     *
+     * @param eventType message event type
+     * @param payload   message payload
+     */
+    protected void sendMessage(String eventType, ObjectNode payload) {
+        // TODO: remove sid
+        parent.connection().sendMessage(eventType, 0, payload);
+    }
+
+    /**
+     * Sends a message back to the client.
      * Here, the message is preformatted; the assumption is it has its
      * eventType, sid and payload attributes already filled in.
      *
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiDevice.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiDevice.java
index 3ed9da0..c029c2b 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiDevice.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiDevice.java
@@ -17,6 +17,7 @@
 package org.onosproject.ui.model.topo;
 
 import com.google.common.base.MoreObjects;
+import org.onosproject.cluster.NodeId;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.region.RegionId;
@@ -30,6 +31,8 @@
     private final Device device;
 
     private RegionId regionId;
+    private NodeId masterId;
+    private boolean online;
 
     /**
      * Creates a new UI device.
@@ -51,6 +54,24 @@
         this.regionId = regionId;
     }
 
+    /**
+     * Sets the ID of the controller node that holds mastership for this device.
+     *
+     * @param masterId master identifier
+     */
+    public void setMasterId(NodeId masterId) {
+        this.masterId = masterId;
+    }
+
+    /**
+     * Sets a flag indicating whether the backing device is online.
+     *
+     * @param online boolen flag
+     */
+    public void setOnline(boolean online) {
+        this.online = online;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
@@ -104,4 +125,32 @@
     public UiRegion uiRegion() {
         return topology.findRegion(regionId);
     }
+
+    /**
+     * Returns a string representation of the type of the backing device.
+     *
+     * @return the device type
+     */
+    public String type() {
+        return device.type().toString().toLowerCase();
+    }
+
+    /**
+     * Returns a boolean indicating whether the backing device is online.
+     *
+     * @return true if device is online, false otherwise
+     */
+    public boolean isOnline() {
+        return online;
+    }
+
+    /**
+     * Returns the identifier for the cluster member that has
+     * mastership over this device.
+     *
+     * @return master cluster member identifier
+     */
+    public NodeId master() {
+        return masterId;
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiNode.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiNode.java
index e39a7d2..79fdf5a 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiNode.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiNode.java
@@ -20,4 +20,41 @@
  * Represents a node drawn on the topology view (region, device, host).
  */
 abstract class UiNode extends UiElement {
+
+    /**
+     * Default "layer" tag.
+     */
+    public static final String LAYER_DEFAULT = "def";
+
+    /**
+     * Packet layer tag.
+     */
+    public static final String LAYER_PACKET = "pkt";
+
+    /**
+     * Optical layer tag.
+     */
+    public static final String LAYER_OPTICAL = "opt";
+
+
+    private String layer = LAYER_DEFAULT;
+
+    /**
+     * Returns the tag for the "layer" that the node should be rendered in
+     * when viewed in the oblique view.
+     *
+     * @return the node's layer
+     */
+    public String layer() {
+        return layer;
+    }
+
+    /**
+     * Sets this node's "layer", for layered rendering.
+     *
+     * @param layerTag the layer tag to set
+     */
+    public void setLayer(String layerTag) {
+        layer = layerTag;
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java
index 9776216..023300c 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java
@@ -22,7 +22,10 @@
 import org.onosproject.net.region.Region;
 import org.onosproject.net.region.RegionId;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
@@ -37,6 +40,8 @@
     private final Set<HostId> hostIds = new HashSet<>();
     private final Set<UiLinkId> uiLinkIds = new HashSet<>();
 
+    private final List<String> layerOrder = new ArrayList<>();
+
     private final UiTopology topology;
 
     private final Region region;
@@ -50,6 +55,8 @@
     public UiRegion(UiTopology topology, Region region) {
         this.topology = topology;
         this.region = region;
+        // unless told otherwise, we'll use a single, default layer
+        layerOrder.add(UiNode.LAYER_DEFAULT);
     }
 
     @Override
@@ -60,6 +67,17 @@
     }
 
     /**
+     * Sets the layer order for this region.
+     * Typically, the {@code UiNode.LAYER_*} constants will be used here.
+     *
+     * @param layers the layers
+     */
+    public void setLayerOrder(String... layers) {
+        layerOrder.clear();
+        Collections.addAll(layerOrder, layers);
+    }
+
+    /**
      * Returns the identity of the region.
      *
      * @return region ID
@@ -170,4 +188,19 @@
     public Set<UiLink> links() {
         return topology.linkSet(uiLinkIds);
     }
+
+    /**
+     * Returns the order in which layers should be rendered. Lower layers
+     * come earlier in the list. For example, to indicate that nodes in the
+     * optical layer should be rendered "below" nodes in the packet layer,
+     * this method should return:
+     * <pre>
+     * [UiNode.LAYER_OPTICAL, UiNode.LAYER_PACKET]
+     * </pre>
+     *
+     * @return layer ordering
+     */
+    public List<String> layerOrder() {
+        return Collections.unmodifiableList(layerOrder);
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java
index 8b421c0..604cf65 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java
@@ -17,6 +17,7 @@
 package org.onosproject.ui.model.topo;
 
 import org.onosproject.net.region.Region;
+import org.onosproject.net.region.RegionId;
 
 /**
  * Represents a specific "subset" of the UI model of the network topology
@@ -41,6 +42,11 @@
         this.parent = parent;
     }
 
+    @Override
+    public String toString() {
+        return "{UiTopoLayout: " + id + "}";
+    }
+
     /**
      * Returns the UI layout identifier.
      *
@@ -51,7 +57,8 @@
     }
 
     /**
-     * Returns the backing region with which this layout is associated.
+     * Returns the backing region with which this layout is associated. Note
+     * that this may be null (for the root layout).
      *
      * @return backing region
      */
@@ -60,6 +67,16 @@
     }
 
     /**
+     * Returns the identifier of the backing region. Will be null if the
+     * region is null.
+     *
+     * @return backing region identifier
+     */
+    public RegionId regionId() {
+        return region == null ? null : region.id();
+    }
+
+    /**
      * Returns the parent layout identifier.
      *
      * @return parent layout identifier
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
index 9555e3a..5ce91da 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
@@ -23,8 +23,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -46,6 +50,9 @@
 
     private static final Logger log = LoggerFactory.getLogger(UiTopology.class);
 
+    private static final Comparator<UiClusterMember> CLUSTER_MEMBER_COMPARATOR =
+            (o1, o2) -> o1.idAsString().compareTo(o2.idAsString());
+
 
     // top level mappings of topology elements by ID
     private final Map<NodeId, UiClusterMember> cnodeLookup = new HashMap<>();
@@ -84,6 +91,18 @@
         linkLookup.clear();
     }
 
+
+    /**
+     * Returns all the cluster members, sorted by their ID.
+     *
+     * @return all cluster members
+     */
+    public List<UiClusterMember> allClusterMembers() {
+        List<UiClusterMember> members = new ArrayList<>(cnodeLookup.values());
+        Collections.sort(members, CLUSTER_MEMBER_COMPARATOR);
+        return members;
+    }
+
     /**
      * Returns the cluster member with the given identifier, or null if no
      * such member exists.
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 7ba4609..873184e 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -185,7 +185,7 @@
     private TimerTask summaryTask = null;
     private boolean summaryRunning = false;
 
-    private boolean listenersRemoved = false;
+    private volatile boolean listenersRemoved = false;
 
 
     @Override
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 110cde2..79f3d77 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -32,7 +32,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -51,11 +50,12 @@
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandlerFactory;
 import org.onosproject.ui.UiPreferencesService;
-import org.onosproject.ui.UiTopoOverlayFactory;
+import org.onosproject.ui.UiTopoMap;
 import org.onosproject.ui.UiTopoMapFactory;
+import org.onosproject.ui.UiTopoOverlayFactory;
 import org.onosproject.ui.UiView;
 import org.onosproject.ui.UiViewHidden;
-import org.onosproject.ui.UiTopoMap;
+import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -120,7 +120,8 @@
     private final ObjectMapper mapper = new ObjectMapper();
 
     private final ExecutorService eventHandlingExecutor =
-            Executors.newSingleThreadExecutor(Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
+            Executors.newSingleThreadExecutor(
+                    Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
 
     // Creates core UI extension
     private UiExtension createCoreExtension() {
@@ -130,6 +131,10 @@
                 new UiView(PLATFORM, "cluster", "Cluster Nodes", "nav_cluster"),
                 new UiView(PLATFORM, "processor", "Packet Processors", "nav_processors"),
                 new UiView(NETWORK, "topo", "Topology", "nav_topo"),
+
+                // FIXME: leave commented out for now, while still under development
+//                new UiView(NETWORK, "topo2", "New-Topo"),
+
                 new UiView(NETWORK, "device", "Devices", "nav_devs"),
                 new UiViewHidden("flow"),
                 new UiViewHidden("port"),
@@ -145,6 +150,7 @@
                 () -> ImmutableList.of(
                         new UserPreferencesMessageHandler(),
                         new TopologyViewMessageHandler(),
+                        new Topo2ViewMessageHandler(),
                         new MapSelectorMessageHandler(),
                         new DeviceViewMessageHandler(),
                         new LinkViewMessageHandler(),
@@ -196,11 +202,11 @@
     @Activate
     public void activate() {
         Serializer serializer = Serializer.using(KryoNamespaces.API,
-                        ObjectNode.class, ArrayNode.class,
-                        JsonNodeFactory.class, LinkedHashMap.class,
-                        TextNode.class, BooleanNode.class,
-                        LongNode.class, DoubleNode.class, ShortNode.class,
-                        IntNode.class, NullNode.class);
+                ObjectNode.class, ArrayNode.class,
+                JsonNodeFactory.class, LinkedHashMap.class,
+                TextNode.class, BooleanNode.class,
+                LongNode.class, DoubleNode.class, ShortNode.class,
+                IntNode.class, NullNode.class);
 
         prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
                 .withName(ONOS_USER_PREFERENCES)
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
index 2585bc8..40f2622 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -92,7 +92,7 @@
         this.userName = userName;
         this.topoSession =
                 new UiTopoSession(this, directory.get(UiSharedTopologyModel.class),
-                                  directory.get(UiTopoLayoutService.class));
+                        directory.get(UiTopoLayoutService.class));
     }
 
     @Override
@@ -122,6 +122,15 @@
     }
 
     /**
+     * Provides a reference to the topology session.
+     *
+     * @return topo session reference
+     */
+    public UiTopoSession topoSession() {
+        return topoSession;
+    }
+
+    /**
      * Issues a close on the connection.
      */
     synchronized void close() {
@@ -175,7 +184,7 @@
         topoSession.destroy();
         destroyHandlersAndOverlays();
         log.info("GUI client disconnected [close-code={}, message={}]",
-                 closeCode, message);
+                closeCode, message);
     }
 
     @Override
@@ -186,13 +195,13 @@
 
     @Override
     public void onMessage(String data) {
-        log.debug("onMessage: {}", data);
         lastActive = System.currentTimeMillis();
         try {
             ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
             String type = message.path(EVENT).asText(UNKNOWN);
             UiMessageHandler handler = handlers.get(type);
             if (handler != null) {
+                log.debug("RX message: {}", message);
                 handler.process(message);
             } else {
                 log.warn("No GUI message handler for type {}", type);
@@ -208,6 +217,7 @@
         try {
             if (connection.isOpen()) {
                 connection.sendMessage(message.toString());
+                log.debug("TX message: {}", message);
             }
         } catch (IOException e) {
             log.warn("Unable to send message {} to GUI due to {}", message, e);
@@ -257,7 +267,7 @@
             }
         });
         log.debug("#handlers = {}, #overlays = {}", handlers.size(),
-                  overlayCache.size());
+                overlayCache.size());
     }
 
     // Destroys message handlers.
@@ -284,7 +294,7 @@
                     .put(ID, node.id().toString())
                     .put(IP, node.ip().toString())
                     .put(TopoConstants.Glyphs.UI_ATTACHED,
-                         node.equals(service.getLocalNode()));
+                            node.equals(service.getLocalNode()));
             instances.add(instance);
         }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
new file mode 100644
index 0000000..be19389
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2016-present 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.onosproject.ui.impl.topo;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.PortStatisticsService;
+import org.onosproject.incubator.net.tunnel.TunnelService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.region.Region;
+import org.onosproject.net.statistic.StatisticService;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.ui.model.topo.UiClusterMember;
+import org.onosproject.ui.model.topo.UiDevice;
+import org.onosproject.ui.model.topo.UiHost;
+import org.onosproject.ui.model.topo.UiLink;
+import org.onosproject.ui.model.topo.UiRegion;
+import org.onosproject.ui.model.topo.UiTopoLayout;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Facility for creating JSON messages to send to the topology view in the
+ * Web client.
+ */
+class Topo2Jsonifier {
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    private ServiceDirectory directory;
+    private ClusterService clusterService;
+    private DeviceService deviceService;
+    private LinkService linkService;
+    private HostService hostService;
+    private MastershipService mastershipService;
+    private IntentService intentService;
+    private FlowRuleService flowService;
+    private StatisticService flowStatsService;
+    private PortStatisticsService portStatsService;
+    private TopologyService topologyService;
+    private TunnelService tunnelService;
+
+
+    /**
+     * Creates an instance with a reference to the services directory, so that
+     * additional information about network elements may be looked up on
+     * on the fly.
+     *
+     * @param directory service directory
+     */
+    Topo2Jsonifier(ServiceDirectory directory) {
+        this.directory = checkNotNull(directory, "Directory cannot be null");
+
+        clusterService = directory.get(ClusterService.class);
+        deviceService = directory.get(DeviceService.class);
+        linkService = directory.get(LinkService.class);
+        hostService = directory.get(HostService.class);
+        mastershipService = directory.get(MastershipService.class);
+        intentService = directory.get(IntentService.class);
+        flowService = directory.get(FlowRuleService.class);
+        flowStatsService = directory.get(StatisticService.class);
+        portStatsService = directory.get(PortStatisticsService.class);
+        topologyService = directory.get(TopologyService.class);
+        tunnelService = directory.get(TunnelService.class);
+
+    }
+
+    private ObjectNode objectNode() {
+        return mapper.createObjectNode();
+    }
+
+    private ArrayNode arrayNode() {
+        return mapper.createArrayNode();
+    }
+
+    private String nullIsEmpty(Object o) {
+        return o == null ? "" : o.toString();
+    }
+
+
+    /**
+     * Returns a JSON representation of the cluster members (ONOS instances).
+     *
+     * @param instances the instance model objects
+     * @return a JSON representation of the data
+     */
+    ObjectNode instances(List<UiClusterMember> instances) {
+        NodeId local = clusterService.getLocalNode().id();
+        ObjectNode payload = objectNode();
+
+        ArrayNode members = arrayNode();
+        payload.set("members", members);
+        for (UiClusterMember member : instances) {
+            members.add(json(member, member.id().equals(local)));
+        }
+
+        return payload;
+    }
+
+    private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
+        return objectNode()
+                .put("id", member.id().toString())
+                .put("ip", member.ip().toString())
+                .put("online", member.isOnline())
+                .put("ready", member.isReady())
+                .put("uiAttached", isUiAttached)
+                .put("switches", member.deviceCount());
+    }
+
+    /**
+     * Returns a JSON representation of the layout to use for displaying in
+     * the topology view.
+     *
+     * @param layout the layout to transform
+     * @return a JSON representation of the data
+     */
+    ObjectNode layout(UiTopoLayout layout) {
+        return objectNode()
+                .put("id", layout.id().toString())
+                .put("parent", nullIsEmpty(layout.parent()))
+                .put("region", nullIsEmpty(layout.regionId()))
+                .put("regionName", regionName(layout.region()));
+    }
+
+    private String regionName(Region region) {
+        return region == null ? "" : region.name();
+    }
+
+    /**
+     * Returns a JSON representation of the region to display in the topology
+     * view.
+     *
+     * @param region the region to transform to JSON
+     * @return a JSON representation of the data
+     */
+    ObjectNode region(UiRegion region) {
+        ObjectNode payload = objectNode();
+
+        if (region == null) {
+            payload.put("note", "no-region");
+            return payload;
+        }
+
+        payload.put("id", region.id().toString());
+
+        ArrayNode layerOrder = arrayNode();
+        payload.set("layerOrder", layerOrder);
+        region.layerOrder().forEach(layerOrder::add);
+
+        ArrayNode devices = arrayNode();
+        payload.set("devices", devices);
+        for (UiDevice device : region.devices()) {
+            devices.add(json(device));
+        }
+
+        ArrayNode hosts = arrayNode();
+        payload.set("hosts", hosts);
+        for (UiHost host : region.hosts()) {
+            hosts.add(json(host));
+        }
+
+        ArrayNode links = arrayNode();
+        payload.set("links", links);
+        for (UiLink link : region.links()) {
+            links.add(json(link));
+        }
+
+        return payload;
+    }
+
+    private ObjectNode json(UiDevice device) {
+        ObjectNode node = objectNode()
+                .put("id", device.id().toString())
+                .put("type", device.type())
+                .put("online", device.isOnline())
+                .put("master", device.master().toString())
+                .put("layer", device.layer());
+
+        // TODO: complete device details
+//        addLabels(node, device);
+//        addProps(node, device);
+//        addGeoLocation(node, device);
+//        addMetaUi(node, device);
+
+        return node;
+    }
+
+    private void addLabels(ObjectNode node, UiDevice device) {
+
+    }
+
+    private ObjectNode json(UiHost host) {
+        return objectNode()
+                .put("id", host.id().toString())
+                .put("layer", host.layer());
+        // TODO: complete host details
+    }
+
+
+    private ObjectNode json(UiLink link) {
+        return objectNode()
+                .put("id", link.id().toString());
+        // TODO: complete link details
+    }
+
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
new file mode 100644
index 0000000..21023fa
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2016-present 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.onosproject.ui.impl.topo;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiConnection;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.impl.UiWebSocket;
+import org.onosproject.ui.model.topo.UiClusterMember;
+import org.onosproject.ui.model.topo.UiRegion;
+import org.onosproject.ui.model.topo.UiTopoLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.List;
+
+/*
+ NOTES:
+
+    The original topology view message handler was broken into two classes
+    TopologyViewMessageHandler, and TopologyViewMessageHandlerBase.
+    We do not need to follow that model necessarily. Starting with a
+    single class, and breaking it apart later if necessary.
+
+    Need to figure out the connection between this message handler and the
+    new way of doing things with UiTopoSession...
+
+ */
+
+/**
+ * Server-side component for interacting with the new "Region aware" topology
+ * view in the Web UI.
+ */
+public class Topo2ViewMessageHandler extends UiMessageHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    // === Inbound event identifiers
+    private static final String TOPO2_START = "topo2Start";
+    private static final String TOPO2_STOP = "topo2Stop";
+
+    // === Outbound event identifiers
+    private static final String CURRENT_LAYOUT = "topo2CurrentLayout";
+    private static final String CURRENT_REGION = "topo2CurrentRegion";
+    private static final String ALL_INSTANCES = "topo2AllInstances";
+    private static final String TOPO_START_DONE = "topo2StartDone";
+
+    private UiTopoSession topoSession;
+    private Topo2Jsonifier t2json;
+
+
+    @Override
+    public void init(UiConnection connection, ServiceDirectory directory) {
+        super.init(connection, directory);
+
+        // get the topo session from the UiWebSocket
+        topoSession = ((UiWebSocket) connection).topoSession();
+        t2json = new Topo2Jsonifier(directory);
+    }
+
+    @Override
+    protected Collection<RequestHandler> createRequestHandlers() {
+        return ImmutableSet.of(
+                new Topo2Start(),
+                new Topo2Stop()
+        );
+    }
+
+    // ==================================================================
+
+
+    private final class Topo2Start extends RequestHandler {
+        private Topo2Start() {
+            super(TOPO2_START);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            // client view is ready to receive data to display; so start up
+            // server-side processing, and send over initial state
+
+            log.debug("topo2Start: {}", payload);
+
+            List<UiClusterMember> instances = topoSession.getAllInstances();
+            sendMessage(ALL_INSTANCES, t2json.instances(instances));
+
+            UiTopoLayout currentLayout = topoSession.currentLayout();
+            sendMessage(CURRENT_LAYOUT, t2json.layout(currentLayout));
+
+            UiRegion region = topoSession.getRegion(currentLayout);
+            sendMessage(CURRENT_REGION, t2json.region(region));
+
+            // TODO: send information about devices/hosts/links in non-region
+            // TODO: send information about "linked, peer" regions
+
+            sendMessage(TOPO_START_DONE, null);
+
+
+            // OLD CODE DID THE FOLLOWING...
+//            addListeners();
+//            sendAllInstances(null);
+//            sendAllDevices();
+//            sendAllLinks();
+//            sendAllHosts();
+//            sendTopoStartDone();
+        }
+    }
+
+    private final class Topo2Stop extends RequestHandler {
+        private Topo2Stop() {
+            super(TOPO2_STOP);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            // client view has gone away; so shut down server-side processing
+            // TODO: implement...
+
+            log.debug("topo2Stop: {}", payload);
+
+            // OLD CODE DID THE FOLLOWING...
+//            removeListeners();
+//            stopSummaryMonitoring();
+//            traffic.stopMonitoring();
+        }
+    }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
index a693565..1c9fc9d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
@@ -21,10 +21,14 @@
 import org.onosproject.ui.impl.topo.model.UiModelEvent;
 import org.onosproject.ui.impl.topo.model.UiModelListener;
 import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
+import org.onosproject.ui.model.topo.UiClusterMember;
+import org.onosproject.ui.model.topo.UiRegion;
 import org.onosproject.ui.model.topo.UiTopoLayout;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 /**
  * Coordinates with the {@link UiTopoLayoutService} to access
  * {@link UiTopoLayout}s, and with the {@link UiSharedTopologyModel} which
@@ -34,6 +38,10 @@
  * Note that an instance of this class will be created for each
  * {@link UiWebSocket} connection, and will contain
  * the state of how the topology is laid out for the logged-in user.
+ * <p>
+ * The expected pattern is for the {@link Topo2ViewMessageHandler} to obtain
+ * a reference to the session instance (via the {@link UiWebSocket}), and
+ * interact with it when topo-related events come in from the client.
  */
 public class UiTopoSession implements UiModelListener {
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -129,4 +137,23 @@
     public void enableEvent(boolean enabled) {
         messagesEnabled = enabled;
     }
+
+    /**
+     * Returns the list of ONOS instances (cluster members).
+     *
+     * @return the list of ONOS instances
+     */
+    public List<UiClusterMember> getAllInstances() {
+        return sharedModel.getClusterMembers();
+    }
+
+    /**
+     * Returns the region for the specified layout.
+     *
+     * @param layout layout filter
+     * @return region that the layout is based upon
+     */
+    public UiRegion getRegion(UiTopoLayout layout) {
+        return sharedModel.getRegion(layout);
+    }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
index 6566511..8754550 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
@@ -41,6 +41,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
 import java.util.Set;
 
 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
@@ -153,6 +154,10 @@
         }
     }
 
+    List<UiClusterMember> getAllClusterMembers() {
+        return uiTopology.allClusterMembers();
+    }
+
 
     // === MASTERSHIP CHANGES
 
@@ -199,7 +204,7 @@
 
     // package private for unit test access
     UiRegion accessRegion(RegionId id) {
-        return uiTopology.findRegion(id);
+        return id == null ? null : uiTopology.findRegion(id);
     }
 
     // invoked from UiSharedTopologyModel region listener
@@ -484,4 +489,5 @@
     public int hostCount() {
         return uiTopology.hostCount();
     }
+
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java
index 37ac126..54c92f6 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java
@@ -16,9 +16,6 @@
 
 package org.onosproject.ui.impl.topo.model;
 
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -64,9 +61,19 @@
 import org.onosproject.net.topology.TopologyService;
 import org.onosproject.ui.impl.topo.UiTopoSession;
 import org.onosproject.ui.model.ServiceBundle;
+import org.onosproject.ui.model.topo.UiClusterMember;
+import org.onosproject.ui.model.topo.UiElement;
+import org.onosproject.ui.model.topo.UiRegion;
+import org.onosproject.ui.model.topo.UiTopoLayout;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 /**
  * Service that creates and maintains the UI-model of the network topology.
  */
@@ -190,6 +197,41 @@
         removeListener(session);
     }
 
+
+    // =======================================================================
+    //  methods that the topo session will use to extract information from us
+
+    /**
+     * Returns the list of cluster members stored in our model cache.
+     *
+     * @return list of cluster members
+     */
+    public List<UiClusterMember> getClusterMembers() {
+        return cache.getAllClusterMembers();
+    }
+
+    public Set<UiElement> getElements(UiTopoLayout layout) {
+        Set<UiElement> results = new HashSet<>();
+
+        // TODO: figure out how to extract the appropriate nodes
+        //       from the cache, for the given layout.
+
+        return results;
+    }
+
+    /**
+     * Returns the region for the given layout.
+     *
+     * @param layout layout filter
+     * @return the region the layout is based upon
+     */
+    public UiRegion getRegion(UiTopoLayout layout) {
+        return cache.accessRegion(layout.regionId());
+    }
+
+    // =====================================================================
+
+
     /**
      * Default implementation of service bundle to return references to our
      * dynamically injected services.
diff --git a/web/gui/src/main/webapp/app/fw/remote/websocket.js b/web/gui/src/main/webapp/app/fw/remote/websocket.js
index 0f370cf..bd1f44d 100644
--- a/web/gui/src/main/webapp/app/fw/remote/websocket.js
+++ b/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -256,6 +256,8 @@
         });
     }
 
+    // TODO: simplify listener handling (see theme.js for sample code)
+
     function addOpenListener(callback) {
         var id = nextListenerId++,
             cb = fs.isF(callback),
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
new file mode 100644
index 0000000..63b5dfb
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (theme) -- CSS file
+ */
+
+/* --- Base SVG Layer --- */
+
+#ov-topo2 svg {
+    /*background-color: #f4f4f4;*/
+    background-color: goldenrod; /* just for testing */
+}
+
+/* --- "No Devices" Layer --- */
+
+#ov-topo2 svg .noDevsBird {
+    fill: #db7773;
+}
+
+#ov-topo2 svg #topo2-noDevsLayer text {
+    fill: #7e9aa8;
+}
+
+/* --- Topo Map --- */
+
+#ov-topo2 svg #topo2-map {
+    stroke-width: 2px;
+    stroke: #f4f4f4;
+    fill: #e5e5e6;
+}
+
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.css b/web/gui/src/main/webapp/app/view/topo2/topo2.css
new file mode 100644
index 0000000..7f878a5
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.css
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (layout) -- CSS file
+ */
+
+/* --- Base SVG Layer --- */
+
+#ov-topo2 svg {
+    /* prevents the little cut/copy/paste square that would appear on iPad */
+    -webkit-user-select: none;
+}
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.html b/web/gui/src/main/webapp/app/view/topo2/topo2.html
new file mode 100644
index 0000000..9b987a6
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.html
@@ -0,0 +1,7 @@
+<!-- Topology View partial HTML -->
+<div id="ov-topo2">
+    <svg viewBox="0 0 1000 1000"
+         resize offset-height="56" offset-width="12"
+         notifier="notifyResize()">
+    </svg>
+</div>
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.js b/web/gui/src/main/webapp/app/view/topo2/topo2.js
new file mode 100644
index 0000000..ce90af6
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+/*
+ ONOS GUI -- Topology View Module
+
+ NOTE: currently under development to support Regions.
+ */
+
+(function () {
+    'use strict';
+
+    // references to injected services
+    var $scope, $log, $loc,
+        fs, mast, ks, zs,
+        gs, ms, sus, flash,
+        wss, ps, th,
+        t2es, t2fs;
+
+    // DOM elements
+    var ovtopo2, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer;
+
+    // Internal state
+    var zoomer, actionMap;
+
+
+    // === Helper Functions
+
+    // callback invoked when the SVG view has been resized..
+    function svgResized(s) {
+        $log.debug("topo2 view resized", s);
+    }
+
+    function setUpKeys(overlayKeys) {
+        $log.debug('topo2: set up keys....');
+    }
+
+    // === Controller Definition -----------------------------------------
+
+    angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote'])
+    .controller('OvTopo2Ctrl', 
+        ['$scope', '$log', '$location',
+        'FnService', 'MastService', 'KeyService', 'ZoomService',
+        'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
+        'WebSocketService', 'PrefsService', 'ThemeService',
+        'Topo2EventService', 'Topo2ForceService',
+
+        function (_$scope_, _$log_, _$loc_,
+                  _fs_, _mast_, _ks_, _zs_,
+                  _gs_, _ms_, _sus_, _flash_,
+                  _wss_, _ps_, _th_,
+                  _t2es_, _t2fs_) {
+
+            var params = _$loc_.search(),
+                projection,
+                dim,
+                wh,
+                uplink = {
+                    // provides function calls back into this space
+                    // showNoDevs: showNoDevs,
+                    // projection: function () { return projection; },
+                    // zoomLayer: function () { return zoomLayer; },
+                    // zoomer: function () { return zoomer; },
+                    // opacifyMap: opacifyMap,
+                    // topoStartDone: topoStartDone
+                };
+
+            $scope = _$scope_;
+            $log = _$log_;
+            $loc = _$loc_;
+
+            fs = _fs_;
+            mast = _mast_;
+            ks = _ks_;
+            zs = _zs_;
+
+            gs = _gs_;
+            ms = _ms_;
+            sus = _sus_;
+            flash = _flash_;
+
+            wss = _wss_;
+            ps = _ps_;
+            th = _th_;
+            
+            t2es = _t2es_;
+            t2fs = _t2fs_;
+
+            // capture selected intent parameters (if they are set in the
+            //  query string) so that the traffic overlay can highlight
+            //  the path for that intent
+            if (params.intentKey && params.intentAppId && params.intentAppName) {
+                $scope.intentData = {
+                    key: params.intentKey,
+                    appId: params.intentAppId,
+                    appName: params.intentAppName
+                };
+            }
+
+            $scope.notifyResize = function () {
+                svgResized(fs.windowSize(mast.mastHeight()));
+            };
+
+            // Cleanup on destroyed scope..
+            $scope.$on('$destroy', function () {
+                $log.log('OvTopo2Ctrl is saying Buh-Bye!');
+                t2es.stop();
+                ks.unbindKeys();
+                t2fs.destroy();
+            });
+
+            // svg layer and initialization of components
+            ovtopo2 = d3.select('#ov-topo2');
+            svg = ovtopo2.select('svg');
+            // set the svg size to match that of the window, less the masthead
+            wh = fs.windowSize(mast.mastHeight());
+            $log.debug('setting topo SVG size to', wh);
+            svg.attr(wh);
+            dim = [wh.width, wh.height];
+
+
+            // set up our keyboard shortcut bindings
+            setUpKeys();
+
+            // make sure we can respond to topology events from the server
+            t2es.bindHandlers();
+
+            // initialize the force layout, ready to render the topology
+            t2fs.init();
+
+
+            // =-=-=-=-=-=-=-=-
+            // TODO: in future, we will load background map data
+            //  asynchronously (hence the promise) and then chain off 
+            //  there to send the topo2start event to the server.
+            // For now, we'll send the event inline...
+            t2es.start();
+
+            
+            // === ORIGINAL CODE ===
+            
+            // setUpKeys();
+            // setUpToolbar();
+            // setUpDefs();
+            // setUpZoom();
+            // setUpNoDevs();
+            /*
+            setUpMap().then(
+                function (proj) {
+                    var z = ps.getPrefs('topo_zoom', { tx:0, ty:0, sc:1 });
+                    zoomer.panZoom([z.tx, z.ty], z.sc);
+                    $log.debug('** Zoom restored:', z);
+
+                    projection = proj;
+                    $log.debug('** We installed the projection:', proj);
+                    flash.enable(false);
+                    toggleMap(prefsState.bg);
+                    flash.enable(true);
+                    mapShader(true);
+
+                    // now we have the map projection, we are ready for
+                    //  the server to send us device/host data...
+                    tes.start();
+                    // need to do the following so we immediately get
+                    //  the summary panel data back from the server
+                    restoreSummaryFromPrefs();
+                }
+            );
+            */
+            // tes.bindHandlers();
+            // setUpSprites();
+
+            // forceG = zoomLayer.append('g').attr('id', 'topo-force');
+            // tfs.initForce(svg, forceG, uplink, dim);
+            // tis.initInst({ showMastership: tfs.showMastership });
+            // tps.initPanels();
+
+            // restoreConfigFromPrefs();
+            // ttbs.setDefaultOverlay(prefsState.ovidx);
+
+            // $log.debug('registered overlays...', tov.list());
+            
+            $log.log('OvTopo2Ctrl has been created');
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
new file mode 100644
index 0000000..83dcae2
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+/*
+ ONOS GUI -- Topology Event Module.
+
+ Defines the conduit between the client and the server:
+ - provides a clean API for sending events to the server
+ - dispatches incoming events from the server to the appropriate sub-module
+
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, wss, t2fs;
+
+    // internal state
+    var handlerMap,
+        openListener;
+
+    // TODO: only add heartbeat timer etc. if we really need to be doing that..
+
+    // ========================== Helper Functions
+
+    function createHandlerMap() {
+        handlerMap = {
+            topo2AllInstances: t2fs,
+            topo2CurrentLayout: t2fs,
+            topo2CurrentRegion: t2fs,
+            topo2StartDone: t2fs
+
+            // Add further event names / module references as needed
+        };
+    }
+
+    function wsOpen(host, url) {
+        $log.debug('topo2Event: WSopen - cluster node:', host, 'URL:', url);
+        // tell the server we are ready to receive topo events
+        wss.sendEvent('topo2Start');
+    }
+
+    // bind our event handlers to the web socket service, so that our
+    //  callbacks get invoked for incoming events
+    function bindHandlers() {
+        wss.bindHandlers(handlerMap);
+        $log.debug('topo2 event handlers bound');
+    }
+
+    // tell the server we are ready to receive topology events
+    function start() {
+        // in case we fail over to a new server,
+        // listen for wsock-open events
+        openListener = wss.addOpenListener(wsOpen);
+        wss.sendEvent('topo2Start');
+        $log.debug('topo2 comms started');
+    }
+
+    // tell the server we no longer wish to receive topology events
+    function stop() {
+        wss.sendEvent('topo2Stop');
+        wss.unbindHandlers(handlerMap);
+        wss.removeOpenListener(openListener);
+        openListener = null;
+        $log.debug('topo2 comms stopped');
+    }
+
+    // ========================== Main Service Definition
+
+    angular.module('ovTopo2')
+    .factory('Topo2EventService',
+        ['$log', 'WebSocketService', 'Topo2ForceService',
+
+        function (_$log_, _wss_, _t2fs_) {
+            $log = _$log_;
+            wss = _wss_;
+            t2fs = _t2fs_;
+
+            // deferred creation of handler map, so module references are good
+            createHandlerMap();
+
+            return {
+                bindHandlers: bindHandlers,
+                start: start,
+                stop: stop
+            };
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
new file mode 100644
index 0000000..db6a1c2
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-present 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.
+ */
+
+/*
+ ONOS GUI -- Topology Force Module.
+ Visualization of the topology in an SVG layer, using a D3 Force Layout.
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, wss;
+
+    // ========================== Helper Functions
+
+    function init() {
+        $log.debug('Initialize topo force layout');
+    }
+
+    function destroy() {
+        $log.debug('Destroy topo force layout');
+    }
+
+    // ========================== Event Handlers
+
+    function allInstances(data) {
+        $log.debug('>> topo2AllInstances event:', data)
+    }
+
+    function currentLayout(data) {
+        $log.debug('>> topo2CurrentLayout event:', data)
+    }
+
+    function currentRegion(data) {
+        $log.debug('>> topo2CurrentRegion event:', data)
+    }
+
+    function startDone(data) {
+        $log.debug('>> topo2StartDone event:', data)
+    }
+    
+    // ========================== Main Service Definition
+
+    angular.module('ovTopo2')
+    .factory('Topo2ForceService',
+        ['$log', 'WebSocketService',
+
+        function (_$log_, _wss_) {
+            $log = _$log_;
+            wss = _wss_;
+            
+            return {
+                init: init,
+                destroy: destroy,
+                topo2AllInstances: allInstances,
+                topo2CurrentLayout: currentLayout,
+                topo2CurrentRegion: currentRegion,
+                topo2StartDone: startDone
+            };
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index e4fa6e2..22865a1 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -126,6 +126,13 @@
     <link rel="stylesheet" href="app/fw/widget/table.css">
     <link rel="stylesheet" href="app/fw/widget/table-theme.css">
 
+    <!-- Under development for Region support. -->
+    <script src="app/view/topo2/topo2.js"></script>
+    <script src="app/view/topo2/topo2Event.js"></script>
+    <script src="app/view/topo2/topo2Force.js"></script>
+    <link rel="stylesheet" href="app/view/topo2/topo2.css">
+    <link rel="stylesheet" href="app/view/topo2/topo2-theme.css">
+
     <!-- Builtin views javascript. -->
     <script src="app/view/topo/topo.js"></script>
     <script src="app/view/topo/topoD3.js"></script>