Enhanced layout service and hooked-in the ui topo session.

Change-Id: I357143766deb3f0d697a3e7963a53968ccdf3bc8
diff --git a/core/api/src/main/java/org/onosproject/ui/UiConnection.java b/core/api/src/main/java/org/onosproject/ui/UiConnection.java
index ebe1ef7..4fb388d 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiConnection.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiConnection.java
@@ -16,6 +16,7 @@
 package org.onosproject.ui;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.ui.model.topo.UiTopoLayout;
 
 /**
  * Abstraction of a user interface session connection.
@@ -30,6 +31,34 @@
     String userName();
 
     /**
+     * Returns the current layout context.
+     *
+     * @return current topology layout
+     */
+    UiTopoLayout currentLayout();
+
+    /**
+     * Changes the current layout context to the specified layout.
+     *
+     * @param topoLayout new topology layout context
+     */
+    void setCurrentLayout(UiTopoLayout topoLayout);
+
+    /**
+     * Returns the current view identifier.
+     *
+     * @return current view
+     */
+    String currentView();
+
+    /**
+     * Sets the currently selected view.
+     *
+     * @param viewId view identifier
+     */
+    void setCurrentView(String viewId);
+
+    /**
      * Sends the specified JSON message to the user interface client.
      *
      * @param message message to send
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java b/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java
index 989c807..0f2c273 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java
@@ -26,6 +26,14 @@
 public interface UiTopoLayoutService {
 
     /**
+     * Returns the top-level root layout, which always exists and cannot
+     * be removed or associated directly with a region.
+     *
+     * @return root topology layout
+     */
+    UiTopoLayout getRootLayout();
+
+    /**
      * Returns the set of available layouts.
      *
      * @return set of available layouts
@@ -40,15 +48,23 @@
      */
     boolean addLayout(UiTopoLayout layout);
 
-
     /**
      * Returns the layout with the specified identifier.
+     *
      * @param layoutId layout identifier
      * @return layout or null if no such layout is found
      */
     UiTopoLayout getLayout(UiTopoLayoutId layoutId);
 
     /**
+     * Returns the set of the child layouts of the specified layout.
+     *
+     * @param layoutId layout identifier
+     * @return set of child layouts; empty set if layout has no children
+     */
+    Set<UiTopoLayout> getChildren(UiTopoLayoutId layoutId);
+
+    /**
      * Removes a layout from the system.
      *
      * @param layout the layout to remove
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 289bb8f..8b421c0 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
@@ -26,16 +26,19 @@
 
     private final UiTopoLayoutId id;
     private final Region region;
+    private final UiTopoLayoutId parent;
 
     /**
      * Created a new UI topology layout.
      *
      * @param id     layout identifier
      * @param region backing region
+     * @param parent identifier of the parent layout
      */
-    public UiTopoLayout(UiTopoLayoutId id, Region region) {
+    public UiTopoLayout(UiTopoLayoutId id, Region region, UiTopoLayoutId parent) {
         this.id = id;
         this.region = region;
+        this.parent = parent;
     }
 
     /**
@@ -56,5 +59,14 @@
         return region;
     }
 
+    /**
+     * Returns the parent layout identifier.
+     *
+     * @return parent layout identifier
+     */
+    public UiTopoLayoutId parent() {
+        return parent;
+    }
+
     // TODO: additional properties pertinent to the layout
 }
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 38af57d..2585bc8 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
@@ -27,7 +27,11 @@
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiTopoLayoutService;
 import org.onosproject.ui.UiTopoOverlayFactory;
+import org.onosproject.ui.impl.topo.UiTopoSession;
+import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
+import org.onosproject.ui.model.topo.UiTopoLayout;
 import org.onosproject.ui.topo.TopoConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -55,6 +59,8 @@
     private static final String USER = "user";
     private static final String BOOTSTRAP = "bootstrap";
 
+    public static final String TOPO = "topo";
+
     private static final long MAX_AGE_MS = 30_000;
 
     private static final byte PING = 0x9;
@@ -63,11 +69,12 @@
 
     private final ObjectMapper mapper = new ObjectMapper();
     private final ServiceDirectory directory;
-//    private final UiTopoSession topoSession;
+    private final UiTopoSession topoSession;
 
     private Connection connection;
     private FrameConnection control;
     private String userName;
+    private String currentView;
 
     private long lastActive = System.currentTimeMillis();
 
