[ONOS-3635] Implement List view for extended application properties

Change-Id: Ie8f985f9c2986857df92bcb47b5bdee876f37230
diff --git a/apps/icon.png b/apps/app.png
similarity index 100%
rename from apps/icon.png
rename to apps/app.png
Binary files differ
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 ba55097..11c8b76 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
@@ -39,10 +39,8 @@
 import org.slf4j.LoggerFactory;
 
 import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferByte;
-import java.awt.image.WritableRaster;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -97,8 +95,9 @@
     private static final String JAVA_PERMISSIONS = "security.permissions.java-perm";
 
     private static final String OAR = ".oar";
+    private static final String PNG = "png";
     private static final String APP_XML = "app.xml";
-    private static final String ICON_PNG = "icon.png";
+    private static final String APP_PNG = "app.png";
     private static final String M2_PREFIX = "m2";
 
     private static final String ROOT = "../";
@@ -197,7 +196,7 @@
             ApplicationDescription desc = plainXml ?
                     parsePlainAppDescription(bis) : parseZippedAppDescription(bis);
             checkState(!appFile(desc.name(), APP_XML).exists(),
-                       "Application %s already installed", desc.name());
+                    "Application %s already installed", desc.name());
 
             if (plainXml) {
                 expandPlainApplication(cache, desc);
@@ -412,6 +411,11 @@
         return new File(new File(appsDir, appName), fileName);
     }
 
