summaryrefslogtreecommitdiffstats
path: root/framework/src/onos/core/api/src/main/java/org/onosproject/ui
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/core/api/src/main/java/org/onosproject/ui
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/core/api/src/main/java/org/onosproject/ui')
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java143
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java142
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java200
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java53
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java207
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java122
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java165
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java46
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java34
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java304
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java111
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java58
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java61
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java42
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java52
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java40
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java39
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java41
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java72
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java20
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java75
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java43
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java31
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java104
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java90
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java70
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java190
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java33
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java147
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java66
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java27
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java252
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java353
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java129
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java25
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java160
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java159
-rw-r--r--framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java21
48 files changed, 4271 insertions, 0 deletions
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
new file mode 100644
index 00000000..2ebb5545
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2015 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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Provides convenience methods for dealing with JSON nodes, arrays etc.
+ */
+public final class JsonUtils {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // non-instantiable
+ private JsonUtils() { }
+
+ /**
+ * Wraps a message payload into an event structure for the given event
+ * type and sequence ID. Generally, the sequence ID should be a copy of
+ * the ID from the client request event.
+ *
+ * @param type event type
+ * @param sid sequence ID
+ * @param payload event payload
+ * @return the object node representation
+ */
+ public static ObjectNode envelope(String type, long sid, ObjectNode payload) {
+ ObjectNode event = MAPPER.createObjectNode();
+ event.put("event", type);
+ if (sid > 0) {
+ event.put("sid", sid);
+ }
+ event.set("payload", payload);
+ return event;
+ }
+
+ /**
+ * Composes a message structure for the given message type and payload.
+ *
+ * @param type message type
+ * @param payload message payload
+ * @return the object node representation
+ */
+ public static ObjectNode envelope(String type, ObjectNode payload) {
+ ObjectNode event = MAPPER.createObjectNode();
+ event.put("event", type);
+ event.set("payload", payload);
+ return event;
+ }
+
+ /**
+ * Returns the event type from the specified event.
+ * If the node does not have an "event" property, "unknown" is returned.
+ *
+ * @param event message event
+ * @return extracted event type
+ */
+ public static String eventType(ObjectNode event) {
+ return string(event, "event", "unknown");
+ }
+
+ /**
+ * Returns the sequence identifier from the specified event, or 0 (zero)
+ * if the "sid" property does not exist.
+ *
+ * @param event message event
+ * @return extracted sequence identifier
+ */
+ public static long sid(ObjectNode event) {
+ return number(event, "sid");
+ }
+
+ /**
+ * Returns the payload from the specified event.
+ *
+ * @param event message event
+ * @return extracted payload object
+ */
+ public static ObjectNode payload(ObjectNode event) {
+ return (ObjectNode) event.path("payload");
+ }
+
+ /**
+ * Returns the specified node property as a number.
+ *
+ * @param node message event
+ * @param name property name
+ * @return property as number
+ */
+ public static long number(ObjectNode node, String name) {
+ return node.path(name).asLong();
+ }
+
+ /**
+ * Returns the specified node property as a string.
+ *
+ * @param node message event
+ * @param name property name
+ * @return property as a string
+ */
+ public static String string(ObjectNode node, String name) {
+ return node.path(name).asText();
+ }
+
+ /**
+ * Returns the specified node property as a string, with a default fallback.
+ *
+ * @param node object node
+ * @param name property name
+ * @param defaultValue fallback value if property is absent
+ * @return property as a string
+ */
+ public static String string(ObjectNode node, String name, String defaultValue) {
+ return node.path(name).asText(defaultValue);
+ }
+
+ /**
+ * Returns the specified node property as an object node.
+ *
+ * @param node object node
+ * @param name property name
+ * @return property as a node
+ */
+ public static ObjectNode node(ObjectNode node, String name) {
+ return (ObjectNode) node.path(name);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
new file mode 100644
index 00000000..1678923d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2015 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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Abstraction of an entity that handles a specific request from the
+ * user interface client.
+ *
+ * @see UiMessageHandler
+ */
+public abstract class RequestHandler {
+
+ protected static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final String eventType;
+ private UiMessageHandler parent;
+
+
+ public RequestHandler(String eventType) {
+ this.eventType = eventType;
+ }
+
+ // package private
+ void setParent(UiMessageHandler parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * Returns the event type that this handler handles.
+ *
+ * @return event type
+ */
+ public String eventType() {
+ return eventType;
+ }
+
+ /**
+ * Processes the incoming message payload from the client.
+ *
+ * @param sid message sequence identifier
+ * @param payload request message payload
+ */
+ // TODO: remove sid from signature
+ public abstract void process(long sid, ObjectNode payload);
+
+
+
+ // ===================================================================
+ // === Convenience methods...
+
+ /**
+ * Returns implementation of the specified service class.
+ *
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return implementation class
+ * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+ */
+ protected <T> T get(Class<T> serviceClass) {
+ return parent.directory().get(serviceClass);
+ }
+
+ /**
+ * Sends a message back to the client.
+ *
+ * @param eventType message event type
+ * @param sid message sequence identifier
+ * @param payload message payload
+ */
+ // TODO: remove sid from signature
+ protected void sendMessage(String eventType, long sid, ObjectNode payload) {
+ parent.connection().sendMessage(eventType, sid, 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.
+ *
+ * @param message the message to send
+ */
+ protected void sendMessage(ObjectNode message) {
+ parent.connection().sendMessage(message);
+ }
+
+ /**
+ * Allows one request handler to pass the event on to another for
+ * further processing.
+ * Note that the message handlers must be defined in the same parent.
+ *
+ * @param eventType event type
+ * @param sid sequence identifier
+ * @param payload message payload
+ */
+ // TODO: remove sid from signature
+ protected void chain(String eventType, long sid, ObjectNode payload) {
+ parent.exec(eventType, sid, payload);
+ }
+
+ // ===================================================================
+
+
+ /**
+ * Returns the specified node property as a string.
+ *
+ * @param node message event
+ * @param key property name
+ * @return property as a string
+ */
+ protected String string(ObjectNode node, String key) {
+ return JsonUtils.string(node, key);
+ }
+
+ /**
+ * Returns the specified node property as a string, with a default fallback.
+ *
+ * @param node object node
+ * @param key property name
+ * @param defValue fallback value if property is absent
+ * @return property as a string
+ */
+ protected String string(ObjectNode node, String key, String defValue) {
+ return JsonUtils.string(node, key, defValue);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java
new file mode 100644
index 00000000..ead7b0dc
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiConnection.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 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;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Abstraction of a user interface session connection.
+ */
+public interface UiConnection {
+
+ /**
+ * Sends the specified JSON message to the user interface client.
+ *
+ * @param message message to send
+ */
+ void sendMessage(ObjectNode message);
+
+ /**
+ * Composes a message into JSON and sends it to the user interface client.
+ *
+ * @param type message type
+ * @param sid message sequence number
+ * @param payload message payload
+ */
+ // TODO: remove sid parameter
+ void sendMessage(String type, long sid, ObjectNode payload);
+
+} \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java
new file mode 100644
index 00000000..1f5fbd48
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtension.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2015 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;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * User interface extension.
+ */
+public final class UiExtension {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String VIEW_PREFIX = "app/view/";
+ private static final String EMPTY = "";
+ private static final String SLASH = "/";
+ private static final String CSS_HTML = "css.html";
+ private static final String JS_HTML = "js.html";
+
+ private final ClassLoader classLoader;
+ private final String resourcePath;
+ private final List<UiView> views;
+ private final UiMessageHandlerFactory messageHandlerFactory;
+ private final UiTopoOverlayFactory topoOverlayFactory;
+
+
+ // private constructor - only the builder calls this
+ private UiExtension(ClassLoader cl, String path, List<UiView> views,
+ UiMessageHandlerFactory mhFactory,
+ UiTopoOverlayFactory toFactory) {
+ this.classLoader = cl;
+ this.resourcePath = path;
+ this.views = views;
+ this.messageHandlerFactory = mhFactory;
+ this.topoOverlayFactory = toFactory;
+ }
+
+
+ /**
+ * Returns input stream containing CSS inclusion statements.
+ *
+ * @return CSS inclusion statements
+ */
+ public InputStream css() {
+ return getStream(resourcePath + CSS_HTML);
+ }
+
+ /**
+ * Returns input stream containing JavaScript inclusion statements.
+ *
+ * @return JavaScript inclusion statements
+ */
+ public InputStream js() {
+ return getStream(resourcePath + JS_HTML);
+ }
+
+ /**
+ * Returns list of user interface views contributed by this extension.
+ *
+ * @return contributed view descriptors
+ */
+ public List<UiView> views() {
+ return views;
+ }
+
+ /**
+ * Returns input stream containing specified view-specific resource.
+ *
+ * @param viewId view identifier
+ * @param path resource path, relative to the view directory
+ * @return resource input stream
+ */
+ public InputStream resource(String viewId, String path) {
+ return getStream(VIEW_PREFIX + viewId + SLASH + path);
+ }
+
+ /**
+ * Returns message handler factory, if one was defined.
+ *
+ * @return message handler factory
+ */
+ public UiMessageHandlerFactory messageHandlerFactory() {
+ return messageHandlerFactory;
+ }
+
+ /**
+ * Returns the topology overlay factory, if one was defined.
+ *
+ * @return topology overlay factory
+ */
+ public UiTopoOverlayFactory topoOverlayFactory() {
+ return topoOverlayFactory;
+ }
+
+
+ // Returns the resource input stream from the specified class-loader.
+ private InputStream getStream(String path) {
+ InputStream stream = classLoader.getResourceAsStream(path);
+ if (stream == null) {
+ log.warn("Unable to find resource {}", path);
+ }
+ return stream;
+ }
+
+
+ /**
+ * UI Extension Builder.
+ */
+ public static class Builder {
+ private ClassLoader classLoader;
+
+ private String resourcePath = EMPTY;
+ private List<UiView> views = new ArrayList<>();
+ private UiMessageHandlerFactory messageHandlerFactory = null;
+ private UiTopoOverlayFactory topoOverlayFactory = null;
+
+ /**
+ * Create a builder with the given class loader.
+ * Resource path defaults to "".
+ * Views defaults to an empty list.
+ * Both Message and TopoOverlay factories default to null.
+ *
+ * @param cl the class loader
+ * @param views list of views contributed by this extension
+ */
+ public Builder(ClassLoader cl, List<UiView> views) {
+ checkNotNull(cl, "Must provide a class loader");
+ checkArgument(views.size() > 0, "Must provide at least one view");
+ this.classLoader = cl;
+ this.views = views;
+ }
+
+ /**
+ * Set the resource path. That is, path to where the CSS and JS
+ * files are located. This value should
+ *
+ * @param path resource path
+ * @return self, for chaining
+ */
+ public Builder resourcePath(String path) {
+ this.resourcePath = path == null ? EMPTY : path + SLASH;
+ return this;
+ }
+
+ /**
+ * Sets the message handler factory for this extension.
+ *
+ * @param mhFactory message handler factory
+ * @return self, for chaining
+ */
+ public Builder messageHandlerFactory(UiMessageHandlerFactory mhFactory) {
+ this.messageHandlerFactory = mhFactory;
+ return this;
+ }
+
+ /**
+ * Sets the topology overlay factory for this extension.
+ *
+ * @param toFactory topology overlay factory
+ * @return self, for chaining
+ */
+ public Builder topoOverlayFactory(UiTopoOverlayFactory toFactory) {
+ this.topoOverlayFactory = toFactory;
+ return this;
+ }
+
+ /**
+ * Builds the UI extension.
+ *
+ * @return UI extension instance
+ */
+ public UiExtension build() {
+ return new UiExtension(classLoader, resourcePath, views,
+ messageHandlerFactory, topoOverlayFactory);
+ }
+
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
new file mode 100644
index 00000000..330fbb7a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 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;
+
+import java.util.List;
+
+/**
+ * Service for registering user interface extensions.
+ */
+public interface UiExtensionService {
+
+ /**
+ * Registers the specified user interface extension.
+ *
+ * @param extension GUI extension to register
+ */
+ void register(UiExtension extension);
+
+ /**
+ * Unregisters the specified user interface extension.
+ *
+ * @param extension GUI extension to unregister
+ */
+ void unregister(UiExtension extension);
+
+ /**
+ * Returns the list of user interface extensions.
+ *
+ * @return list of extensions
+ */
+ List<UiExtension> getExtensions();
+
+ /**
+ * Returns the user interface extension that contributed the specified view.
+ *
+ * @param viewId view identifier
+ * @return user interface extension
+ */
+ UiExtension getViewExtension(String viewId);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
new file mode 100644
index 00000000..9b9a406c
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2015 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;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of an entity capable of processing JSON messages from the user
+ * interface client.
+ * <p>
+ * The message structure is:
+ * </p>
+ * <pre>
+ * {
+ * "type": "<em>event-type</em>",
+ * "payload": {
+ * <em>arbitrary JSON object structure</em>
+ * }
+ * }
+ * </pre>
+ * On {@link #init initialization} the handler will create and cache
+ * {@link RequestHandler} instances, each of which are bound to a particular
+ * <em>event-type</em>. On {@link #process arrival} of a new message,
+ * the <em>event-type</em> is determined, and the message dispatched to the
+ * corresponding <em>RequestHandler</em>'s
+ * {@link RequestHandler#process process} method.
+ */
+public abstract class UiMessageHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private final Map<String, RequestHandler> handlerMap = new HashMap<>();
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private UiConnection connection;
+ private ServiceDirectory directory;
+
+
+ /**
+ * Subclasses must create and return the collection of request handlers
+ * for the message types they handle.
+ * <p>
+ * Note that request handlers should be stateless. When we are
+ * {@link #destroy destroyed}, we will simply drop our references to them
+ * and allow them to be garbage collected.
+ *
+ * @return the message handler instances
+ */
+ protected abstract Collection<RequestHandler> createRequestHandlers();
+
+ /**
+ * Returns the set of message types which this handler is capable of
+ * processing.
+ *
+ * @return set of message types
+ */
+ public Set<String> messageTypes() {
+ return Collections.unmodifiableSet(handlerMap.keySet());
+ }
+
+ /**
+ * Processes a JSON message from the user interface client.
+ *
+ * @param message JSON message
+ */
+ public void process(ObjectNode message) {
+ String type = JsonUtils.eventType(message);
+ ObjectNode payload = JsonUtils.payload(message);
+ // TODO: remove sid
+ exec(type, 0, payload);
+ }
+
+ /**
+ * Finds the appropriate handler and executes the process method.
+ *
+ * @param eventType event type
+ * @param sid sequence identifier
+ * @param payload message payload
+ */
+ // TODO: remove sid from signature
+ void exec(String eventType, long sid, ObjectNode payload) {
+ RequestHandler requestHandler = handlerMap.get(eventType);
+ if (requestHandler != null) {
+ requestHandler.process(sid, payload);
+ } else {
+ log.warn("no request handler for event type {}", eventType);
+ }
+ }
+
+ /**
+ * Initializes the handler with the user interface connection and
+ * service directory context.
+ *
+ * @param connection user interface connection
+ * @param directory service directory
+ */
+ public void init(UiConnection connection, ServiceDirectory directory) {
+ this.connection = connection;
+ this.directory = directory;
+
+ Collection<RequestHandler> handlers = createRequestHandlers();
+ checkNotNull(handlers, "Handlers cannot be null");
+ checkArgument(!handlers.isEmpty(), "Handlers cannot be empty");
+
+ for (RequestHandler h : handlers) {
+ h.setParent(this);
+ handlerMap.put(h.eventType(), h);
+ }
+ }
+
+ /**
+ * Destroys the message handler context.
+ */
+ public void destroy() {
+ this.connection = null;
+ this.directory = null;
+ handlerMap.clear();
+ }
+
+ /**
+ * Returns the user interface connection with which this handler was primed.
+ *
+ * @return user interface connection
+ */
+ public UiConnection connection() {
+ return connection;
+ }
+
+ /**
+ * Returns the user interface connection with which this handler was primed.
+ *
+ * @return user interface connection
+ */
+ public ServiceDirectory directory() {
+ return directory;
+ }
+
+ /**
+ * Returns implementation of the specified service class.
+ *
+ * @param serviceClass service class
+ * @param <T> type of service
+ * @return implementation class
+ * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+ */
+ protected <T> T get(Class<T> serviceClass) {
+ return directory.get(serviceClass);
+ }
+
+ /**
+ * Returns a freshly minted object node.
+ *
+ * @return new object node
+ */
+ protected ObjectNode objectNode() {
+ return mapper.createObjectNode();
+ }
+
+ /**
+ * Returns a freshly minted array node.
+ *
+ * @return new array node
+ */
+ protected ArrayNode arrayNode() {
+ return mapper.createArrayNode();
+ }
+
+ /**
+ * Sends the specified data to the client.
+ * It is expected that the data is in the prescribed JSON format for
+ * events to the client.
+ *
+ * @param data data to be sent
+ */
+ protected synchronized void sendMessage(ObjectNode data) {
+ UiConnection connection = connection();
+ if (connection != null) {
+ connection.sendMessage(data);
+ }
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
new file mode 100644
index 00000000..522daa8f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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;
+
+import java.util.Collection;
+
+/**
+ * Abstraction of an entity capable of producing a set of message handlers
+ * specific to the given user interface connection.
+ */
+public interface UiMessageHandlerFactory {
+
+ /**
+ * Produces a collection of new message handlers.
+ *
+ * @return collection of new handlers
+ */
+ Collection<UiMessageHandler> newHandlers();
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
new file mode 100644
index 00000000..2889422a
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015 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;
+
+import org.onosproject.ui.topo.PropertyPanel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents user interface topology view overlay.
+ */
+public class UiTopoOverlay {
+
+ /**
+ * Logger for this overlay.
+ */
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final String id;
+
+ private boolean isActive = false;
+
+ /**
+ * Creates a new user interface topology view overlay descriptor.
+ *
+ * @param id overlay identifier
+ */
+ public UiTopoOverlay(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the identifier for this overlay.
+ *
+ * @return the identifier
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Callback invoked to initialize this overlay, soon after creation.
+ * This default implementation does nothing.
+ */
+ public void init() {
+ }
+
+ /**
+ * Callback invoked when this overlay is activated.
+ */
+ public void activate() {
+ isActive = true;
+ }
+
+ /**
+ * Callback invoked when this overlay is deactivated.
+ */
+ public void deactivate() {
+ isActive = false;
+ }
+
+ /**
+ * Returns true if this overlay is currently active.
+ *
+ * @return true if overlay active
+ */
+ public boolean isActive() {
+ return isActive;
+ }
+
+ /**
+ * Callback invoked to destroy this instance by cleaning up any
+ * internal state ready for garbage collection.
+ * This default implementation holds no state and does nothing.
+ */
+ public void destroy() {
+ }
+
+ /**
+ * Callback to modify the contents of the summary panel.
+ * This default implementation does nothing.
+ *
+ * @param pp property panel model of summary data
+ */
+ public void modifySummary(PropertyPanel pp) {
+ }
+
+ /**
+ * Callback to modify the contents of the details panel for
+ * a selected device.
+ * This default implementation does nothing.
+ *
+ * @param pp property panel model of summary data
+ */
+ public void modifyDeviceDetails(PropertyPanel pp) {
+ }
+
+ /**
+ * Callback to modify the contents of the details panel for
+ * a selected host.
+ * This default implementation does nothing.
+ *
+ * @param pp property panel model of summary data
+ */
+ public void modifyHostDetails(PropertyPanel pp) {
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java
new file mode 100644
index 00000000..bd2f2fe6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 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;
+
+import java.util.Collection;
+
+/**
+ * Abstraction of an entity capable of producing one or more topology
+ * overlay handlers specific to a given user interface connection.
+ */
+public interface UiTopoOverlayFactory {
+
+ /**
+ * Produces a collection of new overlay handlers.
+ *
+ * @return collection of new overlay handlers
+ */
+ Collection<UiTopoOverlay> newOverlays();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java
new file mode 100644
index 00000000..2b8b7fa2
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiView.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 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;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Represents user interface view addition.
+ */
+public class UiView {
+
+ /**
+ * Designates navigation menu category.
+ */
+ public enum Category {
+ /**
+ * Represents platform related views.
+ */
+ PLATFORM("Platform"),
+
+ /**
+ * Represents network-control related views.
+ */
+ NETWORK("Network"),
+
+ /**
+ * Represents miscellaneous views.
+ */
+ OTHER("Other"),
+
+ /**
+ * Represents views that do not show in the navigation menu.
+ * This category should not be specified directly; rather, use
+ * the {@link UiViewHidden} constructor instead of {@link UiView}.
+ */
+ HIDDEN("(hidden)");
+
+ private final String label;
+
+ Category(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Returns display label for the category.
+ *
+ * @return display label
+ */
+ public String label() {
+ return label;
+ }
+ }
+
+ private final Category category;
+ private final String id;
+ private final String label;
+ private final String iconId;
+
+ /**
+ * Creates a new user interface view descriptor. The navigation item
+ * will appear in the navigation menu under the specified category.
+ *
+ * @param category view category
+ * @param id view identifier
+ * @param label view label
+ */
+ public UiView(Category category, String id, String label) {
+ this(category, id, label, null);
+ }
+
+ /**
+ * Creates a new user interface view descriptor. The navigation item
+ * will appear in the navigation menu under the specified category,
+ * with the specified icon adornment.
+ *
+ * @param category view category
+ * @param id view identifier
+ * @param label view label
+ * @param iconId icon id
+ */
+ public UiView(Category category, String id, String label, String iconId) {
+ this.category = category;
+ this.id = id;
+ this.label = label;
+ this.iconId = iconId;
+ }
+
+ /**
+ * Returns the navigation category.
+ *
+ * @return navigation category
+ */
+ public Category category() {
+ return category;
+ }
+
+ /**
+ * Returns the view identifier.
+ *
+ * @return view id
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the view label.
+ *
+ * @return view label
+ */
+ public String label() {
+ return label;
+ }
+
+ /**
+ * Returns the icon ID.
+ *
+ * @return icon ID
+ */
+ public String iconId() {
+ return iconId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final UiView other = (UiView) obj;
+ return Objects.equals(this.id, other.id);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("category", category)
+ .add("id", id)
+ .add("label", label)
+ .add("iconId", iconId)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
new file mode 100644
index 00000000..b7fea8fe
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/UiViewHidden.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents user interface view addition, except that this one should not
+ * have an entry in the navigation panel.
+ */
+public class UiViewHidden extends UiView {
+
+ /**
+ * Creates a new user interface hidden view descriptor.
+ *
+ * @param id view identifier
+ */
+ public UiViewHidden(String id) {
+ super(Category.HIDDEN, id, null);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id())
+ .toString();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java
new file mode 100644
index 00000000..dd832a59
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/**
+ * Mechanism for managing dynamically registered user interface extensions.
+ */
+package org.onosproject.ui;
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java
new file mode 100644
index 00000000..84d11344
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellComparator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 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.table;
+
+/**
+ * Defines a comparator for cell values.
+ */
+public interface CellComparator {
+
+ /**
+ * Compares its two arguments for order. Returns a negative integer,
+ * zero, or a positive integer as the first argument is less than, equal
+ * to, or greater than the second.<p>
+ *
+ * Note that nulls are permitted, and should be sorted to the beginning
+ * of an ascending sort; i.e. null is considered to be "smaller" than
+ * non-null values.
+ *
+ * @see java.util.Comparator#compare(Object, Object)
+ *
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return a negative integer, zero, or a positive integer as the
+ * first argument is less than, equal to, or greater than the
+ * second.
+ * @throws ClassCastException if the arguments' types prevent them from
+ * being compared by this comparator.
+ */
+ int compare(Object o1, Object o2);
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java
new file mode 100644
index 00000000..854ac27f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/CellFormatter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 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.table;
+
+/**
+ * Defines a formatter for cell values.
+ */
+public interface CellFormatter {
+
+ /**
+ * Formats the specified value into a string appropriate for displaying
+ * in a table cell. Note that null values are acceptable, and will result
+ * in the empty string.
+ *
+ * @param value the value
+ * @return the formatted string
+ */
+ String format(Object value);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
new file mode 100644
index 00000000..d0fccb65
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableModel.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2015 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.table;
+
+import com.google.common.collect.Sets;
+import org.onosproject.ui.table.cell.DefaultCellComparator;
+import org.onosproject.ui.table.cell.DefaultCellFormatter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A simple model of table data.
+ * <p>
+ * Note that this is not a full MVC type model; the expected usage pattern
+ * is to create an empty table, add rows (by consulting the business model),
+ * sort rows (based on client request parameters), and finally produce the
+ * sorted list of rows.
+ * <p>
+ * The table also provides a mechanism for defining how cell values for a
+ * particular column should be formatted into strings, to help facilitate
+ * the encoding of the table data into a JSON structure.
+ * <p>
+ * Note that it is expected that all values for a particular column will
+ * be the same class.
+ */
+public class TableModel {
+
+ private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE;
+ private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE;
+
+ private final String[] columnIds;
+ private final Set<String> idSet;
+ private final Map<String, CellComparator> comparators = new HashMap<>();
+ private final Map<String, CellFormatter> formatters = new HashMap<>();
+ private final List<Row> rows = new ArrayList<>();
+
+
+ /**
+ * Constructs a table (devoid of data) with the given column IDs.
+ *
+ * @param columnIds column identifiers
+ */
+ public TableModel(String... columnIds) {
+ checkNotNull(columnIds, "columnIds cannot be null");
+ checkArgument(columnIds.length > 0, "must be at least one column");
+
+ idSet = Sets.newHashSet(columnIds);
+ if (idSet.size() != columnIds.length) {
+ throw new IllegalArgumentException("duplicate column ID(s) detected");
+ }
+
+ this.columnIds = Arrays.copyOf(columnIds, columnIds.length);
+ }
+
+ private void checkId(String id) {
+ checkNotNull(id, "must provide a column ID");
+ if (!idSet.contains(id)) {
+ throw new IllegalArgumentException("unknown column id: " + id);
+ }
+ }
+
+ /**
+ * Returns the number of rows in this table model.
+ *
+ * @return number of rows
+ */
+ public int rowCount() {
+ return rows.size();
+ }
+
+ /**
+ * Returns the number of columns in this table model.
+ *
+ * @return number of columns
+ */
+ public int columnCount() {
+ return columnIds.length;
+ }
+
+ /**
+ * Returns the array of column IDs for this table model.
+ * <p>
+ * Implementation note: we are knowingly passing you a reference to
+ * our internal array to avoid copying. Don't mess with it. It's your
+ * table you'll break if you do!
+ *
+ * @return the column identifiers
+ */
+ public String[] getColumnIds() {
+ return columnIds;
+ }
+
+ /**
+ * Returns the raw {@link Row} representation of the rows in this table.
+ *
+ * @return raw table rows
+ */
+ public Row[] getRows() {
+ return rows.toArray(new Row[rows.size()]);
+ }
+
+ /**
+ * Sets a cell comparator for the specified column.
+ *
+ * @param columnId column identifier
+ * @param comparator comparator to use
+ */
+ public void setComparator(String columnId, CellComparator comparator) {
+ checkNotNull(comparator, "must provide a comparator");
+ checkId(columnId);
+ comparators.put(columnId, comparator);
+ }
+
+ /**
+ * Returns the cell comparator to use on values in the specified column.
+ *
+ * @param columnId column identifier
+ * @return an appropriate cell comparator
+ */
+ private CellComparator getComparator(String columnId) {
+ checkId(columnId);
+ CellComparator cmp = comparators.get(columnId);
+ return cmp == null ? DEF_CMP : cmp;
+ }
+
+ /**
+ * Sets a cell formatter for the specified column.
+ *
+ * @param columnId column identifier
+ * @param formatter formatter to use
+ */
+ public void setFormatter(String columnId, CellFormatter formatter) {
+ checkNotNull(formatter, "must provide a formatter");
+ checkId(columnId);
+ formatters.put(columnId, formatter);
+ }
+
+ /**
+ * Returns the cell formatter to use on values in the specified column.
+ *
+ * @param columnId column identifier
+ * @return an appropriate cell formatter
+ */
+ public CellFormatter getFormatter(String columnId) {
+ checkId(columnId);
+ CellFormatter fmt = formatters.get(columnId);
+ return fmt == null ? DEF_FMT : fmt;
+ }
+
+ /**
+ * Adds a row to the table model.
+ *
+ * @return the row, for chaining
+ */
+ public Row addRow() {
+ Row r = new Row();
+ rows.add(r);
+ return r;
+ }
+
+ /**
+ * Sorts the table rows based on the specified column, in the
+ * specified direction.
+ *
+ * @param columnId column identifier
+ * @param dir sort direction
+ */
+ public void sort(String columnId, SortDir dir) {
+ Collections.sort(rows, new RowComparator(columnId, dir));
+ }
+
+
+ /** Designates sorting direction. */
+ public enum SortDir {
+ /** Designates an ascending sort. */
+ ASC,
+ /** Designates a descending sort. */
+ DESC
+ }
+
+ /**
+ * Row comparator.
+ */
+ private class RowComparator implements Comparator<Row> {
+ private final String columnId;
+ private final SortDir dir;
+ private final CellComparator cellComparator;
+
+ /**
+ * Constructs a row comparator based on the specified
+ * column identifier and sort direction.
+ *
+ * @param columnId column identifier
+ * @param dir sort direction
+ */
+ public RowComparator(String columnId, SortDir dir) {
+ this.columnId = columnId;
+ this.dir = dir;
+ cellComparator = getComparator(columnId);
+ }
+
+ @Override
+ public int compare(Row a, Row b) {
+ Object cellA = a.get(columnId);
+ Object cellB = b.get(columnId);
+ int result = cellComparator.compare(cellA, cellB);
+ return dir == SortDir.ASC ? result : -result;
+ }
+ }
+
+ /**
+ * Model of a row.
+ */
+ public class Row {
+ private final Map<String, Object> cells = new HashMap<>();
+
+ /**
+ * Sets the cell value for the given column of this row.
+ *
+ * @param columnId column identifier
+ * @param value value to set
+ * @return self, for chaining
+ */
+ public Row cell(String columnId, Object value) {
+ checkId(columnId);
+ cells.put(columnId, value);
+ return this;
+ }
+
+ /**
+ * Returns the value of the cell in the given column for this row.
+ *
+ * @param columnId column identifier
+ * @return cell value
+ */
+ public Object get(String columnId) {
+ return cells.get(columnId);
+ }
+
+ /**
+ * Returns the value of the cell as a string, using the
+ * formatter appropriate for the column.
+ *
+ * @param columnId column identifier
+ * @return formatted cell value
+ */
+ String getAsString(String columnId) {
+ return getFormatter(columnId).format(get(columnId));
+ }
+
+ /**
+ * Returns the row as an array of formatted strings.
+ *
+ * @return the formatted row data
+ */
+ public String[] getAsFormattedStrings() {
+ List<String> formatted = new ArrayList<>(columnCount());
+ for (String c : columnIds) {
+ formatted.add(getAsString(c));
+ }
+ return formatted.toArray(new String[formatted.size()]);
+ }
+ }
+
+ private static final String DESC = "desc";
+
+ /**
+ * Returns the appropriate sort direction for the given string.
+ * <p>
+ * The expected strings are "asc" for {@link SortDir#ASC ascending} and
+ * "desc" for {@link SortDir#DESC descending}. Any other value will
+ * default to ascending.
+ *
+ * @param s sort direction string encoding
+ * @return sort direction
+ */
+ public static SortDir sortDir(String s) {
+ return !DESC.equals(s) ? SortDir.ASC : SortDir.DESC;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
new file mode 100644
index 00000000..b8d48575
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableRequestHandler.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2015 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.table;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.ui.JsonUtils;
+import org.onosproject.ui.RequestHandler;
+
+/**
+ * Message handler specifically for table views.
+ */
+public abstract class TableRequestHandler extends RequestHandler {
+
+ private final String respType;
+ private final String nodeName;
+
+ /**
+ * Constructs a table request handler for a specific table view. When
+ * table requests come in, the handler will generate the appropriate
+ * table rows, sort them according the the request sort parameters, and
+ * send back the response to the client.
+ *
+ * @param reqType type of the request event
+ * @param respType type of the response event
+ * @param nodeName name of JSON node holding row data
+ */
+ public TableRequestHandler(String reqType, String respType, String nodeName) {
+ super(reqType);
+ this.respType = respType;
+ this.nodeName = nodeName;
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ TableModel tm = createTableModel();
+ populateTable(tm, payload);
+
+ String sortCol = JsonUtils.string(payload, "sortCol", defaultColumnId());
+ String sortDir = JsonUtils.string(payload, "sortDir", "asc");
+ tm.sort(sortCol, TableModel.sortDir(sortDir));
+
+ ObjectNode rootNode = MAPPER.createObjectNode();
+ rootNode.set(nodeName, TableUtils.generateArrayNode(tm));
+ sendMessage(respType, 0, rootNode);
+ }
+
+ /**
+ * Creates the table model (devoid of data) using {@link #getColumnIds()}
+ * to initialize it, ready to be populated.
+ * <p>
+ * This default implementation returns a table model with default
+ * formatters and comparators for all columns.
+ *
+ * @return an empty table model
+ */
+ protected TableModel createTableModel() {
+ return new TableModel(getColumnIds());
+ }
+
+ /**
+ * Returns the default column ID to be used when one is not supplied in
+ * the payload as the column on which to sort.
+ * <p>
+ * This default implementation returns "id".
+ *
+ * @return default sort column identifier
+ */
+ protected String defaultColumnId() {
+ return "id";
+ }
+
+ /**
+ * Subclasses should return the array of column IDs with which
+ * to initialize their table model.
+ *
+ * @return the column IDs
+ */
+ protected abstract String[] getColumnIds();
+
+ /**
+ * Subclasses should populate the table model by adding
+ * {@link TableModel.Row rows}.
+ * <pre>
+ * tm.addRow()
+ * .cell(COL_ONE, ...)
+ * .cell(COL_TWO, ...)
+ * ... ;
+ * </pre>
+ * The request payload is provided in case there are request filtering
+ * parameters (other than sort column and sort direction) that are required
+ * to generate the appropriate data.
+ *
+ * @param tm the table model
+ * @param payload request payload
+ */
+ protected abstract void populateTable(TableModel tm, ObjectNode payload);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
new file mode 100644
index 00000000..eb2dff78
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 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.table;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Provides static utility methods for dealing with tables.
+ */
+public final class TableUtils {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // non-instantiable
+ private TableUtils() { }
+
+ /**
+ * Generates a JSON array node from a table model.
+ *
+ * @param tm the table model
+ * @return the array node representation
+ */
+ public static ArrayNode generateArrayNode(TableModel tm) {
+ ArrayNode array = MAPPER.createArrayNode();
+ for (TableModel.Row r : tm.getRows()) {
+ array.add(toJsonNode(r, tm));
+ }
+ return array;
+ }
+
+ private static JsonNode toJsonNode(TableModel.Row row, TableModel tm) {
+ ObjectNode result = MAPPER.createObjectNode();
+ String[] keys = tm.getColumnIds();
+ String[] cells = row.getAsFormattedStrings();
+ int n = keys.length;
+ for (int i = 0; i < n; i++) {
+ result.put(keys[i], cells[i]);
+ }
+ return result;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java
new file mode 100644
index 00000000..6113fc3f
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellComparator.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.ui.table.CellComparator;
+
+/**
+ * Base implementation of a {@link CellComparator}. This class takes care
+ * of dealing with null inputs; subclasses should implement their comparison
+ * knowing that both inputs are guaranteed to be non-null.
+ */
+public abstract class AbstractCellComparator implements CellComparator {
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1 == null && o2 == null) {
+ return 0; // o1 == o2
+ }
+ if (o1 == null) {
+ return -1; // o1 < o2
+ }
+ if (o2 == null) {
+ return 1; // o1 > o2
+ }
+ return nonNullCompare(o1, o2);
+ }
+
+ /**
+ * Compares its two arguments for order. Returns a negative integer,
+ * zero, or a positive integer as the first argument is less than, equal
+ * to, or greater than the second.<p>
+ *
+ * Note that both objects are guaranteed to be non-null.
+ *
+ * @see java.util.Comparator#compare(Object, Object)
+ *
+ * @param o1 the first object to be compared.
+ * @param o2 the second object to be compared.
+ * @return a negative integer, zero, or a positive integer as the
+ * first argument is less than, equal to, or greater than the
+ * second.
+ * @throws ClassCastException if the arguments' types prevent them from
+ * being compared by this comparator.
+ */
+ protected abstract int nonNullCompare(Object o1, Object o2);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java
new file mode 100644
index 00000000..33ce2ab5
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AbstractCellFormatter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Base implementation of a {@link CellFormatter}. This class takes care of
+ * dealing with null inputs; subclasses should implement their format method
+ * knowing that the input is guaranteed to be non-null.
+ */
+public abstract class AbstractCellFormatter implements CellFormatter {
+
+ @Override
+ public String format(Object value) {
+ return value == null ? "" : nonNullFormat(value);
+ }
+
+ /**
+ * Formats the specified value into a string appropriate for displaying
+ * in a table cell. Note that value is guaranteed to be non-null.
+ *
+ * @param value the value
+ * @return the formatted string
+ */
+ protected abstract String nonNullFormat(Object value);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java
new file mode 100644
index 00000000..42d684b6
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/AppIdFormatter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats an application identifier as "(app-id) : (app-name)".
+ */
+public final class AppIdFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private AppIdFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ ApplicationId appId = (ApplicationId) value;
+ return appId.id() + " : " + appId.name();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new AppIdFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java
new file mode 100644
index 00000000..fee26154
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/ConnectPointFormatter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats a connect point as "(element-id)/(port)".
+ */
+public final class ConnectPointFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private ConnectPointFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ ConnectPoint cp = (ConnectPoint) value;
+ return cp.elementId() + "/" + cp.port();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new ConnectPointFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java
new file mode 100644
index 00000000..093a20d3
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellComparator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.ui.table.CellComparator;
+
+/**
+ * A default cell comparator.
+ * <p>
+ * Verifies that the objects being compared are the same class.
+ * Looks to see if the objects being compared implement comparable and, if so,
+ * delegates to that; otherwise, implements a lexicographical compare function
+ * (i.e. string sorting). Uses the objects' toString() method and then
+ * compares the resulting strings. Note that null values are acceptable and
+ * are considered "smaller" than any non-null value.
+ */
+public final class DefaultCellComparator extends AbstractCellComparator {
+
+ // non-instantiable
+ private DefaultCellComparator() { }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected int nonNullCompare(Object o1, Object o2) {
+ if (o1 instanceof Comparable) {
+ // if o2 is not the same class as o1, then compareTo will
+ // throw ClassCastException for us
+ return ((Comparable) o1).compareTo(o2);
+ }
+ return o1.toString().compareTo(o2.toString());
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellComparator INSTANCE = new DefaultCellComparator();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java
new file mode 100644
index 00000000..0efa2ebd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/DefaultCellFormatter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * A default cell formatter. Uses the object's toString() method.
+ */
+public final class DefaultCellFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private DefaultCellFormatter() { }
+
+ @Override
+ public String nonNullFormat(Object value) {
+ return value.toString();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new DefaultCellFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java
new file mode 100644
index 00000000..5b89a0b7
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/EnumFormatter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+import static org.apache.commons.lang.WordUtils.capitalizeFully;
+
+/**
+ * Formats enum types to be readable strings.
+ */
+public final class EnumFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private EnumFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ return capitalizeFully(value.toString().replace("_", " "));
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new EnumFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java
new file mode 100644
index 00000000..e09982ea
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HexFormatter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats integer values as hex strings with a "0x" prefix.
+ */
+public final class HexFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private HexFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ return "0x" + Integer.toHexString((Integer) value);
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new HexFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java
new file mode 100644
index 00000000..fe87c61b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/HostLocationFormatter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.onosproject.net.HostLocation;
+import org.onosproject.ui.table.CellFormatter;
+
+/**
+ * Formats a host location as "(device-id)/(port)".
+ */
+public final class HostLocationFormatter extends AbstractCellFormatter {
+
+ // non-instantiable
+ private HostLocationFormatter() { }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ HostLocation loc = (HostLocation) value;
+ return loc.deviceId() + "/" + loc.port();
+ }
+
+ /**
+ * An instance of this class.
+ */
+ public static final CellFormatter INSTANCE = new HostLocationFormatter();
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java
new file mode 100644
index 00000000..44dc1940
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/TimeFormatter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 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.table.cell;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.util.Locale;
+
+/**
+ * Formats time values using {@link DateTimeFormatter}.
+ */
+public final class TimeFormatter extends AbstractCellFormatter {
+
+ private DateTimeFormatter dtf;
+
+ // NOTE: Unlike other formatters in this package, this one is not
+ // implemented as a Singleton, because instances may be
+ // decorated with alternate locale and/or timezone.
+
+ /**
+ * Constructs a time formatter that uses the default locale and timezone.
+ */
+ public TimeFormatter() {
+ dtf = DateTimeFormat.longTime();
+ }
+
+ /**
+ * Sets the locale to use for formatting the time.
+ *
+ * @param locale locale to use for formatting
+ * @return self, for chaining
+ */
+ public TimeFormatter withLocale(Locale locale) {
+ dtf = dtf.withLocale(locale);
+ return this;
+ }
+
+ /**
+ * Sets the time zone to use for formatting the time.
+ *
+ * @param zone time zone to use
+ * @return self, for chaining
+ */
+ public TimeFormatter withZone(DateTimeZone zone) {
+ dtf = dtf.withZone(zone);
+ return this;
+ }
+
+ @Override
+ protected String nonNullFormat(Object value) {
+ return dtf.print((DateTime) value);
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java
new file mode 100644
index 00000000..c25bcb06
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/cell/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/**
+ * Set of table cell renderers and comparators for use by GUI apps.
+ */
+package org.onosproject.ui.table.cell; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java
new file mode 100644
index 00000000..ee975d11
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/table/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/**
+ * Facilities for creating tabular models of data for the GUI.
+ */
+package org.onosproject.ui.table; \ No newline at end of file
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
new file mode 100644
index 00000000..ab2ced36
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 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.topo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Partial implementation of the highlighting to apply to topology
+ * view elements.
+ */
+public abstract class AbstractHighlight {
+ private final TopoElementType type;
+ private final String elementId;
+ private boolean keepSubdued = false;
+
+ /**
+ * Constructs the highlight.
+ *
+ * @param type highlight element type
+ * @param elementId element identifier
+ */
+ public AbstractHighlight(TopoElementType type, String elementId) {
+ this.type = checkNotNull(type);
+ this.elementId = checkNotNull(elementId);
+ }
+
+ /**
+ * Sets a flag to tell the renderer to keep this element subdued.
+ */
+ public void keepSubdued() {
+ keepSubdued = true;
+ }
+
+ /**
+ * Returns the element type.
+ *
+ * @return element type
+ */
+ public TopoElementType type() {
+ return type;
+ }
+
+ /**
+ * Returns the element identifier.
+ *
+ * @return element identifier
+ */
+ public String elementId() {
+ return elementId;
+ }
+
+ /**
+ * Returns the subdued flag.
+ *
+ * @return subdued flag
+ */
+ public boolean subdued() {
+ return keepSubdued;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java
new file mode 100644
index 00000000..c37c129b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLink.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 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.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+/**
+ * A simple concrete implementation of a {@link BiLink}.
+ * Note that this implementation does not generate any link highlights.
+ */
+public class BaseLink extends BiLink {
+
+ /**
+ * Constructs a base link for the given key and initial link.
+ *
+ * @param key canonical key for this base link
+ * @param link first link
+ */
+ public BaseLink(LinkKey key, Link link) {
+ super(key, link);
+ }
+
+ @Override
+ public LinkHighlight highlight(Enum<?> type) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java
new file mode 100644
index 00000000..720eca49
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BaseLinkMap.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 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.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+/**
+ * Collection of {@link BaseLink}s.
+ */
+public class BaseLinkMap extends BiLinkMap<BaseLink> {
+ @Override
+ public BaseLink create(LinkKey key, Link link) {
+ return new BaseLink(key, link);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java
new file mode 100644
index 00000000..8c95e15d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLink.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 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.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of a link and its inverse, as a partial implementation.
+ * <p>
+ * Subclasses will decide how to generate the link highlighting (coloring
+ * and labeling) for the topology view.
+ */
+public abstract class BiLink {
+
+ private final LinkKey key;
+ private final Link one;
+ private Link two;
+
+ /**
+ * Constructs a bi-link for the given key and initial link. It is expected
+ * that the caller will have used {@link TopoUtils#canonicalLinkKey(Link)}
+ * to generate the key.
+ *
+ * @param key canonical key for this bi-link
+ * @param link first link
+ */
+ public BiLink(LinkKey key, Link link) {
+ this.key = checkNotNull(key);
+ this.one = checkNotNull(link);
+ }
+
+ /**
+ * Sets the second link for this bi-link.
+ *
+ * @param link second link
+ */
+ public void setOther(Link link) {
+ this.two = checkNotNull(link);
+ }
+
+ /**
+ * Returns the link identifier in the form expected on the Topology View
+ * in the web client.
+ *
+ * @return link identifier
+ */
+ public String linkId() {
+ return TopoUtils.compactLinkString(one);
+ }
+
+ /**
+ * Returns the key for this bi-link.
+ *
+ * @return the key
+ */
+ public LinkKey key() {
+ return key;
+ }
+
+ /**
+ * Returns the first link in this bi-link.
+ *
+ * @return the first link
+ */
+ public Link one() {
+ return one;
+ }
+
+ /**
+ * Returns the second link in this bi-link.
+ *
+ * @return the second link
+ */
+ public Link two() {
+ return two;
+ }
+
+ /**
+ * Returns the link highlighting to use, based on this bi-link's current
+ * state.
+ *
+ * @param type optional highlighting type parameter
+ * @return link highlighting model
+ */
+ public abstract LinkHighlight highlight(Enum<?> type);
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java
new file mode 100644
index 00000000..7bc0e65d
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/BiLinkMap.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015 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.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a collection of {@link BiLink} concrete classes. These maps
+ * are used to collate a set of unidirectional {@link Link}s into a smaller
+ * set of bi-directional {@link BiLink} derivatives.
+ * <p>
+ * @param <B> the type of bi-link subclass
+ */
+public abstract class BiLinkMap<B extends BiLink> {
+
+ private final Map<LinkKey, B> map = new HashMap<>();
+
+ /**
+ * Creates a new instance of a bi-link. Concrete subclasses should
+ * instantiate and return the appropriate bi-link subclass.
+ *
+ * @param key the link key
+ * @param link the initial link
+ * @return a new instance
+ */
+ protected abstract B create(LinkKey key, Link link);
+
+ /**
+ * Adds the given link to our collection, returning the corresponding
+ * bi-link (creating one if needed necessary).
+ *
+ * @param link the link to add to the collection
+ * @return the corresponding bi-link wrapper
+ */
+ public B add(Link link) {
+ LinkKey key = TopoUtils.canonicalLinkKey(checkNotNull(link));
+ B blink = map.get(key);
+ if (blink == null) {
+ // no bi-link yet exists for this link
+ blink = create(key, link);
+ map.put(key, blink);
+ } else {
+ // we have a bi-link for this link.
+ if (!blink.one().equals(link)) {
+ blink.setOther(link);
+ }
+ }
+ return blink;
+ }
+
+ /**
+ * Returns the bi-link instances in the collection.
+ *
+ * @return the bi-links in this map
+ */
+ public Collection<B> biLinks() {
+ return map.values();
+ }
+
+ /**
+ * Returns the number of bi-links in the collection.
+ *
+ * @return number of bi-links
+ */
+ public int size() {
+ return map.size();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java
new file mode 100644
index 00000000..ca2ecccd
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/ButtonId.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 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.topo;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Designates the identity of a button on the topology view panels.
+ */
+public class ButtonId {
+
+ private final String id;
+
+ /**
+ * Creates a button ID with the given identifier.
+ *
+ * @param id identifier for the button
+ */
+ public ButtonId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the identifier for this button.
+ *
+ * @return identifier
+ */
+ public String id() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("id", id()).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ButtonId that = (ButtonId) o;
+ return id.equals(that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
new file mode 100644
index 00000000..2985d3d4
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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.topo;
+
+/**
+ * Denotes the highlighting to apply to a device.
+ */
+public class DeviceHighlight extends NodeHighlight {
+
+ public DeviceHighlight(String deviceId) {
+ super(TopoElementType.DEVICE, deviceId);
+ }
+
+ // TODO: implement device highlighting:
+ // - visual highlight
+ // - badging
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
new file mode 100644
index 00000000..be59c26b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2015 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.topo;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encapsulates highlights to be applied to the topology view, such as
+ * highlighting links, displaying link labels, perhaps even decorating
+ * nodes with badges, etc.
+ */
+public class Highlights {
+
+ private static final String EMPTY = "";
+ private static final String MIN = "min";
+ private static final String MAX = "max";
+
+ /**
+ * A notion of amount.
+ */
+ public enum Amount {
+ ZERO(EMPTY),
+ MINIMALLY(MIN),
+ MAXIMALLY(MAX);
+
+ private final String s;
+ Amount(String str) {
+ s = str;
+ }
+
+ @Override
+ public String toString() {
+ return s;
+ }
+ }
+
+ private final Map<String, DeviceHighlight> devices = new HashMap<>();
+ private final Map<String, HostHighlight> hosts = new HashMap<>();
+ private final Map<String, LinkHighlight> links = new HashMap<>();
+
+ private Amount subdueLevel = Amount.ZERO;
+
+
+ /**
+ * Adds highlighting information for a device.
+ *
+ * @param dh device highlight
+ * @return self, for chaining
+ */
+ public Highlights add(DeviceHighlight dh) {
+ devices.put(dh.elementId(), dh);
+ return this;
+ }
+
+ /**
+ * Adds highlighting information for a host.
+ *
+ * @param hh host highlight
+ * @return self, for chaining
+ */
+ public Highlights add(HostHighlight hh) {
+ hosts.put(hh.elementId(), hh);
+ return this;
+ }
+
+ /**
+ * Adds highlighting information for a link.
+ *
+ * @param lh link highlight
+ * @return self, for chaining
+ */
+ public Highlights add(LinkHighlight lh) {
+ links.put(lh.elementId(), lh);
+ return this;
+ }
+
+ /**
+ * Marks the amount by which all other elements (devices, hosts, links)
+ * not explicitly referenced here will be "subdued" visually.
+ *
+ * @param amount amount to subdue other elements
+ * @return self, for chaining
+ */
+ public Highlights subdueAllElse(Amount amount) {
+ subdueLevel = checkNotNull(amount);
+ return this;
+ }
+
+ /**
+ * Returns the collection of device highlights.
+ *
+ * @return device highlights
+ */
+ public Collection<DeviceHighlight> devices() {
+ return Collections.unmodifiableCollection(devices.values());
+ }
+
+ /**
+ * Returns the collection of host highlights.
+ *
+ * @return host highlights
+ */
+ public Collection<HostHighlight> hosts() {
+ return Collections.unmodifiableCollection(hosts.values());
+ }
+
+ /**
+ * Returns the collection of link highlights.
+ *
+ * @return link highlights
+ */
+ public Collection<LinkHighlight> links() {
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ /**
+ * Returns the amount by which all other elements not explicitly
+ * referenced here should be "subdued".
+ *
+ * @return amount to subdue other elements
+ */
+ public Amount subdueLevel() {
+ return subdueLevel;
+ }
+
+ /**
+ * Returns the node highlight (device or host) for the given element
+ * identifier, or null if no match.
+ *
+ * @param id element identifier
+ * @return corresponding node highlight
+ */
+ public NodeHighlight getNode(String id) {
+ NodeHighlight nh = devices.get(id);
+ return nh != null ? nh : hosts.get(id);
+ }
+
+ /**
+ * Returns the device highlight for the given device identifier,
+ * or null if no match.
+ *
+ * @param id device identifier
+ * @return corresponding device highlight
+ */
+ public DeviceHighlight getDevice(String id) {
+ return devices.get(id);
+ }
+
+ /**
+ * Returns the host highlight for the given host identifier,
+ * or null if no match.
+ *
+ * @param id host identifier
+ * @return corresponding host highlight
+ */
+ public HostHighlight getHost(String id) {
+ return hosts.get(id);
+ }
+
+ /**
+ * Returns the link highlight for the given link identifier,
+ * or null if no match.
+ *
+ * @param id link identifier
+ * @return corresponding link highlight
+ */
+ public LinkHighlight getLink(String id) {
+ return links.get(id);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
new file mode 100644
index 00000000..76669a84
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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.topo;
+
+/**
+ * Denotes the highlighting to apply to a host.
+ */
+public class HostHighlight extends NodeHighlight {
+
+ public HostHighlight(String hostId) {
+ super(TopoElementType.HOST, hostId);
+ }
+
+ // TODO: implement host highlighting:
+ // - visual highlight
+ // - badging
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
new file mode 100644
index 00000000..b4e43304
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015 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.topo;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Denotes the highlighting to be applied to a link.
+ * {@link Flavor} is a closed set of NO-, PRIMARY-, or SECONDARY- highlighting.
+ * {@link Mod} is an open ended set of additional modifications (CSS classes)
+ * that may also be applied.
+ * Note that {@link #MOD_OPTICAL} and {@link #MOD_ANIMATED} are pre-defined mods.
+ * Label text may be set, which will also be displayed on the link.
+ */
+public class LinkHighlight extends AbstractHighlight {
+
+ private static final String PLAIN = "plain";
+ private static final String PRIMARY = "primary";
+ private static final String SECONDARY = "secondary";
+ private static final String EMPTY = "";
+ private static final String SPACE = " ";
+
+ private final Flavor flavor;
+ private final Set<Mod> mods = new TreeSet<>();
+ private String label = EMPTY;
+
+ /**
+ * Constructs a link highlight entity.
+ *
+ * @param linkId the link identifier
+ * @param flavor the highlight flavor
+ */
+ public LinkHighlight(String linkId, Flavor flavor) {
+ super(TopoElementType.LINK, linkId);
+ this.flavor = checkNotNull(flavor);
+ }
+
+ /**
+ * Adds a highlighting modification to this link highlight.
+ *
+ * @param mod mod to be added
+ * @return self, for chaining
+ */
+ public LinkHighlight addMod(Mod mod) {
+ mods.add(checkNotNull(mod));
+ return this;
+ }
+
+ /**
+ * Adds a label to be displayed on the link.
+ *
+ * @param label the label text
+ * @return self, for chaining
+ */
+ public LinkHighlight setLabel(String label) {
+ this.label = label == null ? EMPTY : label;
+ return this;
+ }
+
+ /**
+ * Returns the highlight flavor.
+ *
+ * @return highlight flavor
+ */
+ public Flavor flavor() {
+ return flavor;
+ }
+
+ /**
+ * Returns the highlight modifications.
+ *
+ * @return highlight modifications
+ */
+ public Set<Mod> mods() {
+ return Collections.unmodifiableSet(mods);
+ }
+
+ /**
+ * Generates the CSS classes string from the {@link #flavor} and
+ * any optional {@link #mods}.
+ *
+ * @return CSS classes string
+ */
+ public String cssClasses() {
+ StringBuilder sb = new StringBuilder(flavor.toString());
+ mods.forEach(m -> sb.append(SPACE).append(m));
+ return sb.toString();
+ }
+
+ /**
+ * Returns the label text.
+ *
+ * @return label text
+ */
+ public String label() {
+ return label;
+ }
+
+ /**
+ * Link highlighting flavor.
+ */
+ public enum Flavor {
+ NO_HIGHLIGHT(PLAIN),
+ PRIMARY_HIGHLIGHT(PRIMARY),
+ SECONDARY_HIGHLIGHT(SECONDARY);
+
+ private String cssName;
+
+ Flavor(String s) {
+ cssName = s;
+ }
+
+ @Override
+ public String toString() {
+ return cssName;
+ }
+ }
+
+ /**
+ * Denotes a link to be tagged as an optical link.
+ */
+ public static final Mod MOD_OPTICAL = new Mod("optical");
+
+ /**
+ * Denotes a link to be tagged with animated traffic ("marching ants").
+ */
+ public static final Mod MOD_ANIMATED = new Mod("animated");
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java
new file mode 100644
index 00000000..d21a8724
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/Mod.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 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.topo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Highlighting modification.
+ * <p>
+ * Note that (for link highlights) this translates to a CSS class name
+ * that is applied to the link in the Topology UI.
+ */
+public final class Mod implements Comparable<Mod> {
+ private final String modId;
+
+ /**
+ * Constructs a mod with the given identifier.
+ *
+ * @param modId modification identifier
+ */
+ public Mod(String modId) {
+ this.modId = checkNotNull(modId);
+ }
+
+ @Override
+ public String toString() {
+ return modId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Mod mod = (Mod) o;
+ return modId.equals(mod.modId);
+ }
+
+ @Override
+ public int hashCode() {
+ return modId.hashCode();
+ }
+
+ @Override
+ public int compareTo(Mod o) {
+ return this.modId.compareTo(o.modId);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java
new file mode 100644
index 00000000..735f8166
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 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.topo;
+
+/**
+ * Parent class of {@link DeviceHighlight} and {@link HostHighlight}.
+ */
+public abstract class NodeHighlight extends AbstractHighlight {
+ public NodeHighlight(TopoElementType type, String elementId) {
+ super(type, elementId);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
new file mode 100644
index 00000000..b284de1b
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2015 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.topo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.Device;
+import org.onosproject.net.Element;
+import org.onosproject.net.Host;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.ui.JsonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.HostId.hostId;
+
+/**
+ * Encapsulates a selection of devices and/or hosts from the topology view.
+ */
+public class NodeSelection {
+
+ private static final Logger log =
+ LoggerFactory.getLogger(NodeSelection.class);
+
+ private static final String IDS = "ids";
+ private static final String HOVER = "hover";
+
+ private final DeviceService deviceService;
+ private final HostService hostService;
+
+ private final Set<String> ids;
+ private final String hover;
+
+ private final Set<Device> devices = new HashSet<>();
+ private final Set<Host> hosts = new HashSet<>();
+ private Element hovered;
+
+ /**
+ * Creates a node selection entity, from the given payload, using the
+ * supplied device and host services. Note that if a device or host was
+ * hovered over by the mouse, it is available via {@link #hovered()}.
+ *
+ * @param payload message payload
+ * @param deviceService device service
+ * @param hostService host service
+ */
+ public NodeSelection(ObjectNode payload,
+ DeviceService deviceService,
+ HostService hostService) {
+ this.deviceService = deviceService;
+ this.hostService = hostService;
+
+ ids = extractIds(payload);
+ hover = extractHover(payload);
+
+ // start by extracting the hovered element if any
+ if (isNullOrEmpty(hover)) {
+ hovered = null;
+ } else {
+ setHoveredElement();
+ }
+
+ // now go find the devices and hosts that are in the selection list
+ Set<String> unmatched = findDevices(ids);
+ unmatched = findHosts(unmatched);
+ if (unmatched.size() > 0) {
+ log.debug("Skipping unmatched IDs {}", unmatched);
+ }
+
+ }
+
+ /**
+ * Returns a view of the selected devices (hover not included).
+ *
+ * @return selected devices
+ */
+ public Set<Device> devices() {
+ return Collections.unmodifiableSet(devices);
+ }
+
+ /**
+ * Returns a view of the selected devices, including the hovered device
+ * if there was one.
+ *
+ * @return selected (plus hovered) devices
+ */
+ public Set<Device> devicesWithHover() {
+ Set<Device> withHover;
+ if (hovered != null && hovered instanceof Device) {
+ withHover = new HashSet<>(devices);
+ withHover.add((Device) hovered);
+ } else {
+ withHover = devices;
+ }
+ return Collections.unmodifiableSet(withHover);
+ }
+
+ /**
+ * Returns a view of the selected hosts (hover not included).
+ *
+ * @return selected hosts
+ */
+ public Set<Host> hosts() {
+ return Collections.unmodifiableSet(hosts);
+ }
+
+ /**
+ * Returns a view of the selected hosts, including the hovered host
+ * if thee was one.
+ *
+ * @return selected (plus hovered) hosts
+ */
+ public Set<Host> hostsWithHover() {
+ Set<Host> withHover;
+ if (hovered != null && hovered instanceof Host) {
+ withHover = new HashSet<>(hosts);
+ withHover.add((Host) hovered);
+ } else {
+ withHover = hosts;
+ }
+ return Collections.unmodifiableSet(withHover);
+ }
+
+ /**
+ * Returns the element (host or device) over which the mouse was hovering,
+ * or null.
+ *
+ * @return element hovered over
+ */
+ public Element hovered() {
+ return hovered;
+ }
+
+ /**
+ * Returns true if nothing is selected.
+ *
+ * @return true if nothing selected
+ */
+ public boolean none() {
+ return devices().size() == 0 && hosts().size() == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "NodeSelection{" +
+ "ids=" + ids +
+ ", hover='" + hover + '\'' +
+ ", #devices=" + devices.size() +
+ ", #hosts=" + hosts.size() +
+ '}';
+ }
+
+ // == helper methods
+
+ private Set<String> extractIds(ObjectNode payload) {
+ ArrayNode array = (ArrayNode) payload.path(IDS);
+ if (array == null || array.size() == 0) {
+ return Collections.emptySet();
+ }
+
+ Set<String> ids = new HashSet<>();
+ for (JsonNode node : array) {
+ ids.add(node.asText());
+ }
+ return ids;
+ }
+
+ private String extractHover(ObjectNode payload) {
+ return JsonUtils.string(payload, HOVER);
+ }
+
+ private void setHoveredElement() {
+ Set<String> unmatched;
+ unmatched = new HashSet<>();
+ unmatched.add(hover);
+ unmatched = findDevices(unmatched);
+ if (devices.size() == 1) {
+ hovered = devices.iterator().next();
+ devices.clear();
+ } else {
+ unmatched = findHosts(unmatched);
+ if (hosts.size() == 1) {
+ hovered = hosts.iterator().next();
+ hosts.clear();
+ } else {
+ hovered = null;
+ log.debug("Skipping unmatched HOVER {}", unmatched);
+ }
+ }
+ }
+
+ private Set<String> findDevices(Set<String> ids) {
+ Set<String> unmatched = new HashSet<>();
+ Device device;
+
+ for (String id : ids) {
+ try {
+ device = deviceService.getDevice(deviceId(id));
+ if (device != null) {
+ devices.add(device);
+ } else {
+ unmatched.add(id);
+ }
+ } catch (Exception e) {
+ unmatched.add(id);
+ }
+ }
+ return unmatched;
+ }
+
+ private Set<String> findHosts(Set<String> ids) {
+ Set<String> unmatched = new HashSet<>();
+ Host host;
+
+ for (String id : ids) {
+ try {
+ host = hostService.getHost(hostId(id));
+ if (host != null) {
+ hosts.add(host);
+ } else {
+ unmatched.add(id);
+ }
+ } catch (Exception e) {
+ unmatched.add(id);
+ }
+ }
+ return unmatched;
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
new file mode 100644
index 00000000..121e0834
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2015 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.topo;
+
+import com.google.common.collect.Sets;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Models a panel displayed on the Topology View.
+ */
+public class PropertyPanel {
+
+ private static final DecimalFormat DF0 = new DecimalFormat("#,###");
+
+ private String title;
+ private String typeId;
+ private String id;
+ private List<Prop> properties = new ArrayList<>();
+ private List<ButtonId> buttons = new ArrayList<>();
+
+ /**
+ * Constructs a property panel model with the given title and
+ * type identifier (icon to display).
+ *
+ * @param title title text
+ * @param typeId type (icon) ID
+ */
+ public PropertyPanel(String title, String typeId) {
+ this.title = title;
+ this.typeId = typeId;
+ }
+
+ /**
+ * Adds an ID field to the panel data, to be included in
+ * the returned JSON data to the client.
+ *
+ * @param id the identifier
+ * @return self, for chaining
+ */
+ public PropertyPanel id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, String value) {
+ properties.add(new Prop(key, value));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data, using a decimal formatter.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, int value) {
+ properties.add(new Prop(key, DF0.format(value)));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data, using a decimal formatter.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, long value) {
+ properties.add(new Prop(key, DF0.format(value)));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data. Note that the value's
+ * {@link Object#toString toString()} method is used to convert the
+ * value to a string.
+ *
+ * @param key property key
+ * @param value property value
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, Object value) {
+ properties.add(new Prop(key, value.toString()));
+ return this;
+ }
+
+ /**
+ * Adds a property to the panel data. Note that the value's
+ * {@link Object#toString toString()} method is used to convert the
+ * value to a string, from which the characters defined in the given
+ * regular expression string are stripped.
+ *
+ * @param key property key
+ * @param value property value
+ * @param reStrip regexp characters to strip from value string
+ * @return self, for chaining
+ */
+ public PropertyPanel addProp(String key, Object value, String reStrip) {
+ String val = value.toString().replaceAll(reStrip, "");
+ properties.add(new Prop(key, val));
+ return this;
+ }
+
+ /**
+ * Adds a separator to the panel data.
+ *
+ * @return self, for chaining
+ */
+ public PropertyPanel addSeparator() {
+ properties.add(new Separator());
+ return this;
+ }
+
+ /**
+ * Returns the title text.
+ *
+ * @return title text
+ */
+ public String title() {
+ return title;
+ }
+
+ /**
+ * Returns the type identifier.
+ *
+ * @return type identifier
+ */
+ public String typeId() {
+ return typeId;
+ }
+
+ /**
+ * Returns the internal ID.
+ *
+ * @return the ID
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the list of properties to be displayed.
+ *
+ * @return the property list
+ */
+ // TODO: consider protecting this?
+ public List<Prop> properties() {
+ return properties;
+ }
+
+ /**
+ * Returns the list of button descriptors.
+ *
+ * @return the button list
+ */
+ // TODO: consider protecting this?
+ public List<ButtonId> buttons() {
+ return buttons;
+ }
+
+ // == MUTATORS
+
+ /**
+ * Sets the title text.
+ *
+ * @param title title text
+ * @return self, for chaining
+ */
+ public PropertyPanel title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * Sets the type identifier (icon ID).
+ *
+ * @param typeId type identifier
+ * @return self, for chaining
+ */
+ public PropertyPanel typeId(String typeId) {
+ this.typeId = typeId;
+ return this;
+ }
+
+ /**
+ * Removes properties with the given keys from the list.
+ *
+ * @param keys keys of properties to remove
+ * @return self, for chaining
+ */
+ public PropertyPanel removeProps(String... keys) {
+ Set<String> forRemoval = Sets.newHashSet(keys);
+ List<Prop> toKeep = new ArrayList<>();
+ for (Prop p: properties) {
+ if (!forRemoval.contains(p.key())) {
+ toKeep.add(p);
+ }
+ }
+ properties = toKeep;
+ return this;
+ }
+
+ /**
+ * Removes all currently defined properties.
+ *
+ * @return self, for chaining
+ */
+ public PropertyPanel removeAllProps() {
+ properties.clear();
+ return this;
+ }
+
+ /**
+ * Adds the given button descriptor to the panel data.
+ *
+ * @param button button descriptor
+ * @return self, for chaining
+ */
+ public PropertyPanel addButton(ButtonId button) {
+ buttons.add(button);
+ return this;
+ }
+
+ /**
+ * Removes buttons with the given descriptors from the list.
+ *
+ * @param descriptors descriptors to remove
+ * @return self, for chaining
+ */
+ public PropertyPanel removeButtons(ButtonId... descriptors) {
+ Set<ButtonId> forRemoval = Sets.newHashSet(descriptors);
+ List<ButtonId> toKeep = new ArrayList<>();
+ for (ButtonId bd: buttons) {
+ if (!forRemoval.contains(bd)) {
+ toKeep.add(bd);
+ }
+ }
+ buttons = toKeep;
+ return this;
+ }
+
+ /**
+ * Removes all currently defined buttons.
+ *
+ * @return self, for chaining
+ */
+ public PropertyPanel removeAllButtons() {
+ buttons.clear();
+ return this;
+ }
+
+ // ====================
+
+
+ /**
+ * Simple data carrier for a property, composed of a key/value pair.
+ */
+ public static class Prop {
+ private final String key;
+ private final String value;
+
+ /**
+ * Constructs a property data value.
+ *
+ * @param key property key
+ * @param value property value
+ */
+ public Prop(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Returns the property's key.
+ *
+ * @return the key
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Returns the property's value.
+ *
+ * @return the value
+ */
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Prop prop = (Prop) o;
+ return key.equals(prop.key) && value.equals(prop.value);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = key.hashCode();
+ result = 31 * result + value.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + key + " -> " + value + "}";
+ }
+ }
+
+ /**
+ * Auxiliary class representing a separator property.
+ */
+ public static class Separator extends Prop {
+ public Separator() {
+ super("-", "");
+ }
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
new file mode 100644
index 00000000..38a8f036
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2015 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.topo;
+
+/**
+ * Defines string constants used in the Topology View of the ONOS GUI.
+ * <p>
+ * See also:
+ * <ul>
+ * <li> https://wiki.onosproject.org/display/ONOS/UI+Service+-+GlyphService </li>
+ * </ul>
+ */
+public final class TopoConstants {
+
+ /**
+ * Defines constants for standard glyph identifiers.
+ */
+ public static final class Glyphs {
+ public static final String UNKNOWN = "unknown";
+ public static final String BIRD = "bird";
+ public static final String NODE = "node";
+ public static final String SWITCH = "switch";
+ public static final String ROADM = "roadm";
+ public static final String ENDSTATION = "endstation";
+ public static final String ROUTER = "router";
+ public static final String BGP_SPEAKER = "bgpSpeaker";
+ public static final String CHAIN = "chain";
+ public static final String CROWN = "crown";
+ public static final String TOPO = "topo";
+ public static final String REFRESH = "refresh";
+ public static final String GARBAGE = "garbage";
+ public static final String FLOW_TABLE = "flowTable";
+ public static final String PORT_TABLE = "portTable";
+ public static final String GROUP_TABLE = "groupTable";
+ public static final String SUMMARY = "summary";
+ public static final String DETAILS = "details";
+ public static final String PORTS = "ports";
+ public static final String MAP = "map";
+ public static final String CYCLE_LABELS = "cycleLabels";
+ public static final String OBLIQUE = "oblique";
+ public static final String FILTERS = "filters";
+ public static final String RESET_ZOOM = "resetZoom";
+ public static final String RELATED_INTENTS = "relatedIntents";
+ public static final String NEXT_INTENT = "nextIntent";
+ public static final String PREV_INTENT = "prevIntent";
+ public static final String INTENT_TRAFFIC = "intentTraffic";
+ public static final String ALL_TRAFFIC = "allTraffic";
+ public static final String FLOWS = "flows";
+ public static final String EQ_MASTER = "eqMaster";
+ public static final String UI_ATTACHED = "uiAttached";
+ public static final String CHECK_MARK = "checkMark";
+ public static final String X_MARK = "xMark";
+ public static final String TRIANGLE_UP = "triangleUp";
+ public static final String TRIANGLE_DOWN = "triangleDown";
+ public static final String PLUS = "plus";
+ public static final String MINUS = "minus";
+ public static final String PLAY = "play";
+ public static final String STOP = "stop";
+ public static final String CLOUD = "cloud";
+ }
+
+ /**
+ * Defines constants for property names on the default summary and
+ * details panels.
+ */
+ public static final class Properties {
+ public static final String SEPARATOR = "-";
+
+ // summary panel
+ public static final String DEVICES = "Devices";
+ public static final String LINKS = "Links";
+ public static final String HOSTS = "Hosts";
+ public static final String TOPOLOGY_SSCS = "Topology SCCs";
+ public static final String INTENTS = "Intents";
+ public static final String TUNNELS = "Tunnels";
+ public static final String FLOWS = "Flows";
+ public static final String VERSION = "Version";
+
+ // device details
+ public static final String URI = "URI";
+ public static final String VENDOR = "Vendor";
+ public static final String HW_VERSION = "H/W Version";
+ public static final String SW_VERSION = "S/W Version";
+ public static final String SERIAL_NUMBER = "Serial Number";
+ public static final String PROTOCOL = "Protocol";
+ public static final String LATITUDE = "Latitude";
+ public static final String LONGITUDE = "Longitude";
+ public static final String PORTS = "Ports";
+
+ // host details
+ public static final String MAC = "MAC";
+ public static final String IP = "IP";
+ public static final String VLAN = "VLAN";
+ }
+
+ /**
+ * Defines identities of core buttons that appear on the topology
+ * details panel.
+ */
+ public static final class CoreButtons {
+ public static final ButtonId SHOW_DEVICE_VIEW =
+ new ButtonId("showDeviceView");
+
+ public static final ButtonId SHOW_FLOW_VIEW =
+ new ButtonId("showFlowView");
+
+ public static final ButtonId SHOW_PORT_VIEW =
+ new ButtonId("showPortView");
+
+ public static final ButtonId SHOW_GROUP_VIEW =
+ new ButtonId("showGroupView");
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java
new file mode 100644
index 00000000..dc327464
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 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.topo;
+
+/**
+ * The topology element types to which a highlight can be applied.
+ */
+public enum TopoElementType {
+ DEVICE, HOST, LINK
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
new file mode 100644
index 00000000..a94068ee
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2015 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.topo;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onosproject.ui.JsonUtils.envelope;
+
+/**
+ * JSON utilities for the Topology View.
+ */
+public final class TopoJson {
+ // package-private for unit test access
+ static final String SHOW_HIGHLIGHTS = "showHighlights";
+
+ static final String DEVICES = "devices";
+ static final String HOSTS = "hosts";
+ static final String LINKS = "links";
+ static final String SUBDUE = "subdue";
+
+ static final String ID = "id";
+ static final String LABEL = "label";
+ static final String CSS = "css";
+
+ static final String TITLE = "title";
+ static final String TYPE = "type";
+ static final String PROP_ORDER = "propOrder";
+ static final String PROPS = "props";
+ static final String BUTTONS = "buttons";
+
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private static ObjectNode objectNode() {
+ return MAPPER.createObjectNode();
+ }
+
+ private static ArrayNode arrayNode() {
+ return MAPPER.createArrayNode();
+ }
+
+ // non-instantiable
+ private TopoJson() { }
+
+ /**
+ * Returns a formatted message ready to send to the topology view
+ * to render highlights.
+ *
+ * @param highlights highlights model to transform
+ * @return fully formatted "show highlights" message
+ */
+ public static ObjectNode highlightsMessage(Highlights highlights) {
+ return envelope(SHOW_HIGHLIGHTS, json(highlights));
+ }
+
+ /**
+ * Transforms the given highlights model into a JSON message payload.
+ *
+ * @param highlights the model to transform
+ * @return JSON payload
+ */
+ public static ObjectNode json(Highlights highlights) {
+ ObjectNode payload = objectNode();
+
+ ArrayNode devices = arrayNode();
+ ArrayNode hosts = arrayNode();
+ ArrayNode links = arrayNode();
+
+ payload.set(DEVICES, devices);
+ payload.set(HOSTS, hosts);
+ payload.set(LINKS, links);
+
+ highlights.devices().forEach(dh -> devices.add(json(dh)));
+ highlights.hosts().forEach(hh -> hosts.add(json(hh)));
+ highlights.links().forEach(lh -> links.add(json(lh)));
+
+ Highlights.Amount toSubdue = highlights.subdueLevel();
+ if (!toSubdue.equals(Highlights.Amount.ZERO)) {
+ payload.put(SUBDUE, toSubdue.toString());
+ }
+ return payload;
+ }
+
+ private static ObjectNode json(DeviceHighlight dh) {
+ ObjectNode n = objectNode()
+ .put(ID, dh.elementId());
+ if (dh.subdued()) {
+ n.put(SUBDUE, true);
+ }
+ return n;
+ }
+
+ private static ObjectNode json(HostHighlight hh) {
+ ObjectNode n = objectNode()
+ .put(ID, hh.elementId());
+ if (hh.subdued()) {
+ n.put(SUBDUE, true);
+ }
+ return n;
+ }
+
+ private static ObjectNode json(LinkHighlight lh) {
+ ObjectNode n = objectNode()
+ .put(ID, lh.elementId())
+ .put(LABEL, lh.label())
+ .put(CSS, lh.cssClasses());
+ if (lh.subdued()) {
+ n.put(SUBDUE, true);
+ }
+ return n;
+ }
+
+ /**
+ * Translates the given property panel into JSON, for returning
+ * to the client.
+ *
+ * @param pp the property panel model
+ * @return JSON payload
+ */
+ public static ObjectNode json(PropertyPanel pp) {
+ ObjectNode result = objectNode()
+ .put(TITLE, pp.title())
+ .put(TYPE, pp.typeId())
+ .put(ID, pp.id());
+
+ ObjectNode pnode = objectNode();
+ ArrayNode porder = arrayNode();
+ for (PropertyPanel.Prop p : pp.properties()) {
+ porder.add(p.key());
+ pnode.put(p.key(), p.value());
+ }
+ result.set(PROP_ORDER, porder);
+ result.set(PROPS, pnode);
+
+ ArrayNode buttons = arrayNode();
+ for (ButtonId b : pp.buttons()) {
+ buttons.add(b.id());
+ }
+ result.set(BUTTONS, buttons);
+ return result;
+ }
+
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
new file mode 100644
index 00000000..f92d5798
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/TopoUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2015 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.topo;
+
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+
+import java.text.DecimalFormat;
+
+import static org.onosproject.net.LinkKey.linkKey;
+
+/**
+ * Utility methods for helping out with formatting data for the Topology View
+ * in the web client.
+ */
+public final class TopoUtils {
+
+ // explicit decision made to not 'javadoc' these self explanatory constants
+ public static final double KILO = 1024;
+ public static final double MEGA = 1024 * KILO;
+ public static final double GIGA = 1024 * MEGA;
+
+ public static final String GBITS_UNIT = "Gb";
+ public static final String MBITS_UNIT = "Mb";
+ public static final String KBITS_UNIT = "Kb";
+ public static final String BITS_UNIT = "b";
+ public static final String GBYTES_UNIT = "GB";
+ public static final String MBYTES_UNIT = "MB";
+ public static final String KBYTES_UNIT = "KB";
+ public static final String BYTES_UNIT = "B";
+
+
+ private static final DecimalFormat DF2 = new DecimalFormat("#,###.##");
+
+ private static final String COMPACT = "%s/%s-%s/%s";
+ private static final String EMPTY = "";
+ private static final String SPACE = " ";
+ private static final String PER_SEC = "ps";
+ private static final String FLOW = "flow";
+ private static final String FLOWS = "flows";
+
+ // non-instantiable
+ private TopoUtils() { }
+
+ /**
+ * Returns a compact identity for the given link, in the form
+ * used to identify links in the Topology View on the client.
+ *
+ * @param link link
+ * @return compact link identity
+ */
+ public static String compactLinkString(Link link) {
+ return String.format(COMPACT, link.src().elementId(), link.src().port(),
+ link.dst().elementId(), link.dst().port());
+ }
+
+ /**
+ * Produces a canonical link key, that is, one that will match both a link
+ * and its inverse.
+ *
+ * @param link the link
+ * @return canonical key
+ */
+ public static LinkKey canonicalLinkKey(Link link) {
+ String sn = link.src().elementId().toString();
+ String dn = link.dst().elementId().toString();
+ return sn.compareTo(dn) < 0 ?
+ linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
+ }
+
+ /**
+ * Returns human readable count of bytes, to be displayed as a label.
+ *
+ * @param bytes number of bytes
+ * @return formatted byte count
+ */
+ public static String formatBytes(long bytes) {
+ String unit;
+ double value;
+ if (bytes > GIGA) {
+ value = bytes / GIGA;
+ unit = GBYTES_UNIT;
+ } else if (bytes > MEGA) {
+ value = bytes / MEGA;
+ unit = MBYTES_UNIT;
+ } else if (bytes > KILO) {
+ value = bytes / KILO;
+ unit = KBYTES_UNIT;
+ } else {
+ value = bytes;
+ unit = BYTES_UNIT;
+ }
+ return DF2.format(value) + SPACE + unit;
+ }
+
+ /**
+ * Returns human readable bit rate, to be displayed as a label.
+ *
+ * @param bytes bytes per second
+ * @return formatted bits per second
+ */
+ public static String formatBitRate(long bytes) {
+ String unit;
+ double value;
+
+ //Convert to bits
+ long bits = bytes * 8;
+ if (bits > GIGA) {
+ value = bits / GIGA;
+ unit = GBITS_UNIT;
+
+ // NOTE: temporary hack to clip rate at 10.0 Gbps
+ // Added for the CORD Fabric demo at ONS 2015
+ // TODO: provide a more elegant solution to this issue
+ if (value > 10.0) {
+ value = 10.0;
+ }
+
+ } else if (bits > MEGA) {
+ value = bits / MEGA;
+ unit = MBITS_UNIT;
+ } else if (bits > KILO) {
+ value = bits / KILO;
+ unit = KBITS_UNIT;
+ } else {
+ value = bits;
+ unit = BITS_UNIT;
+ }
+ return DF2.format(value) + SPACE + unit + PER_SEC;
+ }
+
+ /**
+ * Returns human readable flow count, to be displayed as a label.
+ *
+ * @param flows number of flows
+ * @return formatted flow count
+ */
+ public static String formatFlows(long flows) {
+ if (flows < 1) {
+ return EMPTY;
+ }
+ return String.valueOf(flows) + SPACE + (flows > 1 ? FLOWS : FLOW);
+ }
+}
diff --git a/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java
new file mode 100644
index 00000000..85ac7fea
--- /dev/null
+++ b/framework/src/onos/core/api/src/main/java/org/onosproject/ui/topo/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/**
+ * Mechanism for dynamically extending topology view with information and
+ * behaviour overlays.
+ */
+package org.onosproject.ui.topo; \ No newline at end of file