ONOS-542 Added ability for app bundle to carry it's own artifacts, including feature repo. Fixed onos-package script. Added JSON output to CLI.

Change-Id: If4f2c774d3fc2d68c0a8e91b3084b99d7c75d927
diff --git a/cli/src/main/java/org/onosproject/cli/Comparators.java b/cli/src/main/java/org/onosproject/cli/Comparators.java
index 764b227..6efb139 100644
--- a/cli/src/main/java/org/onosproject/cli/Comparators.java
+++ b/cli/src/main/java/org/onosproject/cli/Comparators.java
@@ -18,6 +18,7 @@
 import java.util.Comparator;
 
 import org.onosproject.cluster.ControllerNode;
+import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Element;
@@ -43,6 +44,13 @@
         }
     };
 
+    public static final Comparator<Application> APP_COMPARATOR = new Comparator<Application>() {
+        @Override
+        public int compare(Application app1, Application app2) {
+            return app1.id().id() - app2.id().id();
+        }
+    };
+
     public static final Comparator<ElementId> ELEMENT_ID_COMPARATOR = new Comparator<ElementId>() {
         @Override
         public int compare(ElementId id1, ElementId id2) {
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java
index 2b90f66..13d5cd8 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java
@@ -15,11 +15,19 @@
  */
 package org.onosproject.cli.app;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.app.ApplicationService;
 import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.Comparators;
 import org.onosproject.core.Application;
 
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
 import static org.onosproject.app.ApplicationState.ACTIVE;
 
 /**
@@ -36,11 +44,44 @@
     @Override
     protected void execute() {
         ApplicationService service = get(ApplicationService.class);
-        for (Application app : service.getApplications()) {
-            print(FMT, service.getState(app.id()) == ACTIVE ? "*" : " ",
-                  app.id().id(), app.id().name(), app.version(), app.origin(),
-                  app.description(), app.features(), app.featuresRepo(), app.permissions());
+        List<Application> apps = newArrayList(service.getApplications());
+        Collections.sort(apps, Comparators.APP_COMPARATOR);
+
+        if (outputJson()) {
+            print("%s", json(service, apps));
+        } else {
+            for (Application app : apps) {
+                print(FMT, service.getState(app.id()) == ACTIVE ? "*" : " ",
+                      app.id().id(), app.id().name(), app.version(), app.origin(),
+                      app.description(), app.features(),
+                      app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "",
+                      app.permissions());
+            }
         }
     }
 
+    private JsonNode json(ApplicationService service, List<Application> apps) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+        for (Application app : apps) {
+            result.add(json(service, mapper, app));
+        }
+        return result;
+    }
+
+    protected JsonNode json(ApplicationService service, ObjectMapper mapper,
+                            Application app) {
+        return mapper.createObjectNode()
+                .put("name", app.id().name())
+                .put("id", app.id().id())
+                .put("version", app.version().toString())
+                .put("description", app.description())
+                .put("origin", app.origin())
+                .put("permissions", app.permissions().toString())
+                .put("featuresRepo", app.featuresRepo().isPresent() ?
+                        app.featuresRepo().get().toString() : "")
+                .put("features", app.features().toString())
+                .put("state", service.getState(app.id()).toString());
+    }
+
 }
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java b/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
index ec7958d..64f04a6 100644
--- a/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
@@ -19,6 +19,7 @@
 import org.onosproject.core.Version;
 
 import java.net.URI;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
@@ -71,10 +72,10 @@
     Optional<URI> featuresRepo();
 
     /**
-     * Returns the set of features comprising the application. At least one
+     * Returns the list of features comprising the application. At least one
      * feature must be given.
      *
      * @return application features
      */
-    Set<String> features();
+    List<String> features();
 }
diff --git a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
index 9c53542..78b902b 100644
--- a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
+++ b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
@@ -19,6 +19,7 @@
 import org.onosproject.core.Version;
 
 import java.net.URI;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
@@ -37,7 +38,7 @@
     private final String origin;
     private final Set<Permission> permissions;
     private final Optional<URI> featuresRepo;
-    private final Set<String> features;
+    private final List<String> features;
 
     /**
      * Creates a new application descriptor using the supplied data.
@@ -53,7 +54,7 @@
     public DefaultApplicationDescription(String name, Version version,
                                          String description, String origin,
                                          Set<Permission> permissions,
-                                         URI featuresRepo, Set<String> features) {
+                                         URI featuresRepo, List<String> features) {
         this.name = checkNotNull(name, "Name cannot be null");
         this.version = checkNotNull(version, "Version cannot be null");
         this.description = checkNotNull(description, "Description cannot be null");
@@ -95,7 +96,7 @@
     }
 
     @Override
-    public Set<String> features() {
+    public List<String> features() {
         return features;
     }
 
diff --git a/core/api/src/main/java/org/onosproject/core/Application.java b/core/api/src/main/java/org/onosproject/core/Application.java
index d876a67..da2be13 100644
--- a/core/api/src/main/java/org/onosproject/core/Application.java
+++ b/core/api/src/main/java/org/onosproject/core/Application.java
@@ -16,6 +16,7 @@
 package org.onosproject.core;
 
 import java.net.URI;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
@@ -68,10 +69,10 @@
     Optional<URI> featuresRepo();
 
     /**
-     * Returns the set of features comprising the application. At least one
+     * Returns the list of features comprising the application. At least one
      * feature must be given.
      *
      * @return application features
      */
-    Set<String> features();
+    List<String> features();
 }
