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/api/src/main/java/org/onosproject/app/ApplicationAdminService.java b/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java
index 18babd5..e0ea6ec 100644
--- a/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java
@@ -29,7 +29,9 @@
/**
* Installs the application contained in the specified application archive
- * input stream.
+ * input stream. This can be either a ZIP stream containing a compressed
+ * application archive or a plain XML stream containing just the
+ * {@code app.xml} application descriptor file.
*
* @param appDescStream application descriptor input stream
* @return installed application descriptor
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 {
diff --git a/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java b/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java
index 3d86dda..240ed96 100644
--- a/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java
+++ b/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java
@@ -30,8 +30,7 @@
import java.util.Random;
import java.util.Set;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
public class ApplicationArchiveTest {
@@ -64,43 +63,69 @@
}
@Test
- public void saveApp() throws IOException {
+ public void saveZippedApp() throws IOException {
InputStream stream = getClass().getResourceAsStream("app.zip");
ApplicationDescription app = aar.saveApplication(stream);
validate(app);
}
@Test
+ public void savePlainApp() throws IOException {
+ InputStream stream = getClass().getResourceAsStream("app.xml");
+ ApplicationDescription app = aar.saveApplication(stream);
+ validate(app);
+ }
+
+ @Test
public void loadApp() throws IOException {
- saveApp();
+ saveZippedApp();
ApplicationDescription app = aar.getApplicationDescription(APP_NAME);
validate(app);
}
@Test
public void getAppNames() throws IOException {
- saveApp();
+ saveZippedApp();
Set<String> names = aar.getApplicationNames();
assertEquals("incorrect names", ImmutableSet.of(APP_NAME), names);
}
@Test
public void purgeApp() throws IOException {
- saveApp();
+ saveZippedApp();
aar.purgeApplication(APP_NAME);
assertEquals("incorrect names", ImmutableSet.<String>of(),
aar.getApplicationNames());
}
@Test
- public void getAppStream() throws IOException {
- saveApp();
+ public void getAppZipStream() throws IOException {
+ saveZippedApp();
InputStream stream = aar.getApplicationInputStream(APP_NAME);
byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.zip"));
byte[] loaded = ByteStreams.toByteArray(stream);
assertArrayEquals("incorrect stream", orig, loaded);
}
+ @Test
+ public void getAppXmlStream() throws IOException {
+ savePlainApp();
+ InputStream stream = aar.getApplicationInputStream(APP_NAME);
+ byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.xml"));
+ byte[] loaded = ByteStreams.toByteArray(stream);
+ assertArrayEquals("incorrect stream", orig, loaded);
+ }
+
+ @Test
+ public void active() throws IOException {
+ savePlainApp();
+ assertFalse("should not be active", aar.isActive(APP_NAME));
+ aar.setActive(APP_NAME);
+ assertTrue("should not be active", aar.isActive(APP_NAME));
+ aar.clearActive(APP_NAME);
+ assertFalse("should not be active", aar.isActive(APP_NAME));
+ }
+
@Test(expected = ApplicationException.class)
public void getBadAppDesc() throws IOException {
aar.getApplicationDescription("org.foo.BAD");
@@ -111,4 +136,14 @@
aar.getApplicationInputStream("org.foo.BAD");
}
+ @Test(expected = ApplicationException.class)
+ public void setBadActive() throws IOException {
+ aar.setActive("org.foo.BAD");
+ }
+
+ @Test(expected = ApplicationException.class)
+ public void purgeBadApp() throws IOException {
+ aar.purgeApplication("org.foo.BAD");
+ }
+
}
\ No newline at end of file
diff --git a/core/common/src/test/resources/org/onosproject/common/app/app.xml b/core/common/src/test/resources/org/onosproject/common/app/app.xml
index 3b8bc62..39f328b 100644
--- a/core/common/src/test/resources/org/onosproject/common/app/app.xml
+++ b/core/common/src/test/resources/org/onosproject/common/app/app.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2015 Open Networking Laboratory
~