Updates to the SDN-IP CLI:

 * Added command options to show summary of the routes:
   - "onos:routes -s" or "onos:routes --summary"
     shows summary of the SDN-IP routes
   - "onos:bgp-routes -s" or "onos:bgp-routes --summary"
     shows summary of the BGP routes

 * Implemented displaying JSON output for the "onos:routes" and
   "onos:bgp-routes" commands (and the routes summary)

Also, added static methods BgpConstants.Update.AsPath.typeToString()
and BgpConstants.Update.Origin.typeToString() to return the
BGP AS_PATH type and BGP UPDATE ORIGIN type as a string.

Change-Id: I505c55a924721838bbbaf4ffccc30ffd61e90120
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
index 92f4f07..596720c 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
@@ -119,6 +119,31 @@
 
             /** BGP UPDATE ORIGIN: INCOMPLETE. */
             public static final int INCOMPLETE = 2;
+
+            /**
+             * Gets the BGP UPDATE origin type as a string.
+             *
+             * @param type the BGP UPDATE origin type
+             * @return the BGP UPDATE origin type as a string
+             */
+            public static String typeToString(int type) {
+                String typeString = "UNKNOWN";
+
+                switch (type) {
+                case IGP:
+                    typeString = "IGP";
+                    break;
+                case EGP:
+                    typeString = "EGP";
+                    break;
+                case INCOMPLETE:
+                    typeString = "INCOMPLETE";
+                    break;
+                default:
+                    break;
+                }
+                return typeString;
+            }
         }
 
         /**
@@ -142,6 +167,28 @@
 
             /** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */
             public static final int AS_SEQUENCE = 2;
+
+            /**
+             * Gets the BGP AS_PATH type as a string.
+             *
+             * @param type the BGP AS_PATH type
+             * @return the BGP AS_PATH type as a string
+             */
+            public static String typeToString(int type) {
+                String typeString = "UNKNOWN";
+
+                switch (type) {
+                case AS_SET:
+                    typeString = "AS_SET";
+                    break;
+                case AS_SEQUENCE:
+                    typeString = "AS_SEQUENCE";
+                    break;
+                default:
+                    break;
+                }
+                return typeString;
+            }
         }
 
         /**
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
index cd36f72..203e03c 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
@@ -309,7 +309,7 @@
         @Override
         public String toString() {
             return MoreObjects.toStringHelper(getClass())
-                .add("type", this.type)
+                .add("type", BgpConstants.Update.AsPath.typeToString(type))
                 .add("segmentAsNumbers", this.segmentAsNumbers)
                 .toString();
         }
@@ -444,7 +444,7 @@
             .add("prefix", prefix())
             .add("nextHop", nextHop())
             .add("bgpId", bgpSession.getRemoteBgpId())
-            .add("origin", origin)
+            .add("origin", BgpConstants.Update.Origin.typeToString(origin))
             .add("asPath", asPath)
             .add("localPref", localPref)
             .add("multiExitDisc", multiExitDisc)
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/BgpRoutesListCommand.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/BgpRoutesListCommand.java
index 260cfe6..15cb554 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/BgpRoutesListCommand.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/BgpRoutesListCommand.java
@@ -15,10 +15,18 @@
  */
 package org.onlab.onos.sdnip.cli;
 