diff --git a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
index 4da85a5..b765e70 100644
--- a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
+++ b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
@@ -16,6 +16,7 @@
 package org.onosproject.core;
 
 import java.net.URI;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -35,7 +36,7 @@
     private final String origin;
     private final Set<Permission> permissions;
     private final Optional<URI> featuresRepo;
-    private final Set<String> features;
+    private final List<String> features;
 
     /**
      * Creates a new application descriptor using the supplied data.
@@ -51,7 +52,7 @@
     public DefaultApplication(ApplicationId appId, Version version,
                               String description, String origin,
                               Set<Permission> permissions,
-                              Optional<URI> featuresRepo, Set<String> features) {
+                              Optional<URI> featuresRepo, List<String> features) {
         this.appId = checkNotNull(appId, "ID cannot be null");
         this.version = checkNotNull(version, "Version cannot be null");
         this.description = checkNotNull(description, "Description cannot be null");
@@ -93,7 +94,7 @@
     }
 
     @Override
-    public Set<String> features() {
+    public List<String> features() {
         return features;
     }
 
diff --git a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
index 7079427..3c33a7f 100644
--- a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
+++ b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
@@ -15,12 +15,14 @@
  */
 package org.onosproject.app;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 import org.onosproject.core.Permission;
 import org.onosproject.core.Version;
 
 import java.net.URI;
+import java.util.List;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
@@ -37,7 +39,7 @@
     public static final String ORIGIN = "Circus";
     public static final Set<Permission> PERMS = ImmutableSet.of();
     public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
