Adding support for user interface extensions.

Change-Id: I1e41d16efc11be31ad4c2fb0c09e86e3dfd26706
diff --git a/web/gui/pom.xml b/web/gui/pom.xml
index d3b289f..ff8799f 100644
--- a/web/gui/pom.xml
+++ b/web/gui/pom.xml
@@ -53,4 +53,36 @@
             <classifier>tests</classifier>
         </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>2.7</version>
+                <executions>
+                    <execution>
+                        <id>copy-resources</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${basedir}/target/classes/WEB-INF/classes/app/view</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/webapp/app/view</directory>
+                                    <filtering>true</filtering>
+                                    <excludes>
+                                        <exclude>_sdh/**</exclude>
+                                        <exclude>test/**</exclude>
+                                    </excludes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/AbstractInjectionResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/AbstractInjectionResource.java
new file mode 100644
index 0000000..2ed4ecd
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/AbstractInjectionResource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.impl;
+
+import org.onlab.rest.BaseResource;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Resource for serving semi-static resources.
+ */
+public class AbstractInjectionResource extends BaseResource {
+
+    /**
+     * Returns the index into the supplied string where the end of the
+     * specified pattern is located.
+     *
+     * @param string      string to split
+     * @param start       index where to start looking for pattern
+     * @param stopPattern optional pattern where to stop
+     */
+    protected int split(String string, int start, String stopPattern) {
+        int i = stopPattern != null ? string.indexOf(stopPattern, start) : string.length();
+        checkArgument(i > 0, "Unable to locate stop pattern %s", stopPattern);
+        return i + (stopPattern != null ? stopPattern.length() : 0);
+    }
+
+    /**
+     * Produces an input stream from the bytes of the specified sub-string.
+     *
+     * @param string source string
+     * @param start  index where to start stream
+     * @param end    index where to end stream
+     */
+    protected InputStream stream(String string, int start, int end) {
+        return new ByteArrayInputStream(string.substring(start, end).getBytes());
+    }
+
+    /**
+     * Auxiliary enumeration to sequence input streams.
+     */
+    protected class StreamEnumeration implements Enumeration<InputStream> {
+        private final Iterator<InputStream> iterator;
+
+        StreamEnumeration(List<InputStream> streams) {
+            this.iterator = streams.iterator();
+        }
+
+        @Override
+        public boolean hasMoreElements() {
+            return iterator.hasNext();
+        }
+
+        @Override
+        public InputStream nextElement() {
+            return iterator.next();
+        }
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/gui/AbstractTableRow.java b/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTableRow.java
similarity index 97%
rename from web/gui/src/main/java/org/onosproject/gui/AbstractTableRow.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/AbstractTableRow.java
index 7be7172..66db400 100644
--- a/web/gui/src/main/java/org/onosproject/gui/AbstractTableRow.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTableRow.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
diff --git a/web/gui/src/main/java/org/onosproject/gui/DeviceGuiResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceGuiResource.java
similarity index 98%
rename from web/gui/src/main/java/org/onosproject/gui/DeviceGuiResource.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/DeviceGuiResource.java
index 403b8c6..d409ebf 100644
--- a/web/gui/src/main/java/org/onosproject/gui/DeviceGuiResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceGuiResource.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
diff --git a/web/gui/src/main/java/org/onosproject/gui/DeviceTableRow.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceTableRow.java
similarity index 98%
rename from web/gui/src/main/java/org/onosproject/gui/DeviceTableRow.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/DeviceTableRow.java
index 423657a..7e67e67 100644
--- a/web/gui/src/main/java/org/onosproject/gui/DeviceTableRow.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceTableRow.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import org.onosproject.net.Device;
 import org.onosproject.net.device.DeviceService;
diff --git a/web/gui/src/main/java/org/onosproject/gui/GuiWebSocketServlet.java b/web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java
similarity index 98%
rename from web/gui/src/main/java/org/onosproject/gui/GuiWebSocketServlet.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java
index 8d0eabe..5a660e0 100644
--- a/web/gui/src/main/java/org/onosproject/gui/GuiWebSocketServlet.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import org.eclipse.jetty.websocket.WebSocket;
 import org.eclipse.jetty.websocket.WebSocketServlet;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/MainExtResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/MainExtResource.java
new file mode 100644
index 0000000..a1eae41
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/MainExtResource.java
@@ -0,0 +1,109 @@
+/*
+ * 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.impl;
+
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiView;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.io.ByteStreams.toByteArray;
+
+/**
+ * Resource for serving the dynamically composed onos.js.
+ */
+@Path("/")
+public class MainExtResource extends AbstractInjectionResource {
+
+    private static final String MAIN_JS = "/onos-template.js";
+    private static final String NAV_HTML = "/nav-template.html";
+
+    private static final String INJECT_VIEW_IDS = "// {INJECTED-VIEW-IDS}";
+    private static final String INJECT_VIEW_ITEMS = "<!-- {INJECTED-VIEW-NAV} -->";
+
+    private static final String NAV_FORMAT =
+            "    <li> <a ng-click=\"navCtrl.hideNav()\" href=\"#/%s\">%s</a></li>";
+
+    @Path("/onos.js")
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    public Response getMainModule() throws IOException {
+        UiExtensionService service = get(UiExtensionService.class);
+        InputStream jsTemplate = getClass().getClassLoader().getResourceAsStream(MAIN_JS);
+        String js = new String(toByteArray(jsTemplate));
+
+        int p1 = split(js, 0, INJECT_VIEW_IDS);
+        int p2 = split(js, p1, null);
+
+        StreamEnumeration streams =
+                new StreamEnumeration(of(stream(js, 0, p1),
+                                         includeViewIds(service),
+                                         stream(js, p1, p2)));
+
+        return Response.ok(new SequenceInputStream(streams)).build();
+    }
+
+    // Produces an input stream including view id injections from all extensions.
+    private InputStream includeViewIds(UiExtensionService service) {
+        StringBuilder sb = new StringBuilder("\n");
+        for (UiExtension extension : service.getExtensions()) {
+            for (UiView view : extension.views()) {
+                sb.append("        '").append(view.id()).append("',");
+            }
+        }
+        return new ByteArrayInputStream(sb.toString().getBytes());
+    }
+
+    @Path("/nav/nav.html")
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    public Response getNavigation() throws IOException {
+        UiExtensionService service = get(UiExtensionService.class);
+        InputStream navTemplate = getClass().getClassLoader().getResourceAsStream(NAV_HTML);
+        String js = new String(toByteArray(navTemplate));
+
+        int p1 = split(js, 0, INJECT_VIEW_ITEMS);
+        int p2 = split(js, p1, null);
+
+        StreamEnumeration streams =
+                new StreamEnumeration(of(stream(js, 0, p1),
+                                         includeNavItems(service),
+                                         stream(js, p1, p2)));
+
+        return Response.ok(new SequenceInputStream(streams)).build();
+    }
+
+    // Produces an input stream including nav item injections from all extensions.
+    private InputStream includeNavItems(UiExtensionService service) {
+        StringBuilder sb = new StringBuilder("\n");
+        for (UiExtension extension : service.getExtensions()) {
+            for (UiView view : extension.views()) {
+                sb.append(String.format(NAV_FORMAT, view.id(), view.label()));
+            }
+        }
+        return new ByteArrayInputStream(sb.toString().getBytes());
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java
new file mode 100644
index 0000000..da4079f
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/MainIndexResource.java
@@ -0,0 +1,85 @@
+/*
+ * 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.impl;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.io.ByteStreams.toByteArray;
+
+/**
+ * Resource for serving the dynamically composed index.html.
+ */
+@Path("/")
+public class MainIndexResource extends AbstractInjectionResource {
+
+    private static final String INDEX = "/index-template.html";
+
+    private static final String INJECT_CSS = "<!-- {INJECTED-STYLESHEETS} -->";
+    private static final String INJECT_JS = "<!-- {INJECTED-JAVASCRIPT} -->";
+
+    @Path("/")
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    public Response getMainIndex() throws IOException {
+        UiExtensionService service = get(UiExtensionService.class);
+        InputStream indexTemplate = getClass().getClassLoader().getResourceAsStream(INDEX);
+        String index = new String(toByteArray(indexTemplate));
+
+        int p1 = split(index, 0, INJECT_JS);
+        int p2 = split(index, p1, INJECT_CSS);
+        int p3 = split(index, p2, null);
+
+        StreamEnumeration streams =
+                new StreamEnumeration(of(stream(index, 0, p1),
+                                         includeJs(service),
+                                         stream(index, p1, p2),
+                                         includeCss(service),
+                                         stream(index, p2, p3)));
+
+        return Response.ok(new SequenceInputStream(streams)).build();
+    }
+
+    // Produces an input stream including CSS injections from all extensions.
+    private InputStream includeCss(UiExtensionService service) {
+        ImmutableList.Builder<InputStream> builder = ImmutableList.builder();
+        for (UiExtension extension : service.getExtensions()) {
+            builder.add(extension.css());
+        }
+        return new SequenceInputStream(new StreamEnumeration(builder.build()));
+    }
+
+    // Produces an input stream including JS injections from all extensions.
+    private InputStream includeJs(UiExtensionService service) {
+        ImmutableList.Builder<InputStream> builder = ImmutableList.builder();
+        for (UiExtension extension : service.getExtensions()) {
+            builder.add(extension.js());
+        }
+        return new SequenceInputStream(new StreamEnumeration(builder.build()));
+    }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java
new file mode 100644
index 0000000..26b5d8c
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/MainViewResource.java
@@ -0,0 +1,59 @@
+/*
+ * 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.impl;
+
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static javax.ws.rs.core.MediaType.TEXT_HTML;
+
+/**
+ * Resource for serving the dynamically composed onos.js.
+ */
+@Path("/")
+public class MainViewResource extends AbstractInjectionResource {
+
+    private static final String CONTENT_TYPE = "Content-Type";
+    private static final String STYLESHEET = "text/css";
+    private static final String SCRIPT = "text/javascript";
+
+    @Path("{view}/{resource}")
+    @GET
+    public Response getViewResource(@PathParam("view") String viewId,
+                                    @PathParam("resource") String resource) throws IOException {
+        UiExtensionService service = get(UiExtensionService.class);
+        UiExtension extension = service.getViewExtension(viewId);
+        return extension != null ?
+                Response.ok(extension.resource(viewId, resource))
+                        .header(CONTENT_TYPE, contentType(resource)).build() :
+                Response.status(Response.Status.NOT_FOUND).build();
+    }
+
+    static String contentType(String resource) {
+        return resource.endsWith(".html") ? TEXT_HTML :
+                resource.endsWith(".css") ? STYLESHEET :
+                        resource.endsWith(".js") ? SCRIPT :
+                                APPLICATION_OCTET_STREAM;
+    }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/gui/RowComparator.java b/web/gui/src/main/java/org/onosproject/ui/impl/RowComparator.java
similarity index 98%
rename from web/gui/src/main/java/org/onosproject/gui/RowComparator.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/RowComparator.java
index 7c79fc2..b588f48 100644
--- a/web/gui/src/main/java/org/onosproject/gui/RowComparator.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/RowComparator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import java.util.Comparator;
 
diff --git a/web/gui/src/main/java/org/onosproject/gui/TableRow.java b/web/gui/src/main/java/org/onosproject/ui/impl/TableRow.java
similarity index 96%
rename from web/gui/src/main/java/org/onosproject/gui/TableRow.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/TableRow.java
index 67c8449..81a14ba 100644
--- a/web/gui/src/main/java/org/onosproject/gui/TableRow.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TableRow.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
diff --git a/web/gui/src/main/java/org/onosproject/gui/TopologyResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java
similarity index 98%
rename from web/gui/src/main/java/org/onosproject/gui/TopologyResource.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java
index 980be31..95f9a07 100644
--- a/web/gui/src/main/java/org/onosproject/gui/TopologyResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyResource.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
diff --git a/web/gui/src/main/java/org/onosproject/gui/TopologyViewIntentFilter.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewIntentFilter.java
similarity index 99%
rename from web/gui/src/main/java/org/onosproject/gui/TopologyViewIntentFilter.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewIntentFilter.java
index 0549604..f8b1d6c 100644
--- a/web/gui/src/main/java/org/onosproject/gui/TopologyViewIntentFilter.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewIntentFilter.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
diff --git a/web/gui/src/main/java/org/onosproject/gui/TopologyViewMessages.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java
similarity index 99%
rename from web/gui/src/main/java/org/onosproject/gui/TopologyViewMessages.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java
index 746c37e..dd891cd 100644
--- a/web/gui/src/main/java/org/onosproject/gui/TopologyViewMessages.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/web/gui/src/main/java/org/onosproject/gui/TopologyViewWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java
similarity index 99%
rename from web/gui/src/main/java/org/onosproject/gui/TopologyViewWebSocket.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java
index 44ad026..e259cb5 100644
--- a/web/gui/src/main/java/org/onosproject/gui/TopologyViewWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
new file mode 100644
index 0000000..6ca1e21
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -0,0 +1,96 @@
+/*
+ * 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.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.ImmutableList.of;
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * Manages the user interface extensions.
+ */
+@Component(immediate = true)
+@Service
+public class UiExtensionManager implements UiExtensionService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    // List of all extensions
+    private final List<UiExtension> extensions = Lists.newArrayList();
+
+    // Map of views to extensions
+    private final Map<String, UiExtension> views = Maps.newHashMap();
+
+    // Core views & core extension
+    private final List<UiView> coreViews = of(new UiView("sample", "Sample"),
+                                              new UiView("topo", "Topology View"),
+                                              new UiView("device", "Devices"));
+
+    private final UiExtension core = new UiExtension(coreViews, getClass().getClassLoader());
+
+    @Activate
+    public void activate() {
+        register(core);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        unregister(core);
+        log.info("Stopped");
+    }
+
+    @Override
+    public synchronized void register(UiExtension extension) {
+        if (!extensions.contains(extension)) {
+            extensions.add(extension);
+            for (UiView view : extension.views()) {
+                views.put(view.id(), extension);
+            }
+        }
+    }
+
+    @Override
+    public synchronized void unregister(UiExtension extension) {
+        extensions.remove(extension);
+        extension.views().stream().map(UiView::id).collect(toSet()).forEach(views::remove);
+    }
+
+    @Override
+    public synchronized List<UiExtension> getExtensions() {
+        return ImmutableList.copyOf(extensions);
+    }
+
+    @Override
+    public synchronized UiExtension getViewExtension(String viewId) {
+        return views.get(viewId);
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/gui/package-info.java b/web/gui/src/main/java/org/onosproject/ui/impl/package-info.java
similarity index 95%
rename from web/gui/src/main/java/org/onosproject/gui/package-info.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/package-info.java
index 7a91308..cb9ae2f 100644
--- a/web/gui/src/main/java/org/onosproject/gui/package-info.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/package-info.java
@@ -17,4 +17,4 @@
 /**
  * Set of resources providing data for the ONOS GUI.
  */
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
diff --git a/web/gui/src/main/resources/WEB-INF/classes/css.html b/web/gui/src/main/resources/WEB-INF/classes/css.html
new file mode 100644
index 0000000..daf58b6
--- /dev/null
+++ b/web/gui/src/main/resources/WEB-INF/classes/css.html
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="app/view/sample/sample.css">
+<link rel="stylesheet" href="app/view/topo/topo.css">
+<link rel="stylesheet" href="app/view/device/device.css">
diff --git a/web/gui/src/main/resources/WEB-INF/classes/index-template.html b/web/gui/src/main/resources/WEB-INF/classes/index-template.html
new file mode 100644
index 0000000..6f85db4
--- /dev/null
+++ b/web/gui/src/main/resources/WEB-INF/classes/index-template.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+  ~ 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.
+  -->
+<html>
+<head>
+    <meta charset="utf-8">
+    <link rel="shortcut icon" href="data/img/onos-logo.png">
+    <title>ONOS Angular</title>
+
+    <!-- Third party library code included here -->
+    <!--TODO: use minified versions, once debugging is complete -->
+    <script src="tp/angular.js"></script>
+    <script src="tp/angular-route.js"></script>
+
+    <script src="tp/d3.js"></script>
+    <script src="tp/topojson.v1.min.js"></script>
+
+    <!-- ONOS UI Framework included here -->
+    <!-- TODO: use a single catenated-minified file here -->
+    <script src="dyn/onos.js"></script>
+    <script src="app/directives.js"></script>
+
+    <script src="app/fw/util/util.js"></script>
+    <script src="app/fw/util/fn.js"></script>
+    <script src="app/fw/util/theme.js"></script>
+    <script src="app/fw/util/keys.js"></script>
+
+    <script src="app/fw/mast/mast.js"></script>
+    <script src="app/fw/nav/nav.js"></script>
+
+    <script src="app/fw/svg/svg.js"></script>
+    <script src="app/fw/svg/glyph.js"></script>
+    <script src="app/fw/svg/icon.js"></script>
+    <script src="app/fw/svg/geodata.js"></script>
+    <script src="app/fw/svg/map.js"></script>
+    <script src="app/fw/svg/zoom.js"></script>
+    <script src="app/fw/svg/svgUtil.js"></script>
+
+    <script src="app/fw/remote/remote.js"></script>
+    <script src="app/fw/remote/urlfn.js"></script>
+    <script src="app/fw/remote/rest.js"></script>
+    <script src="app/fw/remote/websocket.js"></script>
+    <script src="app/fw/remote/wsevent.js"></script>
+
+    <script src="app/fw/widget/widget.js"></script>
+    <script src="app/fw/widget/table.js"></script>
+
+    <script src="app/fw/layer/layer.js"></script>
+    <script src="app/fw/layer/panel.js"></script>
+
+    <!-- Framework and library stylesheets included here -->
+    <!-- TODO: use a single catenated-minified file here -->
+    <link rel="stylesheet" href="app/onos.css">
+    <link rel="stylesheet" href="app/common.css">
+    <link rel="stylesheet" href="app/fw/mast/mast.css">
+    <link rel="stylesheet" href="app/fw/svg/glyph.css">
+    <link rel="stylesheet" href="app/fw/svg/icon.css">
+    <link rel="stylesheet" href="app/fw/layer/panel.css">
+    <link rel="stylesheet" href="app/fw/nav/nav.css">
+
+    <!-- This is where contributed javascript will get injected -->
+    <!-- {INJECTED-JAVASCRIPT} -->
+
+    <!-- This is where contributed stylesheets will get injected -->
+    <!-- {INJECTED-STYLESHEETS} -->
+</head>
+<body class="light" ng-app="onosApp">
+    <div id="frame" ng-controller="OnosCtrl as onosCtrl">
+        <div id="mast"
+             ng-controller="MastCtrl as mastCtrl"
+             ng-include="'app/fw/mast/mast.html'"></div>
+
+        <div id="view" ng-view></div>
+
+        <div id="nav"
+             ng-controller="NavCtrl as navCtrl"
+             ng-include="'dyn/nav/nav.html'"></div>
+
+        <div id="floatpanels"></div>
+        <div id="alerts"></div>
+        <div id="flash"></div>
+        <div id="quickhelp"></div>
+        <div id="deathmask"></div>
+    </div>
+</body>
+</html>
diff --git a/web/gui/src/main/resources/WEB-INF/classes/js.html b/web/gui/src/main/resources/WEB-INF/classes/js.html
new file mode 100644
index 0000000..e7b2db3
--- /dev/null
+++ b/web/gui/src/main/resources/WEB-INF/classes/js.html
@@ -0,0 +1,7 @@
+<script src="app/view/sample/sample.js"></script>
+<script src="app/view/topo/topo.js"></script>
+<script src="app/view/topo/topoEvent.js"></script>
+<script src="app/view/topo/topoForce.js"></script>
+<script src="app/view/topo/topoPanel.js"></script>
+<script src="app/view/topo/topoInst.js"></script>
+<script src="app/view/device/device.js"></script>
diff --git a/web/gui/src/main/resources/WEB-INF/classes/nav-template.html b/web/gui/src/main/resources/WEB-INF/classes/nav-template.html
new file mode 100644
index 0000000..25c6e36
--- /dev/null
+++ b/web/gui/src/main/resources/WEB-INF/classes/nav-template.html
@@ -0,0 +1,7 @@
+<!-- Navigation partial HTML -->
+<h2>Navigation</h2>
+<h3>(Note - this is temporary)</h3>
+
+<ul>
+    <!-- {INJECTED-VIEW-NAV} -->
+</ul>
diff --git a/web/gui/src/main/resources/WEB-INF/classes/onos-template.js b/web/gui/src/main/resources/WEB-INF/classes/onos-template.js
new file mode 100644
index 0000000..3272cef
--- /dev/null
+++ b/web/gui/src/main/resources/WEB-INF/classes/onos-template.js
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Main Application Module
+ */
+
+(function () {
+    'use strict';
+
+    // define core module dependencies here...
+    var coreDependencies = [
+        'ngRoute',
+        'onosMast',
+        'onosNav',
+        'onosUtil',
+        'onosSvg',
+        'onosRemote',
+        'onosLayer',
+        'onosWidget'
+    ];
+
+    // view IDs.. note the first view listed is loaded at startup
+    var viewIds = [
+        // TODO: inject view IDs server side
+        // {INJECTED-VIEW-IDS}
+        // (end of injected views)
+
+        // dummy entry
+        ''
+    ];
+
+    var viewDependencies = [];
+
+    viewIds.forEach(function (id) {
+        if (id) {
+            viewDependencies.push('ov' + capitalize(id));
+        }
+    });
+
+    var moduleDependencies = coreDependencies.concat(viewDependencies);
+
+    function capitalize(word) {
+        return word ? word[0].toUpperCase() + word.slice(1) : word;
+    }
+
+    angular.module('onosApp', moduleDependencies)
+
+        .controller('OnosCtrl', [
+            '$log', '$route', '$routeParams', '$location',
+            'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
+
+        function ($log, $route, $routeParams, $location, ks, ts, gs, ps) {
+            var self = this;
+
+            self.$route = $route;
+            self.$routeParams = $routeParams;
+            self.$location = $location;
+            self.version = '1.1.0';
+
+            // initialize services...
+            ts.init();
+            ks.installOn(d3.select('body'));
+            gs.init();
+            ps.init();
+
+            $log.log('OnosCtrl has been created');
+
+            $log.debug('route: ', self.$route);
+            $log.debug('routeParams: ', self.$routeParams);
+            $log.debug('location: ', self.$location);
+        }])
+
+        .config(['$routeProvider', function ($routeProvider) {
+            // If view ID not provided, route to the first view in the list.
+            $routeProvider
+                .otherwise({
+                    redirectTo: '/' + viewIds[0]
+                });
+
+            function viewCtrlName(vid) {
+                return 'Ov' + capitalize(vid) + 'Ctrl';
+            }
+            function viewTemplateUrl(vid) {
+                return 'view/' + vid + '/' + vid + '.html';
+            }
+
+            // Add routes for each defined view.
+            viewIds.forEach(function (vid) {
+                if (vid) {
+                    $routeProvider.when('/' + vid, {
+                        controller: viewCtrlName(vid),
+                        controllerAs: 'ctrl',
+                        templateUrl: viewTemplateUrl(vid)
+                    });
+                }
+            });
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/WEB-INF/web.xml b/web/gui/src/main/webapp/WEB-INF/web.xml
index afe4e3c..d3e83bf 100644
--- a/web/gui/src/main/webapp/WEB-INF/web.xml
+++ b/web/gui/src/main/webapp/WEB-INF/web.xml
@@ -14,8 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xmlns="http://java.sun.com/xml/ns/javaee"
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          id="ONOS" version="2.5">
     <display-name>ONOS GUI</display-name>
@@ -24,12 +24,78 @@
         <welcome-file>index.html</welcome-file>
     </welcome-file-list>
 
+    <!--
+    <servlet>
+        <servlet-name>Index Page</servlet-name>
+        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>org.onosproject.gui.impl.MainIndexResource</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Index Page</servlet-name>
+        <url-pattern>/main.html</url-pattern>
+    </servlet-mapping>
+
+    <servlet>
+        <servlet-name>Main Module</servlet-name>
+        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>org.onosproject.gui.impl.MainExtResource</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Main Module</servlet-name>
+        <url-pattern>/dyn/*</url-pattern>
+    </servlet-mapping>
+
+    <servlet>
+        <servlet-name>View Module</servlet-name>
+        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>org.onosproject.gui.impl.MainViewResource</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>View Module</servlet-name>
+        <url-pattern>/app/view/*</url-pattern>
+    </servlet-mapping>
+    -->
+
     <servlet>
         <servlet-name>JAX-RS Service</servlet-name>
         <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
         <init-param>
-            <param-name>com.sun.jersey.config.property.packages</param-name>
-            <param-value>org.onosproject.gui</param-value>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>
+                org.onosproject.ui.impl.TopologyResource,
+                org.onosproject.ui.impl.DeviceGuiResource
+            </param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
@@ -41,7 +107,7 @@
 
     <servlet>
         <servlet-name>Web Socket Service</servlet-name>
-        <servlet-class>org.onosproject.gui.GuiWebSocketServlet</servlet-class>
+        <servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
         <load-on-startup>2</load-on-startup>
     </servlet>
 
@@ -50,5 +116,4 @@
         <url-pattern>/ws/*</url-pattern>
     </servlet-mapping>
 
-
 </web-app>
diff --git a/web/gui/src/test/java/org/onosproject/gui/README.txt b/web/gui/src/test/java/org/onosproject/ui/README.txt
similarity index 100%
rename from web/gui/src/test/java/org/onosproject/gui/README.txt
rename to web/gui/src/test/java/org/onosproject/ui/README.txt
diff --git a/web/gui/src/test/java/org/onosproject/gui/TopologyViewWebSocketTest.java b/web/gui/src/test/java/org/onosproject/ui/impl/TopologyViewWebSocketTest.java
similarity index 99%
rename from web/gui/src/test/java/org/onosproject/gui/TopologyViewWebSocketTest.java
rename to web/gui/src/test/java/org/onosproject/ui/impl/TopologyViewWebSocketTest.java
index cdf85dc..8937971 100644
--- a/web/gui/src/test/java/org/onosproject/gui/TopologyViewWebSocketTest.java
+++ b/web/gui/src/test/java/org/onosproject/ui/impl/TopologyViewWebSocketTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.gui;
+package org.onosproject.ui.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;