+import java.util.Collection;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.sdnip.SdnIpService;
-import org.onlab.onos.sdnip.bgp.BgpConstants;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Update.AsPath;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Update.Origin;
 import org.onlab.onos.sdnip.bgp.BgpRouteEntry;
 
 /**
@@ -27,46 +35,134 @@
 @Command(scope = "onos", name = "bgp-routes",
          description = "Lists all routes received from BGP")
 public class BgpRoutesListCommand extends AbstractShellCommand {
+    @Option(name = "-s", aliases = "--summary",
+            description = "BGP routes summary",
+            required = false, multiValued = false)
+    private boolean routesSummary = false;
 
-    private static final String FORMAT =
+    private static final String FORMAT_SUMMARY = "Total BGP routes = %d";
+    private static final String FORMAT_ROUTE =
             "prefix=%s, nexthop=%s, origin=%s, localpref=%s, med=%s, aspath=%s, bgpid=%s";
 
     @Override
     protected void execute() {
         SdnIpService service = get(SdnIpService.class);
 
-        for (BgpRouteEntry route : service.getBgpRoutes()) {
-            printRoute(route);
+        // Print summary of the routes
+        if (routesSummary) {
+            printSummary(service.getBgpRoutes());
+            return;
+        }
+
+        // Print all routes
+        printRoutes(service.getBgpRoutes());
+    }
+
+    /**
+     * Prints summary of the routes.
+     *
+     * @param routes the routes
+     */
+    private void printSummary(Collection<BgpRouteEntry> routes) {
+        if (outputJson()) {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode result = mapper.createObjectNode();
+            result.put("totalRoutes", routes.size());
+            print("%s", result);
+        } else {
+            print(FORMAT_SUMMARY, routes.size());
         }
     }
 
+    /**
+     * Prints all routes.
+     *
+     * @param routes the routes to print
+     */
+    private void printRoutes(Collection<BgpRouteEntry> routes) {
+        if (outputJson()) {
+            print("%s", json(routes));
+        } else {
+            for (BgpRouteEntry route : routes) {
+                printRoute(route);
+            }
+        }
+    }
+
+    /**
+     * Prints a BGP route.
+     *
+     * @param route the route to print
+     */
     private void printRoute(BgpRouteEntry route) {
         if (route != null) {
-            print(FORMAT, route.prefix(), route.nextHop(),
-                    originToString(route.getOrigin()), route.getLocalPref(),
-                    route.getMultiExitDisc(), route.getAsPath(),
-                    route.getBgpSession().getRemoteBgpId());
+            print(FORMAT_ROUTE, route.prefix(), route.nextHop(),
+                  Origin.typeToString(route.getOrigin()),
+                  route.getLocalPref(), route.getMultiExitDisc(),
+                  route.getAsPath(), route.getBgpSession().getRemoteBgpId());
         }
     }
 
-    private static String originToString(int origin) {
-        String originString = "UNKNOWN";
+    /**
+     * Produces a JSON array of routes.
+     *
+     * @param routes the routes with the data
+     * @return JSON array with the routes
+     */
+    private JsonNode json(Collection<BgpRouteEntry> routes) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
 
-        switch (origin) {
-        case BgpConstants.Update.Origin.IGP:
-            originString = "IGP";
-            break;
-        case BgpConstants.Update.Origin.EGP:
-            originString = "EGP";
-            break;
-        case BgpConstants.Update.Origin.INCOMPLETE:
-            originString = "INCOMPLETE";
-            break;
-        default:
-            break;
+        for (BgpRouteEntry route : routes) {
+            result.add(json(mapper, route));
         }
-
-        return originString;
+        return result;
     }
 
+    /**
+     * Produces JSON object for a route.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param route the route with the data
+     * @return JSON object for the route
+     */
+    private ObjectNode json(ObjectMapper mapper, BgpRouteEntry route) {
+        ObjectNode result = mapper.createObjectNode();
+
+        result.put("prefix", route.prefix().toString());
+        result.put("nextHop", route.nextHop().toString());
+        result.put("bgpId", route.getBgpSession().getRemoteBgpId().toString());
+        result.put("origin", Origin.typeToString(route.getOrigin()));
+        result.put("asPath", json(mapper, route.getAsPath()));
+        result.put("localPref", route.getLocalPref());
+        result.put("multiExitDisc", route.getMultiExitDisc());
+
+        return result;
+    }
+
+    /**
+     * Produces JSON object for an AS path.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param asPath the AS path with the data
+     * @return JSON object for the AS path
+     */
+    private ObjectNode json(ObjectMapper mapper, BgpRouteEntry.AsPath asPath) {
+        ObjectNode result = mapper.createObjectNode();
+        ArrayNode pathSegmentsJson = mapper.createArrayNode();
+        for (BgpRouteEntry.PathSegment pathSegment : asPath.getPathSegments()) {
+            ObjectNode pathSegmentJson = mapper.createObjectNode();
+            pathSegmentJson.put("type",
+                                AsPath.typeToString(pathSegment.getType()));
+            ArrayNode segmentAsNumbersJson = mapper.createArrayNode();
+            for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+                segmentAsNumbersJson.add(asNumber);
+            }
+            pathSegmentJson.put("segmentAsNumbers", segmentAsNumbersJson);
+            pathSegmentsJson.add(pathSegmentJson);
+        }
+        result.put("pathSegments", pathSegmentsJson);
+
+        return result;
+    }
 }
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/RoutesListCommand.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/RoutesListCommand.java
index 0c44453..e9c25c4 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/RoutesListCommand.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/cli/RoutesListCommand.java
@@ -15,7 +15,14 @@
  */
 package org.onlab.onos.sdnip.cli;
 
