[ONOS-6958] Add glyph registration java code

Change-Id: I954c790062f8ff5498c34c334827c4f695278a9e
diff --git a/core/api/src/main/java/org/onosproject/security/AppPermission.java b/core/api/src/main/java/org/onosproject/security/AppPermission.java
index c0f7fe9..c0ab697 100644
--- a/core/api/src/main/java/org/onosproject/security/AppPermission.java
+++ b/core/api/src/main/java/org/onosproject/security/AppPermission.java
@@ -85,7 +85,9 @@
         UI_WRITE,
         UPGRADE_READ,
         UPGRADE_WRITE,
-        UPGRADE_EVENT
+        UPGRADE_EVENT,
+        GLYPH_READ,
+        GLYPH_WRITE
     }
 
     protected Type type;
diff --git a/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java b/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
index f760abb..d5bae5e 100644
--- a/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
+++ b/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
@@ -85,6 +85,10 @@
 
     public static final String CLOUD = "m_cloud";
 
+    public static final String ID = "id";
+    public static final String VIEWBOX = "viewbox";
+    public static final String PATH = "path";
+
     // non-instantiable
     private GlyphConstants() {
     }
diff --git a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
index 4764412..9e03a51 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
@@ -17,6 +17,7 @@
 
 import org.onosproject.ui.lion.LionBundle;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -39,6 +40,22 @@
     void unregister(UiExtension extension);
 
     /**
+     * Registers the specified user interface glyph factory.
+     *
+     * @param factory UI glyph factory to register
+     */
+    default void register(UiGlyphFactory factory) {
+    }
+
+    /**
+     * Unregisters the specified user interface glyph factory.
+     *
+     * @param factory UI glyph factory to unregister
+     */
+    default void unregister(UiGlyphFactory factory) {
+    }
+
+    /**
      * Returns the list of registered user interface extensions.
      *
      * @return list of extensions
@@ -54,6 +71,15 @@
     UiExtension getViewExtension(String viewId);
 
     /**
+     * Returns the list of registered user interface glyphs.
+     *
+     * @return list of glyphs
+     */
+    default List<UiGlyph> getGlyphs() {
+        return new ArrayList<UiGlyph>();
+    }
+
+    /**
      * Returns the navigation pane localization bundle.
      *
      * @return the navigation localization bundle
diff --git a/core/api/src/main/java/org/onosproject/ui/UiGlyph.java b/core/api/src/main/java/org/onosproject/ui/UiGlyph.java
new file mode 100644
index 0000000..767ccd4
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiGlyph.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 Open Networking Foundation
+ *
+ * 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;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Represents a glyph to be used in the user interface topology view. Instances
+ * of this class are immutable.
+ */
+public class UiGlyph {
+
+    private final String id;
+    private final String viewbox;
+    private final String path;
+
+
+    /**
+     * Creates a new glyph.
+     *
+     * The value of the viewbox parameter is a string of four numbers min-x,
+     * min-y, width and height, separated by whitespace and/or a comma.
+     *
+     * The path parameter specifies how this element is to be drawn inside of
+     * the viewbox. The ONOS GUI only uses single paths – not rectangles,
+     * strokes, circles, or anything else. One path definition has to be used
+     * for the entire glyph.
+     *
+     * @param id       glyph identifier
+     * @param viewbox  glyph viewbox
+     * @param path     glyph path
+     */
+    public UiGlyph(String id, String viewbox, String path) {
+        this.id = id;
+        this.viewbox = viewbox;
+        this.path = path;
+    }
+
+    /**
+     * Returns the identifier for this glyph.
+     *
+     * @return the identifier
+     */
+    public String id() {
+        return id;
+    }
+
+    /**
+     * Returns the viewbox for this glyph.
+     *
+     * @return the viewbox
+     */
+    public String viewbox() {
+        return viewbox;
+    }
+
+    /**
+     * Returns the path for this glyph.
+     *
+     * @return the path
+     */
+    public String path() {
+        return path;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("id", id)
+                .add("viewbox", viewbox)
+                .add("path", path)
+                .toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/UiGlyphFactory.java b/core/api/src/main/java/org/onosproject/ui/UiGlyphFactory.java
new file mode 100644
index 0000000..c98c0e2
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiGlyphFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 Open Networking Foundation
+ *
+ * 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;
+
+import java.util.List;
+
+/**
+ * Abstraction of an entity capable of producing one or more glyphs for the
+ * topology view.
+ */
+public interface UiGlyphFactory {
+
+    /**
+     * Produces a list of glyphns to be added to the topology view.
+     *
+     * @return list of glyphs
+     */
+    List<UiGlyph> glyphs();
+}
diff --git a/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisDriversLoader.java b/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisDriversLoader.java
index 759f1b8..a5cb3f3 100644
--- a/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisDriversLoader.java
+++ b/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisDriversLoader.java
@@ -16,9 +16,18 @@
 
 package org.onosproject.drivers.polatis.netconf;
 
+import com.google.common.collect.ImmutableList;
+
+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.onosproject.net.driver.AbstractDriverLoader;
 import org.onosproject.net.optical.OpticalDevice;
+import org.onosproject.ui.UiGlyph;
+import org.onosproject.ui.UiGlyphFactory;
+import org.onosproject.ui.UiExtensionService;
 
 /**
  * Loader for Polatis device drivers.
@@ -30,7 +39,50 @@
     @SuppressWarnings("unused")
     private OpticalDevice optical;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected UiExtensionService uiExtensionService;
+
+    private UiGlyphFactory glyphFactory =
+        () -> ImmutableList.of(
+            new UiGlyph("policon", "0 0 64 64",
+                "M 32.024746,2 30.163615,19.069136 24.258784,3.015638 "
+                + "26.879599,19.985033 17.021343,6.007051 23.943688,21.71947 "
+                + "10.8045,10.769161 21.557349,24.15439 6.031794,16.978659 "
+                + "19.883076,27.1245 3.027943,24.21114 19.033986,30.42674 "
+                + "2,31.97526 19.069136,33.83639 3.015638,39.74122 "
+                + "19.985033,37.12041 6.007051,46.97866 21.719466,40.05632 "
+                + "10.769161,53.19551 24.154391,42.44265 16.978659,57.96822 "
+                + "27.124504,44.11693 24.21114,60.97206 30.426738,44.96602 "
+                + "31.975259,62 33.83639,44.93086 39.74122,60.98437 "
+                + "37.120405,44.01497 46.978663,57.99296 40.056317,42.28054 "
+                + "53.195507,53.23084 42.442656,39.84561 57.968215,47.02135 "
+                + "44.116927,36.8755 60.972063,39.78886 44.966018,33.57327 "
+                + "62,32.02475 44.930865,30.16362 60.984369,24.25878 "
+                + "44.014972,26.8796 57.992959,17.021342 42.280539,23.94369 "
+                + "53.23084,10.8045 39.845614,21.55735 47.021349,6.031794 "
+                + "36.875501,19.883076 39.788865,3.027943 33.573267,19.033986 Z "
+                + "m -0.05497,19.23081 A 10.768943,10.768943 0 0 1 "
+                + "42.769201,31.96977 10.768943,10.768943 0 0 1 "
+                + "32.030235,42.7692 10.768943,10.768943 0 0 1 "
+                + "21.230812,32.03023 10.768943,10.768943 0 0 1 "
+                + "31.969778,21.23081 Z")
+            );
+
     public PolatisDriversLoader() {
         super("/polatis-drivers.xml");
     }
+
+    @Activate
+    @Override
+    protected void activate() {
+        uiExtensionService.register(glyphFactory);
+        super.activate();
+    }
+
+    @Deactivate
+    @Override
+    protected void deactivate() {
+        uiExtensionService.unregister(glyphFactory);
+        super.deactivate();
+    }
 }
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 9b7472b..bb24c89 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
@@ -48,6 +48,8 @@
 import org.onosproject.store.service.StorageService;
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiGlyph;
+import org.onosproject.ui.UiGlyphFactory;
 import org.onosproject.ui.UiMessageHandlerFactory;
 import org.onosproject.ui.UiPreferencesService;
 import org.onosproject.ui.UiSessionToken;
@@ -82,6 +84,8 @@
 import static org.onosproject.security.AppGuard.checkPermission;
 import static org.onosproject.security.AppPermission.Type.UI_READ;
 import static org.onosproject.security.AppPermission.Type.UI_WRITE;
+import static org.onosproject.security.AppPermission.Type.GLYPH_READ;
+import static org.onosproject.security.AppPermission.Type.GLYPH_WRITE;
 import static org.onosproject.ui.UiView.Category.NETWORK;
 import static org.onosproject.ui.UiView.Category.PLATFORM;
 import static org.onosproject.ui.impl.lion.BundleStitcher.generateBundles;
@@ -102,6 +106,8 @@
     private static final String CORE = "core";
     private static final String GUI_ADDED = "guiAdded";
     private static final String GUI_REMOVED = "guiRemoved";
+    private static final String GLYPH_ADDED = "glyphAdded";
+    private static final String GLYPH_REMOVED = "glyphRemoved";
     private static final String UPDATE_PREFS = "updatePrefs";
     private static final String SLASH = "/";
 
@@ -134,6 +140,8 @@
     // List of all extensions
     private final List<UiExtension> extensions = Lists.newArrayList();
 
+    private final List<UiGlyph> glyphs = Lists.newArrayList();
+
     // Map of views to extensions
     private final Map<String, UiExtension> views = Maps.newHashMap();
 
@@ -332,12 +340,46 @@
     }
 
     @Override
+    public synchronized void register(UiGlyphFactory glyphFactory) {
+        checkPermission(GLYPH_WRITE);
+        boolean glyphAdded = false;
+        for (UiGlyph glyph : glyphFactory.glyphs()) {
+            if (!glyphs.contains(glyph)) {
+                glyphs.add(glyph);
+                glyphAdded = true;
+            }
+        }
+        if (glyphAdded) {
+            UiWebSocketServlet.sendToAll(GLYPH_ADDED, null);
+        }
+    }
+
+    @Override
+    public synchronized void unregister(UiGlyphFactory glyphFactory) {
+        checkPermission(GLYPH_WRITE);
+        boolean glyphRemoved = false;
+        for (UiGlyph glyph : glyphFactory.glyphs()) {
+            glyphs.remove(glyph);
+            glyphRemoved = true;
+        }
+        if (glyphRemoved) {
+            UiWebSocketServlet.sendToAll(GLYPH_REMOVED, null);
+        }
+    }
+
+    @Override
     public synchronized List<UiExtension> getExtensions() {
         checkPermission(UI_READ);
         return ImmutableList.copyOf(extensions);
     }
 
     @Override
+    public synchronized List<UiGlyph> getGlyphs() {
+        checkPermission(GLYPH_READ);
+        return ImmutableList.copyOf(glyphs);
+    }
+
+    @Override
     public synchronized UiExtension getViewExtension(String viewId) {
         checkPermission(UI_READ);
         return views.get(viewId);
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 ea7e5ed..32f4981 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,6 +27,7 @@
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiGlyph;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.UiMessageHandlerFactory;
 import org.onosproject.ui.UiSessionToken;
@@ -77,6 +78,8 @@
 
     private static final String TOPO = "topo";
 
+    private static final String GLYPHS = "glyphs";
+
     private static final long MAX_AGE_MS = 30_000;
 
     private static final byte PING = 0x9;
@@ -436,9 +439,21 @@
             instances.add(instance);
         }
 
+        ArrayNode glyphInstances = arrayNode();
+        UiExtensionService uiExtensionService = directory.get(UiExtensionService.class);
+        for (UiGlyph glyph : uiExtensionService.getGlyphs()) {
+            ObjectNode glyphInstance = objectNode()
+                    .put(GlyphConstants.ID, glyph.id())
+                    .put(GlyphConstants.VIEWBOX, glyph.viewbox())
+                    .put(GlyphConstants.PATH, glyph.path());
+            glyphInstances.add(glyphInstance);
+        }
+
         ObjectNode payload = objectNode();
         payload.set(CLUSTER_NODES, instances);
+        payload.set(GLYPHS, glyphInstances);
         payload.put(USER, userName);
+
         sendMessage(BOOTSTRAP, payload);
     }
 
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 ff6c063..bd3e9e1 100644
--- a/web/gui/src/main/webapp/app/fw/remote/websocket.js
+++ b/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -21,7 +21,7 @@
     'use strict';
 
     // injected refs
-    var $log, $loc, fs, ufs, wsock, vs, ls;
+    var $log, $loc, fs, gs, ufs, wsock, vs, ls;
 
     // internal state
     var webSockOpts, // web socket options
@@ -33,6 +33,7 @@
         url, // web socket URL
         clusterNodes = [], // ONOS instances data for failover
         clusterIndex = -1, // the instance to which we are connected
+        glyphs = [],
         connectRetries = 0, // limit our attempts at reconnecting
         openListeners = {}, // registered listeners for websocket open()
         nextListenerId = 1, // internal ID for open listeners
@@ -52,6 +53,13 @@
                     // TODO: add connect info to masthead somewhere
                 }
             });
+            glyphs = data.glyphs;
+            glyphs.forEach(function (d, i) {
+                var gdata = {};
+                gdata['_' + d.id] = d.viewbox;
+                gdata[d.id] = d.path;
+                gs.registerGlyphs(gdata);
+            });
         },
 
         error: function (data) {
@@ -187,6 +195,7 @@
         handlers = {};
         clusterNodes = [];
         clusterIndex = -1;
+        glyphs = [];
         connectRetries = 0;
         openListeners = {};
         nextListenerId = 1;
@@ -324,12 +333,13 @@
     // ===== Definition of module
     angular.module('onosRemote')
     .factory('WebSocketService',
-        ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
+        ['$log', '$location', 'FnService', 'GlyphService', 'UrlFnService', 'WSock',
 
-        function (_$log_, _$loc_, _fs_, _ufs_, _wsock_) {
+        function (_$log_, _$loc_, _fs_, _gs_, _ufs_, _wsock_) {
             $log = _$log_;
             $loc = _$loc_;
             fs = _fs_;
+            gs = _gs_;
             ufs = _ufs_;
             wsock = _wsock_;
 
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
index 3421efc..91cea6c 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
@@ -20,7 +20,7 @@
 describe('factory: fw/util/prefs.js', function() {
     var $cookies, ps, fs;
 
-    beforeEach(module('onosUtil', 'onosRemote'));
+    beforeEach(module('onosUtil', 'onosSvg', 'onosRemote'));
 
     var mockCookies = {
         foo: 'bar'
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
index 53df5f7..e6a7f9c 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
@@ -20,7 +20,7 @@
 describe('factory: fw/util/theme.js', function() {
     var ts, $log, fs;
 
-    beforeEach(module('onosUtil', 'onosRemote'));
+    beforeEach(module('onosUtil', 'onosSvg', 'onosRemote'));
 
     beforeEach(inject(function (ThemeService, _$log_, FnService) {
         ts = ThemeService;