Adding learningswitch to samples 86/8386/1
authorAnirudh Ramachandran <anirudhvr@gmail.com>
Thu, 26 Jun 2014 22:11:32 +0000 (15:11 -0700)
committerAnirudh Ramachandran <anirudhvr@gmail.com>
Thu, 26 Jun 2014 22:11:32 +0000 (15:11 -0700)
Change-Id: I4ce61561d83fc342662519343137e57c707e4927
Signed-off-by: Anirudh Ramachandran <anirudhvr@gmail.com>
35 files changed:
samples/learningswitch/.gitignore [new file with mode: 0644]
samples/learningswitch/README.md [new file with mode: 0644]
samples/learningswitch/pom.xml [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/ILearningSwitch.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/LearningSwitchOptions.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/MacTable.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/NodeConnectorPlus.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/NodeTable.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/Table.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/internal/Activator.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/internal/LearningSwitch.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/northbound/AppNorthbound.java [new file with mode: 0644]
samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/web/AppWeb.java [new file with mode: 0644]
samples/learningswitch/src/main/resources/META-INF/spring.factories [new file with mode: 0644]
samples/learningswitch/src/main/resources/META-INF/spring.handlers [new file with mode: 0644]
samples/learningswitch/src/main/resources/META-INF/spring.schemas [new file with mode: 0644]
samples/learningswitch/src/main/resources/META-INF/spring.tooling [new file with mode: 0644]
samples/learningswitch/src/main/resources/WEB-INF/AppWeb-servlet.xml [new file with mode: 0644]
samples/learningswitch/src/main/resources/WEB-INF/jsp/main.jsp [new file with mode: 0644]
samples/learningswitch/src/main/resources/WEB-INF/web.xml [new file with mode: 0644]
samples/learningswitch/src/main/resources/css/.simple.css.swp [new file with mode: 0644]
samples/learningswitch/src/main/resources/css/bower.json [new file with mode: 0644]
samples/learningswitch/src/main/resources/css/simple.css [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/.main.js.swp [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/app.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/collections/FlowCollection.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/collections/SimpleCollection.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/main.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/models/FlowModel.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/models/SimpleModel.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/templates/flowtemplate.html [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/templates/simple.html [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/templates/test.html [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/views/FlowTableView.js [new file with mode: 0644]
samples/learningswitch/src/main/resources/js/views/View.js [new file with mode: 0644]

diff --git a/samples/learningswitch/.gitignore b/samples/learningswitch/.gitignore
new file mode 100644 (file)
index 0000000..a48e45b
--- /dev/null
@@ -0,0 +1 @@
+/target-ide
diff --git a/samples/learningswitch/README.md b/samples/learningswitch/README.md
new file mode 100644 (file)
index 0000000..301de61
--- /dev/null
@@ -0,0 +1,3 @@
+Open Daylight learning switch module based on SDN Hub's learning switch,
+ ported to Opendaylight Tookit
+
diff --git a/samples/learningswitch/pom.xml b/samples/learningswitch/pom.xml
new file mode 100644 (file)
index 0000000..4c8bc01
--- /dev/null
@@ -0,0 +1,137 @@
+<?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>
+
+
+  <groupId>org.sdnhub.odl</groupId>
+  <artifactId>learningswitch</artifactId>
+  <version>0.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.controller.forwardingrulesmanager.*,            
+              org.opendaylight.toolkit.web,
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.reader.*,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.sal.action,
+              org.opendaylight.controller.sal.flowprogrammer,
+              org.opendaylight.controller.sal.match,
+              org.opendaylight.controller.sal.authorization,
+              org.opendaylight.controller.switchmanager.*,
+              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>/learningswitch</Web-ContextPath>
+            <Jaxrs-Resources>,${classes;ANNOTATION;javax.ws.rs.Path}</Jaxrs-Resources>
+            <Bundle-Activator>
+              org.sdnhub.odl.learningswitch.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>
+ <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+    </dependency>
+ <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>forwardingrulesmanager</artifactId>
+    </dependency>
+
+  </dependencies>
+</project>
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/ILearningSwitch.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/ILearningSwitch.java
new file mode 100644 (file)
index 0000000..8205689
--- /dev/null
@@ -0,0 +1,17 @@
+
+package org.sdnhub.odl.learningswitch;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+import org.sdnhub.odl.learningswitch.MacTable.MacPortTableElem;
+
+public interface ILearningSwitch {
+       public Table getData();
+       public void deleteData();
+       public String getFunction();
+       public Boolean setFunction(String function);
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/LearningSwitchOptions.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/LearningSwitchOptions.java
new file mode 100644 (file)
index 0000000..b5b00a8
--- /dev/null
@@ -0,0 +1,22 @@
+package org.sdnhub.odl.learningswitch;
+
+public class LearningSwitchOptions {
+
+       public static final int NUM_OPTIONS = 7;
+       
+       public static final Long SRC_MAC = 1L;
+       public static final Long DST_MAC = 2L;
+       public static final Long SRC_IPv4 = 4L;
+       public static final Long DST_IPv4 = 8L;
+       public static final Long IPv4_PROT = 16L;
+       public static final Long SRC_PORT = 32L;
+       public static final Long DST_PORT = 64L;
+       
+
+       
+       public Long options;
+       
+       public LearningSwitchOptions() {
+               options = 0L;
+       }
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/MacTable.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/MacTable.java
new file mode 100644 (file)
index 0000000..fe1fb6a
--- /dev/null
@@ -0,0 +1,124 @@
+
+package org.sdnhub.odl.learningswitch;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+@XmlRootElement(name="MacToPortTable")
+@XmlAccessorType(XmlAccessType.NONE)
+public class MacTable {
+
+    // For each switch port, track list of learned MACs
+    private Map<NodeConnector, Set<Long>> table;
+
+    public NodeConnector getNodeConnector(Node n, Long mac) {
+        for (Map.Entry<NodeConnector, Set<Long>> entry : this.table.entrySet())  {
+            if (((NodeConnector)entry.getKey()).getNode().equals(n))
+                if (((Set<Long>)entry.getValue()).contains(mac))
+                    return (NodeConnector)entry.getKey();
+        }
+        return null;
+    }
+
+    public void setNodeConnector(NodeConnector nc, Long mac) {
+        if (table.containsKey(nc)) {
+            table.get(nc).add(mac);
+        }
+        else
+            table.put(nc, new HashSet<Long>(Collections.singleton(mac)));
+    }
+
+    public void clearNodeConnector(NodeConnector nc) {
+        table.remove(nc);
+    }
+
+    public void initNode(Node n) {
+        //Nothing to do right now
+    }
+
+    public void clear() {
+       table.clear();
+    }
+    public void clearNode(Node n) {
+        Set<NodeConnector> nodeConnectors = new LinkedHashSet<NodeConnector>();
+
+        for (Map.Entry<NodeConnector, Set<Long>> entry : this.table.entrySet()) {
+            if (((NodeConnector)entry.getKey()).getNode().equals(n)) {
+                nodeConnectors.add((NodeConnector)entry.getKey());
+            }
+        }
+
+        for (NodeConnector nc: nodeConnectors) {
+               clearNodeConnector(nc);
+        }
+    }
+
+    public MacTable() {
+        super();
+        table = new HashMap<NodeConnector, Set<Long> >();
+    }
+
+    public class MacPortTableElem {
+        @XmlElement
+        String mac;
+        @XmlElement
+        String connector;
+        public MacPortTableElem() {
+            // TODO Auto-generated constructor stub
+            super();
+        }
+
+        public MacPortTableElem(String mac, String connector) {
+            super();
+            this.mac = mac;
+            this.connector = connector;
+        }
+    }
+
+    @XmlElement(name="entries")
+    public List< MacPortTableElem >  getMap() {
+        NodeConnector nc;
+        List< MacPortTableElem > entries = new ArrayList<MacPortTableElem>();
+
+        for (Map.Entry<NodeConnector, Set<Long> > entry : this.table.entrySet()) {
+            MacPortTableElem elem = new MacPortTableElem();
+            nc = (NodeConnector)entry.getKey();
+
+            for (Long mac: (Set<Long>)entry.getValue()) {
+                elem.mac = HexEncode.longToHexString(mac);
+                elem.connector = nc.toString();
+                entries.add(elem);
+            }
+        }
+        return entries;
+    }
+
+    public String toString() {
+        NodeConnector nc;
+        String str = "MAC Table: " + table.size() + " entries\n";
+        for (Map.Entry<NodeConnector, Set<Long> > entry : table.entrySet()) {
+            nc = (NodeConnector)entry.getKey();
+            for (Long mac: (Set<Long>)entry.getValue())
+                str += HexEncode.longToHexString(mac) + " - " +
+                    nc.toString() + "\n";
+        }
+        return str;
+    }
+}
+
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/NodeConnectorPlus.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/NodeConnectorPlus.java
new file mode 100644 (file)
index 0000000..17138be
--- /dev/null
@@ -0,0 +1,42 @@
+package org.sdnhub.odl.learningswitch;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+class Pair<F, S> {
+    @XmlElement(name="first")
+       private F first;
+    @XmlElement(name="second")
+    private S second;
+    public Pair(F f, S s) {
+       first = f;
+       second = s;
+    }
+}
+
+@XmlRootElement(name="NodeConnector")
+public class NodeConnectorPlus {
+       
+       @XmlElement(name="nodeconnector")
+       NodeConnector nc;
+       // History of all MACs / IPs / whatever seen on this node conncetor
+       List<Pair<String, Date> > history; 
+       // Add an
+       public NodeConnectorPlus(NodeConnector nc) {
+               this.nc = nc;
+               history = new ArrayList<Pair<String, Date>>();
+       }
+       
+       public String toString() {
+               return nc.toString();
+       }
+
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/NodeTable.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/NodeTable.java
new file mode 100644 (file)
index 0000000..736c576
--- /dev/null
@@ -0,0 +1,128 @@
+package org.sdnhub.odl.learningswitch;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.IPv4;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.packet.BitBufferHelper;
+import org.opendaylight.controller.sal.utils.HexEncode;
+
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name="NodeTable")
+public class NodeTable {
+       Set <NodeConnectorPlus> nodeConnectors;
+       
+       Map <String, NodeConnectorPlus> table;
+       @XmlElement(name="node")
+       Node n;
+       
+       public NodeTable(Node n) {
+               this.n = n;
+               table = new ConcurrentHashMap<String, NodeConnectorPlus>();
+               nodeConnectors = new HashSet<NodeConnectorPlus>();
+       }
+       
+       private String createSourceKey(Packet pkt, LearningSwitchOptions lo) {
+               String key = "";
+
+               if ( pkt instanceof Ethernet &&                         
+                               (lo.options & LearningSwitchOptions.SRC_MAC) > 0) {
+                       
+                       key += HexEncode.bytesToHexString(((Ethernet)pkt).getSourceMACAddress());
+               } 
+               if ((lo.options & LearningSwitchOptions.SRC_IPv4) > 0) {
+                       if ((Packet)pkt instanceof IPv4) {
+                               key += String.valueOf(((IPv4)pkt).getSourceAddress());
+                       }
+               }
+               // Remaining checks not implemented
+
+               return key;
+       }
+       
+       private String createDestKey(Packet pkt, LearningSwitchOptions lo) {
+               String key = "";
+
+               if ( pkt instanceof Ethernet &&                         
+                               (lo.options & LearningSwitchOptions.DST_MAC) > 0) {
+                       key += HexEncode.bytesToHexString(((Ethernet)pkt).getDestinationMACAddress());
+               } 
+               if ((lo.options & LearningSwitchOptions.DST_IPv4) > 0) {
+                       if ((Packet)pkt instanceof IPv4) {
+                               key += String.valueOf(((IPv4)pkt).getDestinationAddress());
+                       }
+               }
+               // Remaining checks not implemented
+
+               return key;
+       }
+
+       
+       public void learnSourceNodeConnector(Packet pkt, NodeConnector incomingNodeConnector, LearningSwitchOptions lo)
+       {
+               String key = createSourceKey(pkt, lo);
+               
+               NodeConnectorPlus ncplus = table.get(key);
+               if (ncplus == null) {
+                       ncplus = new NodeConnectorPlus(incomingNodeConnector);
+                       ncplus.history.add(new Pair<String, Date>(key, new Date()));
+                       table.put(key, ncplus);
+               } else {
+                       if (!ncplus.nc.equals(incomingNodeConnector)) {
+                               ncplus.nc = incomingNodeConnector;
+                       }
+                       ncplus.history.add(new Pair<String, Date>(key, new Date()));
+               }
+       }
+       
+       public NodeConnector findDestinationNodeConnector(Packet pkt, LearningSwitchOptions lo) {
+               String key = createDestKey(pkt, lo);
+               NodeConnectorPlus ncplus = table.get(key);
+               if (ncplus != null) {
+                       return ncplus.nc;
+               } else {
+                       return null;
+               }
+       }
+       
+       @XmlElement(name="table")
+       public List<Pair<String, NodeConnectorPlus>> getNodeTable()
+       {
+               List<Pair<String, NodeConnectorPlus>> t = new ArrayList<Pair<String, NodeConnectorPlus>>();
+               for (Map.Entry<String, NodeConnectorPlus> entry : table.entrySet()) {
+                       Pair<String, NodeConnectorPlus> p = new Pair<String, NodeConnectorPlus>(entry.getKey(), entry.getValue());
+                       t.add(p);
+               }
+               return t;
+       }
+       
+       
+       public String toString() {
+               return table.toString();
+       }
+       
+       public List<NodeConnector> getNodeConnectors()
+       {
+               List<NodeConnector> list_nc = new ArrayList<NodeConnector>();
+               for (NodeConnectorPlus ncp : nodeConnectors) {
+                       list_nc.add(ncp.nc);
+               }
+               return list_nc;
+       }
+       
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/Table.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/Table.java
new file mode 100644 (file)
index 0000000..b4bdc0e
--- /dev/null
@@ -0,0 +1,93 @@
+package org.sdnhub.odl.learningswitch;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap; 
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.sdnhub.odl.learningswitch.NodeTable;
+import org.sdnhub.odl.learningswitch.LearningSwitchOptions;
+
+@XmlRootElement(name="LearningTable")
+@XmlAccessorType(XmlAccessType.NONE)
+public class Table {
+       
+       Map <Node, NodeTable> table;
+       
+       public Table()
+       {
+               table = new ConcurrentHashMap<Node, NodeTable>();
+       }
+       
+       public void addNode(Node n)
+       {
+               NodeTable nt = (NodeTable) table.get(n);
+               if (nt == null) {
+                       NodeTable new_nt = new NodeTable(n);
+                       table.put(n,  new_nt);
+               }
+       }
+       
+       public void deleteNode(Node n)
+       {
+               NodeTable nt = (NodeTable) table.get(n);
+               if (nt != null) {
+                       table.remove(n);
+               }
+       }
+       
+       public void learnSourceFields(Packet pkt, NodeConnector incomingNodeConnector, LearningSwitchOptions lo) throws NoSuchFieldException
+       {
+               NodeTable nt = (NodeTable) table.get(incomingNodeConnector.getNode());
+               if (nt == null) {
+                       throw new NoSuchFieldException("This node was not found in the node table");
+               }
+               
+               nt.learnSourceNodeConnector(pkt, incomingNodeConnector, lo);
+       }
+       
+       public NodeConnector getDestinationNodeConnector(Packet pkt,  Node incomingNode, LearningSwitchOptions lo) throws NoSuchFieldException
+       {
+               NodeTable nt = (NodeTable) table.get(incomingNode);
+               if (nt == null) {
+                       throw new NoSuchFieldException("This node was not found in the node table");
+               }
+
+               return nt.findDestinationNodeConnector(pkt, lo);
+       }
+       public String toString() {
+               return this.table.toString();
+       }
+       
+       public void clear()
+       {
+               table.clear();
+       }
+       
+       @XmlElement(name="table")
+       public List<NodeTable> getTable()
+       {
+               List<NodeTable> l = new ArrayList<NodeTable>();
+               for (Map.Entry<Node, NodeTable> e : table.entrySet()) {
+                       l.add(e.getValue());
+               }
+               return l;
+       }
+       
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/internal/Activator.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/internal/Activator.java
new file mode 100644 (file)
index 0000000..1151b1a
--- /dev/null
@@ -0,0 +1,123 @@
+
+package org.sdnhub.odl.learningswitch.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.sdnhub.odl.learningswitch.ILearningSwitch;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+
+
+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 = { LearningSwitch.class };
+        return res;
+    }*/
+    public Object[] getImplementations() {
+        // TODO: Call your Class.class
+
+        Object[] res = { LearningSwitch.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(LearningSwitch.class)) {
+            Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+            String interfaces[] = null;
+            interfaces = new String[] { ILearningSwitch.class.getName() };
+            c.setInterface(interfaces, props);
+        }
+    }
+*/    
+    public void configureInstance(Component c, Object imp, String containerName) {
+
+        // TODO: configure instance
+       log.info("Learningswithc activator - container name: {}", containerName);
+
+        if (imp.equals(LearningSwitch.class)) {
+                  // export the services
+            Dictionary<String, String> props = new Hashtable<String, String>();
+            props.put("salListenerName", "LearningSwitch");
+            c.setInterface(new String[] {   IListenDataPacket.class.getName(), 
+                                            ILearningSwitch.class.getName(),
+                                            IInventoryListener.class.getName()}, props);
+
+            // register dependent modules
+            c.add(createContainerServiceDependency(containerName).setService(
+                    ISwitchManager.class).setCallbacks("setSwitchManager",
+                    "unsetSwitchManager").setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IDataPacketService.class).setCallbacks(
+                    "setDataPacketService", "unsetDataPacketService")
+                    .setRequired(true));
+
+            c.add(createContainerServiceDependency(containerName).setService(
+                    IFlowProgrammerService.class).setCallbacks(
+                    "setFlowProgrammerService", "unsetFlowProgrammerService")
+                    .setRequired(true));
+
+        }
+    }
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/internal/LearningSwitch.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/internal/LearningSwitch.java
new file mode 100644 (file)
index 0000000..2711bf4
--- /dev/null
@@ -0,0 +1,265 @@
+
+package org.sdnhub.odl.learningswitch.internal;
+
+import org.sdnhub.odl.learningswitch.ILearningSwitch;
+import org.sdnhub.odl.learningswitch.LearningSwitchOptions;
+import org.sdnhub.odl.learningswitch.MacTable;
+import org.sdnhub.odl.learningswitch.MacTable.MacPortTableElem;
+import org.sdnhub.odl.learningswitch.Table;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchField;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.packet.BitBufferHelper;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.IPv4;
+import org.opendaylight.controller.sal.packet.TCP;
+import org.opendaylight.controller.sal.packet.UDP;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.opendaylight.controller.sal.packet.PacketResult;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+public class LearningSwitch implements IListenDataPacket, ILearningSwitch, IInventoryListener {
+    protected static final Logger logger = LoggerFactory.getLogger(LearningSwitch.class);
+    private IDataPacketService dataPacketService = null;
+    private ISwitchManager switchManager = null;
+    private IFlowProgrammerService programmer = null;
+    
+    private Table table = null;
+    private LearningSwitchOptions src_opts = null, dst_opts = null;
+
+    private String function = "switch";
+
+    void init() {
+        logger.info("Initializing Simple application");
+        table = new Table();
+        src_opts = new LearningSwitchOptions();
+        src_opts.options  |= LearningSwitchOptions.SRC_MAC;
+        dst_opts = new LearningSwitchOptions();
+        dst_opts.options  |= LearningSwitchOptions.DST_MAC;
+    }
+    void start() {
+        logger.info("Simple application starting");
+    }
+
+    void stop() {
+        logger.info("Simple application stopping");
+    }
+
+    void setDataPacketService(IDataPacketService s) {
+        this.dataPacketService = s;
+    }
+
+    void unsetDataPacketService(IDataPacketService s) {
+        if (this.dataPacketService == s) {
+            this.dataPacketService = null;
+        }
+    }
+
+    public void setFlowProgrammerService(IFlowProgrammerService s)
+    {
+        this.programmer = s;
+    }
+
+    public void unsetFlowProgrammerService(IFlowProgrammerService s) {
+        if (this.programmer == s) {
+            this.programmer = null;
+        }
+    }
+
+    public void notifyNode(Node node, UpdateType type,
+            Map<String, Property> propMap) {
+       if (type == UpdateType.ADDED)
+               this.table.addNode(node);
+       else if (type == UpdateType.REMOVED)
+               this.table.deleteNode(node);
+
+    }
+
+    public void notifyNodeConnector(NodeConnector nodeConnector,
+            UpdateType type, Map<String, Property> propMap) {
+
+//     if (type == UpdateType.ADDED)
+//            this.macToPortTable.initNodeConnector(nodeConnector);
+//        else if (type == UpdateType.REMOVED)
+//            this.macToPortTable.clearNodeConnector(nodeConnector);
+
+    }
+
+    void setSwitchManager(ISwitchManager s) {
+        logger.debug("SwitchManager set");
+        this.switchManager = s;
+    }
+
+    void unsetSwitchManager(ISwitchManager s) {
+        if (this.switchManager == s) {
+            logger.debug("SwitchManager removed!");
+            this.switchManager = null;
+        }
+    }
+
+    private void floodPacket(RawPacket inPkt) {
+       NodeConnector incoming_connector = inPkt.getIncomingNodeConnector();
+        Node incoming_node = incoming_connector.getNode();
+
+        Set<NodeConnector> nodeConnectors =
+                this.switchManager.getUpNodeConnectors(incoming_node);
+
+        for (NodeConnector nc : nodeConnectors) {
+            if (!nc.equals(incoming_connector) &&
+                       (nc.getType() != NodeConnector.NodeConnectorIDType.SWSTACK)) {
+                try {
+                    RawPacket destPkt = new RawPacket(inPkt);
+                    destPkt.setOutgoingNodeConnector(nc);
+                    this.dataPacketService.transmitDataPacket(destPkt);
+                } catch (ConstructionException e2) {
+                    continue;
+                }
+            }
+        }
+    }
+
+    @Override
+    public PacketResult receiveDataPacket(RawPacket inPkt) {
+        if (inPkt == null) {
+            return PacketResult.IGNORED;
+        }
+
+        Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
+        if (!(formattedPak instanceof Ethernet)) {
+            return PacketResult.IGNORED;
+        }
+
+        //Ignore LLDP packets. They should never be flooded out
+        Ethernet etherPak = (Ethernet)formattedPak;
+        if (etherPak.getEtherType() == EtherTypes.LLDP.intValue())
+            return PacketResult.IGNORED;
+
+        // Hub implementation
+        if (function.equals("hub")) {
+            floodPacket(inPkt);
+        } else {
+            NodeConnector incoming_connector = inPkt.getIncomingNodeConnector();
+            
+            try {
+                       this.table.learnSourceFields((Packet)etherPak, incoming_connector, src_opts);
+//                     logger.info("Table entries: {}" + this.table.toString());
+               } catch (NoSuchFieldException e) {
+                       // TODO Auto-generated catch block
+                       logger.error("Error learning source fields: {}", e.toString());
+               }
+            
+            NodeConnector outgoing_connector = null;
+            try {
+                       outgoing_connector = this.table.getDestinationNodeConnector((Packet)etherPak, incoming_connector.getNode(), dst_opts);
+               } catch (NoSuchFieldException e) {
+                       // TODO Auto-generated catch block
+                       logger.error("Error learning source fields: {}", e.toString());
+               }
+                        
+            if (outgoing_connector == null) {
+                floodPacket(inPkt);
+            } else {
+                if (!programFlow(etherPak, incoming_connector,
+                            outgoing_connector)) {
+                    return PacketResult.IGNORED;
+                }
+            }
+        }
+        return PacketResult.CONSUME;
+    }
+
+    private boolean programFlow(Ethernet etherPak,
+            NodeConnector incoming_connector,
+            NodeConnector outgoing_connector) {
+        byte[] dstMAC = etherPak.getDestinationMACAddress();
+
+        Match match = new Match();
+        match.setField(new MatchField(MatchType.IN_PORT, incoming_connector) );
+        match.setField(new MatchField(MatchType.DL_DST, dstMAC.clone()));
+
+        List<Action> actions = new ArrayList<Action>();
+        actions.add(new Output(outgoing_connector));
+
+        Flow f = new Flow(match, actions);
+        f.setIdleTimeout((short)5);
+
+        // Modify the flow on the network node
+        Node incoming_node = incoming_connector.getNode();
+        Status status = programmer.addFlow(incoming_node, f);
+
+        if (!status.isSuccess()) {
+            logger.warn("SDN Plugin failed to program the flow: {}. The failure is: {}",
+                    f, status.getDescription());
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public Table  getData()
+    {
+        //return macToPortTable.getMap();
+       return this.table;
+    }
+    
+    @Override
+    public void deleteData()
+    {
+       this.table.clear();
+    }
+    
+    
+    @Override
+    public String getFunction() {
+       return this.function;
+    }
+    
+    @Override
+    public Boolean setFunction(String fn) {
+       Boolean ret = Boolean.TRUE;
+       if (fn.equals("hub")) {
+               function = "hub";
+       } else if (fn.equals("switch")) {
+               function = "switch";
+       } else { 
+               ret = Boolean.FALSE;
+       }
+       return ret;
+    }
+}
diff --git a/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/northbound/AppNorthbound.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/northbound/AppNorthbound.java
new file mode 100644 (file)
index 0000000..4a9b380
--- /dev/null
@@ -0,0 +1,466 @@
+
+package org.sdnhub.odl.learningswitch.northbound;
+
+import org.sdnhub.odl.learningswitch.ILearningSwitch;
+import org.sdnhub.odl.learningswitch.Table;
+import org.sdnhub.odl.learningswitch.MacTable.MacPortTableElem;
+import org.sdnhub.odl.learningswitch.internal.LearningSwitch;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.sal.reader.IReadService;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.HexEncode;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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 javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+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.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Description;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+
+
+
+
+/**
+ * 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;
+       }
+
+
+
+       @XmlRootElement(name="SwitchFunction")
+       class SwitchFunction {
+               @XmlElement(name="function")
+               String function;
+               public SwitchFunction(String fn) {
+                       function = fn;
+               }
+       }
+
+       /**
+        *
+        * Get current function (hub or switch) - GET REST API call
+        *
+        * @return A response string
+        *
+        * <pre>
+        * Example:
+        *
+        * Request URL:
+        * http://localhost:8080/app/northbound/learningswitch/function/
+        *
+        * Response body in JSON:
+        * { "function" : "hub"}
+        * </pre>
+        */
+       @Path("/learningswitch/function/")
+       @GET
+       @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes()   
+       public SwitchFunction getFunction() {
+               if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+                       throw new UnauthorizedException("User is not authorized to perform this operation");
+               }
+               //LearningSwitch simple = (LearningSwitch) ServiceHelper.getInstance(LearningSwitch.class, "default", this);
+               ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getInstance(ILearningSwitch.class, "default", this);
+               if (simple == null) {
+                       throw new ServiceUnavailableException("LearningSwitch Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               SwitchFunction sf = new SwitchFunction(simple.getFunction());
+               return sf;
+       }
+
+       /**
+        *
+        * Set current function (hub or switch) - PUT REST API call
+        *
+        * @return Response
+        *
+        * <pre>
+        * Example:
+        *
+        * Request URL:
+        * http://localhost:8080/app/northbound/learningswitch/function
+        *
+        * </pre>
+        */
+       @Path("/learningswitch/function/")
+       @PUT
+       @Consumes({ MediaType.APPLICATION_JSON})
+       @StatusCodes()
+       public Response setFunction(@TypeHint(SwitchFunction.class) SwitchFunction fn) {
+               if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+                       throw new UnauthorizedException("User is not authorized to perform this operation");
+               }
+               ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getInstance(ILearningSwitch.class, "default", this);
+               if (simple == null) {
+                       throw new ServiceUnavailableException("LearningSwitch Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               if (simple.setFunction(fn.function) == true) {
+                       return Response.status(Response.Status.OK).build();
+               } else {
+                       return Response.status(Response.Status.NOT_FOUND).build();
+               }
+       }
+
+
+
+       /**
+        *
+        * Sample GET REST API call
+        *
+        * @return A response string
+        *
+        * <pre>
+        * Example:
+        *
+        * Request URL:
+        * http://localhost:8080/app/northbound/learningswitch/mactable
+        *
+        * 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("/learningswitch/table")
+       @GET
+       @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes()
+       public Table  getTableEntries() {
+               if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+                       throw new UnauthorizedException("User is not authorized to perform this operation");
+               }
+               //LearningSwitch simple = (LearningSwitch) ServiceHelper.getInstance(LearningSwitch.class, "default", this);
+               ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getInstance(ILearningSwitch.class, "default", this);
+               if (simple == null) {
+                       throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               return simple.getData();
+       }
+
+
+       /**
+        *
+        * Sample Delete REST API call
+        *
+        * @return A response string
+        *
+        *         <pre>
+        * Example:
+        *
+        * Request URL:
+        * http://localhost:8080/app/northbound/learningswitch/{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("/learningswitch/table")
+       @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 deleteTableEntries() {
+               if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+                       throw new UnauthorizedException("User is not authorized to perform this operation");
+               }
+               ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.class, this);
+               if (simple == null) {
+                       throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+               }
+
+               simple.deleteData();
+               return Response.status(Response.Status.OK).build();
+       }
+
+
+       @XmlRootElement(name="NodeToFlowEntries")
+       class NodeToFlowEntries {
+               @XmlElement(name="node")
+               String node;
+               @XmlElement(name="flows")
+               List <String> flows;
+               public NodeToFlowEntries() {
+                       flows = new ArrayList<String>();
+               }
+       }
+
+       @Path("/learningswitch/flowtable")
+       @GET
+       @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       @StatusCodes()
+       public List<NodeToFlowEntries> getNodeFlows() {
+               if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+                       throw new UnauthorizedException("User is not authorized to perform this operation");
+               }
+               ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, "default",
+                               this);
+               if (switchManager == null) {
+                       return null;
+               }   
+               IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper.getInstance(
+                               IForwardingRulesManager.class, "default", this);
+               if (frm == null) {
+                       return null;
+               }   
+
+               IReadService rds = (IReadService) ServiceHelper.getInstance(IReadService.class, "default", this);
+
+               List< NodeToFlowEntries > output = new ArrayList<NodeToFlowEntries>();
+               //Map<String, Object> output = new HashMap<String, Object>(2);
+
+               for (Switch sw : switchManager.getNetworkDevices()) {
+                       Node node = sw.getNode();
+                       NodeToFlowEntries nfentries = new NodeToFlowEntries();
+
+                       nfentries.node = node.toString();
+
+                       //List<FlowConfig> staticFlowList = frm.getStaticFlows(node);
+                       List<FlowOnNode> flowList = rds.readAllFlows(node);
+                       for (FlowOnNode flow : flowList) {
+                               nfentries.flows.add(flow.getFlow().toString());
+                       }   
+                       output.add(nfentries);
+               }
+               return output;
+       }
+
+       private String getNodeDesc(Node node, ISwitchManager switchManager) {
+               Description desc = (Description) switchManager.getNodeProp(node, Description.propertyName);
+               String description = (desc == null) ? "" : desc.getValue();
+               return (description.isEmpty() || description.equalsIgnoreCase("none")) ? node.toString() : description;
+       }
+
+
+
+       //    @Path("/learningswitch/{uuid}")
+       //    @GET
+       //    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+       //    @TypeHint(MacToPortTable.class)
+       //    @StatusCodes()
+       //    public MacToPortTable getData(@PathParam("uuid") String uuid) {
+       //        if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+       //            throw new UnauthorizedException("User is not authorized to perform this operation");
+       //        }
+       //        ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.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/learningswitch
+        *
+        * 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("/learningswitch")
+       //    @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(MacToPortTable.class) MacToPortTable data) {
+       //        if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+       //            throw new UnauthorizedException("User is not authorized to perform this operation");
+       //        }     
+       //        ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.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/learningswitch/{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("/learningswitch/{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(MacToPortTable.class) MacToPortTable data) {
+       //       if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+       //           throw new UnauthorizedException("User is not authorized to perform this operation");
+       //       }
+       //       ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.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/learningswitch/{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("/learningswitch/{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");
+       //      }
+       //      ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.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/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/web/AppWeb.java b/samples/learningswitch/src/main/java/org/sdnhub/odl/learningswitch/web/AppWeb.java
new file mode 100644 (file)
index 0000000..6631f8b
--- /dev/null
@@ -0,0 +1,58 @@
+package org.sdnhub.odl.learningswitch.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;
+
+/**
+ * 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 = "learningswitch App";
+    private static final String WEB_ID = "learningswitch";
+    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/samples/learningswitch/src/main/resources/META-INF/spring.factories b/samples/learningswitch/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/samples/learningswitch/src/main/resources/META-INF/spring.handlers b/samples/learningswitch/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/samples/learningswitch/src/main/resources/META-INF/spring.schemas b/samples/learningswitch/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/samples/learningswitch/src/main/resources/META-INF/spring.tooling b/samples/learningswitch/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/samples/learningswitch/src/main/resources/WEB-INF/AppWeb-servlet.xml b/samples/learningswitch/src/main/resources/WEB-INF/AppWeb-servlet.xml
new file mode 100644 (file)
index 0000000..5dbba91
--- /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.sdnhub.odl.learningswitch"/>
+
+  <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/samples/learningswitch/src/main/resources/WEB-INF/jsp/main.jsp b/samples/learningswitch/src/main/resources/WEB-INF/jsp/main.jsp
new file mode 100644 (file)
index 0000000..7ee97d6
--- /dev/null
@@ -0,0 +1,88 @@
+<!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="/learningswitch/web/css/simple.css"/>
+
+        <!-- scripts -->
+        <script data-main="/learningswitch/web/js/main" src="/js/ext/requirejs/require.js"></script>
+
+    </head>
+
+    <body>
+
+
+        <h2> Learning Switch </h2>
+
+        <h4> Currently functioning as <div id="mode"></div> </h4>
+        <button id="togglemode" class="pure-button
+            button-success">Toggle</button>
+
+        <h3> Select node </h3>
+        <select id="nodeselect"> </select>
+        <button id="refreshNodeList" class="pure-button
+            button-success">Refresh Node List</button>
+
+        <!-- full-width outer box  -->
+
+        <h3> Switch Mac Table </h3>
+
+        <div class="pure-g">
+            <div class="pure-u-1 pure-u-md-1-1"> 
+                <!-- split-width inner box for heaer  -->
+                <table class="pure-table" id="mactable" style="">
+                    <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>MAC</th>
+                            <th>Nodeconnector</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                </table>
+
+            </div> 
+        </div> <!-- outer box ends -->
+        <p />
+        <button id="refreshMacTable" class="pure-button button-success">Refresh</button>
+        <button id="clearMacTable" class="pure-button button-error">Clear</button>
+
+
+        <p /><p />
+
+
+
+        <h3>Flow tables</h3>
+        <div class="pure-g">
+            <div class="pure-u-1 pure-u-md-1-1"> 
+                <table class="pure-table" id="flowtable" style="">
+                    <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Flow Details</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+        <p />
+        <button id="refreshFlowTable" class="pure-button button-success">Refresh</button>
+
+
+
+
+    </body>
+</html>
diff --git a/samples/learningswitch/src/main/resources/WEB-INF/web.xml b/samples/learningswitch/src/main/resources/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..8233639
--- /dev/null
@@ -0,0 +1,138 @@
+<?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">
+
+  <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>App</web-resource-name>
+      <url-pattern>/*</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>
+    </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>
+
+  <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>/northbound/*</url-pattern>
+  </filter-mapping>
+
+</web-app>
diff --git a/samples/learningswitch/src/main/resources/css/.simple.css.swp b/samples/learningswitch/src/main/resources/css/.simple.css.swp
new file mode 100644 (file)
index 0000000..c7207be
Binary files /dev/null and b/samples/learningswitch/src/main/resources/css/.simple.css.swp differ
diff --git a/samples/learningswitch/src/main/resources/css/bower.json b/samples/learningswitch/src/main/resources/css/bower.json
new file mode 100644 (file)
index 0000000..87e64a2
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "name" : "OpenDaylight Phoenix External CSS",
+  "version" : "0.0.1",
+  "dependencies" : {
+    "pure" : "v0.4.2"
+  }
+}
diff --git a/samples/learningswitch/src/main/resources/css/simple.css b/samples/learningswitch/src/main/resources/css/simple.css
new file mode 100644 (file)
index 0000000..d24bf68
--- /dev/null
@@ -0,0 +1,63 @@
+/* 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 */
+}
+
+
+fieldset {
+}
+
+fieldset > label {
+  display: block;
+}
+
+fieldset > input {
+  width: 100%;
+}
+
+#simpleContainer {
+  margin-top: 15px;
+}
+body {
+    background: #fff;
+    padding-top:20px;
+    padding-left:30px;
+    padding-right:50px;
+    padding-bottom:10px;
+    background-color:#efefef;
+}
+
+h2 {
+  color: #646464;
+}
+
+h3 {
+  color: #464646;
+}
+
+ol > li {
+  padding: 2px 0;
+}
+
+code {
+  background: #F3F781;
+  padding: 2px 4px;
+  font-weight: bold;
+  color: #000;
+  border-radius: 2px;
+}
diff --git a/samples/learningswitch/src/main/resources/js/.main.js.swp b/samples/learningswitch/src/main/resources/js/.main.js.swp
new file mode 100644 (file)
index 0000000..7f6205e
Binary files /dev/null and b/samples/learningswitch/src/main/resources/js/.main.js.swp differ
diff --git a/samples/learningswitch/src/main/resources/js/app.js b/samples/learningswitch/src/main/resources/js/app.js
new file mode 100644 (file)
index 0000000..4ac464f
--- /dev/null
@@ -0,0 +1,220 @@
+//Filename: app.js
+
+define
+([
+ // These are path alias that we configured in our bootstrap
+ 'jquery',
+ 'jquery-ui',
+ 'underscore',
+ ], function($, _) 
+ { 
+   // ajax settings
+   $.ajaxSetup({
+   data: {}, 
+   dataType: 'json',
+   xhrFields: {
+     withCredentials: true
+   },  
+   crossDomain: true,
+   success: 'callback',
+   headers: {
+     'Accept': 'application/json',
+   'Content-Type': 'application/json'
+   }   
+   }); 
+
+   var controller_uri = "http://127.0.0.1:8080";
+
+   var switchfunction = "";
+
+
+   var nodelist = [];
+
+   var mactable =  {
+     findNode: function (nodename) 
+     {
+       var ret = [];
+       if (!mactable.obj) {
+         return undefined;
+       }
+
+       nodelist = [];
+       $.each(mactable.obj.table, function(i, entry) {
+         if (entry.node) {
+           var nodenm =  entry.node.type + "|" + entry.node.id;
+           nodelist.push(nodenm);
+           if (nodenm == nodename) {
+             $.each(entry.table, function(ii, elem) {
+               var x = {};
+               x.mac = elem.first;
+               x.nodeconnector = elem.second.nodeconnector.type + "|" + elem.second.nodeconnector.id;
+               ret.push(x);
+             });
+           }
+         }
+       });
+       return ret;
+     },
+
+     obj: undefined
+   };
+
+   var flowtable = {
+     findNode:  function(nodename) 
+     {
+       var ret = [];
+       if (!flowtable.obj) {
+         return undefined;
+       }
+
+       nodelist = [];
+       $.each(flowtable.obj, function(i, entry) {
+         if (entry.node)  {
+
+           nodelist.push(entry.node);
+
+           if (entry.node == nodename) {
+             $.each(entry.flows, function(ii, flow) {
+               ret.push(flow);
+             });
+           }
+         }
+       });
+       return ret;
+     },
+     obj: undefined
+   };
+
+   function populateFlowTable(data) {
+
+     var tbody = $('#flowtable tbody');
+     var rows = $('#flowtable tbody > tr');
+     rows.remove();
+
+     var selectednode = $("#nodeselect option:selected").val();
+     var data = flowtable.findNode(selectednode);
+     console.log("flow table data: " + JSON.stringify(data));
+
+     $.each(data, function(i, elem) {
+       var tr = $(document.createElement('tr'));
+       tr.append($(document.createElement('td')).append(i));
+       tr.append($(document.createElement('td')).append(elem));
+       tbody.append(tr);
+       console.log("appending : " + tr);
+     }); 
+   }
+
+   function populateMacTable(data) {
+     var tbody = $('#mactable tbody');
+     var rows = $('#mactable tbody > tr');
+     rows.remove();
+
+     var selectednode = $("#nodeselect option:selected").val();
+     var data = mactable.findNode(selectednode);
+     console.log("mac table data: " + JSON.stringify(data));
+
+     $.each(data, function(i, elem) {
+       var tr = $(document.createElement('tr'));
+
+       tr.append($(document.createElement('td')).append(i));
+       tr.append($(document.createElement('td')).append(elem.mac));
+       tr.append($(document.createElement('td')).append(elem.nodeconnector));
+
+       tbody.append(tr);
+       console.log("appending : " + tr);
+     }); 
+   }
+
+   // bind form submit
+   //  $('button').click(function() {
+   //          var simple = {}; 
+   //          simple.foo = $('#foo').val();
+   //          simple.bar = $('#bar').val();
+   //          $.post('http://127.0.0.1:8080/simple/northbound/simple', JSON.stringify(simple), function(result) {
+   //                  console.log(result);
+   //          });
+   //  });
+
+   function refreshNodeList() {
+     var foo = flowtable.findNode("asdf"); // dummy to get node list to refresh
+     var select = $("#nodeselect");
+     var selectoptions = $("#nodeselect option");
+     selectoptions.remove();
+     $.each(nodelist, function(i, node) {
+       select.append('<option value="' + node + '">' + node + '</option>');
+     });
+   }
+
+   function updateMode() {
+       $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/function', function(result) {
+           console.log("Got data " + JSON.stringify(result)); 
+           switchfunction = result.function;
+           $("#mode").html(result.function); 
+       });
+   }
+
+   $('#refreshNodeList').click(function() {
+     // populate table
+     $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/flowtable', function(result) {
+       flowtable.obj = result;
+       refreshNodeList();
+       });
+   });
+
+   $('#refreshMacTable').click(function() {
+     // populate table
+     $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/table', function(result) {
+       console.log("Got result: " + JSON.stringify(result));
+       mactable.obj = result;
+       populateMacTable();
+       });
+   });
+
+
+   $('#refreshFlowTable').click(function() {
+     // populate table
+     $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/flowtable', function(result) {
+       console.log("Got result: " + JSON.stringify(result));
+       flowtable.obj = result;
+       populateFlowTable();
+       });
+   });
+
+   $('#clearFlowTable').click(function() {
+     // populate table
+     $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/flowtable', function(result) {
+       console.log("Got result: " + JSON.stringify(result));
+       flowtable.obj = result;
+       populateFlowTable();
+       });
+   });
+
+   $("#nodeselect").change(function() {
+     populateMacTable();
+     populateFlowTable();
+   });
+
+   $('#clearMacTable').click(function() {
+     // populate table
+     $.ajax({
+       url: controller_uri + '/learningswitch/northbound/learningswitch/table',
+       type: 'DELETE',
+        });
+
+     updateMode();
+   });
+
+   $('#togglemode').click(function() {
+     $.ajax({
+         type: 'PUT',
+         url: controller_uri + '/learningswitch/northbound/learningswitch/function',
+         contentType: 'application/json',
+         data: {"function": (switchfunction.function == "hub" ? "switch" : "hub") }
+     });
+
+     updateMode();
+   });
+
+   $(updateMode());
+
+});
diff --git a/samples/learningswitch/src/main/resources/js/collections/FlowCollection.js b/samples/learningswitch/src/main/resources/js/collections/FlowCollection.js
new file mode 100644 (file)
index 0000000..03d41b3
--- /dev/null
@@ -0,0 +1,13 @@
+define(['backbone','underscore','/learningswitch/web/js/models/FlowModel.js'], 
+               function(Backbone, _, FlowModel) {
+       var FlowCollection = Backbone.Collection.extend({
+               model : FlowModel,
+               url : '/learningswitch/northbound/learningswitch/flowtable',
+               parse: function(data) {
+                       console.log("Received data" + data);
+                       
+               }
+                       
+       });
+       return FlowCollection;
+});
diff --git a/samples/learningswitch/src/main/resources/js/collections/SimpleCollection.js b/samples/learningswitch/src/main/resources/js/collections/SimpleCollection.js
new file mode 100644 (file)
index 0000000..732426b
--- /dev/null
@@ -0,0 +1,12 @@
+define(
+  [
+    'backbone',
+    'underscore',
+    '/learningswitch/web/js/models/SimpleModel.js'
+    ], function(Backbone, _, SimpleModel) {
+      var SimpleCollection = Backbone.Collection.extend({
+        model : SimpleModel,
+        url : '/learningswitch/northbound/learningswitch/mactable'
+      });
+      return SimpleCollection;
+    });
diff --git a/samples/learningswitch/src/main/resources/js/main.js b/samples/learningswitch/src/main/resources/js/main.js
new file mode 100644 (file)
index 0000000..06ff69f
--- /dev/null
@@ -0,0 +1,22 @@
+// 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', '/js/phoenix.js'
+], function(App, Phoenix) {
+    new Phoenix.initialize();
+
+});
diff --git a/samples/learningswitch/src/main/resources/js/models/FlowModel.js b/samples/learningswitch/src/main/resources/js/models/FlowModel.js
new file mode 100644 (file)
index 0000000..7f6915f
--- /dev/null
@@ -0,0 +1,17 @@
+define(['backbone', 'underscore'], function(Backbone, _) {
+  var FlowModel = Backbone.Model.extend({
+    idAttribute : 'node',
+    defaults : {
+      node : '',
+      flows : new Array()
+    },
+    initialize : function() {
+    },
+    setUrlRoot: function() {
+      this.urlRoot = '/learningswitch/northbound/learningswitch/flowtable';
+    }
+  });
+  return FlowModel;
+});
+
+
diff --git a/samples/learningswitch/src/main/resources/js/models/SimpleModel.js b/samples/learningswitch/src/main/resources/js/models/SimpleModel.js
new file mode 100644 (file)
index 0000000..d89d6e4
--- /dev/null
@@ -0,0 +1,15 @@
+define(['backbone', 'underscore'], function(Backbone, _) {
+  var SimpleModel = Backbone.Model.extend({
+    idAttribute : 'mac',
+    defaults : {
+      mac : '',
+      nodeconnector : ''
+    },
+    initialize : function() {
+    },
+    setUrlRoot: function() {
+      this.urlRoot = '/learningswitch/northbound/learningswitch/mactable';
+    }
+  });
+  return SimpleModel;
+});
diff --git a/samples/learningswitch/src/main/resources/js/templates/flowtemplate.html b/samples/learningswitch/src/main/resources/js/templates/flowtemplate.html
new file mode 100644 (file)
index 0000000..6696624
--- /dev/null
@@ -0,0 +1,24 @@
+<script type="text/template" id="flowContainer">
+  <div id="flowDiv" style="margin-left:20px; margin-right: 100px; float: left;">
+
+<h4>Programmed flows</h4>    
+     
+    <% _.each(flowdata, function(flowdata1) { %>
+<h5>Flows for <%= flowdata1.attributes.node %> </h5>
+<table id="programmedFlowTablefor<%= flowdata1.attributes.node %>" class="pure-table pure-table-bordered">
+   <thead>
+      <tr>
+         <th>Flow</th>
+      </tr>
+   </thead>
+   <tbody>
+      <% _.each(flowdata1.attributes.flows, function(flow) { %>
+      <tr>
+         <td><%= flow %></td>
+      </tr>
+      <% }); %>
+   </tbody>
+</table>
+<% }); %>
+</div>
+</script>
\ No newline at end of file
diff --git a/samples/learningswitch/src/main/resources/js/templates/simple.html b/samples/learningswitch/src/main/resources/js/templates/simple.html
new file mode 100644 (file)
index 0000000..7af1f77
--- /dev/null
@@ -0,0 +1,36 @@
+<script type="text/template" id="simpleContainer">
+  <div id="simpleDiv" style="margin-left:20px; margin-right:100px; float: left;">
+    <h3>Learning Switch </h3>
+    
+    <h4>Mac -> Port Table</h4>
+    <table id="simpleTable" class="pure-table pure-table-bordered">
+      <thead>
+        <tr>
+          <th>MAC Address</th>
+          <th>Port ID</th>
+          <th>Actions</th>
+        </tr>
+      </thead>
+      <tbody>
+        <% _.each(simple, function(simpleton) { %>
+          <tr data-id="<%= simpleton.mac %>">
+            <td><%= simpleton.attributes.mac %></td>
+            <td><%= simpleton.attributes.connector %></td>
+            <td><button id="removeMacPortEntry" class="pure-button button-error" onclick="return false;">Remove</button></td>
+          </tr>
+        <% }); %>
+          <tr>
+              <td><input type="text" id="macInput" placeholder="Mac addr, like 00:00:72:41:4c:e6:c2:01" /></td>
+              <td><input type="text" id="ncInput" placeholder="Port info, like OF|1@OF|00:00:00:00:00:00:00:01" /></td>
+              <td><button id="addMacPortEntry" class="pure-button button-success" onclick="return false;">Add</button></td>
+          </tr>
+        
+      </tbody>
+    </table>
+   
+ </div>
+  
+  
+  
+  
+</script>
diff --git a/samples/learningswitch/src/main/resources/js/templates/test.html b/samples/learningswitch/src/main/resources/js/templates/test.html
new file mode 100644 (file)
index 0000000..0fd6f0c
--- /dev/null
@@ -0,0 +1,12 @@
+\r
+    <div id="main">\r
+               <div id="chart1"></div>    \r
+   </div>\r
+  \r
+  <script class="code" language="javascript" type="text/javascript">\r
+        \r
+         alert("page loaded! ");\r
+         \r
+         \r
+\r
+       </script>\r
diff --git a/samples/learningswitch/src/main/resources/js/views/FlowTableView.js b/samples/learningswitch/src/main/resources/js/views/FlowTableView.js
new file mode 100644 (file)
index 0000000..22aa1d9
--- /dev/null
@@ -0,0 +1,55 @@
+define(
+  [
+    'jquery',
+    'backbone',
+    'underscore',
+    '/learningswitch/web/js/models/FlowModel.js',
+    '/learningswitch/web/js/collections/FlowCollection.js',
+    '/js/ext/text/text.js!/learningswitch/web/js/templates/flowtemplate.html'
+    ], function($, Backbone, _,  FlowCollection, FlowModel, Template) {
+      var FlowTableView = Backbone.View.extend({
+        el: $("#flowtablediv"),
+        initialize: function() {
+          var self = this;
+            
+          this.flowcollection = new FlowCollection();
+          this.flowcollection.url = '/learningswitch/northbound/learningswitch/flowtable';
+          this.flowcollection.fetch({
+            success : function(call, response) {
+              self.renderflowtable();
+            }
+          });
+        },
+
+        renderflowtable: function() {
+            var that = this;
+            var compiledTemplate = _.template(Template, 
+            {
+               flowdata  : that.flowcollection.models
+            });
+            $(this.el).append($(compiledTemplate).html());
+          },
+        
+        events : {
+               'click #simpleContainer button' : 'handleSimpleButton',
+          'click #simpleTable tbody tr' : 'tableRowClicked'
+        },
+        
+        handleSwitchHubToggle : function(evt) {
+               debugger;
+               var self = this;
+            var $button = $(evt.currentTarget);
+            if ($button.attr('id') == 'toggleButton') {
+               $.get( "/learningswitch/northbound/learningswitch/toggle", function( data ) {
+                         $("#toggleButton").val(data);
+                       });
+            }
+        },
+        updateView : function() {
+          $('#flowContainer').remove();
+          this.initialize();
+        }
+      });
+      return FlowTableView;
+    });
diff --git a/samples/learningswitch/src/main/resources/js/views/View.js b/samples/learningswitch/src/main/resources/js/views/View.js
new file mode 100644 (file)
index 0000000..60ddc77
--- /dev/null
@@ -0,0 +1,89 @@
+define(
+  [
+    'jquery',
+    'backbone',
+    'underscore',
+    '/learningswitch/web/js/collections/SimpleCollection.js',
+    '/learningswitch/web/js/models/SimpleModel.js',
+    '/js/ext/text/text.js!/learningswitch/web/js/templates/simple.html'
+    ], function($, Backbone, _, SimpleCollection, SimpleModel, Template) {
+      var View = Backbone.View.extend({
+        el: $("#mactablediv"),
+        initialize: function() {
+          var self = this;
+          this.maccollection = new SimpleCollection();
+          this.maccollection.url = '/learningswitch/northbound/learningswitch/mactable';
+          this.maccollection.fetch({
+            success : function(call, response) {
+              self.rendermactable();
+            }
+          });
+
+  
+                 
+        },
+        
+        rendermactable: function() {
+          var that = this;
+          var compiledTemplate = _.template(Template, 
+          {
+            simple : that.maccollection.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 = '/learningswitch/northbound/learningswitch';
+            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;
+    });