+    // Returns the icon file located under the specified app directory.
+    private File iconFile(String appName, String fileName) {
+        return new File(new File(appsDir, appName), fileName);
+    }
+
     // Returns the set of Permissions specified in the app.xml file
     private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) {
         List<Permission> permissionList = Lists.newArrayList();
@@ -441,29 +445,25 @@
 
     // Returns the byte stream from icon.png file in oar application archive.
     private byte[] getApplicationIcon(String appName) {
-        // open image
-        File iconFile = appFile(appName, ICON_PNG);
+
+        byte[] icon = new byte[0];
+        File iconFile = iconFile(appName, APP_PNG);
 
         if (!iconFile.exists()) {
-            iconFile = new File(appsDir, ICON_PNG);
+            // assume that we can always fallback to default icon
+            iconFile = new File(appsDir, APP_PNG);
         }
 
-        if (!iconFile.exists()) {
-            return null;
-        }
-
-        BufferedImage bufferedImage = null;
         try {
-            bufferedImage = ImageIO.read(iconFile);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            ImageIO.write(ImageIO.read(iconFile), PNG, bos);
+            icon = bos.toByteArray();
+            bos.close();
         } catch (IOException e) {
             e.printStackTrace();
         }
 
-        // get DataBufferBytes from Raster
-        WritableRaster raster = bufferedImage .getRaster();
-        DataBufferByte data = (DataBufferByte) raster.getDataBuffer();
-
-        return data.getData();
+        return icon;
     }
 
     // Returns application role type
diff --git a/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java b/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
index d0541d2..24c876b 100644
--- a/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
+++ b/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import org.apache.commons.configuration.ConfigurationException;
 import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.plugin.AbstractMojo;
@@ -61,7 +62,7 @@
     private static final String ARTIFACT = "artifact";
 
     private static final String APP_XML = "app.xml";
-    private static final String ICON_PNG = "icon.png";
+    private static final String APP_PNG = "app.png";
     private static final String FEATURES_XML = "features.xml";
 
     private static final String MVN_URL = "mvn:";
@@ -83,7 +84,7 @@
     private static final String DEFAULT_ORIGIN = "ON.Lab";
     private static final String DEFAULT_VERSION = "${project.version}";
 
-    private static final String DEFAULT_CATEGORY = "Default";
+    private static final String DEFAULT_CATEGORY = "default";
     private static final String DEFAULT_URL = "http://onosproject.org";
 
     private static final String DEFAULT_FEATURES_REPO =
@@ -161,6 +162,7 @@
     @Override
     public void execute() throws MojoExecutionException {
         File appFile = new File(baseDir, APP_XML);
+        File iconFile = new File(baseDir, APP_PNG);
         File featuresFile = new File(baseDir, FEATURES_XML);
 
         name = (String) project.getProperties().get(ONOS_APP_NAME);
@@ -204,6 +206,7 @@
 
             if (stageDirectory.exists() || stageDirectory.mkdirs()) {
                 processAppXml(appFile);
+                processAppPng(iconFile);
                 processFeaturesXml(featuresFile);
                 processArtifacts();
                 generateAppPackage();
@@ -259,6 +262,19 @@
         }
     }
 
+    // Stages the app.png file of a specific application.
+    private void processAppPng(File iconFile) throws MojoExecutionException {
+        try {
+            File stagedIconFile = new File(stageDirectory, APP_PNG);
+
+            if (iconFile.exists()) {
+                FileUtils.copyFile(iconFile, stagedIconFile);
+            }
+        } catch (IOException e) {
+            throw new MojoExecutionException("Unable to copy app.png", e);
+        }
+    }
+
     private void processFeaturesXml(File featuresFile) throws MojoExecutionException {
         boolean specified = featuresRepo != null && featuresRepo.length() > 0;
 
diff --git a/tools/test/bin/onos-stage-apps b/tools/test/bin/onos-stage-apps
index 4c2c230..d7dafda 100755
--- a/tools/test/bin/onos-stage-apps
+++ b/tools/test/bin/onos-stage-apps
@@ -23,7 +23,10 @@
     name=$(grep "name=" $AUX/app.xml | sed 's/<app name="//g;s/".*//g')
     mkdir -p $APPS/$name
     cp $AUX/app.xml $APPS/$name/app.xml
+    [ -f $AUX/app.png ] && cp $AUX/app.png $APPS/$name/app.png
     cp $AUX/*.oar $APPS/$name/$name.oar
     cp -rf $AUX/m2/* $KARAF_M2
     rm -fr $AUX
 done
+
+cp $ONOS_ROOT/apps/app.png $APPS/app.png
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
index 398bfee..7601478 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
@@ -18,10 +18,15 @@
 import com.sun.jersey.multipart.FormDataParam;
 import org.onlab.rest.BaseResource;
 import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
@@ -41,4 +46,13 @@
         return Response.ok().build();
     }
 
+    @Path("{name}/icon")
+    @GET
+    @Produces("image/png")
+    public Response getIcon(@PathParam("name") String name) throws IOException {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = service.getId(name);
+        Application app = service.getApplication(appId);
+        return Response.ok(app.icon()).build();
+    }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
index 6f8eee9..80c1e75 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
@@ -45,15 +45,18 @@
     private static final String STATE = "state";
     private static final String STATE_IID = "_iconid_state";
     private static final String ID = "id";
+    private static final String ICON = "icon";
     private static final String VERSION = "version";
+    private static final String CATEGORY = "category";
     private static final String ORIGIN = "origin";
     private static final String DESC = "desc";
+    private static final String URL = "url";
 
     private static final String ICON_ID_ACTIVE = "active";
     private static final String ICON_ID_INACTIVE = "appInactive";
 
     private static final String[] COL_IDS = {
-            STATE, STATE_IID, ID, VERSION, ORIGIN, DESC
+            STATE, STATE_IID, ID, ICON, VERSION, CATEGORY, ORIGIN, DESC, URL
     };
 
     @Override
@@ -99,9 +102,12 @@
             row.cell(STATE, state)
                 .cell(STATE_IID, iconId)
                 .cell(ID, id.name())
+                .cell(ICON, id.name())
                 .cell(VERSION, app.version())
+                .cell(CATEGORY, app.category())
                 .cell(ORIGIN, app.origin())
-                .cell(DESC, app.description());
+                .cell(DESC, app.description())
+                .cell(URL, app.url());
         }
     }
 
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index f3c892e..2cd9d65 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -41,10 +41,13 @@
             <table>
                 <tr>
                     <td colId="state" class="table-icon" sortable></td>
+                    <td colId="icon" col-width="36px">Icon </td>
                     <td colId="id" sortable>App ID </td>
                     <td colId="version" sortable>Version </td>
+                    <td colId="category" sortable>Category </td>
                     <td colId="origin" sortable>Origin </td>
                     <td colId="desc" col-width="475px">Description </td>
+                    <td col-width="50px">URL </td>
                 </tr>
             </table>
         </div>
@@ -64,10 +67,13 @@
                     <td class="table-icon">
                         <div icon icon-id="{{app._iconid_state}}"></div>
                     </td>
+                    <td><img data-ng-src="./rs/applications/{{app.icon}}/icon" height="28px" width="28px" /></td>
                     <td>{{app.id}}</td>
                     <td>{{app.version}}</td>
+                    <td>{{app.category}}</td>
                     <td>{{app.origin}}</td>
                     <td>{{app.desc}}</td>
+                    <td><a href="{{app.url}}" target="_blank">LINK</a></td>
                 </tr>
             </table>
         </div>