-    public static final Set<String> FEATURES = ImmutableSet.of("foo");
+    public static final List<String> FEATURES = ImmutableList.of("foo", "bar");
 
     @Test
     public void basics() {
diff --git a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
index 61cb2e3..0260863 100644
--- a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
+++ b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.common.app;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Files;
@@ -40,6 +41,7 @@
 import java.io.InputStream;
 import java.net.URI;
 import java.nio.file.NoSuchFileException;
+import java.util.List;
 import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -191,8 +193,7 @@
             ZipEntry entry;
             while ((entry = zis.getNextEntry()) != null) {
                 if (entry.getName().equals(APP_XML)) {
-                    byte[] data = new byte[(int) entry.getSize()];
-                    ByteStreams.readFully(zis, data);
+                    byte[] data = ByteStreams.toByteArray(zis);
                     XMLConfiguration cfg = new XMLConfiguration();
                     try {
                         cfg.load(new ByteArrayInputStream(data));
@@ -209,6 +210,7 @@
 
     private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
         cfg.setAttributeSplittingDisabled(true);
+        cfg.setDelimiterParsingDisabled(true);
         String name = cfg.getString(NAME);
         Version version = Version.version(cfg.getString(VERSION));
         String desc = cfg.getString(DESCRIPTION);
@@ -216,7 +218,7 @@
         Set<Permission> perms = ImmutableSet.of();
         String featRepo = cfg.getString(FEATURES_REPO);
         URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
-        Set<String> features = ImmutableSet.copyOf(cfg.getString(FEATURES).split(","));
+        List<String> features = ImmutableList.copyOf(cfg.getStringArray(FEATURES));
 
         return new DefaultApplicationDescription(name, version, desc, origin,
                                                  perms, featuresRepo, features);
@@ -229,13 +231,14 @@
         ZipEntry entry;
         File appDir = new File(appsDir, desc.name());
         while ((entry = zis.getNextEntry()) != null) {
-            byte[] data = new byte[(int) entry.getSize()];
-            ByteStreams.readFully(zis, data);
-            zis.closeEntry();
+            if (!entry.isDirectory()) {
+                byte[] data = ByteStreams.toByteArray(zis);
+                zis.closeEntry();
 
-            File file = new File(appDir, entry.getName());
-            createParentDirs(file);
-            write(data, file);
+                File file = new File(appDir, entry.getName());
+                createParentDirs(file);
+                write(data, file);
+            }
         }
         zis.close();
     }
diff --git a/tools/build/onos-package b/tools/build/onos-package
index 5a528ba..31551ea 100755
--- a/tools/build/onos-package
+++ b/tools/build/onos-package
@@ -46,9 +46,16 @@
 sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \
     $ONOS_ROOT/tools/package/bin/onos > bin/onos
 
-# Stage the ONOS bundles
+# Stage the ONOS bundles, but only those that match the version
 mkdir -p $KARAF_DIST/system/org/onosproject
-cp -r $M2_REPO/org/onosproject $KARAF_DIST/system/org/
+# cp -r $M2_REPO/org/onosproject/ $KARAF_DIST/system/org/
+find $M2_REPO/org/onosproject/ -type d -name $ONOS_POM_VERSION | while read line; do
+    path=${line#*/onosproject/}
+    artifact=${path%/$ONOS_POM_VERSION}
+    mkdir -p $KARAF_DIST/system/org/onosproject/$artifact
+    cp -r $M2_REPO/org/onosproject/$artifact/$ONOS_POM_VERSION \
+        $KARAF_DIST/system/org/onosproject/$artifact/$ONOS_POM_VERSION
+done
 
 export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-rest,onos-gui,onos-openflow,onos-app-fwd,onos-app-foo}"
 
diff --git a/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java b/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
index 7a148de..4812154 100644
--- a/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
+++ b/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
@@ -40,7 +40,7 @@
                 .put("origin", app.origin())
                 .put("permissions", app.permissions().toString())
                 .put("featuresRepo", app.featuresRepo().isPresent() ?
-                        app.featuresRepo().toString() : "")
+                        app.featuresRepo().get().toString() : "")
                 .put("features", app.features().toString())
                 .put("state", service.getState(app.id()).toString());
         return result;
diff --git a/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
index 132a739..45ee612 100644
--- a/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
@@ -15,10 +15,11 @@
  */
 package org.onosproject.rest;
 
-import java.io.InputStream;
-import java.net.URI;
-import java.util.Optional;
-
+import com.eclipsesource.json.JsonArray;
+import com.eclipsesource.json.JsonObject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.client.WebResource;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.After;
@@ -40,22 +41,14 @@
 import org.onosproject.core.DefaultApplicationId;
 import org.onosproject.core.Version;
 
-import com.eclipsesource.json.JsonArray;
-import com.eclipsesource.json.JsonObject;
-import com.google.common.collect.ImmutableSet;
-import com.sun.jersey.api.client.WebResource;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Optional;
 
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.*;
 import static org.easymock.EasyMock.isA;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.*;
 
 /**
  * Unit tests for applications REST APIs.
@@ -88,20 +81,20 @@
 
     private Application app1 =
             new DefaultApplication(id1, VER,
-                    "app1", "origin1", ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableSet.of("My Feature"));
+                                   "app1", "origin1", ImmutableSet.of(), Optional.of(FURL),
+                                   ImmutableList.of("My Feature"));
     private Application app2 =
             new DefaultApplication(id2, VER,
-                    "app2", "origin2", ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableSet.of("My Feature"));
+                                   "app2", "origin2", ImmutableSet.of(), Optional.of(FURL),
+                                   ImmutableList.of("My Feature"));
     private Application app3 =
             new DefaultApplication(id3, VER,
-                    "app3", "origin3", ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableSet.of("My Feature"));
+                                   "app3", "origin3", ImmutableSet.of(), Optional.of(FURL),
+                                   ImmutableList.of("My Feature"));
     private Application app4 =
             new DefaultApplication(id4, VER,
-                    "app4", "origin4", ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableSet.of("My Feature"));
+                                   "app4", "origin4", ImmutableSet.of(), Optional.of(FURL),
+                                   ImmutableList.of("My Feature"));
 
     /**
      * Hamcrest matcher to check that an device representation in JSON matches