Backporting the fix from onos-1.6 (change-id : I82e1e0e55bbc017d6c0cce7d9a6af7a578d7196e ) for ONOS-4641 fix

Change-Id: Ia857107564232eb9775f83d4e804ec36b04bf1db
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/ApplicationIdCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/ApplicationIdCodec.java
new file mode 100644
index 0000000..fe06dae
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ApplicationIdCodec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.ApplicationId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * ApplicationId JSON codec.
+ */
+public final class ApplicationIdCodec extends JsonCodec<ApplicationId> {
+
+    private static final String APP_NAME = "name";
+
+    private static final String MISSING_MEMBER_MESSAGE = " member is required in ApplicationId";
+
+    @Override
+    public ObjectNode encode(ApplicationId appId, CodecContext context) {
+        checkNotNull(appId, "ApplicationId cannot be null");
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("name", appId.name());
+
+        return result;
+    }
+
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index 2223970..421af3d 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -26,6 +26,7 @@
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
 import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -87,6 +88,7 @@
     public void activate() {
         codecs.clear();
         registerCodec(Application.class, new ApplicationCodec());
+        registerCodec(ApplicationId.class, new ApplicationIdCodec());
         registerCodec(ControllerNode.class, new ControllerNodeCodec());
         registerCodec(Annotations.class, new AnnotationsCodec());
         registerCodec(Device.class, new DeviceCodec());
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/ApplicationIdCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/ApplicationIdCodecTest.java
new file mode 100644
index 0000000..dcc20db
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/ApplicationIdCodecTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for ApplicationId codec.
+ */
+public final class ApplicationIdCodecTest {
+
+    MockCodecContext context;
+    JsonCodec<ApplicationId> applicationIdCodec;
+
+    /**
+     * Sets up for each test. Creates a context and fetches the applicationId
+     * codec.
+     */
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        applicationIdCodec = context.codec(ApplicationId.class);
+        assertThat(applicationIdCodec, notNullValue());
+    }
+
+    /**
+     * Tests encoding of an application id object.
+     */
+    @Test
+    public void testApplicationIdEncode() {
+
+        int id = 1;
+        String name = "org.onosproject.foo";
+        ApplicationId appId = new DefaultApplicationId(id, name);
+
+        ObjectNode applicationIdJson = applicationIdCodec.encode(appId, context);
+        assertThat(applicationIdJson, ApplicationIdJsonMatcher.matchesApplicationId(appId));
+    }
+
+    private static final class ApplicationIdJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+        private final ApplicationId applicationId;
+
+        private ApplicationIdJsonMatcher(ApplicationId applicationId) {
+            this.applicationId = applicationId;
+        }
+
+        @Override
+        protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+            String jsonAppName = jsonNode.get("name").asText();
+            String appName = applicationId.name();
+
+            if (!jsonAppName.equals(appName)) {
+                description.appendText("application name was " + jsonAppName);
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(applicationId.toString());
+        }
+
+        static ApplicationIdJsonMatcher matchesApplicationId(ApplicationId applicationId) {
+            return new ApplicationIdJsonMatcher(applicationId);
+        }
+    }
+}
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/ApplicationId.json b/core/common/src/test/resources/org/onosproject/codec/impl/ApplicationId.json
new file mode 100644
index 0000000..c0d461a
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/ApplicationId.json
@@ -0,0 +1,3 @@
+{
+  "name": "org.onosproject.foo"
+}
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
index f038334..00106f3 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
@@ -18,6 +18,7 @@
 import org.onosproject.app.ApplicationAdminService;
 import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
 import org.onosproject.rest.AbstractWebResource;
 
 import javax.ws.rs.Consumes;
@@ -143,9 +144,45 @@
         return response(service, appId);
     }
 