+import java.util.Collection;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.sdnip.RouteEntry;
 import org.onlab.onos.sdnip.SdnIpService;
@@ -26,22 +33,100 @@
 @Command(scope = "onos", name = "routes",
         description = "Lists all routes known to SDN-IP")
 public class RoutesListCommand extends AbstractShellCommand {
+    @Option(name = "-s", aliases = "--summary",
+            description = "SDN-IP routes summary",
+            required = false, multiValued = false)
+    private boolean routesSummary = false;
 
-    private static final String FORMAT =
+    private static final String FORMAT_SUMMARY = "Total SDN-IP routes = %d";
+    private static final String FORMAT_ROUTE =
             "prefix=%s, nexthop=%s";
 
     @Override
     protected void execute() {
         SdnIpService service = get(SdnIpService.class);
 
-        for (RouteEntry route : service.getRoutes()) {
-            printRoute(route);
+        // Print summary of the routes
+        if (routesSummary) {
+            printSummary(service.getRoutes());
+            return;
+        }
+
+        // Print all routes
+        printRoutes(service.getRoutes());
+    }
+
+    /**
+     * Prints summary of the routes.
+     *
+     * @param routes the routes
+     */
+    private void printSummary(Collection<RouteEntry> routes) {
+        if (outputJson()) {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode result = mapper.createObjectNode();
+            result.put("totalRoutes", routes.size());
+            print("%s", result);
+        } else {
+            print(FORMAT_SUMMARY, routes.size());
         }
     }
 
+    /**
+     * Prints all routes.
+     *
+     * @param routes the routes to print
+     */
+    private void printRoutes(Collection<RouteEntry> routes) {
+        if (outputJson()) {
+            print("%s", json(routes));
+        } else {
+            for (RouteEntry route : routes) {
+                printRoute(route);
+            }
+        }
+    }
+
+    /**
+     * Prints a route.
+     *
+     * @param route the route to print
+     */
     private void printRoute(RouteEntry route) {
         if (route != null) {
-            print(FORMAT, route.prefix(), route.nextHop());
+            print(FORMAT_ROUTE, route.prefix(), route.nextHop());
         }
     }
+
+    /**
+     * Produces a JSON array of routes.
+     *
+     * @param routes the routes with the data
+     * @return JSON array with the routes
+     */
+    private JsonNode json(Collection<RouteEntry> routes) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+
+        for (RouteEntry route : routes) {
+            result.add(json(mapper, route));
+        }
+        return result;
+    }
+
+    /**
+     * Produces JSON object for a route.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param route the route with the data
+     * @return JSON object for the route
+     */
+    private ObjectNode json(ObjectMapper mapper, RouteEntry route) {
+        ObjectNode result = mapper.createObjectNode();
+
+        result.put("prefix", route.prefix().toString());
+        result.put("nextHop", route.nextHop().toString());
+
+        return result;
+    }
 }