[ONOS-4409] Support applicationId registration and query via REST

With this commit, we can register and query on/off platform
applications through REST API.

Change-Id: I82e1e0e55bbc017d6c0cce7d9a6af7a578d7196e
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..446be58
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ApplicationIdCodec.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.onosproject.core.DefaultApplicationId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * ApplicationId JSON codec.
+ */
+public final class ApplicationIdCodec extends JsonCodec<ApplicationId> {
+
+    private static final String APP_ID = "id";
+    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("id", appId.id())
+                .put("name", appId.name());
+
+        return result;
+    }
+
+    @Override
+    public ApplicationId decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        // parse application identifier
+        int id = nullIsIllegal(json.get(APP_ID), APP_ID + MISSING_MEMBER_MESSAGE).asInt();
+
+        // parse application name
+        String name = nullIsIllegal(json.get(APP_NAME), APP_NAME + MISSING_MEMBER_MESSAGE).asText();
+
+        return new DefaultApplicationId(id, name);
+    }
+}
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 908e8ee..91ad4ff 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
@@ -27,6 +27,7 @@
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
 import org.onosproject.incubator.net.virtual.TenantId;
 import org.onosproject.incubator.net.virtual.VirtualDevice;
 import org.onosproject.incubator.net.virtual.VirtualLink;
