Added ability to upload apps as both app.xml or app.zip.
Added a number of app.xml files for built-in apps.
Added ability to install & activate in one command.

Change-Id: I3fa5fa487ef76d9fe3da4d6dce8045d538cba423
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 0260863..5a818e4 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
@@ -40,6 +40,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.nio.charset.Charset;
 import java.nio.file.NoSuchFileException;
 import java.util.List;
 import java.util.Set;
@@ -57,6 +58,13 @@
 public class ApplicationArchive
         extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> {
 
+    // Magic strings to search for at the beginning of the archive stream
+    private static final String XML_MAGIC = "<?xml ";
+
+    // Magic strings to search for and how deep to search it into the archive stream
+    private static final String APP_MAGIC = "<app ";
+    private static final int APP_MAGIC_DEPTH = 1024;
+
     private static final String NAME = "[@name]";
     private static final String ORIGIN = "[@origin]";
     private static final String VERSION = "[@version]";
@@ -144,13 +152,21 @@
         try (InputStream ais = stream) {
             byte[] cache = toByteArray(ais);
             InputStream bis = new ByteArrayInputStream(cache);
-            ApplicationDescription desc = parseAppDescription(bis);
-            bis.reset();
 
-            expandApplication(bis, desc);
-            bis.reset();
+            boolean plainXml = isPlainXml(cache);
+            ApplicationDescription desc = plainXml ?
+                    parsePlainAppDescription(bis) : parseZippedAppDescription(bis);
 
-            saveApplication(bis, desc);
+            if (plainXml) {
+                expandPlainApplication(cache, desc);
+            } else {
+                bis.reset();
+                expandZippedApplication(bis, desc);
+
+                bis.reset();
+                saveApplication(bis, desc);
+            }
+
             installArtifacts(desc);
             return desc;
         } catch (IOException e) {
@@ -158,28 +174,45 @@
         }
     }
 
+    // Indicates whether the stream encoded in the given bytes is plain XML.
+    private boolean isPlainXml(byte[] bytes) {
+        return substring(bytes, XML_MAGIC.length()).equals(XML_MAGIC) ||
+                substring(bytes, APP_MAGIC_DEPTH).contains(APP_MAGIC);
+    }
+
+    // Returns the substring of maximum possible length from the specified bytes.
+    private String substring(byte[] bytes, int length) {
+        return new String(bytes, 0, Math.min(bytes.length, length), Charset.forName("UTF-8"));
+    }
+
     /**
      * Purges the application archive directory.
      *
      * @param appName application name
      */
     public void purgeApplication(String appName) {
+        File appDir = new File(appsDir, appName);
         try {
-            Tools.removeDirectory(new File(appsDir, appName));
+            Tools.removeDirectory(appDir);
         } catch (IOException e) {
             throw new ApplicationException("Unable to purge application " + appName, e);
         }
+        if (appDir.exists()) {
+            throw new ApplicationException("Unable to purge application " + appName);
+        }
     }
 
     /**
-     * Returns application archive stream for the specified application.
+     * Returns application archive stream for the specified application. This
+     * will be either the application ZIP file or the application XML file.
      *
      * @param appName application name
      * @return application archive stream
      */
     public InputStream getApplicationInputStream(String appName) {
         try {
-            return new FileInputStream(appFile(appName, appName + ".zip"));
+            File appFile = appFile(appName, appName + ".zip");
+            return new FileInputStream(appFile.exists() ? appFile : appFile(appName, APP_XML));
         } catch (FileNotFoundException e) {
             throw new ApplicationException("Application " + appName + " not found");
         }
@@ -187,20 +220,14 @@
 
     // Scans the specified ZIP stream for app.xml entry and parses it producing
     // an application descriptor.
-    private ApplicationDescription parseAppDescription(InputStream stream)
+    private ApplicationDescription parseZippedAppDescription(InputStream stream)
             throws IOException {
         try (ZipInputStream zis = new ZipInputStream(stream)) {
             ZipEntry entry;
             while ((entry = zis.getNextEntry()) != null) {
                 if (entry.getName().equals(APP_XML)) {
                     byte[] data = ByteStreams.toByteArray(zis);
-                    XMLConfiguration cfg = new XMLConfiguration();
-                    try {
-                        cfg.load(new ByteArrayInputStream(data));
-                        return loadAppDescription(cfg);
-                    } catch (ConfigurationException e) {
-                        throw new IOException("Unable to parse " + APP_XML, e);
-                    }
+                    return parsePlainAppDescription(new ByteArrayInputStream(data));
                 }
                 zis.closeEntry();
             }
@@ -208,6 +235,18 @@
         throw new IOException("Unable to locate " + APP_XML);
     }
 
+    // Scans the specified XML stream and parses it producing an application descriptor.
+    private ApplicationDescription parsePlainAppDescription(InputStream stream)
+            throws IOException {
+        XMLConfiguration cfg = new XMLConfiguration();
+        try {
+            cfg.load(stream);
+            return loadAppDescription(cfg);
+        } catch (ConfigurationException e) {
+            throw new IOException("Unable to parse " + APP_XML, e);
+        }
+    }
+
     private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
         cfg.setAttributeSplittingDisabled(true);
         cfg.setDelimiterParsingDisabled(true);
@@ -225,7 +264,7 @@
     }
 
     // Expands the specified ZIP stream into app-specific directory.
-    private void expandApplication(InputStream stream, ApplicationDescription desc)
+    private void expandZippedApplication(InputStream stream, ApplicationDescription desc)
             throws IOException {
         ZipInputStream zis = new ZipInputStream(stream);
         ZipEntry entry;
@@ -234,7 +273,6 @@
             if (!entry.isDirectory()) {
                 byte[] data = ByteStreams.toByteArray(zis);
                 zis.closeEntry();
-
                 File file = new File(appDir, entry.getName());
                 createParentDirs(file);
                 write(data, file);
@@ -243,6 +281,15 @@
         zis.close();
     }
 
+    // Saves the specified XML stream into app-specific directory.
+    private void expandPlainApplication(byte[] stream, ApplicationDescription desc)
+            throws IOException {
+        File file = appFile(desc.name(), APP_XML);
+        createParentDirs(file);
+        write(stream, file);
+    }
+
+
     // Saves the specified ZIP stream into a file under app-specific directory.
     private void saveApplication(InputStream stream, ApplicationDescription desc)
             throws IOException {