+    /**
+     * Registers an on or off platform application.
+     *
+     * @param name application name
+     * @return 200 OK; 404; 401
+     * @onos.rsModel ApplicationId
+     */
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{name}/register")
+    public Response registerAppId(@PathParam("name") String name) {
+        CoreService service = get(CoreService.class);
+        ApplicationId appId = service.registerApplication(name);
+        return response(appId);
+    }
+
+    /**
+     * Gets a collection of application ids.
+     * Returns array of all registered application ids.
+     *
+     * @return 200 OK; 404; 401
+     * @onos.rsModel ApplicationIds
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("ids")
+    public Response getAppIds() {
+        CoreService service = get(CoreService.class);
+        Set<ApplicationId> appIds = service.getAppIds();
+        return ok(encodeArray(ApplicationId.class, "applicationIds", appIds)).build();
+    }
+
     private Response response(ApplicationAdminService service, ApplicationId appId) {
         Application app = service.getApplication(appId);
         return ok(codec(Application.class).encode(app, this)).build();
     }
 
+    private Response response(ApplicationId appId) {
+        return ok(codec(ApplicationId.class).encode(appId, this)).build();
+    }
+
 }
diff --git a/web/api/src/main/resources/definitions/ApplicationId.json b/web/api/src/main/resources/definitions/ApplicationId.json
new file mode 100644
index 0000000..27eb136
--- /dev/null
+++ b/web/api/src/main/resources/definitions/ApplicationId.json
@@ -0,0 +1,13 @@
+{
+  "type": "object",
+  "title": "applicationId",
+  "required": [
+    "name"
+  ],
+  "properties": {
+    "name": {
+      "type": "string",
+      "example": "org.onosproject.distributedprimitives"
+    }
+  }
+}
\ No newline at end of file
diff --git a/web/api/src/main/resources/definitions/ApplicationIds.json b/web/api/src/main/resources/definitions/ApplicationIds.json
new file mode 100644
index 0000000..5897024
--- /dev/null
+++ b/web/api/src/main/resources/definitions/ApplicationIds.json
@@ -0,0 +1,29 @@
+{
+  "type": "object",
+  "title": "applicationIds",
+  "required": [
+    "applicationIds"
+  ],
+  "properties": {
+    "applicationIds": {
+      "type": "array",
+      "xml": {
+        "name": "applicationIds",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "applicationId",
+        "required": [
+          "name"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "example": "org.onosproject.distributedprimitives"
+          }
+        }
+      }
+    }
+  }
+}
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 ca960a0..0ffefdf 100644
--- a/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
@@ -23,7 +23,6 @@
 import com.sun.jersey.api.client.WebResource;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.osgi.ServiceDirectory;
@@ -39,6 +38,7 @@
 import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultApplication;
 import org.onosproject.core.DefaultApplicationId;
 import org.onosproject.core.Version;
@@ -60,21 +60,36 @@
 
 public class ApplicationsResourceTest extends ResourceTest {
 
-    private static class MockCodecContextWithService extends MockCodecContext {
-        private ApplicationAdminService service;
+    private static class MockCodecContextWithAppService extends MockCodecContext {
+        private ApplicationAdminService appService;
 
-        MockCodecContextWithService(ApplicationAdminService service) {
-            this.service = service;
+        MockCodecContextWithAppService(ApplicationAdminService appService) {
+            this.appService = appService;
         }
 
         @Override
         @SuppressWarnings("unchecked")
         public <T> T getService(Class<T> serviceClass) {
-            return (T) service;
+            return (T) appService;
         }
     }
 
-    private ApplicationAdminService service;
+    private static class MockCodecContextWithCoreService extends MockCodecContext {
+        private CoreService coreService;
+
+        MockCodecContextWithCoreService(CoreService coreService) {
+            this.coreService = coreService;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> T getService(Class<T> serviceClass) {
+            return (T) coreService;
+        }
+    }
+
+    private ApplicationAdminService appService;
+    private CoreService coreService;
     private ApplicationId id1 = new DefaultApplicationId(1, "app1");
     private ApplicationId id2 = new DefaultApplicationId(2, "app2");
     private ApplicationId id3 = new DefaultApplicationId(3, "app3");
@@ -163,29 +178,69 @@
     }
 
     /**
+     * Hamcrest matcher to check that an application id representation in JSON.
+     */
+    private static final class AppIdJsonMatcher extends TypeSafeMatcher<JsonObject> {
+        private final ApplicationId appId;
+        private String reason = "";
+
+        private AppIdJsonMatcher(ApplicationId appId) {
+            this.appId = appId;
+        }
+
+        @Override
+        protected boolean matchesSafely(JsonObject jsonAppId) {
+            // check name
+            String jsonName = jsonAppId.get("name").asString();
+            if (!jsonName.equals(appId.name())) {
+                reason = "name " + appId.name();
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate an application Id matcher.
+     *
+     * @param appId application Id object we are looking for
+     * @return matcher
+     */
+    private static AppIdJsonMatcher matchesAppId(ApplicationId appId) {
+        return new AppIdJsonMatcher(appId);
+    }
+
+    /**
      * Initializes test mocks and environment.
      */
     @Before
     public void setUpMocks() {
-        service = createMock(ApplicationAdminService.class);
+        appService = createMock(ApplicationAdminService.class);
+        coreService = createMock(CoreService.class);
 
-        expect(service.getId("one"))
+        expect(appService.getId("one"))
                 .andReturn(id1)
                 .anyTimes();
-        expect(service.getId("two"))
+        expect(appService.getId("two"))
                 .andReturn(id2)
                 .anyTimes();
-        expect(service.getId("three"))
+        expect(appService.getId("three"))
                 .andReturn(id3)
                 .anyTimes();
-        expect(service.getId("four"))
+        expect(appService.getId("four"))
                 .andReturn(id4)
                 .anyTimes();
 
-        expect(service.getApplication(id3))
+        expect(appService.getApplication(id3))
                 .andReturn(app3)
                 .anyTimes();
-        expect(service.getState(isA(ApplicationId.class)))
+        expect(appService.getState(isA(ApplicationId.class)))
                 .andReturn(ApplicationState.ACTIVE)
                 .anyTimes();
 
@@ -194,33 +249,27 @@
         codecService.activate();
         ServiceDirectory testDirectory =
                 new TestServiceDirectory()
-                        .add(ApplicationAdminService.class, service)
-                        .add(ApplicationService.class, service)
+                        .add(ApplicationAdminService.class, appService)
+                        .add(ApplicationService.class, appService)
+                        .add(CoreService.class, coreService)
                         .add(CodecService.class, codecService);
 
         BaseResource.setServiceDirectory(testDirectory);
     }
 
     /**
-     * Verifies test mocks.
-     */
-    @After
-    public void tearDownMocks() {
-        verify(service);
-    }
-
-    /**
      * Tests a GET of all applications when no applications are present.
      */
     @Test
     public void getAllApplicationsEmpty() {
-        expect(service.getApplications())
+        expect(appService.getApplications())
                 .andReturn(ImmutableSet.of());
-        replay(service);
+        replay(appService);
 
         WebResource rs = resource();
         String response = rs.path("applications").get(String.class);
         assertThat(response, is("{\"applications\":[]}"));
+        verify(appService);
     }
 
     /**
@@ -228,9 +277,9 @@
      */
     @Test
     public void getAllApplicationsPopulated() {
-        expect(service.getApplications())
+        expect(appService.getApplications())
                 .andReturn(ImmutableSet.of(app1, app2, app3, app4));
-        replay(service);
+        replay(appService);
 
         WebResource rs = resource();
         String response = rs.path("applications").get(String.class);
@@ -250,6 +299,7 @@
         assertThat(jsonApps.get(1).asObject(), matchesApp(app2));
         assertThat(jsonApps.get(2).asObject(), matchesApp(app3));
         assertThat(jsonApps.get(3).asObject(), matchesApp(app4));
+        verify(appService);
     }
 
     /**
@@ -257,7 +307,7 @@
      */
     @Test
     public void getSingleApplication() {
-        replay(service);
+        replay(appService);
 
         WebResource rs = resource();
         String response = rs.path("applications/three").get(String.class);
@@ -266,6 +316,7 @@
         assertThat(result, notNullValue());
 
         assertThat(result, matchesApp(app3));
+        verify(appService);
     }
 
     /**
@@ -274,13 +325,14 @@
      */
     @Test
     public void deleteApplication() {
-        service.uninstall(id3);
+        appService.uninstall(id3);
         expectLastCall();
 
-        replay(service);
+        replay(appService);
 
         WebResource rs = resource();
         rs.path("applications/three").delete();
+        verify(appService);
     }
 
     /**
@@ -289,13 +341,14 @@
      */
     @Test
     public void deleteActiveApplication() {
-        service.deactivate(id3);
+        appService.deactivate(id3);
         expectLastCall();
 
-        replay(service);
+        replay(appService);
 
         WebResource rs = resource();
         rs.path("applications/three/active").delete();
+        verify(appService);
     }
 
     /**
@@ -304,13 +357,14 @@
      */
     @Test
     public void postActiveApplication() {
-        service.activate(id3);
+        appService.activate(id3);
         expectLastCall();
 
-        replay(service);
+        replay(appService);
 
         WebResource rs = resource();
         rs.path("applications/three/active").post();
+        verify(appService);
     }
 
     /**
@@ -319,15 +373,15 @@
      */
     @Test
     public void postApplication() {
-        expect(service.install(isA(InputStream.class)))
+        expect(appService.install(isA(InputStream.class)))
                 .andReturn(app4)
                 .once();
 
-        replay(service);
+        replay(appService);
 
         ApplicationCodec codec = new ApplicationCodec();
         String app4Json = codec.encode(app4,
-                                       new MockCodecContextWithService(service))
+                                       new MockCodecContextWithAppService(appService))
                 .asText();
 
         WebResource rs = resource();
@@ -337,5 +391,52 @@
         assertThat(result, notNullValue());
 
         assertThat(result, matchesApp(app4));
+        verify(appService);
     }
+
+    /**
+     * Tests a POST operation to register appid.
+     */
+    @Test
+    public void postApplicationId() {
+        expect(coreService.registerApplication("app1")).andReturn(id1).once();
+
+        replay(coreService);
+
+        WebResource rs = resource();
+        rs.path("applications/app1/register").post();
+        verify(coreService);
+    }
+
+    /**
+     * Tests a GET of all application Ids.
+     */
+    @Test
+    public void getAllApplicationIdsPopulated() {
+        expect(coreService.getAppIds())
+                .andReturn(ImmutableSet.of(id1, id2, id3, id4));
+        replay(coreService);
+
+        WebResource rs = resource();
+        String response = rs.path("applications/ids").get(String.class);
+        assertThat(response, containsString("{\"applicationIds\":["));
+
+        JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("applicationIds"));
+
+        JsonArray jsonApps = result.get("applicationIds").asArray();
+        assertThat(jsonApps, notNullValue());
+        assertThat(jsonApps.size(), is(4));
+
+        assertThat(jsonApps.get(0).asObject(), matchesAppId(id1));
+        assertThat(jsonApps.get(1).asObject(), matchesAppId(id2));
+        assertThat(jsonApps.get(2).asObject(), matchesAppId(id3));
+        assertThat(jsonApps.get(3).asObject(), matchesAppId(id4));
+
+        verify(coreService);
+    }
+
 }