@@ -95,6 +96,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..b246939
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/ApplicationIdCodecTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+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));
+    }
+
+    /**
+     * Tests decoding of an application id object.
+     */
+    @Test
+    public void testApplicationIdDecode() throws IOException {
+        ApplicationId appId = getApplicationId("ApplicationId.json");
+
+        assertThat((int) appId.id(), is(1));
+        assertThat(appId.name(), is("org.onosproject.foo"));
+    }
+
+    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) {
+
+            // check application role
+            int jsonAppId = jsonNode.get("id").asInt();
+            int appId = applicationId.id();
+            if (jsonAppId != appId) {
+                description.appendText("application ID was " + jsonAppId);
+                return false;
+            }
+
+            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);
+        }
+    }
+
+    /**
+     * Reads in a application id from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded application id
+     * @throws IOException if processing the resource fails
+     */
+    private ApplicationId getApplicationId(String resourceName) throws IOException {
+        InputStream jsonStream = ApplicationIdCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        assertThat(json, notNullValue());
+        ApplicationId applicationId = applicationIdCodec.decode((ObjectNode) json, context);
+        assertThat(applicationId, notNullValue());
+        return 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..c24c91a
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/ApplicationId.json
@@ -0,0 +1,4 @@
+{
+  "id": 1,
+  "name": "org.onosproject.foo"
+}
\ No newline at end of file
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 41e238b..ee6a6b5 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;
@@ -44,8 +45,8 @@
      * Get all installed applications.
      * Returns array of all installed applications.
      *
-     * @onos.rsModel Applications
      * @return 200 OK
+     * @onos.rsModel Applications
      */
     @GET
     public Response getApps() {
@@ -57,9 +58,10 @@
     /**
      * Get application details.
      * Returns details of the specified application.
-     * @onos.rsModel Application
+     *
      * @param name application name
      * @return 200 OK; 404; 401
+     * @onos.rsModel Application
      */
     @GET
     @Path("{name}")
@@ -143,9 +145,76 @@
         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 application Id entry by short id.
+     *
+     * @param shortId numerical id of application
+     * @return 200 OK; 404; 401
+     * @onos.rsModel ApplicationId
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("ids/short")
+    public Response getAppIdByShortId(@QueryParam("id") int shortId) {
+        CoreService service = get(CoreService.class);
+        ApplicationId appId = service.getAppId((short) shortId);
+        return response(appId);
+    }
+
+    /**
+     * Gets application Id entry by name.
+     *
+     * @param name name of application
+     * @return 200 OK; 404; 401
+     * @onos.rsModel ApplicationId
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("ids/name")
+    public Response getAppIdByName(@QueryParam("name") String name) {
+        CoreService service = get(CoreService.class);
+        ApplicationId appId = service.getAppId(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..e02321c
--- /dev/null
+++ b/web/api/src/main/resources/definitions/ApplicationId.json
@@ -0,0 +1,19 @@
+{
+  "type": "object",
+  "title": "applicationId",
+  "required": [
+    "name",
+    "id"
+  ],
+  "properties": {
+    "name": {
+      "type": "string",
+      "example": "org.onosproject.distributedprimitives"
+    },
+    "id": {
+      "type": "integer",
+      "format": "int64",
+      "example": 1
+    }
+  }
+}
\ 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..e415a7f
--- /dev/null
+++ b/web/api/src/main/resources/definitions/ApplicationIds.json
@@ -0,0 +1,35 @@
+{
+  "type": "object",
+  "title": "applicationIds",
+  "required": [
+    "applicationIds"
+  ],
+  "properties": {
+    "applicationIds": {
+      "type": "array",
+      "xml": {
+        "name": "applicationIds",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "applicationId",
+        "required": [
+          "name",
+          "id"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "example": "org.onosproject.distributedprimitives"
+          },
+          "id": {
+            "type": "integer",
+            "format": "int64",
+            "example": 1
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java
index 052c4320..e2a6bb1 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java
@@ -22,7 +22,6 @@
 import com.google.common.collect.ImmutableSet;
 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;
@@ -38,6 +37,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;
@@ -67,21 +67,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");
@@ -92,28 +107,28 @@
 
     private Application app1 =
             new DefaultApplication(id1, VER, "title1",
-                                   "desc1", "origin1", "category1", "url1",
-                                   "readme1", new byte[0], ApplicationRole.ADMIN,
-                                   ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"), ImmutableList.of());
+                    "desc1", "origin1", "category1", "url1",
+                    "readme1", new byte[0], ApplicationRole.ADMIN,
+                    ImmutableSet.of(), Optional.of(FURL),
+                    ImmutableList.of("My Feature"), ImmutableList.of());
     private Application app2 =
             new DefaultApplication(id2, VER, "title2",
-                                   "desc2", "origin2", "category2", "url2",
-                                   "readme2", new byte[0], ApplicationRole.ADMIN,
-                                   ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"), ImmutableList.of());
+                    "desc2", "origin2", "category2", "url2",
+                    "readme2", new byte[0], ApplicationRole.ADMIN,
+                    ImmutableSet.of(), Optional.of(FURL),
+                    ImmutableList.of("My Feature"), ImmutableList.of());
     private Application app3 =
             new DefaultApplication(id3, VER, "title3",
-                                   "desc3", "origin3", "category3", "url3",
-                                   "readme3", new byte[0], ApplicationRole.ADMIN,
-                                   ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"), ImmutableList.of());
+                    "desc3", "origin3", "category3", "url3",
+                    "readme3", new byte[0], ApplicationRole.ADMIN,
+                    ImmutableSet.of(), Optional.of(FURL),
+                    ImmutableList.of("My Feature"), ImmutableList.of());
     private Application app4 =
             new DefaultApplication(id4, VER, "title4",
-                                   "desc4", "origin4", "category4", "url4",
-                                   "readme4", new byte[0], ApplicationRole.ADMIN,
-                                   ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"), ImmutableList.of());
+                    "desc4", "origin4", "category4", "url4",
+                    "readme4", new byte[0], ApplicationRole.ADMIN,
+                    ImmutableSet.of(), Optional.of(FURL),
+                    ImmutableList.of("My Feature"), ImmutableList.of());
 
     /**
      * Hamcrest matcher to check that an application representation in JSON matches
@@ -160,6 +175,42 @@
     }
 
     /**
+     * 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 id
+            short jsonId = (short) jsonAppId.get("id").asInt();
+            if (jsonId != appId.id()) {
+                reason = "id " + appId.id();
+                return false;
+            }
+
+            // 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 matcher.
      *
      * @param app application object we are looking for
@@ -170,29 +221,40 @@
     }
 
     /**
+     * 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();
 
@@ -201,33 +263,28 @@
         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);
 
         WebTarget wt = target();
         String response = wt.path("applications").request().get(String.class);
         assertThat(response, is("{\"applications\":[]}"));
+
+        verify(appService);
     }
 
     /**
@@ -235,9 +292,9 @@
      */
     @Test
     public void getAllApplicationsPopulated() {
-        expect(service.getApplications())
+        expect(appService.getApplications())
                 .andReturn(ImmutableSet.of(app1, app2, app3, app4));
-        replay(service);
+        replay(appService);
 
         WebTarget wt = target();
         String response = wt.path("applications").request().get(String.class);
@@ -257,6 +314,8 @@
         assertThat(jsonApps.get(1).asObject(), matchesApp(app2));
         assertThat(jsonApps.get(2).asObject(), matchesApp(app3));
         assertThat(jsonApps.get(3).asObject(), matchesApp(app4));
+
+        verify(appService);
     }
 
     /**
@@ -264,7 +323,7 @@
      */
     @Test
     public void getSingleApplication() {
-        replay(service);
+        replay(appService);
 
         WebTarget wt = target();
         String response = wt.path("applications/three").request().get(String.class);
@@ -273,6 +332,8 @@
         assertThat(result, notNullValue());
 
         assertThat(result, matchesApp(app3));
+
+        verify(appService);
     }
 
     /**
@@ -281,13 +342,15 @@
      */
     @Test
     public void deleteApplication() {
-        service.uninstall(id3);
+        appService.uninstall(id3);
         expectLastCall();
 
-        replay(service);
+        replay(appService);
 
         WebTarget wt = target();
         wt.path("applications/three").request().delete();
+
+        verify(appService);
     }
 
     /**
@@ -296,13 +359,15 @@
      */
     @Test
     public void deleteActiveApplication() {
-        service.deactivate(id3);
+        appService.deactivate(id3);
         expectLastCall();
 
-        replay(service);
+        replay(appService);
 
         WebTarget wt = target();
         wt.path("applications/three/active").request().delete();
+
+        verify(appService);
     }
 
     /**
@@ -311,13 +376,15 @@
      */
     @Test
     public void postActiveApplication() {
-        service.activate(id3);
+        appService.activate(id3);
         expectLastCall();
 
-        replay(service);
+        replay(appService);
 
         WebTarget wt = target();
         wt.path("applications/three/active").request().post(null);
+
+        verify(appService);
     }
 
     /**
@@ -326,15 +393,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();
 
         WebTarget wt = target();
@@ -345,5 +412,109 @@
         assertThat(result, notNullValue());
 
         assertThat(result, matchesApp(app4));
+
+        verify(appService);
+    }
+
+    /**
+     * Tests a POST operation. This should attempt to register an on/off platform
+     * application ID.
+     */
+    @Test
+    public void postRegisterAppId() {
+        expect(coreService.registerApplication("app1")).andReturn(id1).anyTimes();
+        replay(coreService);
+
+        WebTarget wt = target();
+        wt.path("applications/app1/register").request().post(null);
+
+        verify(coreService);
+    }
+
+    /**
+     * Tests a GET of all application Ids when no applications are present.
+     */
+    @Test
+    public void getAllApplicationIdsEmpty() {
+        expect(coreService.getAppIds()).andReturn(ImmutableSet.of());
+        replay(coreService);
+
+        WebTarget wt = target();
+        String response = wt.path("applications/ids").request().get(String.class);
+        assertThat(response, is("{\"applicationIds\":[]}"));
+
+        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);
+
+        WebTarget wt = target();
+        String response = wt.path("applications/ids").request().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);
+    }
+
+    /**
+     * Tests a GET of an applicationId entry with the given numeric id.
+     */
+    @Test
+    public void getAppIdByShortId() {
+        expect(coreService.getAppId((short) 1)).andReturn(id1);
+        replay(coreService);
+
+        WebTarget wt = target();
+        String response = wt.path("applications/ids/short")
+                            .queryParam("id", 1).request().get(String.class);
+
+        JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result, matchesAppId(id1));
+
+        verify(coreService);
+    }
+
+    /**
+     * Tests a GET of an applicationId entry with the given application name.
+     */
+    @Test
+    public void getAppIdByName() {
+        expect(coreService.getAppId("app2")).andReturn(id2);
+        replay(coreService);
+
+        WebTarget wt = target();
+        String response = wt.path("applications/ids/name")
+                .queryParam("name", "app2").request().get(String.class);
+
+        JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result, matchesAppId(id2));
+
+        verify(coreService);
     }
 }