@@ -83,8 +90,9 @@
     public UiWebSocket(ServiceDirectory directory, String userName) {
         this.directory = directory;
         this.userName = userName;
-//        this.topoSession =
-//                new UiTopoSession(this, directory.get(UiSharedTopologyModel.class));
+        this.topoSession =
+                new UiTopoSession(this, directory.get(UiSharedTopologyModel.class),
+                                  directory.get(UiTopoLayoutService.class));
     }
 
     @Override
@@ -92,6 +100,27 @@
         return userName;
     }
 
+    @Override
+    public UiTopoLayout currentLayout() {
+        return topoSession.currentLayout();
+    }
+
+    @Override
+    public void setCurrentLayout(UiTopoLayout topoLayout) {
+        topoSession.setCurrentLayout(topoLayout);
+    }
+
+    @Override
+    public String currentView() {
+        return currentView;
+    }
+
+    @Override
+    public void setCurrentView(String viewId) {
+        currentView = viewId;
+        topoSession.enableEvent(viewId.equals(TOPO));
+    }
+
     /**
      * Issues a close on the connection.
      */
@@ -128,7 +157,7 @@
         this.connection = connection;
         this.control = (FrameConnection) connection;
         try {
-//            topoSession.init();
+            topoSession.init();
             createHandlersAndOverlays();
             sendBootstrapData();
             log.info("GUI client connected -- user <{}>", userName);
@@ -143,10 +172,10 @@
 
     @Override
     public synchronized void onClose(int closeCode, String message) {
-//        topoSession.destroy();
+        topoSession.destroy();
         destroyHandlersAndOverlays();
         log.info("GUI client disconnected [close-code={}, message={}]",
-                closeCode, message);
+                 closeCode, message);
     }
 
     @Override
@@ -228,7 +257,7 @@
             }
         });
         log.debug("#handlers = {}, #overlays = {}", handlers.size(),
-                overlayCache.size());
+                  overlayCache.size());
     }
 
     // Destroys message handlers.
@@ -255,7 +284,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/UiTopoLayoutManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
index bc58121..60797f4 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
@@ -35,7 +35,11 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Manages the user interface topology layouts.
@@ -47,6 +51,11 @@
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
+    private static final String ID_NULL = "Layout ID cannot be null";
+    private static final String LAYOUT_NULL = "Layout cannot be null";
+
+    private static final UiTopoLayoutId DEFAULT_ID = UiTopoLayoutId.layoutId("_default_");
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
 
@@ -57,6 +66,7 @@
     public void activate() {
         KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
                 .register(KryoNamespaces.API)
+                .register(UiTopoLayoutId.class)
                 .register(UiTopoLayout.class);
 
         layouts = storageService.<UiTopoLayoutId, UiTopoLayout>consistentMapBuilder()
@@ -66,6 +76,9 @@
                 .build();
         layoutMap = layouts.asJavaMap();
 
+        // Create and add the default layout, if needed.
+        layoutMap.computeIfAbsent(DEFAULT_ID, k -> new UiTopoLayout(k, null, null));
+
         log.info("Started");
     }
 
@@ -76,22 +89,38 @@
 
 
     @Override
+    public UiTopoLayout getRootLayout() {
+        return getLayout(DEFAULT_ID);
+    }
+
+    @Override
     public Set<UiTopoLayout> getLayouts() {
         return ImmutableSet.copyOf(layoutMap.values());
     }
 
     @Override
     public boolean addLayout(UiTopoLayout layout) {
+        checkNotNull(layout, LAYOUT_NULL);
         return layouts.put(layout.id(), layout) == null;
     }
 
     @Override
     public UiTopoLayout getLayout(UiTopoLayoutId layoutId) {
+        checkNotNull(layoutId, ID_NULL);
         return layoutMap.get(layoutId);
     }
 
     @Override
