Sample app and node server 53/7653/4
authorAndrew Kim <h.andrew.kim@gmail.com>
Thu, 1 May 2014 23:11:55 +0000 (18:11 -0500)
committerAndrew Kim <h.andrew.kim@gmail.com>
Tue, 3 Jun 2014 18:57:27 +0000 (13:57 -0500)
Simple app is included for now as a standalone
Node development environment is included under node with symlinks to the simple app

Change-Id: Ia3879862e39fb264bbc35d73ffef60a284e4a4c5
Signed-off-by: Andrew Kim <h.andrew.kim@gmail.com>
34 files changed:
README.md
node/css [new symlink]
node/img [new symlink]
node/index.html [new symlink]
node/js [new symlink]
node/server.js [new file with mode: 0644]
node/simple/web/css [new symlink]
node/simple/web/js [new symlink]
node/static.js [new file with mode: 0644]
simple/.gitignore [new file with mode: 0644]
simple/pom.xml [new file with mode: 0644]
simple/src/main/java/org/opendaylight/toolkit/simple/ISimple.java [new file with mode: 0644]
simple/src/main/java/org/opendaylight/toolkit/simple/SimpleData.java [new file with mode: 0644]
simple/src/main/java/org/opendaylight/toolkit/simple/internal/Activator.java [new file with mode: 0644]
simple/src/main/java/org/opendaylight/toolkit/simple/internal/Simple.java [new file with mode: 0644]
simple/src/main/java/org/opendaylight/toolkit/simple/northbound/AppNorthbound.java [new file with mode: 0644]
simple/src/main/java/org/opendaylight/toolkit/simple/web/AppWeb.java [new file with mode: 0644]
simple/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
simple/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
simple/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
simple/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
simple/src/main/resources/WEB-INF/AppWeb-servlet.xml [new file with mode: 0644]
simple/src/main/resources/WEB-INF/jsp/main.jsp [new file with mode: 0644]
simple/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
simple/src/main/resources/css/simple.css [new file with mode: 0644]
simple/src/main/resources/js/app.js [new file with mode: 0644]
simple/src/main/resources/js/collections/SimpleCollection.js [new file with mode: 0644]
simple/src/main/resources/js/main.js [new file with mode: 0644]
simple/src/main/resources/js/models/SimpleModel.js [new file with mode: 0644]
simple/src/main/resources/js/templates/simple.html [new file with mode: 0644]
simple/src/main/resources/js/views/View.js [new file with mode: 0644]
web/src/main/java/org/opendaylight/toolkit/web/CorsFilter.java [new file with mode: 0644]
web/src/main/resources/WEB-INF/web.xml
web/src/main/resources/js/bower.json

index aaac234de1a33f0a099c0043119086e58b39ac96..144fd3512488e3f5526f93be5f4f428c4f3e8c92 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,5 +1,27 @@
 # OpenDaylight Toolkit
 
+App Web Development with NodeJS
+-------------------------------
+
+1) web/src/main/java/org/opendaylight/toolkit/web/CorsFilter.java and replace *your-ip* (e.g. http://localhost:8000)
+
+2) mvn clean install project's root directory
+
+3) Go to simple/ and issue mvn clean install
+
+4) Run the controller main/target/main-osgipackage/opendaylight/run.sh
+
+5) In a new window, go to node/ and run the node server *node server.js*
+
+>Note: you may need to install missing modules *npm install module_name*
+
+6) Go to http://your-ip:8000 in your browser and start developing from simple/
+
+>Disclaimer: you may point node to any app you wish to develop on top of, not just simple, but that will have to be done manually for now
+
+>Note: ensure bower components are installed for web/, refer to section below
+
+
 Quick HowTo
 -----------
 
@@ -32,6 +54,7 @@ Quick HowTo
 
 7) *[optional]* If you installed the bower components, you can access the toolkit web UI at <code>http://localhost:8080</code>
 
+
 Troubleshooting
 ---------------
 