+    public Set<UiTopoLayout> getChildren(UiTopoLayoutId layoutId) {
+        checkNotNull(layoutId, ID_NULL);
+        return layoutMap.values().stream()
+                .filter(l -> Objects.equals(l.parent(), layoutId))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
     public boolean removeLayout(UiTopoLayout layout) {
+        checkNotNull(layout, LAYOUT_NULL);
         return layouts.remove(layout.id()) != null;
     }
 
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 70698ec..0ffe2ca 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
@@ -45,19 +45,24 @@
 
     private boolean registered = false;
 
-    private UiTopoLayoutService service;
+    private UiTopoLayoutService layoutService;
     private UiTopoLayout currentLayout;
+    private boolean messagesEnabled;
 
     /**
      * Creates a new topology session for the specified web socket connection.
      *
-     * @param webSocket web socket
-     * @param model share topology model
+     * @param webSocket     web socket
+     * @param model         share topology model
+     * @param layoutService topology layout service
      */
-    public UiTopoSession(UiWebSocket webSocket, UiSharedTopologyModel model) {
+    public UiTopoSession(UiWebSocket webSocket,
+                         UiSharedTopologyModel model,
+                         UiTopoLayoutService layoutService) {
         this.webSocket = webSocket;
         this.username = webSocket.userName();
         this.sharedModel = model;
+        this.layoutService = layoutService;
     }
 
     /**
@@ -67,6 +72,7 @@
         if (!registered) {
             log.debug("{} : Registering with shared model", this);
             sharedModel.register(this);
+            currentLayout = layoutService.getRootLayout();
             registered = true;
         } else {
             log.warn("already registered");
@@ -96,4 +102,31 @@
         log.info("Event received: {}", event);
         // TODO: handle model events from the cache...
     }
+
+    /**
+     * Returns the current layout context.
+     *
+     * @return current topology layout
+     */
+    public UiTopoLayout currentLayout() {
+        return currentLayout;
+    }
+
+    /**
+     * Changes the current layout context to the specified layout.
+     *
+     * @param topoLayout new topology layout context
+     */
+    public void setCurrentLayout(UiTopoLayout topoLayout) {
+        currentLayout = topoLayout;
+    }
+
+    /**
+     * Enables or disables the transmission of topology event update messages.
+     *
+     * @param enabled true if messages should be sent
+     */
+    public void enableEvent(boolean enabled) {
+        messagesEnabled = enabled;
+    }
 }
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 19ab015..6566511 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
@@ -224,7 +224,10 @@
     }
 
     private void updateDevice(UiDevice device) {
-        device.setRegionId(services.region().getRegionForDevice(device.id()).id());
+        Region regionForDevice = services.region().getRegionForDevice(device.id());
+        if (regionForDevice != null) {
+            device.setRegionId(regionForDevice.id());
+        }
     }
 
     private void loadDevices() {
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 7dca64e..7cb0ffc 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
@@ -66,7 +66,7 @@
 /**
  * Service that creates and maintains the UI-model of the network topology.
  */
-@Component(immediate = true, enabled = false)
+@Component(immediate = true, enabled = true)
 @Service(value = UiSharedTopologyModel.class)
 public final class UiSharedTopologyModel
         extends AbstractListenerManager<UiModelEvent, UiModelListener> {
diff --git a/web/gui/src/test/java/org/onosproject/ui/impl/topo/UiTopoLayoutManagerTest.java b/web/gui/src/test/java/org/onosproject/ui/impl/topo/UiTopoLayoutManagerTest.java
index c58e1a2..17302f1 100644
--- a/web/gui/src/test/java/org/onosproject/ui/impl/topo/UiTopoLayoutManagerTest.java
+++ b/web/gui/src/test/java/org/onosproject/ui/impl/topo/UiTopoLayoutManagerTest.java
@@ -40,11 +40,11 @@
     private static final UiTopoLayout L1 =
             new UiTopoLayout(UiTopoLayoutId.layoutId("l1"),
                              new DefaultRegion(RegionId.regionId("r1"), "R1",
-                                               Region.Type.CAMPUS, null));
+                                               Region.Type.CAMPUS, null), null);
     private static final UiTopoLayout L2 =
             new UiTopoLayout(UiTopoLayoutId.layoutId("l2"),
                              new DefaultRegion(RegionId.regionId("r2"), "R2",
-                                               Region.Type.CAMPUS, null));
+                                               Region.Type.CAMPUS, null), null);
 
     @Before
     public void setUp() {
@@ -62,14 +62,14 @@
 
     @Test
     public void basics() {
-        assertTrue("should be no layout", svc.getLayouts().isEmpty());
+        assertEquals("should be just default layout", 1, svc.getLayouts().size());
         svc.addLayout(L1);
         svc.addLayout(L2);
-        assertEquals("incorrect number of layouts", 2, svc.getLayouts().size());
+        assertEquals("incorrect number of layouts", 3, svc.getLayouts().size());
         assertEquals("incorrect layout", L1.id(), svc.getLayout(L1.id()).id());
         assertEquals("incorrect layout", L2.id(), svc.getLayout(L2.id()).id());
         svc.removeLayout(L1);
-        assertEquals("incorrect number of layouts", 1, svc.getLayouts().size());
+        assertEquals("incorrect number of layouts", 2, svc.getLayouts().size());
         assertNull("layout should be gone", svc.getLayout(L1.id()));
         assertEquals("incorrect layout", L2.id(), svc.getLayout(L2.id()).id());
     }