diff --git a/node/css b/node/css
new file mode 120000 (symlink)
index 0000000..5250bf7
--- /dev/null
+++ b/node/css
@@ -0,0 +1 @@
+../web/src/main/resources/css
\ No newline at end of file
diff --git a/node/img b/node/img
new file mode 120000 (symlink)
index 0000000..be66514
--- /dev/null
+++ b/node/img
@@ -0,0 +1 @@
+../web/src/main/resources/img
\ No newline at end of file
diff --git a/node/index.html b/node/index.html
new file mode 120000 (symlink)
index 0000000..8778253
--- /dev/null
@@ -0,0 +1 @@
+../simple/src/main/resources/WEB-INF/jsp/main.jsp
\ No newline at end of file
diff --git a/node/js b/node/js
new file mode 120000 (symlink)
index 0000000..2a3a6bb
--- /dev/null
+++ b/node/js
@@ -0,0 +1 @@
+../web/src/main/resources/js
\ No newline at end of file
diff --git a/node/server.js b/node/server.js
new file mode 100644 (file)
index 0000000..6da3f6e
--- /dev/null
@@ -0,0 +1,7 @@
+var http = require('http'),
+static = require('./static');
+
+/* Server start */
+var server = http.createServer(function(req, res) {
+  static(req, res); // if no matches, then serve a static file
+}).listen(8000);
diff --git a/node/simple/web/css b/node/simple/web/css
new file mode 120000 (symlink)
index 0000000..3082c22
--- /dev/null
@@ -0,0 +1 @@
+../../../simple/src/main/resources/css
\ No newline at end of file
diff --git a/node/simple/web/js b/node/simple/web/js
new file mode 120000 (symlink)
index 0000000..e21174c
--- /dev/null
@@ -0,0 +1 @@
+../../../simple/src/main/resources/js
\ No newline at end of file
diff --git a/node/static.js b/node/static.js
new file mode 100644 (file)
index 0000000..9411d1d
--- /dev/null
@@ -0,0 +1,37 @@
+var url = require('url'),
+path = require('path'),
+fs = require('fs'),
+mime = require('mime');
+
+var Static = function(request, response) {
+  var uri = url.parse(request.url).pathname, filename = path.join(process.cwd(), uri);
+  fs.exists(filename, function(exists) {
+    if(!exists) {
+      response.writeHead(404, {"Content-Type": "text/plain"});
+      response.write("404 Not Found\n");
+      response.end();
+      return;
+    }
+
+    if (fs.statSync(filename).isDirectory()) filename += '/index.html';
+
+    fs.readFile(filename, "binary", function(err, file) {
+      if(err) {
+        response.writeHead(500, {"Content-Type": "text/plain"});
+        response.write(err + "\n");
+        response.end();
+        return;
+      }
+
+      console.log(request.url);
+      response.writeHead(200, {
+        "Content-Type": mime.lookup(filename),
+        "Access-Control-Allow-Origin": "*",
+      });
+      response.write(file, "binary");
+      response.end();
+    });
+  });
+}
+
+module.exports = Static;
diff --git a/simple/.gitignore b/simple/.gitignore
new file mode 100644 (file)
index 0000000..a48e45b
--- /dev/null
@@ -0,0 +1 @@
+/target-ide
diff --git a/simple/pom.xml b/simple/pom.xml
new file mode 100644 (file)
index 0000000..e4a45df
--- /dev/null
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.toolkit</groupId>
+    <artifactId>common</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <relativePath>../common</relativePath>
+  </parent>
+
+  <artifactId>simple</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>${bundle.plugin.version}</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Import-Package>
+              org.opendaylight.toolkit.web,
+              org.opendaylight.controller.sal.authorization,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.utils,
+              javax.annotation,
+              javax.naming,
+              javax.servlet,
+              javax.servlet.annotation,
+              javax.servlet.http,
+              javax.servlet.jsp,
+              javax.servlet.jsp.el,
+              javax.servlet.jsp.jstl.core,
+              javax.servlet.jsp.jstl.fmt,
+              javax.servlet.jsp.jstl.tlv,
+              javax.servlet.jsp.tagext,
+              javax.servlet.resources,
+              javax.xml.parsers,
+              javax.xml.transform,
+              org.apache.commons.logging,
+              org.apache.taglibs.standard.functions,
+              org.apache.taglibs.standard.resources,
+              org.apache.taglibs.standard.tag.common.core,
+              org.apache.taglibs.standard.tag.common.fmt,
+              org.apache.taglibs.standard.tag.rt.core,
+              org.apache.taglibs.standard.tag.rt.fmt,
+              org.apache.taglibs.standard.tei,
+              org.apache.taglibs.standard.tlv,
+              org.osgi.framework,
+              org.slf4j,
+              org.springframework.beans,
+              org.springframework.beans.factory.xml,
+              org.springframework.context.config,
+              org.springframework.stereotype,
+              org.springframework.ui,
+              org.springframework.web,
+              org.springframework.web.bind.annotation,
+              org.springframework.web.servlet,
+              org.springframework.web.servlet.config,
+              org.springframework.web.servlet.view,
+              org.springframework.web.filter,
+              org.springframework.web.context,
+
+              org.apache.felix.dm,
+
+              org.opendaylight.controller.northbound.commons,
+              org.opendaylight.controller.northbound.commons.exception,
+              org.opendaylight.controller.northbound.commons.utils,
+              com.sun.jersey.spi.container.servlet,
+              com.fasterxml.jackson.annotation,
+              javax.ws.rs,
+              javax.ws.rs.core,
+              javax.xml.bind,
+              javax.xml.bind.annotation,
+              org.apache.catalina.filters,
+              com.fasterxml.jackson.jaxrs.base,
+              com.fasterxml.jackson.jaxrs.json,
+              !org.codehaus.enunciate.jaxrs
+            </Import-Package>
+            <Export-Package></Export-Package>
+            <Web-ContextPath>/simple</Web-ContextPath>
+            <Jaxrs-Resources>,${classes;ANNOTATION;javax.ws.rs.Path}</Jaxrs-Resources>
+            <Bundle-Activator>
+              org.opendaylight.toolkit.simple.internal.Activator
+               </Bundle-Activator>
+          </instructions>
+          <manifestLocation>${project.basedir}/src/main/resources/META-INF</manifestLocation>
+          <buildDirectory>../main/target/main-osgipackage/opendaylight/plugins/</buildDirectory> <!-- TODO use pom var -->
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.toolkit</groupId>
+      <artifactId>web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.enunciate</groupId>
+      <artifactId>enunciate-core-annotations</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>commons.northbound</artifactId>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/simple/src/main/java/org/opendaylight/toolkit/simple/ISimple.java b/simple/src/main/java/org/opendaylight/toolkit/simple/ISimple.java
new file mode 100644 (file)
index 0000000..be5b4e0
--- /dev/null
@@ -0,0 +1,15 @@
+
+package org.opendaylight.toolkit.simple;
+
+import java.util.Map;
+import java.util.UUID;
+
+import org.opendaylight.controller.sal.utils.Status;
+
+public interface ISimple {
+    public UUID createData(SimpleData datum);
+    public SimpleData readData(UUID uuid);
+    public Map<UUID, SimpleData> readData();
+    public Status updateData(UUID uuid, SimpleData data);
+    public Status deleteData(UUID uuid);
+}
diff --git a/simple/src/main/java/org/opendaylight/toolkit/simple/SimpleData.java b/simple/src/main/java/org/opendaylight/toolkit/simple/SimpleData.java
new file mode 100644 (file)
index 0000000..a69aecb
--- /dev/null
@@ -0,0 +1,36 @@
+
+package org.opendaylight.toolkit.simple;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.NONE)
+public class SimpleData {
+    @XmlElement
+    private String uuid;
+    @XmlElement
+    private String foo;
+    @XmlElement
+    private String bar;
+
+    public String getUuid() {
+        return uuid;
+    }
+    public String getFoo() {
+        return foo;
+    }
+    public String getBar() {
+        return bar;
+    }
+    public SimpleData() {
+        super();
+    }
+    public SimpleData(String uuid, String foo, String bar) {
+        super();
+        this.uuid = uuid;
+        this.foo = foo;
+        this.bar = bar;
+    }
+}
diff --git a/simple/src/main/java/org/opendaylight/toolkit/simple/internal/Activator.java b/simple/src/main/java/org/opendaylight/toolkit/simple/internal/Activator.java
new file mode 100644 (file)
index 0000000..b206073
--- /dev/null
@@ -0,0 +1,74 @@
+
+package org.opendaylight.toolkit.simple.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.toolkit.simple.ISimple;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger log = LoggerFactory.getLogger(Activator.class);
+
+    /**
+     * Function called when the activator starts just after some initializations
+     * are done by the ComponentActivatorAbstractBase.
+     *
+     */
+    @Override
+    public void init() {
+    }
+
+    /**
+     * Function called when the activator stops just before the cleanup done by
+     * ComponentActivatorAbstractBase
+     *
+     */
+    @Override
+    public void destroy() {
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the list of
+     * known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     *         instantiated in order to get an fully working implementation
+     *         Object
+     */
+    @Override
+    public Object[] getGlobalImplementations() {
+        Object[] res = { Simple.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies is
+     * required.
+     *
+     * @param c
+     *            dependency manager Component object, used for configuring the
+     *            dependencies exported and imported
+     * @param imp
+     *            Implementation class that is being configured, needed as long
+     *            as the same routine can configure multiple implementations
+     * @param containerName
+     *            The containerName being configured, this allow also optional
+     *            per-container different behavior if needed, usually should not
+     *            be the case though.
+     */
+    @Override
+    public void configureGlobalInstance(Component c, Object imp) {
+        if (imp.equals(Simple.class)) {
+            Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+            String interfaces[] = null;
+            interfaces = new String[] { ISimple.class.getName() };
+            c.setInterface(interfaces, props);
+        }
+    }
+}
diff --git a/simple/src/main/java/org/opendaylight/toolkit/simple/internal/Simple.java b/simple/src/main/java/org/opendaylight/toolkit/simple/internal/Simple.java
new file mode 100644 (file)
index 0000000..605b58a
--- /dev/null
@@ -0,0 +1,55 @@
+
+package org.opendaylight.toolkit.simple.internal;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.UUID;
+import java.util.Map;
+
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.utils.StatusCode;
+import org.opendaylight.toolkit.simple.ISimple;
+import org.opendaylight.toolkit.simple.SimpleData;
+
+public class Simple implements ISimple {
+    private Map<UUID, SimpleData> data;
+    protected static final Logger log = LoggerFactory.getLogger(Simple.class);
+    @Override
+    public UUID createData(SimpleData datum) {
+        UUID uuid = UUID.randomUUID();
+        SimpleData sData = new SimpleData(uuid.toString(), datum.getFoo(), datum.getBar());
+        data.put(uuid, sData);
+        return uuid;
+    }
+    @Override
+    public SimpleData readData(UUID uuid) {
+        return data.get(uuid);
+    }
+    @Override
+    public Map<UUID, SimpleData> readData() {
+        return data;
+    }
+    @Override
+    public Status updateData(UUID uuid, SimpleData datum) {
+        data.put(uuid, datum);
+        return new Status(StatusCode.SUCCESS);
+    }
+    @Override
+    public Status deleteData(UUID uuid) {
+        data.remove(uuid);
+        return new Status(StatusCode.SUCCESS);
+    }
+    void init() {
+        log.info("Initializing Simple application");
+        data = new ConcurrentHashMap<UUID, SimpleData>();
+    }
+    void start() {
+        log.info("Simple application starting");
+    }
+
+    void stop() {
+        log.info("Simple application stopping");
+    }
+}
diff --git a/simple/src/main/java/org/opendaylight/toolkit/simple/northbound/AppNorthbound.java b/simple/src/main/java/org/opendaylight/toolkit/simple/northbound/AppNorthbound.java
new file mode 100644 (file)
index 0000000..794567a
--- /dev/null
@@ -0,0 +1,257 @@
+
+package org.opendaylight.toolkit.simple.northbound;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriInfo;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
+import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
+import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
+import org.opendaylight.controller.sal.authorization.Privilege;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.toolkit.simple.ISimple;
+import org.opendaylight.toolkit.simple.SimpleData;
+
+/**
+ * Northbound REST API
+ *
+ * This entire web class can be accessed via /northbound prefix as specified in
+ * web.xml
+ *
+ * <br>
+ * <br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default.
+ */
+@Path("/")
+public class AppNorthbound {
+    @Context
+    private UriInfo _uriInfo;
+    private String username;
+
+    @Context
+    public void setSecurityContext(SecurityContext context) {
+        if (context != null && context.getUserPrincipal() != null) {
+            username = context.getUserPrincipal().getName();
+        }
+    }
+
+    protected String getUserName() {
+        return username;
+    }
+
+    /**
+     *
+     * Sample GET REST API call
+     *
+     * @return A response string
+     *
+     * <pre>
+     * Example:
+     *
+     * Request URL:
+     * http://localhost:8080/app/northbound/simple
+     *
+     * Response body in XML:
+     * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
+     * Sample Northbound API
+     *
+     * Response body in JSON:
+     * Sample Northbound API
+     * </pre>
+     */
+    @Path("/simple")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @StatusCodes()
+    public List<SimpleData> getData() {
+        if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+            throw new UnauthorizedException("User is not authorized to perform this operation");
+        }
+        ISimple simple = (ISimple) ServiceHelper.getGlobalInstance(ISimple.class, this);
+        if (simple == null) {
+            throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        Map<UUID, SimpleData> sDataMap = simple.readData();
+        if (sDataMap != null) {
+            return new ArrayList<SimpleData>(sDataMap.values());
+        }
+        return new ArrayList<SimpleData>();
+    }
+
+    @Path("/simple/{uuid}")
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @TypeHint(SimpleData.class)
+    @StatusCodes()
+    public SimpleData getData(@PathParam("uuid") String uuid) {
+        if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+            throw new UnauthorizedException("User is not authorized to perform this operation");
+        }
+        ISimple simple = (ISimple) ServiceHelper.getGlobalInstance(ISimple.class, this);
+        if (simple == null) {
+            throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+
+        return simple.readData(UUID.fromString(uuid));
+    }
+
+    /**
+     *
+     * Sample POST REST API call
+     *
+     * @return A response string
+     *
+     *         <pre>
+     * Example:
+     *
+     * Request URL:
+     * http://localhost:8080/app/northbound/simple
+     *
+     * Response body in XML:
+     * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
+     * Sample Northbound API
+     *
+     * Response body in JSON:
+     * Sample Northbound API
+     * </pre>
+     */
+    @Path("/simple")
+    @POST
+    @StatusCodes({ @ResponseCode(code = 201, condition = "Data Inserted successfully"),
+        @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+        @ResponseCode(code = 500, condition = "Error inserting data"),
+        @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+    @Consumes({ MediaType.APPLICATION_JSON})
+    public Response createData(@TypeHint(SimpleData.class) SimpleData data) {
+        if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+            throw new UnauthorizedException("User is not authorized to perform this operation");
+        }
+        ISimple simple = (ISimple) ServiceHelper.getGlobalInstance(ISimple.class, this);
+        if (simple == null) {
+            throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+        }
+        
+        UUID uuid = simple.createData(data);
+        if (uuid == null) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+        return Response.status(Response.Status.CREATED)
+                .header("Location", String.format("%s/%s", _uriInfo.getAbsolutePath().toString(),
+                                                            uuid.toString()))
+                .entity(uuid.toString())
+                .build();
+    }
+
+    /**
+    *
+    * Sample PUT REST API call
+    *
+    * @return A response string
+    *
+    *         <pre>
+    * Example:
+    *
+    * Request URL:
+    * http://localhost:8080/app/northbound/simple/{uuid}
+    *
+    * Response body in XML:
+    * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
+    * Sample Northbound API
+    *
+    * Response body in JSON:
+    * Sample Northbound API
+    * </pre>
+    */
+   @Path("/simple/{uuid}")
+   @PUT
+   @StatusCodes({ @ResponseCode(code = 200, condition = "Data Updated successfully"),
+       @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+       @ResponseCode(code = 500, condition = "Error updating data"),
+       @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+   @Consumes({ MediaType.APPLICATION_JSON})
+   public Response updateData(@PathParam("uuid") String uuid, @TypeHint(SimpleData.class) SimpleData data) {
+       if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+           throw new UnauthorizedException("User is not authorized to perform this operation");
+       }
+       ISimple simple = (ISimple) ServiceHelper.getGlobalInstance(ISimple.class, this);
+       if (simple == null) {
+           throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+       }
+       
+       Status status = simple.updateData(UUID.fromString(uuid), data);
+       if (!status.isSuccess()) {
+           return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+       }
+       return Response.status(Response.Status.OK).build();
+   }
+
+   /**
+   *
+   * Sample Delete REST API call
+   *
+   * @return A response string
+   *
+   *         <pre>
+   * Example:
+   *
+   * Request URL:
+   * http://localhost:8080/app/northbound/simple/{uuid}
+   *
+   * Response body in XML:
+   * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
+   * Sample Northbound API
+   *
+   * Response body in JSON:
+   * Sample Northbound API
+   * </pre>
+   */
+  @Path("/simple/{uuid}")
+  @DELETE
+  @StatusCodes({ @ResponseCode(code = 200, condition = "Data Deleted successfully"),
+                 @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+                 @ResponseCode(code = 500, condition = "Error deleting data"),
+                 @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+  @Consumes({ MediaType.APPLICATION_JSON})
+  public Response updateData(@PathParam("uuid") String uuid) {
+      if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+          throw new UnauthorizedException("User is not authorized to perform this operation");
+      }
+      ISimple simple = (ISimple) ServiceHelper.getGlobalInstance(ISimple.class, this);
+      if (simple == null) {
+          throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+      }
+      
+      Status status = simple.deleteData(UUID.fromString(uuid));
+      if (!status.isSuccess()) {
+          return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+      }
+      return Response.status(Response.Status.OK).build();
+  }
+
+}
diff --git a/simple/src/main/java/org/opendaylight/toolkit/simple/web/AppWeb.java b/simple/src/main/java/org/opendaylight/toolkit/simple/web/AppWeb.java
new file mode 100644 (file)
index 0000000..b38fe65
--- /dev/null
@@ -0,0 +1,59 @@
+package org.opendaylight.toolkit.simple.web;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.toolkit.web.IDaylightWeb;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * This entire web class can be accessed via /web prefix as specified in web.xml
+ */
+@Controller
+@RequestMapping("/")
+public class AppWeb implements IDaylightWeb {
+    private static final String WEB_NAME = "simple App";
+    private static final String WEB_ID = "simple";
+    private static final short WEB_ORDER = 1;
+    private static final UserLevel AUTH_LEVEL = UserLevel.CONTAINERUSER;
+
+    public AppWeb() {
+        ServiceHelper.registerGlobalService(IDaylightWeb.class, this, null);
+    }
+
+    @RequestMapping(value = "")
+    public String index(Model model, HttpServletRequest request) {
+        return "main";
+    }
+
+    @Override
+    public String getWebName() {
+        return WEB_NAME;
+    }
+
+    @Override
+    public String getWebId() {
+        return WEB_ID;
+    }
+
+    @Override
+    public short getWebOrder() {
+        return WEB_ORDER;
+    }
+
+    @Override
+    public boolean isAuthorized(UserLevel userLevel) {
+        return userLevel.ordinal() <= AUTH_LEVEL.ordinal();
+    }
+
+    @RequestMapping(value = "login")
+    public String login(final HttpServletRequest request, final HttpServletResponse response) {
+        return "forward:" + "/";
+    }
+
+}
diff --git a/simple/src/main/resources/META-INF/spring.factories b/simple/src/main/resources/META-INF/spring.factories
new file mode 100644 (file)
index 0000000..93db02e
--- /dev/null
@@ -0,0 +1 @@
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
diff --git a/simple/src/main/resources/META-INF/spring.handlers b/simple/src/main/resources/META-INF/spring.handlers
new file mode 100644 (file)
index 0000000..957af91
--- /dev/null
@@ -0,0 +1,10 @@
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
diff --git a/simple/src/main/resources/META-INF/spring.schemas b/simple/src/main/resources/META-INF/spring.schemas
new file mode 100644 (file)
index 0000000..d865edc
--- /dev/null
@@ -0,0 +1,49 @@
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
diff --git a/simple/src/main/resources/META-INF/spring.tooling b/simple/src/main/resources/META-INF/spring.tooling
new file mode 100644 (file)
index 0000000..057d834
--- /dev/null
@@ -0,0 +1,39 @@
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
diff --git a/simple/src/main/resources/WEB-INF/AppWeb-servlet.xml b/simple/src/main/resources/WEB-INF/AppWeb-servlet.xml
new file mode 100644 (file)
index 0000000..b5a993f
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xmlns:mvc="http://www.springframework.org/schema/mvc"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
+                      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <context:component-scan base-package="org.opendaylight.toolkit.simple"/>
+
+  <mvc:resources mapping="/js/**" location="/js/" />
+  <mvc:resources mapping="/css/**" location="/css/" />
+  <mvc:resources mapping="/img/**" location="/img/" />
+  <mvc:annotation-driven/>
+
+  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+        <property name="prefix" value="/WEB-INF/jsp/"/>
+        <property name="suffix" value=".jsp"/>
+  </bean>
+</beans>
diff --git a/simple/src/main/resources/WEB-INF/jsp/main.jsp b/simple/src/main/resources/WEB-INF/jsp/main.jsp
new file mode 100644 (file)
index 0000000..b1ef662
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>App</title>
+    <meta name="description" content="">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <!-- style -->
+    <link rel="stylesheet" href="/css/ext/pure/pure.css"/>
+    <link rel="stylesheet" href="/css/phoenix.css"/>
+
+    <!-- style app -->
+    <link rel="stylesheet" href="/simple/web/css/simple.css"/>
+
+    <!-- scripts -->
+    <script data-main="/simple/web/js/main" src="/js/ext/requirejs/require.js"></script>
+  </head>
+  <body>
+    <h1>FOO</h1>
+    <h1>Simple OpenDaylight App</h1>
+    <form class="pure-form pure-form-stacked" onsubmit="return false;">
+      <legend>Simple Form</legend>
+      <input type="text" placeholder="Foo" id="foo"/>
+      <input type="text" placeholder="Bar" id="bar"/>
+      <button type="submit" class="pure-button pure-button-primary">Submit</button>
+    </form>
+
+    <table class="pure-table">
+      <thead>
+        <tr>
+          <th>UUID</th>
+          <th>Foo</th>
+          <th>Bar</th>
+        </tr>
+      </thead>
+      <tbody>
+      </tbody>
+    </table>
+  </body>
+</html>
diff --git a/simple/src/main/resources/WEB-INF/web.xml b/simple/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..c9cc7a6
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+  version="3.0">
+
+  <filter>
+    <filter-name>CorsFilter</filter-name>
+    <!-- <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> -->
+    <filter-class>org.opendaylight.toolkit.web.CorsFilter</filter-class>
+  </filter>
+  <filter-mapping>
+    <filter-name>CorsFilter</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>
+
+  <security-constraint>
+    <display-name>App</display-name>
+    <web-resource-collection>
+      <web-resource-name>AppWeb</web-resource-name>
+      <url-pattern>/web/js/*</url-pattern>
+      <url-pattern>/web/images/*</url-pattern>
+      <url-pattern>/web/css/*</url-pattern>
+      <url-pattern>/web/favicon.ico</url-pattern>
+    </web-resource-collection>
+    <web-resource-collection>
+      <web-resource-name>AppNorthbound</web-resource-name>
+      <url-pattern>/northbound/*</url-pattern>
+      <http-method>POST</http-method>
+      <http-method>GET</http-method>
+      <http-method>PUT</http-method>
+      <http-method>PATCH</http-method>
+      <http-method>DELETE</http-method>
+      <http-method>HEAD</http-method>
+      <http-method>OPTIONS</http-method>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>System-Admin</role-name>
+      <role-name>Network-Admin</role-name>
+      <role-name>Network-Operator</role-name>
+      <role-name>Container-User</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-role>
+    <role-name>System-Admin</role-name>
+  </security-role>
+  <security-role>
+    <role-name>Network-Admin</role-name>
+  </security-role>
+  <security-role>
+    <role-name>Network-Operator</role-name>
+  </security-role>
+  <security-role>
+    <role-name>Container-User</role-name>
+  </security-role>
+
+  <!-- <login-config> // enabling this auto directs to login page, considering removing this
+    <auth-method>FORM</auth-method>
+    <form-login-config>
+      <form-login-page>/WEB-INF/jsp/login.jsp</form-login-page>
+      <form-error-page>/WEB-INF/jsp/error.jsp</form-error-page>
+    </form-login-config>
+  </login-config>-->
+
+  <login-config>
+    <auth-method>BASIC</auth-method>
+    <realm-name>opendaylight</realm-name>
+  </login-config>
+
+  <!-- <error-page>
+    <error-code>403</error-code>
+    <location>/WEB-INF/jsp/autherror.jsp</location>
+  </error-page> -->
+
+  <!-- web -->
+  <servlet>
+    <servlet-name>AppWeb</servlet-name>
+    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>AppWeb</servlet-name>
+    <url-pattern>/web/*</url-pattern>
+  </servlet-mapping>
+
+  <listener>
+    <listener-class>org.opendaylight.toolkit.web.ControllerUISessionManager</listener-class>
+  </listener>
+
+  <!-- <session-config> // needs further testing
+    <cookie-config>
+      <path>/</path>
+    </cookie-config>
+  </session-config>-->
+
+  <!-- northbound -->
+  <servlet>
+    <servlet-name>AppNorthbound</servlet-name>
+    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+    <init-param>
+      <param-name>javax.ws.rs.Application</param-name>
+      <param-value>org.opendaylight.controller.northbound.commons.NorthboundApplication</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>AppNorthbound</servlet-name>
+    <url-pattern>/northbound/*</url-pattern>
+  </servlet-mapping>
+
+</web-app>
diff --git a/simple/src/main/resources/css/simple.css b/simple/src/main/resources/css/simple.css
new file mode 100644 (file)
index 0000000..5031d37
--- /dev/null
@@ -0,0 +1,46 @@
+/* pure */
+.selected {
+  background: #F3F781
+}
+
+.button-success,
+.button-error {
+  color: white;
+  border-radius: 4px;
+  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+}
+
+.button-success {
+  background: rgb(28, 184, 65); /* this is a green */
+}
+
+.button-error {
+  background: rgb(202, 60, 60); /* this is a maroon */
+}
+
+table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+fieldset {
+}
+
+fieldset > label {
+  display: block;
+}
+
+fieldset > input {
+  width: 100%;
+}
+
+#simpleContainer {
+  margin-top: 15px;
+}
+
+form {
+  width: 250px;
+  border: 1px dotted #000;
+  padding: 10px;
+  margin: 10px;
+}
diff --git a/simple/src/main/resources/js/app.js b/simple/src/main/resources/js/app.js
new file mode 100644 (file)
index 0000000..4150efa
--- /dev/null
@@ -0,0 +1,52 @@
+// Filename: app.js
+
+define([
+  // These are path alias that we configured in our bootstrap
+  'jquery',
+  'jquery-ui',
+  'underscore'
+], function($, _){
+  // ajax settings
+  $.ajaxSetup({
+    type: 'POST',
+    data: {},
+    dataType: 'json',
+    xhrFields: {
+      withCredentials: true
+    },
+    crossDomain: true,
+    success: 'callback',
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    }
+  });
+
+  function populateTable(data) {
+    var $tbody = $('table tbody');
+    $.each(data, function(idx, d) {
+      var $tr = $(document.createElement('tr'));
+      var $uuid = $(document.createElement('td')).append(d.uuid);
+      var $foo = $(document.createElement('td')).append(d.foo);
+      var $bar = $(document.createElement('td')).append(d.bar);
+      $tr.append($uuid).append($foo).append($bar);
+      $tbody.append($tr);
+    });
+  }
+
+  // bind form submit
+  $('button').click(function() {
+    var simple = {};
+    simple.foo = $('#foo').val();
+    simple.bar = $('#bar').val();
+    $.post('http://localhost:8080/simple/northbound/simple', JSON.stringify(simple), function(result) {
+      console.log(result);
+    });
+  });
+
+  // populate table
+  $.getJSON('http://localhost:8080/simple/northbound/simple', function(result) {
+    populateTable(result);
+  });
+
+});
diff --git a/simple/src/main/resources/js/collections/SimpleCollection.js b/simple/src/main/resources/js/collections/SimpleCollection.js
new file mode 100644 (file)
index 0000000..d550e9a
--- /dev/null
@@ -0,0 +1,12 @@
+define(
+  [
+    'backbone',
+    'underscore',
+    '/simple/web/js/models/SimpleModel.js'
+    ], function(Backbone, _, SimpleModel) {
+      var SimpleCollection = Backbone.Collection.extend({
+        model : SimpleModel,
+        url : '/simple/northbound/simple'
+      });
+      return SimpleCollection;
+    });
diff --git a/simple/src/main/resources/js/main.js b/simple/src/main/resources/js/main.js
new file mode 100644 (file)
index 0000000..46974a5
--- /dev/null
@@ -0,0 +1,20 @@
+// Filename: main.js
+
+require.config({
+  paths: {
+    'jquery': '/js/ext/jquery/dist/jquery',
+    'jquery-ui': '/js/ext/jquery-ui/ui/minified/jquery-ui.min',
+    'underscore': '/js/ext/underscore/underscore'
+  },
+  shim: {
+    'jquery-ui' : {
+      exports: '$',
+      deps: ['jquery']
+    }
+  }
+});
+
+require([
+  'app'
+], function(App) {
+});
diff --git a/simple/src/main/resources/js/models/SimpleModel.js b/simple/src/main/resources/js/models/SimpleModel.js
new file mode 100644 (file)
index 0000000..45add43
--- /dev/null
@@ -0,0 +1,15 @@
+define(['backbone', 'underscore'], function(Backbone, _) {
+  var SimpleModel = Backbone.Model.extend({
+    idAttribute : 'uuid',
+    defaults : {
+      foo : '',
+      bar : ''
+    },
+    initialize : function() {
+    },
+    setUrlRoot: function() {
+      this.urlRoot = '/simple/northbound/simple';
+    }
+  });
+  return SimpleModel;
+});
diff --git a/simple/src/main/resources/js/templates/simple.html b/simple/src/main/resources/js/templates/simple.html
new file mode 100644 (file)
index 0000000..2ad8bc6
--- /dev/null
@@ -0,0 +1,37 @@
+<script type="text/template" id="simpleContainer">
+  <div id="simpleDiv" style="margin-left:20px; float: left;">
+    <h3>Connection Application</h3>
+    <table id="simpleTable" class="pure-table pure-table-bordered">
+      <thead>
+        <tr>
+          <th>UUID</th>
+          <th>A</th>
+          <th>B</th>
+        </tr>
+      </thead>
+      <tbody>
+        <% _.each(simple, function(simpleton) { %>
+          <tr data-id="<%= simpleton.id %>">
+            <td><%= simpleton.attributes.uuid %></td>
+            <td><%= simpleton.attributes.foo %></td>
+            <td><%= simpleton.attributes.bar %></td>
+          </tr>
+        <% }); %>
+      </tbody>
+    </table>
+    <form class="pure-form">
+      <fieldset>
+        <legend>Connection Form</legend>
+        <label for="simpleFooInput">Input A</label>
+        <input type="text" id="simpleFooInput" placeholder="Input A" />
+        <label for="simpleBarInput">Input B</label>
+        <input type="text" id="simpleBarInput" placeholder="Input B" />
+        <div id="simpleContainer">
+          <button id="simpleButton" class="pure-button button-success" onclick="return false;">Submit</button>
+          <button id="simpleRemoveButton" class="pure-button button-error" onclick="return false;">Remove</button>
+          <button class="pure-button" onclick="return false;">Cancel</button>
+        </div>
+      </fieldset>
+    </form>
+  </div>
+</script>
diff --git a/simple/src/main/resources/js/views/View.js b/simple/src/main/resources/js/views/View.js
new file mode 100644 (file)
index 0000000..4972dbf
--- /dev/null
@@ -0,0 +1,81 @@
+define(
+  [
+    'jquery',
+    'backbone',
+    'underscore',
+    '/simple/web/js/collections/SimpleCollection.js',
+    '/simple/web/js/models/SimpleModel.js',
+    '/js/ext/text/text.js!/simple/web/js/templates/simple.html'
+    ], function($, Backbone, _, SimpleCollection, SimpleModel, Template) {
+      var View = Backbone.View.extend({
+        el: $("#main"),
+        initialize: function() {
+          var self = this;
+          this.collection = new SimpleCollection();
+          this.collection.url = 'http://localhost:8080/simple/northbound/simple';
+          this.collection.fetch({
+            success : function(call, response) {
+              self.render();
+            }
+          });
+        },
+        render: function() {
+          var that = this;
+          var compiledTemplate = _.template(Template, 
+          {
+            simple : that.collection.models
+          });
+          $(this.el).append($(compiledTemplate).html());
+        },
+        events : {
+          'click #simpleContainer button' : 'handleSimpleButton',
+          'click #simpleTable tbody tr' : 'tableRowClicked'
+        },
+        handleSimpleButton : function(evt) {
+          var self = this;
+          var $button = $(evt.currentTarget);
+          if ($button.attr('id') == 'simpleButton') {
+            var simpleModel = new SimpleModel({
+              foo : $('#simpleFooInput').val(),
+              bar : $('#simpleBarInput').val()
+            });
+            simpleModel.urlRoot = '/simple/northbound/simple';
+            simpleModel.save(null, {
+              dataType: 'text',
+              success: function(model, response) {
+                $('#main').empty();
+                self.updateView();
+              }
+            });
+          } else if ($button.attr('id') == 'simpleRemoveButton') {
+            var id = $('#simpleTable tbody tr.selected').attr('data-id');
+            var simpleModel = self.collection.get(id);
+            simpleModel.setUrlRoot();
+            simpleModel.destroy({
+              dataType: 'text',
+              success: function() {
+                $('#main').empty();
+                self.updateView();
+              },
+              error: function() {
+                $('#main').empty();
+                self.updateView();
+              }
+            });
+          } else {
+            // cancel button
+            $('#simpleInput').val('');
+          }
+        },
+        tableRowClicked : function(evt) {
+          $('#simpleTable tbody tr.selected').removeClass('selected');
+          var $tr = $(evt.currentTarget);
+          $tr.addClass('selected');
+        },
+        updateView : function() {
+          $('#simpleContainer').remove();
+          this.initialize();
+        }
+      });
+      return View;
+    });
diff --git a/web/src/main/java/org/opendaylight/toolkit/web/CorsFilter.java b/web/src/main/java/org/opendaylight/toolkit/web/CorsFilter.java
new file mode 100644 (file)
index 0000000..d3d3915
--- /dev/null
@@ -0,0 +1,46 @@
+package org.opendaylight.toolkit.web;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Temporary workaround because built-in tomcat cors filter
+ * isn't working at the moment
+ * 
+ * @author Andrew Kim
+ *
+ */
+public class CorsFilter implements Filter {
+       public static String VALID_METHODS = "DELETE, HEAD, GET, OPTIONS, POST, PUT";
+
+       @Override
+       public void destroy() {
+       }
+
+       @Override
+       public void doFilter(ServletRequest req, ServletResponse res,
+                       FilterChain chain) throws IOException, ServletException {
+               HttpServletResponse response = (HttpServletResponse) res;
+               /* Uncomment next line */
+               //response.setHeader("Access-Control-Allow-Origin", "http://<<your-ip>>:8000");
+               response.setHeader("Access-Control-Allow-Credentials", "true");
+               response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
+               response.setHeader("Access-Control-Max-Age", "3600");
+               response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers");
+               chain.doFilter(req, res);
+       }
+
+       @Override
+       public void init(FilterConfig arg0) throws ServletException {
+               // TODO Auto-generated method stub
+       }
+
+}
index 06ebe154c4a759da2ce7e4f3185f735b371665e4..182338b5554ba2c4e95198707ed093ca730d85e4 100644 (file)
 <?xml version="1.0" encoding="ISO-8859-1"?>
-<!-- <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
-        version="2.4"> -->
 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
-        version="3.0">
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+  version="3.0">
 
-        <filter>
-          <filter-name>CorsFilter</filter-name>
-          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
-          <init-param>
-            <param-name>cors.allowed.origins</param-name>
-            <param-value>*</param-value>
-          </init-param>
-          <init-param>
-            <param-name>cors.allowed.methods</param-name>
-            <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
-          </init-param>
-          <init-param>
-            <param-name>cors.allowed.headers</param-name>
-            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
-          </init-param>
-          <init-param>
-            <param-name>cors.exposed.headers</param-name>
-            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
-          </init-param>
-          <init-param>
-            <param-name>cors.support.credentials</param-name>
-            <param-value>true</param-value>
-          </init-param>
-          <init-param>
-            <param-name>cors.preflight.maxage</param-name>
-            <param-value>10</param-value>
-          </init-param>
-        </filter>
-        <filter-mapping>
-          <filter-name>CorsFilter</filter-name>
-          <url-pattern>/*</url-pattern>
-        </filter-mapping>
+  <filter>
+    <filter-name>CorsFilter</filter-name>
+    <!-- <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>-->
+    <filter-class>org.opendaylight.toolkit.web.CorsFilter</filter-class>
+  </filter>
+  <filter-mapping>
+    <filter-name>CorsFilter</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>
 
-        <security-constraint>
-          <web-resource-collection>
-             <web-resource-name>free access</web-resource-name>
-             <url-pattern>/js/*</url-pattern>
-             <url-pattern>/img/*</url-pattern>
-             <url-pattern>/css/*</url-pattern>
-             <url-pattern>/favicon.ico</url-pattern>
-             <url-pattern>/versionProperty/*</url-pattern>
-          </web-resource-collection>
-        </security-constraint>
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>free access</web-resource-name>
+      <url-pattern>/js/*</url-pattern>
+      <url-pattern>/img/*</url-pattern>
+      <url-pattern>/css/*</url-pattern>
+      <url-pattern>/favicon.ico</url-pattern>
+      <url-pattern>/versionProperty/*</url-pattern>
+    </web-resource-collection>
+  </security-constraint>
 
-        <security-constraint>
-           <display-name>RootApp</display-name>
-           <web-resource-collection>
-              <web-resource-name>RootGUI</web-resource-name>
-              <url-pattern>/*</url-pattern>
-              <http-method>POST</http-method>
-              <http-method>GET</http-method>
-              <http-method>PUT</http-method>
-              <http-method>DELETE</http-method>
-              <http-method>HEAD</http-method>
-           </web-resource-collection>
-           <auth-constraint>
-               <role-name>System-Admin</role-name>
-               <role-name>Network-Admin</role-name>
-               <role-name>Network-Operator</role-name>
-               <role-name>Container-User</role-name>
-           </auth-constraint>
-        </security-constraint>
+  <security-constraint>
+    <display-name>RootApp</display-name>
+    <web-resource-collection>
+      <web-resource-name>RootGUI</web-resource-name>
+      <url-pattern>/*</url-pattern>
+      <http-method>POST</http-method>
+      <http-method>GET</http-method>
+      <http-method>PUT</http-method>
+      <http-method>DELETE</http-method>
+      <http-method>HEAD</http-method>
+      <http-method>OPTIONS</http-method>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>System-Admin</role-name>
+      <role-name>Network-Admin</role-name>
+      <role-name>Network-Operator</role-name>
+      <role-name>Container-User</role-name>
+    </auth-constraint>
+  </security-constraint>
 
-        <security-role>
-                <role-name>System-Admin</role-name>
-        </security-role>
-        <security-role>
-                <role-name>Network-Admin</role-name>
-        </security-role>
-        <security-role>
-                <role-name>Network-Operator</role-name>
-        </security-role>
-        <security-role>
-                <role-name>Container-User</role-name>
-        </security-role>
+  <security-role>
+    <role-name>System-Admin</role-name>
+  </security-role>
+  <security-role>
+    <role-name>Network-Admin</role-name>
+  </security-role>
+  <security-role>
+    <role-name>Network-Operator</role-name>
+  </security-role>
+  <security-role>
+    <role-name>Container-User</role-name>
+  </security-role>
 
+  <login-config>
+    <auth-method>FORM</auth-method>
+    <form-login-config>
+      <form-login-page>/WEB-INF/jsp/login.jsp</form-login-page>
+      <form-error-page>/WEB-INF/jsp/error.jsp</form-error-page>
+    </form-login-config>
+  </login-config>
 
-        <login-config>
-                <auth-method>FORM</auth-method>
-                <form-login-config>
-                        <form-login-page>/WEB-INF/jsp/login.jsp</form-login-page>
-                        <form-error-page>/WEB-INF/jsp/error.jsp</form-error-page>
-                </form-login-config>
-        </login-config>
+  <error-page>
+    <error-code>403</error-code>
+    <location>/WEB-INF/jsp/autherror.jsp</location>
+  </error-page>
 
-    <error-page>
-            <error-code>403</error-code>
-            <location>/WEB-INF/jsp/autherror.jsp</location>
-    </error-page>
+  <servlet>
+    <servlet-name>RootGUI</servlet-name>
+    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+    <init-param>
+      <param-name>dispatchOptionsRequest</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
 
-        <servlet>
-                <servlet-name>RootGUI</servlet-name>
-                <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-                <load-on-startup>1</load-on-startup>
-        </servlet>
+  <servlet-mapping>
+    <servlet-name>RootGUI</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
 
-        <servlet-mapping>
-                <servlet-name>RootGUI</servlet-name>
-                <url-pattern>/</url-pattern>
-        </servlet-mapping>
+  <display-name>OpenDaylight Toolkit</display-name>
+  <description>OpenDaylight Toolkit</description>
 
-        <display-name>OpenDaylight Toolkit</display-name>
-        <description>OpenDaylight Toolkit</description>
+  <listener>
+    <listener-class>org.opendaylight.toolkit.web.ControllerUISessionManager</listener-class>
+  </listener>
 
-        <listener>
-                <listener-class>org.opendaylight.toolkit.web.ControllerUISessionManager</listener-class>
-        </listener>
-
-        <session-config>
-                <cookie-config>
-                        <path>/</path>
-                </cookie-config>
-        </session-config>
+  <session-config>
+    <cookie-config>
+      <path>/</path>
+    </cookie-config>
+  </session-config>
 
 </web-app>
index 888e21fa807ce65b0ae2fb7f44a7490dab1cc242..fc8198462c124865f6773d8f42fb7c3dfc257ec6 100644 (file)
@@ -2,7 +2,8 @@
   "name" : "OpenDaylight Phoenix External JS",
   "version" : "0.0.1",
   "dependencies" : {
-    "jquery" : "2.1.0",
+    "jquery" : "2.1.1",
+    "jquery-ui" : "1.10.4",
     "backbone" : "1.1.2",
     "underscore" : "1.6.0",
     "requirejs" : "2.1.11",