ONOS-542 Defining application subsystem interfaces & public constructs.

Change-Id: Iba0d2cb69dace5beee8a68def9918059ce755b5c
diff --git a/cli/src/main/java/org/onosproject/cli/ApplicationInstallCommand.java b/cli/src/main/java/org/onosproject/cli/ApplicationInstallCommand.java
new file mode 100644
index 0000000..f520804
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/ApplicationInstallCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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.cli;
+
+import org.apache.karaf.shell.commands.Command;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Lists application ID information.
+ */
+@Command(scope = "onos", name = "app-install",
+        description = "Lists application ID information")
+public class ApplicationInstallCommand extends AbstractShellCommand {
+
+    @Override
+    protected void execute() {
+        // FIXME: merely an experiment for now
+        try (InputStreamReader isr = new InputStreamReader(System.in);
+             BufferedReader br = new BufferedReader(isr)) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                print("%s", line.toUpperCase());
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 1350a4a..5e4e98c 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -21,6 +21,10 @@
         </command>
 
         <command>
+            <action class="org.onosproject.cli.ApplicationInstallCommand"/>
+        </command>
+
+        <command>
             <action class="org.onosproject.cli.MetricsListCommand"/>
         </command>
 
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java b/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java
new file mode 100644
index 0000000..18babd5
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationAdminService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Service for managing network control applications.
+ */
+public interface ApplicationAdminService extends ApplicationService {
+
+    /**
+     * Installs the application contained in the specified application archive
+     * input stream.
+     *
+     * @param appDescStream application descriptor input stream
+     * @return installed application descriptor
+     * @throws org.onosproject.app.ApplicationException if unable to read the app archive stream
+     */
+    Application install(InputStream appDescStream);
+
+    /**
+     * Uninstalls the specified application.
+     *
+     * @param appId application identifier
+     */
+    void uninstall(ApplicationId appId);
+
+    /**
+     * Activates the specified application.
+     *
+     * @param appId application identifier
+     */
+    void activate(ApplicationId appId);
+
+    /**
+     * Deactivates the specified application.
+     *
+     * @param appId application identifier
+     */
+    void deactivate(ApplicationId appId);
+
+    /**
+     * Updates the permissions granted to the applications.
+     *
+     * @param appId       application identifier
+     * @param permissions set of granted permissions
+     */
+    void setPermissions(ApplicationId appId, Set<Permission> permissions);
+}
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java b/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
new file mode 100644
index 0000000..ec7958d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Permission;
+import org.onosproject.core.Version;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Description of a network control/management application.
+ */
+public interface ApplicationDescription {
+
+    /**
+     * Returns the application name id.
+     *
+     * @return application identifier
+     */
+    String name();
+
+    /**
+     * Returns the application version.
+     *
+     * @return application version
+     */
+    Version version();
+
+    /**
+     * Returns description of the application.
+     *
+     * @return application description text
+     */
+    String description();
+
+    /**
+     * Returns the name of the application origin, group or company.
+     *
+     * @return application origin
+     */
+    String origin();
+
+    /**
+     * Returns the permissions requested by the application.
+     *
+     * @return requested permissions
+     */
+    Set<Permission> permissions();
+
+    /**
+     * Returns the feature repository URI. Null value signifies that the
+     * application did not provide its own features repository.
+     *
+     * @return optional feature repo URL
+     */
+    Optional<URI> featuresRepo();
+
+    /**
+     * Returns the set of features comprising the application. At least one
+     * feature must be given.
+     *
+     * @return application features
+     */
+    Set<String> features();
+}
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java b/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java
new file mode 100644
index 0000000..5bf1323
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationEvent.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes application lifecycle event.
+ */
+public class ApplicationEvent extends AbstractEvent<ApplicationEvent.Type, Application> {
+
+    public enum Type {
+        /**
+         * Signifies that an application has been installed.
+         */
+        APP_INSTALLED,
+
+        /**
+         * Signifies that an application has been activated.
+         */
+        APP_ACTIVATED,
+
+        /**
+         * Signifies that an application has been deactivated.
+         */
+        APP_DEACTIVATED,
+
+        /**
+         * Signifies that an application has been uninstalled.
+         */
+        APP_UNINSTALLED,
+
+        /**
+         * Signifies that application granted permissions have changed.
+         */
+        APP_PERMISSIONS_CHANGED
+    }
+
+    /**
+     * Creates an event of a given type and for the specified app and the
+     * current time.
+     *
+     * @param type app event type
+     * @param app  event app subject
+     */
+    public ApplicationEvent(Type type, Application app) {
+        super(type, app);
+    }
+
+    /**
+     * Creates an event of a given type and for the specified app and time.
+     *
+     * @param type app event type
+     * @param app  event app subject
+     * @param time occurrence time
+     */
+    public ApplicationEvent(Type type, Application app, long time) {
+        super(type, app, time);
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationException.java b/core/api/src/main/java/org/onosproject/app/ApplicationException.java
new file mode 100644
index 0000000..2888c70
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 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.app;
+
+/**
+ * Represents class of errors related to application management.
+ */
+public class ApplicationException extends RuntimeException {
+
+    private static final long serialVersionUID = -2287403908433720122L;
+
+    /**
+     * Constructs an exception with no message and no underlying cause.
+     */
+    public ApplicationException() {
+    }
+
+    /**
+     * Constructs an exception with the specified message.
+     *
+     * @param message the message describing the specific nature of the error
+     */
+    public ApplicationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs an exception with the specified message and the underlying cause.
+     *
+     * @param message the message describing the specific nature of the error
+     * @param cause the underlying cause of this error
+     */
+    public ApplicationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/api/src/main/java/org/onosproject/app/ApplicationListener.java
similarity index 68%
copy from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
copy to core/api/src/main/java/org/onosproject/app/ApplicationListener.java
index e39533c..7a68057 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
+package org.onosproject.app;
+
+import org.onosproject.event.EventListener;
 
 /**
- * Created by tom on 10/16/14.
+ * Entity capable of receiving application related events.
  */
-public class DeleteMe {
+public interface ApplicationListener extends EventListener<ApplicationEvent> {
 }
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationService.java b/core/api/src/main/java/org/onosproject/app/ApplicationService.java
new file mode 100644
index 0000000..1aaf092
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationService.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+
+import java.util.Set;
+
+/**
+ * Service for inspecting inventory of network control applications.
+ */
+public interface ApplicationService {
+
+    /**
+     * Returns the set of all installed applications.
+     *
+     * @return set of installed apps
+     */
+    Set<Application> getApplications();
+
+    /**
+     * Returns the registered id of the application with the given name.
+     *
+     * @param name application name
+     * @return registered application id
+     */
+    ApplicationId getId(String name);
+
+    /**
+     * Returns the application with the supplied application identifier.
+     *
+     * @param appId application identifier
+     * @return application descriptor
+     */
+    Application getApplication(ApplicationId appId);
+
+    /**
+     * Return the application state.
+     *
+     * @param appId application identifier
+     * @return application state
+     */
+    ApplicationState getState(ApplicationId appId);
+
+    /**
+     * Returns the permissions currently granted to the applications.
+     *
+     * @param appId application identifier
+     * @return set of granted permissions
+     */
+    Set<Permission> getPermissions(ApplicationId appId);
+
+    /**
+     * Adds the given listener for application lifecycle events.
+     *
+     * @param listener listener to be added
+     */
+    void addListener(ApplicationListener listener);
+
+    /**
+     * Removes the specified listener for application lifecycle events.
+     *
+     * @param listener listener to be removed
+     */
+    void removeListener(ApplicationListener listener);
+
+}
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/api/src/main/java/org/onosproject/app/ApplicationState.java
similarity index 63%
copy from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
copy to core/api/src/main/java/org/onosproject/app/ApplicationState.java
index e39533c..c480a0c 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationState.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
+package org.onosproject.app;
 
 /**
- * Created by tom on 10/16/14.
+ * Representation of an application state.
  */
-public class DeleteMe {
+public enum ApplicationState {
+
+    /**
+     * Indicates that application has been installed, but is not running.
+     */
+    INSTALLED,
+
+    /**
+     * Indicates that application is active.
+     */
+    ACTIVE
+
 }
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationStore.java b/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
new file mode 100644
index 0000000..d20adb5
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+import org.onosproject.store.Store;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Service for managing network control applications.
+ */
+public interface ApplicationStore extends Store<ApplicationEvent, ApplicationStoreDelegate> {
+
+    /**
+     * Returns the set of all installed applications.
+     *
+     * @return set of installed apps
+     */
+    Set<Application> getApplications();
+
+    /**
+     * Returns the registered id of the application with the given name.
+     *
+     * @param name application name
+     * @return registered application id
+     */
+    ApplicationId getId(String name);
+
+    /**
+     * Returns the application with the supplied application identifier.
+     *
+     * @param appId application identifier
+     * @return application descriptor
+     */
+    Application getApplication(ApplicationId appId);
+
+    /**
+     * Returns the current application state.
+     *
+     * @param appId application identifier
+     * @return application state
+     */
+    ApplicationState getState(ApplicationId appId);
+
+    /**
+     * Creates the application from the specified application descriptor
+     * input stream.
+     *
+     * @param appDescStream application archive input stream
+     * @return application descriptor
+     */
+    Application create(InputStream appDescStream);
+
+    /**
+     * Removes the specified application.
+     *
+     * @param appId application identifier
+     */
+    void remove(ApplicationId appId);
+
+    /**
+     * Mark the application as actived.
+     *
+     * @param appId application identifier
+     */
+    void activate(ApplicationId appId);
+
+    /**
+     * Mark the application as deactivated.
+     *
+     * @param appId application identifier
+     */
+    void deactivate(ApplicationId appId);
+
+    /**
+     * Returns the permissions granted to the applications.
+     *
+     * @param appId application identifier
+     * @return set of granted permissions
+     */
+    Set<Permission> getPermissions(ApplicationId appId);
+
+    /**
+     * Updates the permissions granted to the applications.
+     *
+     * @param appId       application identifier
+     * @param permissions set of granted permissions
+     */
+    void setPermissions(ApplicationId appId, Set<Permission> permissions);
+
+}
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java
similarity index 69%
copy from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
copy to core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java
index e39533c..f339e68 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationStoreDelegate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
+package org.onosproject.app;
+
+import org.onosproject.store.StoreDelegate;
 
 /**
- * Created by tom on 10/16/14.
+ * Application store delegate abstraction.
  */
-public class DeleteMe {
+public interface ApplicationStoreDelegate extends StoreDelegate<ApplicationEvent> {
 }
diff --git a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
new file mode 100644
index 0000000..9c53542
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Permission;
+import org.onosproject.core.Version;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of network control/management application descriptor.
+ */
+public class DefaultApplicationDescription implements ApplicationDescription {
+
+    private final String name;
+    private final Version version;
+    private final String description;
+    private final String origin;
+    private final Set<Permission> permissions;
+    private final Optional<URI> featuresRepo;
+    private final Set<String> features;
+
+    /**
+     * Creates a new application descriptor using the supplied data.
+     *
+     * @param name         application name
+     * @param version      application version
+     * @param description  application description
+     * @param origin       origin company
+     * @param permissions  requested permissions
+     * @param featuresRepo optional features repo URI
+     * @param features     application features
+     */
+    public DefaultApplicationDescription(String name, Version version,
+                                         String description, String origin,
+                                         Set<Permission> permissions,
+                                         URI featuresRepo, Set<String> features) {
+        this.name = checkNotNull(name, "Name cannot be null");
+        this.version = checkNotNull(version, "Version cannot be null");
+        this.description = checkNotNull(description, "Description cannot be null");
+        this.origin = checkNotNull(origin, "Origin cannot be null");
+        this.permissions = checkNotNull(permissions, "Permissions cannot be null");
+        this.featuresRepo = Optional.ofNullable(featuresRepo);
+        this.features = checkNotNull(features, "Features cannot be null");
+        checkArgument(!features.isEmpty(), "There must be at least one feature");
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+
+    @Override
+    public String description() {
+        return description;
+    }
+
+    @Override
+    public String origin() {
+        return origin;
+    }
+
+    @Override
+    public Set<Permission> permissions() {
+        return permissions;
+    }
+
+    @Override
+    public Optional<URI> featuresRepo() {
+        return featuresRepo;
+    }
+
+    @Override
+    public Set<String> features() {
+        return features;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("name", name)
+                .add("version", version)
+                .add("description", description)
+                .add("origin", origin)
+                .add("permissions", permissions)
+                .add("featuresRepo", featuresRepo)
+                .add("features", features)
+                .toString();
+    }
+}
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/api/src/main/java/org/onosproject/app/package-info.java
similarity index 79%
copy from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
copy to core/api/src/main/java/org/onosproject/app/package-info.java
index e39533c..f8e5465 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/api/src/main/java/org/onosproject/app/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
 
 /**
- * Created by tom on 10/16/14.
+ * Set of abstractions for managing network control applications.
  */
-public class DeleteMe {
-}
+package org.onosproject.app;
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/core/Application.java b/core/api/src/main/java/org/onosproject/core/Application.java
new file mode 100644
index 0000000..d876a67
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/core/Application.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 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.core;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Abstraction of a network control/management application.
+ */
+public interface Application {
+
+    /**
+     * Returns the application name id.
+     *
+     * @return application identifier
+     */
+    ApplicationId id();
+
+    /**
+     * Returns the application version.
+     *
+     * @return application version
+     */
+    Version version();
+
+    /**
+     * Returns description of the application.
+     *
+     * @return application description text
+     */
+    String description();
+
+    /**
+     * Returns the name of the application origin, group or company.
+     *
+     * @return application origin
+     */
+    String origin();
+
+    /**
+     * Returns the permissions requested by the application.
+     *
+     * @return requested permissions
+     */
+    Set<Permission> permissions();
+
+    /**
+     * Returns the feature repository URI. Null value signifies that the
+     * application did not provide its own features repository.
+     *
+     * @return optional feature repo URL
+     */
+    Optional<URI> featuresRepo();
+
+    /**
+     * Returns the set of features comprising the application. At least one
+     * feature must be given.
+     *
+     * @return application features
+     */
+    Set<String> features();
+}
diff --git a/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java b/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java
index cef3f15..2236fc5 100644
--- a/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java
+++ b/core/api/src/main/java/org/onosproject/core/ApplicationIdStore.java
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 package org.onosproject.core;
+// FIXME: Move to org.onosproject.app package
 
 import java.util.Set;
 
@@ -31,12 +32,21 @@
 
     /**
      * Returns an existing application id from a given id.
+     *
      * @param id the short value of the id
-     * @return an application id
+     * @return an application id; null if no such app registered
      */
     ApplicationId getAppId(Short id);
 
     /**
+     * Returns registered application id from the given name.
+     *
+     * @param name application name
+     * @return an application id; null if no such app registered
+     */
+    ApplicationId getAppId(String name);
+
+    /**
      * Registers a new application by its name, which is expected
      * to follow the reverse DNS convention, e.g.
      * {@code org.flying.circus.app}
diff --git a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
new file mode 100644
index 0000000..4da85a5
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015 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.core;
+
+import java.net.URI;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of network control/management application descriptor.
+ */
+public class DefaultApplication implements Application {
+
+    private final ApplicationId appId;
+    private final Version version;
+    private final String description;
+    private final String origin;
+    private final Set<Permission> permissions;
+    private final Optional<URI> featuresRepo;
+    private final Set<String> features;
+
+    /**
+     * Creates a new application descriptor using the supplied data.
+     *
+     * @param appId        application identifier
+     * @param version      application version
+     * @param description  application description
+     * @param origin       origin company
+     * @param permissions  requested permissions
+     * @param featuresRepo optional features repo URI
+     * @param features     application features
+     */
+    public DefaultApplication(ApplicationId appId, Version version,
+                              String description, String origin,
+                              Set<Permission> permissions,
+                              Optional<URI> featuresRepo, Set<String> features) {
+        this.appId = checkNotNull(appId, "ID cannot be null");
+        this.version = checkNotNull(version, "Version cannot be null");
+        this.description = checkNotNull(description, "Description cannot be null");
+        this.origin = checkNotNull(origin, "Origin cannot be null");
+        this.permissions = checkNotNull(permissions, "Permissions cannot be null");
+        this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null");
+        this.features = checkNotNull(features, "Features cannot be null");
+        checkArgument(!features.isEmpty(), "There must be at least one feature");
+    }
+
+    @Override
+    public ApplicationId id() {
+        return appId;
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+
+    @Override
+    public String description() {
+        return description;
+    }
+
+    @Override
+    public String origin() {
+        return origin;
+    }
+
+    @Override
+    public Set<Permission> permissions() {
+        return permissions;
+    }
+
+    @Override
+    public Optional<URI> featuresRepo() {
+        return featuresRepo;
+    }
+
+    @Override
+    public Set<String> features() {
+        return features;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(appId, version, description, origin, permissions,
+                            featuresRepo, features);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final DefaultApplication other = (DefaultApplication) obj;
+        return Objects.equals(this.appId, other.appId) &&
+                Objects.equals(this.version, other.version) &&
+                Objects.equals(this.description, other.description) &&
+                Objects.equals(this.origin, other.origin) &&
+                Objects.equals(this.permissions, other.permissions) &&
+                Objects.equals(this.featuresRepo, other.featuresRepo) &&
+                Objects.equals(this.features, other.features);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("appId", appId)
+                .add("version", version)
+                .add("description", description)
+                .add("origin", origin)
+                .add("permissions", permissions)
+                .add("featuresRepo", featuresRepo)
+                .add("features", features)
+                .toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java b/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java
index e6f448e..3a07b2b 100644
--- a/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java
+++ b/core/api/src/main/java/org/onosproject/core/DefaultApplicationId.java
@@ -18,6 +18,7 @@
 import java.util.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
 
 /**
  * Application identifier.
@@ -33,8 +34,9 @@
      * @param id   application identifier
      * @param name application name
      */
-    public DefaultApplicationId(Short id, String name) {
-        this.id = id;
+    public DefaultApplicationId(int id, String name) {
+        checkArgument(0 <= id && id <= Short.MAX_VALUE, "id is outside range");
+        this.id = (short) id;
         this.name = name;
     }
 
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/api/src/main/java/org/onosproject/core/Permission.java
similarity index 75%
copy from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
copy to core/api/src/main/java/org/onosproject/core/Permission.java
index e39533c..d32d059 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/api/src/main/java/org/onosproject/core/Permission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
+package org.onosproject.core;
 
 /**
- * Created by tom on 10/16/14.
+ * Representation of an application permission.
  */
-public class DeleteMe {
+public interface Permission {
+    // TODO: to be fleshed out
 }
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java b/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java
new file mode 100644
index 0000000..4e5ccf8
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationAdminServiceAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Adapter for testing against application admin service.
+ */
+public class ApplicationAdminServiceAdapter extends ApplicationServiceAdapter
+        implements ApplicationAdminService {
+    @Override
+    public Set<Application> getApplications() {
+        return null;
+    }
+
+    @Override
+    public Application getApplication(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public ApplicationState getState(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public Set<Permission> getPermissions(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public void addListener(ApplicationListener listener) {
+    }
+
+    @Override
+    public void removeListener(ApplicationListener listener) {
+    }
+
+    @Override
+    public Application install(InputStream appDescStream) {
+        return null;
+    }
+
+    @Override
+    public void uninstall(ApplicationId appId) {
+    }
+
+    @Override
+    public void activate(ApplicationId appId) {
+    }
+
+    @Override
+    public void deactivate(ApplicationId appId) {
+    }
+
+    @Override
+    public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java b/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
new file mode 100644
index 0000000..4a882c3
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.junit.Test;
+import org.onosproject.core.Application;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.event.AbstractEventTest;
+
+import java.util.Optional;
+
+import static org.onosproject.app.ApplicationEvent.Type.APP_ACTIVATED;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+import static org.onosproject.core.DefaultApplicationTest.APP_ID;
+
+/**
+ * Test of the application event.
+ */
+public class ApplicationEventTest extends AbstractEventTest {
+
+    private Application createApp() {
+        return new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
+                                      PERMS, Optional.of(FURL), FEATURES);
+    }
+
+    @Test
+    public void withoutTime() {
+        Application app = createApp();
+        ApplicationEvent event = new ApplicationEvent(APP_ACTIVATED, app, 123L);
+        validateEvent(event, APP_ACTIVATED, app, 123L);
+    }
+
+    @Test
+    public void withTime() {
+        Application app = createApp();
+        long before = System.currentTimeMillis();
+        ApplicationEvent event = new ApplicationEvent(APP_ACTIVATED, app);
+        long after = System.currentTimeMillis();
+        validateEvent(event, APP_ACTIVATED, app, before, after);
+    }
+
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java b/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java
new file mode 100644
index 0000000..a0c7ef1
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onlab.junit.ExceptionTest;
+
+public class ApplicationExceptionTest extends ExceptionTest {
+
+    @Override
+    protected Exception getDefault() {
+        return new ApplicationException();
+    }
+
+    @Override
+    protected Exception getWithMessage() {
+        return new ApplicationException(MESSAGE);
+    }
+
+    @Override
+    protected Exception getWithMessageAndCause() {
+        return new ApplicationException(MESSAGE, CAUSE);
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java b/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java
new file mode 100644
index 0000000..ff1822a
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationServiceAdapter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+
+import java.util.Set;
+
+/**
+ * Adapter for testing against application service.
+ */
+public class ApplicationServiceAdapter implements ApplicationService {
+    @Override
+    public Set<Application> getApplications() {
+        return null;
+    }
+
+    @Override
+    public ApplicationId getId(String name) {
+        return null;
+    }
+
+    @Override
+    public Application getApplication(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public ApplicationState getState(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public Set<Permission> getPermissions(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public void addListener(ApplicationListener listener) {
+    }
+
+    @Override
+    public void removeListener(ApplicationListener listener) {
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java b/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java
new file mode 100644
index 0000000..265c2a6
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationStoreAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 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.app;
+
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+import org.onosproject.store.AbstractStore;
+
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Adapter for application testing against application store.
+ */
+public class ApplicationStoreAdapter
+        extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate>
+        implements ApplicationStore {
+    @Override
+    public Set<Application> getApplications() {
+        return null;
+    }
+
+    @Override
+    public ApplicationId getId(String name) {
+        return null;
+    }
+
+    @Override
+    public Application getApplication(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public ApplicationState getState(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public Application create(InputStream appDescStream) {
+        return null;
+    }
+
+    @Override
+    public void remove(ApplicationId appId) {
+    }
+
+    @Override
+    public void activate(ApplicationId appId) {
+    }
+
+    @Override
+    public void deactivate(ApplicationId appId) {
+    }
+
+    @Override
+    public Set<Permission> getPermissions(ApplicationId appId) {
+        return null;
+    }
+
+    @Override
+    public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+    }
+
+}
diff --git a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
new file mode 100644
index 0000000..7079427
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 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.app;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.onosproject.core.Permission;
+import org.onosproject.core.Version;
+
+import java.net.URI;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Basic tests of the default app description.
+ */
+public class DefaultApplicationDescriptionTest {
+
+    public static final String APP_NAME = "org.foo.app";
+    public static final Version VER = Version.version(1, 2, "a", null);
+    public static final String DESC = "Awesome application from Circus";
+    public static final String ORIGIN = "Circus";
+    public static final Set<Permission> PERMS = ImmutableSet.of();
+    public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
+    public static final Set<String> FEATURES = ImmutableSet.of("foo");
+
+    @Test
+    public void basics() {
+        ApplicationDescription app =
+                new DefaultApplicationDescription(APP_NAME, VER, DESC, ORIGIN,
+                                                  PERMS, FURL, FEATURES);
+        assertEquals("incorrect id", APP_NAME, app.name());
+        assertEquals("incorrect version", VER, app.version());
+        assertEquals("incorrect description", DESC, app.description());
+        assertEquals("incorrect origin", ORIGIN, app.origin());
+        assertEquals("incorrect permissions", PERMS, app.permissions());
+        assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
+        assertEquals("incorrect features", FEATURES, app.features());
+        assertTrue("incorrect toString", app.toString().contains(APP_NAME));
+    }
+
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java b/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java
new file mode 100644
index 0000000..c3d0f23
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/core/ApplicationIdStoreAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 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.core;
+
+import java.util.Set;
+
+/**
+ * Adapter for testing against app id store.
+ */
+public class ApplicationIdStoreAdapter implements ApplicationIdStore {
+    @Override
+    public Set<ApplicationId> getAppIds() {
+        return null;
+    }
+
+    @Override
+    public ApplicationId getAppId(Short id) {
+        return null;
+    }
+
+    @Override
+    public ApplicationId getAppId(String name) {
+        return null;
+    }
+
+    @Override
+    public ApplicationId registerApplication(String identifier) {
+        return null;
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java b/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
new file mode 100644
index 0000000..ed0fe6d
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 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.core;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+
+/**
+ * Basic tests of the default app descriptor.
+ */
+public class DefaultApplicationTest {
+
+    public static final ApplicationId APP_ID = new DefaultApplicationId(2, APP_NAME);
+
+    @Test
+    public void basics() {
+        Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
+                                                 PERMS, Optional.of(FURL), FEATURES);
+        assertEquals("incorrect id", APP_ID, app.id());
+        assertEquals("incorrect version", VER, app.version());
+        assertEquals("incorrect description", DESC, app.description());
+        assertEquals("incorrect origin", ORIGIN, app.origin());
+        assertEquals("incorrect permissions", PERMS, app.permissions());
+        assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
+        assertEquals("incorrect features", FEATURES, app.features());
+        assertTrue("incorrect toString", app.toString().contains(APP_NAME));
+    }
+
+    @Test
+    public void testEquality() {
+        Application a1 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
+                                                PERMS, Optional.of(FURL), FEATURES);
+        Application a2 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
+                                                PERMS, Optional.of(FURL), FEATURES);
+        Application a3 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
+                                                PERMS, Optional.empty(), FEATURES);
+        Application a4 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN + "asd",
+                                                PERMS, Optional.of(FURL), FEATURES);
+        new EqualsTester().addEqualityGroup(a1, a2)
+                .addEqualityGroup(a3).addEqualityGroup(a4).testEquals();
+    }
+
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java b/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java
new file mode 100644
index 0000000..ac565d8
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/core/UnavailableIdExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 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.core;
+
+import org.onlab.junit.ExceptionTest;
+
+public class UnavailableIdExceptionTest extends ExceptionTest {
+
+    @Override
+    protected Exception getDefault() {
+        return new UnavailableIdException();
+    }
+
+    @Override
+    protected Exception getWithMessage() {
+        return new UnavailableIdException(MESSAGE);
+    }
+
+    @Override
+    protected Exception getWithMessageAndCause() {
+        return new UnavailableIdException(MESSAGE, CAUSE);
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/net/intent/IntentOperationsTest.java b/core/api/src/test/java/org/onosproject/net/intent/IntentOperationsTest.java
index ec0ddae..484a44a 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/IntentOperationsTest.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/IntentOperationsTest.java
@@ -45,7 +45,7 @@
     final TrafficSelector selector = new IntentTestsMocks.MockSelector();
     final IntentTestsMocks.MockTreatment treatment = new IntentTestsMocks.MockTreatment();
 
-    private final ApplicationId appId = new DefaultApplicationId((short) 1, "IntentOperationsTest");
+    private final ApplicationId appId = new DefaultApplicationId(1, "IntentOperationsTest");
 
     private Intent intent;
     protected IdGenerator idGenerator = new MockIdGenerator();
diff --git a/core/json/pom.xml b/core/common/pom.xml
similarity index 84%
rename from core/json/pom.xml
rename to core/common/pom.xml
index d9a84c1..8c5b80f 100644
--- a/core/json/pom.xml
+++ b/core/common/pom.xml
@@ -26,16 +26,17 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>onos-json</artifactId>
+    <artifactId>onos-core-common</artifactId>
     <packaging>bundle</packaging>
 
-    <description>ONOS JSON encode/decode facilities</description>
+    <description>ONOS utilities common to the core modules</description>
 
     <dependencies>
         <dependency>
             <groupId>org.onosproject</groupId>
             <artifactId>onos-api</artifactId>
         </dependency>
+
         <dependency>
             <groupId>org.onosproject</groupId>
             <artifactId>onos-api</artifactId>
@@ -44,13 +45,17 @@
         </dependency>
 
         <dependency>
-            <groupId>org.onosproject</groupId>
-            <artifactId>onos-core-trivial</artifactId>
-            <version>${project.version}</version>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
             <scope>test</scope>
         </dependency>
 
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.scr.annotations</artifactId>
         </dependency>
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
new file mode 100644
index 0000000..90e6005
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2015 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.common.app;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.onlab.util.Tools;
+import org.onosproject.app.ApplicationDescription;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationException;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.app.DefaultApplicationDescription;
+import org.onosproject.core.Permission;
+import org.onosproject.core.Version;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+import static com.google.common.io.Files.createParentDirs;
+import static com.google.common.io.Files.write;
+
+/**
+ * Facility for reading application archive stream and managing application
+ * directory structure.
+ */
+public class ApplicationArchive
+        extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> {
+
+    private static final String NAME = "[@name]";
+    private static final String ORIGIN = "[@origin]";
+    private static final String VERSION = "[@version]";
+    private static final String FEATURES_REPO = "[@featuresRepo]";
+    private static final String FEATURES = "[@features]";
+    private static final String DESCRIPTION = "description";
+
+    private static Logger log = LoggerFactory.getLogger(ApplicationArchive.class);
+    private static final String APP_XML = "app.xml";
+    private static final String APPS_ROOT = "data/apps/";
+
+    private File appsDir = new File(APPS_ROOT);
+
+    /**
+     * Sets the root directory where application artifacts are kept.
+     *
+     * @param appsRoot top-level applications directory path
+     */
+    protected void setAppsRoot(String appsRoot) {
+        this.appsDir = new File(appsRoot);
+    }
+
+    /**
+     * Returns the root directory where application artifacts are kept.
+     *
+     * @return top-level applications directory path
+     */
+    protected String getAppsRoot() {
+        return appsDir.getPath();
+    }
+
+    /**
+     * Returns the set of installed application names.
+     *
+     * @return installed application names
+     */
+    public Set<String> getApplicationNames() {
+        ImmutableSet.Builder<String> names = ImmutableSet.builder();
+        File[] files = appsDir.listFiles(File::isDirectory);
+        if (files != null) {
+            for (File file : files) {
+                names.add(file.getName());
+            }
+        }
+        return names.build();
+    }
+
+    /**
+     * Loads the application descriptor from the specified application archive
+     * stream and saves the stream in the appropriate application archive
+     * directory.
+     *
+     * @param appName application name
+     * @return application descriptor
+     * @throws org.onosproject.app.ApplicationException if unable to read application description
+     */
+    public ApplicationDescription getApplicationDescription(String appName) {
+        try {
+            return loadAppDescription(new XMLConfiguration(appFile(appName, APP_XML)));
+        } catch (Exception e) {
+            throw new ApplicationException("Unable to get app description", e);
+        }
+    }
+
+    /**
+     * Loads the application descriptor from the specified application archive
+     * stream and saves the stream in the appropriate application archive
+     * directory.
+     *
+     * @param stream application archive stream
+     * @return application descriptor
+     * @throws org.onosproject.app.ApplicationException if unable to read the
+     *                                                  archive stream or store
+     *                                                  the application archive
+     */
+    public ApplicationDescription saveApplication(InputStream stream) {
+        try (InputStream ais = stream) {
+            byte[] cache = toByteArray(ais);
+            InputStream bis = new ByteArrayInputStream(cache);
+            ApplicationDescription desc = parseAppDescription(bis);
+            bis.reset();
+
+            expandApplication(bis, desc);
+            bis.reset();
+
+            saveApplication(bis, desc);
+            installArtifacts(desc);
+            return desc;
+        } catch (IOException e) {
+            throw new ApplicationException("Unable to save application", e);
+        }
+    }
+
+    /**
+     * Purges the application archive directory.
+     *
+     * @param appName application name
+     */
+    public void purgeApplication(String appName) {
+        try {
+            Tools.removeDirectory(new File(appsDir, appName));
+        } catch (IOException e) {
+            throw new ApplicationException("Unable to purge application " + appName, e);
+        }
+    }
+
+    /**
+     * Returns application archive stream for the specified application.
+     *
+     * @param appName application name
+     * @return application archive stream
+     */
+    public InputStream getApplicationInputStream(String appName) {
+        try {
+            return new FileInputStream(appFile(appName, appName + ".zip"));
+        } catch (FileNotFoundException e) {
+            throw new ApplicationException("Application " + appName + " not found");
+        }
+    }
+
+    // Scans the specified ZIP stream for app.xml entry and parses it producing
+    // an application descriptor.
+    private ApplicationDescription parseAppDescription(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 = new byte[(int) entry.getSize()];
+                    ByteStreams.readFully(zis, data);
+                    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);
+                    }
+                }
+                zis.closeEntry();
+            }
+        }
+        throw new IOException("Unable to locate " + APP_XML);
+    }
+
+    private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
+        cfg.setAttributeSplittingDisabled(true);
+        String name = cfg.getString(NAME);
+        Version version = Version.version(cfg.getString(VERSION));
+        String desc = cfg.getString(DESCRIPTION);
+        String origin = cfg.getString(ORIGIN);
+        Set<Permission> perms = ImmutableSet.of();
+        String featRepo = cfg.getString(FEATURES_REPO);
+        URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
+        Set<String> features = ImmutableSet.copyOf(cfg.getString(FEATURES).split(","));
+
+        return new DefaultApplicationDescription(name, version, desc, origin,
+                                                 perms, featuresRepo, features);
+    }
+
+    // Expands the specified ZIP stream into app-specific directory.
+    private void expandApplication(InputStream stream, ApplicationDescription desc)
+            throws IOException {
+        ZipInputStream zis = new ZipInputStream(stream);
+        ZipEntry entry;
+        File appDir = new File(appsDir, desc.name());
+        while ((entry = zis.getNextEntry()) != null) {
+            byte[] data = new byte[(int) entry.getSize()];
+            ByteStreams.readFully(zis, data);
+            zis.closeEntry();
+
+            File file = new File(appDir, entry.getName());
+            createParentDirs(file);
+            write(data, file);
+        }
+        zis.close();
+    }
+
+    // Saves the specified ZIP stream into a file under app-specific directory.
+    private void saveApplication(InputStream stream, ApplicationDescription desc)
+            throws IOException {
+        Files.write(toByteArray(stream), appFile(desc.name(), desc.name() + ".zip"));
+    }
+
+    // Installs application artifacts into M2 repository.
+    private void installArtifacts(ApplicationDescription desc) {
+        // FIXME: implement M2 repository copy
+    }
+
+    protected boolean setActive(String appName) {
+        try {
+            return appFile(appName, "active").createNewFile();
+        } catch (IOException e) {
+            throw new ApplicationException("Unable to mark app as active", e);
+        }
+    }
+
+    protected boolean clearActive(String appName) {
+        return appFile(appName, "active").delete();
+    }
+
+    protected boolean isActive(String appName) {
+        return appFile(appName, "active").exists();
+    }
+
+
+    // Returns the name of the file located under the specified app directory.
+    private File appFile(String appName, String fileName) {
+        return new File(new File(appsDir, appName), fileName);
+    }
+
+}
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/common/src/main/java/org/onosproject/common/app/package-info.java
similarity index 78%
copy from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
copy to core/common/src/main/java/org/onosproject/common/app/package-info.java
index e39533c..898bad7 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/common/src/main/java/org/onosproject/common/app/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
 
 /**
- * Created by tom on 10/16/14.
+ * Common facilities for construction of application management subsystem.
  */
-public class DeleteMe {
-}
+package org.onosproject.common.app;
\ No newline at end of file
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
new file mode 100644
index 0000000..c1b8e82
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/common/app/ApplicationArchiveTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2015 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.common.app;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.app.ApplicationDescription;
+import org.onosproject.app.ApplicationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+
+public class ApplicationArchiveTest {
+
+    static final String ROOT = "/tmp/app-junit";
+
+    private ApplicationArchive aar = new ApplicationArchive();
+
+    @Before
+    public void setUp() {
+        aar.setAppsRoot(ROOT);
+    }
+
+    private void validate(ApplicationDescription app) {
+        assertEquals("incorrect name", APP_NAME, app.name());
+        assertEquals("incorrect version", VER, app.version());
+        assertEquals("incorrect origin", ORIGIN, app.origin());
+
+        assertEquals("incorrect description", DESC, app.description());
+        assertEquals("incorrect features URI", FURL, app.featuresRepo().get());
+        assertEquals("incorrect permissions", PERMS, app.permissions());
+        assertEquals("incorrect features", FEATURES, app.features());
+    }
+
+    @Test
+    public void saveApp() throws IOException {
+        InputStream stream = getClass().getResourceAsStream("app.zip");
+        ApplicationDescription app = aar.saveApplication(stream);
+        validate(app);
+    }
+
+    @Test
+    public void loadApp() throws IOException {
+        saveApp();
+        ApplicationDescription app = aar.getApplicationDescription(APP_NAME);
+        validate(app);
+    }
+
+    @Test
+    public void getAppNames() throws IOException {
+        saveApp();
+        Set<String> names = aar.getApplicationNames();
+        assertEquals("incorrect names", ImmutableSet.of(APP_NAME), names);
+    }
+
+    @Test
+    public void purgeApp() throws IOException {
+        saveApp();
+        aar.purgeApplication(APP_NAME);
+        assertEquals("incorrect names", ImmutableSet.of(), aar.getApplicationNames());
+    }
+
+    @Test
+    public void getAppStream() throws IOException {
+        saveApp();
+        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(expected = ApplicationException.class)
+    public void getBadAppDesc() throws IOException {
+        aar.getApplicationDescription("org.foo.BAD");
+    }
+
+    @Test(expected = ApplicationException.class)
+    public void getBadAppStream() throws IOException {
+        aar.getApplicationInputStream("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
new file mode 100644
index 0000000..3b8bc62
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/common/app/app.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright 2015 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.
+  -->
+<app name="org.foo.app" origin="Circus" version="1.2.a"
+        featuresRepo="mvn:org.foo-features/1.2a/xml/features"
+        features="foo,bar">
+    <description>Awesome application from Circus, Inc.</description>
+</app>
diff --git a/core/common/src/test/resources/org/onosproject/common/app/app.zip b/core/common/src/test/resources/org/onosproject/common/app/app.zip
new file mode 100644
index 0000000..07705e7
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/common/app/app.zip
Binary files differ
diff --git a/core/json/src/main/java/org/onosproject/json/impl/package-info.java b/core/json/src/main/java/org/onosproject/json/impl/package-info.java
deleted file mode 100644
index 11fa30d..0000000
--- a/core/json/src/main/java/org/onosproject/json/impl/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2014 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.
- */
-
-/**
- * Implementation of JSON codec factory and of the builtin codecs.
- */
-package org.onosproject.json.impl;
diff --git a/core/net/pom.xml b/core/net/pom.xml
index 4150ec0..2bc6a60 100644
--- a/core/net/pom.xml
+++ b/core/net/pom.xml
@@ -52,6 +52,14 @@
         </dependency>
 
         <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-common</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.easymock</groupId>
             <artifactId>easymock</artifactId>
             <scope>test</scope>
@@ -66,6 +74,12 @@
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.scr.annotations</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.features</groupId>
+            <artifactId>org.apache.karaf.features.core</artifactId>
+            <version>3.0.2</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java b/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
new file mode 100644
index 0000000..08c91a4
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2015 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.app.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.karaf.features.FeaturesService;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationListener;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStore;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.Permission;
+import org.onosproject.event.AbstractListenerRegistry;
+import org.onosproject.event.EventDeliveryService;
+import org.slf4j.Logger;
+
+import java.io.InputStream;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the application management service.
+ */
+@Component(immediate = true)
+@Service
+public class ApplicationManager implements ApplicationService, ApplicationAdminService {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String APP_ID_NULL = "Application ID cannot be null";
+
+    protected final AbstractListenerRegistry<ApplicationEvent, ApplicationListener>
+            listenerRegistry = new AbstractListenerRegistry<>();
+
+    private final ApplicationStoreDelegate delegate = new InternalStoreDelegate();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ApplicationStore store;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FeaturesService featuresService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected EventDeliveryService eventDispatcher;
+
+    @Activate
+    public void activate() {
+        store.setDelegate(delegate);
+        eventDispatcher.addSink(ApplicationEvent.class, listenerRegistry);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        store.unsetDelegate(delegate);
+        eventDispatcher.removeSink(ApplicationEvent.class);
+        log.info("Stopped");
+    }
+
+    @Override
+    public Set<Application> getApplications() {
+        return store.getApplications();
+    }
+
+    @Override
+    public ApplicationId getId(String name) {
+        checkNotNull(name, "Name cannot be null");
+        return store.getId(name);
+    }
+
+    @Override
+    public Application getApplication(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        return store.getApplication(appId);
+    }
+
+    @Override
+    public ApplicationState getState(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        return store.getState(appId);
+    }
+
+    @Override
+    public Set<Permission> getPermissions(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        return store.getPermissions(appId);
+    }
+
+    @Override
+    public Application install(InputStream appDescStream) {
+        checkNotNull(appDescStream, "Application archive stream cannot be null");
+        return store.create(appDescStream);
+    }
+
+    @Override
+    public void uninstall(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        try {
+            store.remove(appId);
+        } catch (Exception e) {
+            log.warn("Unable to purge application directory for {}", appId.name());
+        }
+    }
+
+    @Override
+    public void activate(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        store.activate(appId);
+    }
+
+    @Override
+    public void deactivate(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        store.deactivate(appId);
+    }
+
+    @Override
+    public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+        checkNotNull(appId, APP_ID_NULL);
+        checkNotNull(permissions, "Permissions cannot be null");
+        store.setPermissions(appId, permissions);
+    }
+
+    @Override
+    public void addListener(ApplicationListener listener) {
+        listenerRegistry.addListener(listener);
+    }
+
+    @Override
+    public void removeListener(ApplicationListener listener) {
+        listenerRegistry.removeListener(listener);
+    }
+
+    private class InternalStoreDelegate implements ApplicationStoreDelegate {
+        @Override
+        public void notify(ApplicationEvent event) {
+            ApplicationEvent.Type type = event.type();
+            Application app = event.subject();
+            try {
+                if (type == APP_ACTIVATED) {
+                    installAppFeatures(app);
+                    log.info("Application {} has been activated", app.id().name());
+
+                } else if (type == APP_DEACTIVATED) {
+                    uninstallAppFeatures(app);
+                    log.info("Application {} has been deactivated", app.id().name());
+
+                } else if (type == APP_INSTALLED) {
+                    installAppArtifacts(app);
+                    log.info("Application {} has been installed", app.id().name());
+
+                } else if (type == APP_UNINSTALLED) {
+                    uninstallAppFeatures(app);
+                    uninstallAppArtifacts(app);
+                    log.info("Application {} has been uninstalled", app.id().name());
+
+                }
+                eventDispatcher.post(event);
+
+            } catch (Exception e) {
+                log.warn("Unable to perform operation on application " + app.id().name(), e);
+            }
+        }
+    }
+
+    private void installAppArtifacts(Application app) throws Exception {
+        if (app.featuresRepo().isPresent()) {
+            featuresService.addRepository(app.featuresRepo().get());
+        }
+    }
+
+    private void uninstallAppArtifacts(Application app) throws Exception {
+        if (app.featuresRepo().isPresent()) {
+            featuresService.removeRepository(app.featuresRepo().get());
+        }
+    }
+
+    private void installAppFeatures(Application app) throws Exception {
+        for (String name : app.features()) {
+            featuresService.installFeature(name);
+        }
+    }
+
+    private void uninstallAppFeatures(Application app) throws Exception {
+        for (String name : app.features()) {
+            featuresService.uninstallFeature(name);
+        }
+    }
+
+}
diff --git a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java b/core/net/src/main/java/org/onosproject/app/impl/package-info.java
similarity index 80%
rename from core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
rename to core/net/src/main/java/org/onosproject/app/impl/package-info.java
index e39533c..d5f3037 100644
--- a/core/json/src/main/java/org/onosproject/json/impl/DeleteMe.java
+++ b/core/net/src/main/java/org/onosproject/app/impl/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2015 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.
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.json.impl;
 
 /**
- * Created by tom on 10/16/14.
+ * Subsystem for managing applications.
  */
-public class DeleteMe {
-}
+package org.onosproject.app.impl;
\ No newline at end of file
diff --git a/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java b/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
new file mode 100644
index 0000000..3477207
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2015 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.app.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationListener;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStoreAdapter;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.impl.TestEventDispatcher;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.app.ApplicationState.ACTIVE;
+import static org.onosproject.app.ApplicationState.INSTALLED;
+import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
+
+/**
+ * Test of the application manager implementation.
+ */
+public class ApplicationManagerTest {
+
+    public static final DefaultApplicationId APP_ID = new DefaultApplicationId(1, APP_NAME);
+
+    private ApplicationManager mgr = new ApplicationManager();
+    private ApplicationListener listener = new TestListener();
+
+    @Before
+    public void setUp() {
+        mgr.eventDispatcher = new TestEventDispatcher();
+        mgr.featuresService = new TestFeaturesService();
+        mgr.store = new TestStore();
+        mgr.activate();
+        mgr.addListener(listener);
+    }
+
+    @After
+    public void tearDown() {
+        mgr.removeListener(listener);
+        mgr.deactivate();
+    }
+
+    private void validate(Application app) {
+        assertEquals("incorrect name", APP_NAME, app.id().name());
+        assertEquals("incorrect version", VER, app.version());
+        assertEquals("incorrect origin", ORIGIN, app.origin());
+
+        assertEquals("incorrect description", DESC, app.description());
+        assertEquals("incorrect features URI", FURL, app.featuresRepo().get());
+        assertEquals("incorrect features", FEATURES, app.features());
+    }
+
+    @Test
+    public void install() {
+        InputStream stream = ApplicationArchive.class.getResourceAsStream("app.zip");
+        Application app = mgr.install(stream);
+        validate(app);
+        assertEquals("incorrect features URI used", app.featuresRepo().get(),
+                     ((TestFeaturesService) mgr.featuresService).uri);
+        assertEquals("incorrect app count", 1, mgr.getApplications().size());
+        assertEquals("incorrect app", app, mgr.getApplication(APP_ID));
+        assertEquals("incorrect app state", INSTALLED, mgr.getState(APP_ID));
+    }
+
+    @Test
+    public void uninstall() {
+        install();
+        mgr.uninstall(APP_ID);
+        assertEquals("incorrect app count", 0, mgr.getApplications().size());
+    }
+
+    @Test
+    public void activate() {
+        install();
+        mgr.activate(APP_ID);
+        assertEquals("incorrect app state", ACTIVE, mgr.getState(APP_ID));
+    }
+
+    @Test
+    public void deactivate() {
+        activate();
+        mgr.deactivate(APP_ID);
+        assertEquals("incorrect app state", INSTALLED, mgr.getState(APP_ID));
+    }
+
+
+    private class TestListener implements ApplicationListener {
+        private ApplicationEvent event;
+
+        @Override
+        public void event(ApplicationEvent event) {
+            this.event = event;
+        }
+    }
+
+    private class TestStore extends ApplicationStoreAdapter {
+
+        private Application app;
+        private ApplicationState state;
+
+        @Override
+        public Application create(InputStream appDescStream) {
+            app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, PERMS,
+                                         Optional.of(FURL), FEATURES);
+            state = INSTALLED;
+            delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
+            return app;
+        }
+
+        @Override
+        public Set<Application> getApplications() {
+            return app != null ? ImmutableSet.of(app) : ImmutableSet.of();
+        }
+
+        @Override
+        public Application getApplication(ApplicationId appId) {
+            return app;
+        }
+
+        @Override
+        public void remove(ApplicationId appId) {
+            delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app));
+            app = null;
+            state = null;
+        }
+
+        @Override
+        public ApplicationState getState(ApplicationId appId) {
+            return state;
+        }
+
+        @Override
+        public void activate(ApplicationId appId) {
+            state = ApplicationState.ACTIVE;
+            delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
+        }
+
+        @Override
+        public void deactivate(ApplicationId appId) {
+            state = INSTALLED;
+            delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
+        }
+    }
+
+    private class TestFeaturesService extends FeaturesServiceAdapter {
+        private URI uri;
+        private Set<String> features = new HashSet<>();
+
+        @Override
+        public void addRepository(URI uri) throws Exception {
+            this.uri = uri;
+        }
+
+        @Override
+        public void removeRepository(URI uri) throws Exception {
+            this.uri = null;
+        }
+
+        @Override
+        public void installFeature(String name) throws Exception {
+            features.add(name);
+        }
+
+        @Override
+        public void uninstallFeature(String name) throws Exception {
+            features.remove(name);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java b/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java
new file mode 100644
index 0000000..f527bd4
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/app/impl/FeaturesServiceAdapter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2015 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.app.impl;
+
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.Repository;
+
+import java.net.URI;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Adapter for testing against Apache Karaf feature service.
+ */
+public class FeaturesServiceAdapter implements org.apache.karaf.features.FeaturesService {
+    @Override
+    public void validateRepository(URI uri) throws Exception {
+
+    }
+
+    @Override
+    public void addRepository(URI uri) throws Exception {
+
+    }
+
+    @Override
+    public void addRepository(URI uri, boolean install) throws Exception {
+
+    }
+
+    @Override
+    public void removeRepository(URI uri) throws Exception {
+
+    }
+
+    @Override
+    public void removeRepository(URI uri, boolean uninstall) throws Exception {
+
+    }
+
+    @Override
+    public void restoreRepository(URI uri) throws Exception {
+
+    }
+
+    @Override
+    public Repository[] listRepositories() {
+        return new Repository[0];
+    }
+
+    @Override
+    public Repository getRepository(String repoName) {
+        return null;
+    }
+
+    @Override
+    public void installFeature(String name) throws Exception {
+
+    }
+
+    @Override
+    public void installFeature(String name, EnumSet<Option> options) throws Exception {
+
+    }
+
+    @Override
+    public void installFeature(String name, String version) throws Exception {
+
+    }
+
+    @Override
+    public void installFeature(String name, String version, EnumSet<Option> options) throws Exception {
+
+    }
+
+    @Override
+    public void installFeature(Feature f, EnumSet<Option> options) throws Exception {
+
+    }
+
+    @Override
+    public void installFeatures(Set<Feature> features, EnumSet<Option> options) throws Exception {
+
+    }
+
+    @Override
+    public void uninstallFeature(String name, EnumSet<Option> options) throws Exception {
+
+    }
+
+    @Override
+    public void uninstallFeature(String name) throws Exception {
+
+    }
+
+    @Override
+    public void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception {
+
+    }
+
+    @Override
+    public void uninstallFeature(String name, String version) throws Exception {
+
+    }
+
+    @Override
+    public Feature[] listFeatures() throws Exception {
+        return new Feature[0];
+    }
+
+    @Override
+    public Feature[] listInstalledFeatures() {
+        return new Feature[0];
+    }
+
+    @Override
+    public boolean isInstalled(Feature f) {
+        return false;
+    }
+
+    @Override
+    public Feature getFeature(String name, String version) throws Exception {
+        return null;
+    }
+
+    @Override
+    public Feature getFeature(String name) throws Exception {
+        return null;
+    }
+
+    @Override
+    public void refreshRepository(URI uri) throws Exception {
+
+    }
+}
diff --git a/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
index 974af66..5a79b43 100644
--- a/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
@@ -110,7 +110,7 @@
         mgr.addListener(listener);
         provider = new TestProvider(PID);
         providerService = registry.register(provider);
-        appId = new TestApplicationId((short) 0, "FlowRuleManagerTest");
+        appId = new TestApplicationId(0, "FlowRuleManagerTest");
         assertTrue("provider should be registered",
                    registry.getProviders().contains(provider.id()));
     }
@@ -639,8 +639,7 @@
     }
 
     public class TestApplicationId extends DefaultApplicationId {
-
-        public TestApplicationId(short id, String name) {
+        public TestApplicationId(int id, String name) {
             super(id, name);
         }
     }
diff --git a/core/pom.xml b/core/pom.xml
index c826b3b..5e2058b 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -33,9 +33,9 @@
 
     <modules>
         <module>api</module>
+        <module>common</module>
         <module>net</module>
         <module>store</module>
-        <module>json</module>
     </modules>
 
     <dependencies>
diff --git a/core/store/dist/src/main/java/org/onosproject/store/core/impl/DistributedApplicationIdStore.java b/core/store/dist/src/main/java/org/onosproject/store/core/impl/DistributedApplicationIdStore.java
index f55ea54..186fb4e 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/core/impl/DistributedApplicationIdStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/core/impl/DistributedApplicationIdStore.java
@@ -103,6 +103,11 @@
         return appId;
     }
 
+    @Override
+    public ApplicationId getAppId(String name) {
+        return appIdsByName.get(name);
+    }
+
     private void primeAppIds() {
         for (DefaultApplicationId appId : appIdsByName.values()) {
             appIds.put(appId.id(), appId);
@@ -113,7 +118,7 @@
     public ApplicationId registerApplication(String name) {
         DefaultApplicationId appId = appIdsByName.get(name);
         if (appId == null) {
-            short id = (short) lastAppId.getAndIncrement();
+            int id = (int) lastAppId.getAndIncrement();
             appId = putIfAbsent(appIdsByName, name,
                                 new DefaultApplicationId(id, name));
         }
diff --git a/core/store/pom.xml b/core/store/pom.xml
index 334832f..e5078b9 100644
--- a/core/store/pom.xml
+++ b/core/store/pom.xml
@@ -44,6 +44,14 @@
         </dependency>
 
         <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-common</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.scr.annotations</artifactId>
         </dependency>
diff --git a/core/store/trivial/pom.xml b/core/store/trivial/pom.xml
index 5e82ee8..2842ead 100644
--- a/core/store/trivial/pom.xml
+++ b/core/store/trivial/pom.xml
@@ -38,6 +38,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-common</artifactId>
+        </dependency>
+
+        <dependency>
           <groupId>org.onosproject</groupId>
           <artifactId>onos-api</artifactId>
           <classifier>tests</classifier>
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationIdStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationIdStore.java
index 34abd44..d6a9310 100644
--- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationIdStore.java
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationIdStore.java
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public ApplicationId getAppId(String name) {
+        return appIdsByName.get(name);
+    }
+
+    @Override
     public ApplicationId registerApplication(String name) {
         DefaultApplicationId appId = appIdsByName.get(name);
         if (appId == null) {
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationStore.java
new file mode 100644
index 0000000..cecf5fd
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleApplicationStore.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2015 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.store.trivial.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.app.ApplicationDescription;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationState;
+import org.onosproject.app.ApplicationStore;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.DefaultApplication;
+import org.onosproject.core.Permission;
+import org.slf4j.Logger;
+
+import java.io.InputStream;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.app.ApplicationState.ACTIVE;
+import static org.onosproject.app.ApplicationState.INSTALLED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of network control applications.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleApplicationStore extends ApplicationArchive implements ApplicationStore {
+
+    private final Logger log = getLogger(getClass());
+
+    // App inventory & states
+    private final ConcurrentMap<ApplicationId, DefaultApplication> apps = new ConcurrentHashMap<>();
+    private final ConcurrentMap<ApplicationId, ApplicationState> states = new ConcurrentHashMap<>();
+    private final ConcurrentMap<ApplicationId, Set<Permission>> permissions = new ConcurrentHashMap<>();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ApplicationIdStore idStore;
+
+    @Activate
+    public void activate() {
+        loadFromDisk();
+        log.info("Started");
+    }
+
+    private void loadFromDisk() {
+        for (String name : getApplicationNames()) {
+            ApplicationId appId = idStore.registerApplication(name);
+            ApplicationDescription appDesc = getApplicationDescription(name);
+            DefaultApplication app =
+                    new DefaultApplication(appId, appDesc.version(),
+                                           appDesc.description(), appDesc.origin(),
+                                           appDesc.permissions(),
+                                           appDesc.featuresRepo(), appDesc.features());
+            apps.put(appId, app);
+            states.put(appId, isActive(name) ? INSTALLED : ACTIVE);
+            // load app permissions
+        }
+    }
+
+    @Deactivate
+    public void deactivate() {
+        apps.clear();
+        states.clear();
+        permissions.clear();
+        log.info("Stopped");
+    }
+
+    @Override
+    public Set<Application> getApplications() {
+        return ImmutableSet.copyOf(apps.values());
+    }
+
+    @Override
+    public ApplicationId getId(String name) {
+        return idStore.getAppId(name);
+    }
+
+    @Override
+    public Application getApplication(ApplicationId appId) {
+        return apps.get(appId);
+    }
+
+    @Override
+    public ApplicationState getState(ApplicationId appId) {
+        return states.get(appId);
+    }
+
+    @Override
+    public Application create(InputStream appDescStream) {
+        ApplicationDescription appDesc = saveApplication(appDescStream);
+        ApplicationId appId = idStore.registerApplication(appDesc.name());
+        DefaultApplication app =
+                new DefaultApplication(appId, appDesc.version(), appDesc.description(),
+                                       appDesc.origin(), appDesc.permissions(),
+                                       appDesc.featuresRepo(), appDesc.features());
+        apps.put(appId, app);
+        states.put(appId, INSTALLED);
+        delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
+        return app;
+    }
+
+    @Override
+    public void remove(ApplicationId appId) {
+        Application app = apps.remove(appId);
+        if (app != null) {
+            states.remove(appId);
+            delegate.notify(new ApplicationEvent(APP_UNINSTALLED, app));
+            purgeApplication(app.id().name());
+        }
+    }
+
+    @Override
+    public void activate(ApplicationId appId) {
+        Application app = apps.get(appId);
+        if (app != null) {
+            setActive(appId.name());
+            states.put(appId, ACTIVE);
+            delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
+        }
+    }
+
+    @Override
+    public void deactivate(ApplicationId appId) {
+        Application app = apps.get(appId);
+        if (app != null) {
+            clearActive(appId.name());
+            states.put(appId, INSTALLED);
+            delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
+        }
+    }
+
+    @Override
+    public Set<Permission> getPermissions(ApplicationId appId) {
+        return permissions.get(appId);
+    }
+
+    @Override
+    public void setPermissions(ApplicationId appId, Set<Permission> permissions) {
+        Application app = getApplication(appId);
+        if (app != null) {
+            this.permissions.put(appId, permissions);
+            delegate.notify(new ApplicationEvent(APP_PERMISSIONS_CHANGED, app));
+        }
+    }
+}
diff --git a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleApplicationStoreTest.java b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleApplicationStoreTest.java
new file mode 100644
index 0000000..29006c4
--- /dev/null
+++ b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleApplicationStoreTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2015 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.store.trivial.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.app.ApplicationEvent;
+import org.onosproject.app.ApplicationStoreDelegate;
+import org.onosproject.common.app.ApplicationArchive;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.ApplicationIdStoreAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.Permission;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.app.ApplicationEvent.Type.*;
+import static org.onosproject.app.ApplicationState.ACTIVE;
+import static org.onosproject.app.ApplicationState.INSTALLED;
+
+/**
+ * Test of the trivial application store implementation.
+ */
+public class SimpleApplicationStoreTest {
+
+    private SimpleApplicationStore store = new SimpleApplicationStore();
+    private TestDelegate delegate = new TestDelegate();
+
+    @Before
+    public void setUp() {
+        store.idStore = new TestIdStore();
+        store.setDelegate(delegate);
+        store.activate();
+    }
+
+    @After
+    public void tearDown() {
+        store.deactivate();
+    }
+
+    private Application createTestApp() {
+        return store.create(ApplicationArchive.class.getResourceAsStream("app.zip"));
+    }
+
+    @Test
+    public void create() {
+        Application app = createTestApp();
+        assertEquals("incorrect name", "org.foo.app", app.id().name());
+        assertEquals("incorrect app count", 1, store.getApplications().size());
+        assertEquals("incorrect app", app, store.getApplication(app.id()));
+        assertEquals("incorrect app state", INSTALLED, store.getState(app.id()));
+        assertEquals("incorrect event type", APP_INSTALLED, delegate.event.type());
+        assertEquals("incorrect event app", app, delegate.event.subject());
+    }
+
+    @Test
+    public void remove() {
+        Application app = createTestApp();
+        store.remove(app.id());
+        assertEquals("incorrect app count", 0, store.getApplications().size());
+        assertEquals("incorrect event type", APP_UNINSTALLED, delegate.event.type());
+        assertEquals("incorrect event app", app, delegate.event.subject());
+    }
+
+    @Test
+    public void activate() {
+        Application app = createTestApp();
+        store.activate(app.id());
+        assertEquals("incorrect app count", 1, store.getApplications().size());
+        assertEquals("incorrect app state", ACTIVE, store.getState(app.id()));
+        assertEquals("incorrect event type", APP_ACTIVATED, delegate.event.type());
+        assertEquals("incorrect event app", app, delegate.event.subject());
+    }
+
+    @Test
+    public void deactivate() {
+        Application app = createTestApp();
+        store.deactivate(app.id());
+        assertEquals("incorrect app count", 1, store.getApplications().size());
+        assertEquals("incorrect app state", INSTALLED, store.getState(app.id()));
+        assertEquals("incorrect event type", APP_DEACTIVATED, delegate.event.type());
+        assertEquals("incorrect event app", app, delegate.event.subject());
+    }
+
+    @Test
+    public void permissions() {
+        Application app = createTestApp();
+        ImmutableSet<Permission> permissions = ImmutableSet.of(new Permission() {
+        });
+        store.setPermissions(app.id(), permissions);
+        assertEquals("incorrect app perms", 1, store.getPermissions(app.id()).size());
+        assertEquals("incorrect app state", INSTALLED, store.getState(app.id()));
+        assertEquals("incorrect event type", APP_PERMISSIONS_CHANGED, delegate.event.type());
+        assertEquals("incorrect event app", app, delegate.event.subject());
+    }
+
+    private class TestIdStore extends ApplicationIdStoreAdapter {
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return new DefaultApplicationId(1, name);
+        }
+
+        @Override
+        public ApplicationId getAppId(String name) {
+            return new DefaultApplicationId(1, name);
+        }
+    }
+
+    private class TestDelegate implements ApplicationStoreDelegate {
+        private ApplicationEvent event;
+
+        @Override
+        public void notify(ApplicationEvent event) {
+            this.event = event;
+        }
+    }
+}
\ No newline at end of file
diff --git a/docs/external.xml b/docs/external.xml
index f8fa7bd..c730c90 100644
--- a/docs/external.xml
+++ b/docs/external.xml
@@ -49,7 +49,7 @@
                 <version>2.10.1</version>
                 <configuration>
                     <show>package</show>
-                    <excludePackageNames>org.onlab.thirdparty:*.impl:*.impl.*:org.onosproject.provider.*:org.onosproject.gui:org.onosproject.rest:org.onosproject.cli*:org.onosproject.tvue:org.onosproject.foo:org.onosproject.mobility:org.onosproject.proxyarp:org.onosproject.fwd:org.onosproject.ifwd:org.onosproject.optical:org.onosproject.config:org.onosproject.calendar:org.onosproject.sdnip*:org.onosproject.oecfg:org.onosproject.metrics:org.onosproject.store.*:org.onosproject.openflow.*</excludePackageNames>
+                    <excludePackageNames>org.onlab.thirdparty:*.impl:*.impl.*:org.onosproject.provider.*:org.onosproject.gui:org.onosproject.rest:org.onosproject.cli*:org.onosproject.tvue:org.onosproject.foo:org.onosproject.mobility:org.onosproject.proxyarp:org.onosproject.fwd:org.onosproject.ifwd:org.onosproject.optical:org.onosproject.config:org.onosproject.calendar:org.onosproject.sdnip*:org.onosproject.oecfg:org.onosproject.metrics:org.onosproject.store.*:org.onosproject.openflow.*:org.onosproject.common.*</excludePackageNames>
                     <docfilessubdirs>true</docfilessubdirs>
                     <doctitle>ONOS Java API</doctitle>
                     <groups>
diff --git a/docs/pom.xml b/docs/pom.xml
index bb7515e..3a1bfb9 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -61,7 +61,7 @@
                         <group>
                             <title>Core Subsystems</title>
                             <packages>
-                                org.onosproject.impl:org.onosproject.core.impl:org.onosproject.cluster.impl:org.onosproject.net.device.impl:org.onosproject.net.link.impl:org.onosproject.net.host.impl:org.onosproject.net.topology.impl:org.onosproject.net.packet.impl:org.onosproject.net.flow.impl:org.onosproject.net.*.impl:org.onosproject.event.impl:org.onosproject.net.intent.impl:org.onosproject.net.proxyarp.impl:org.onosproject.mastership.impl:org.onosproject.net.resource.impl:org.onosproject.json:org.onosproject.json.*:org.onosproject.provider.host.impl:org.onosproject.provider.lldp.impl:org.onosproject.net.statistic.impl
+                                org.onosproject.impl:org.onosproject.core.impl:org.onosproject.cluster.impl:org.onosproject.net.device.impl:org.onosproject.net.link.impl:org.onosproject.net.host.impl:org.onosproject.net.topology.impl:org.onosproject.net.packet.impl:org.onosproject.net.flow.impl:org.onosproject.net.*.impl:org.onosproject.event.impl:org.onosproject.net.intent.impl:org.onosproject.net.proxyarp.impl:org.onosproject.mastership.impl:org.onosproject.net.resource.impl:org.onosproject.json:org.onosproject.json.*:org.onosproject.provider.host.impl:org.onosproject.provider.lldp.impl:org.onosproject.net.statistic.impl:org.onosproject.app.impl:org.onosproject.common.*
                             </packages>
                         </group>
                         <group>
diff --git a/features/features.xml b/features/features.xml
index b16b57f..26b61e6 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -22,6 +22,7 @@
              description="ONOS 3rd party dependencies">
         <bundle>mvn:commons-lang/commons-lang/2.6</bundle>
         <bundle>mvn:org.apache.commons/commons-lang3/3.3.2</bundle>
+        <bundle>mvn:commons-configuration/commons-configuration/1.10</bundle>
         <bundle>mvn:com.google.guava/guava/18.0</bundle>
         <bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
         <bundle>mvn:io.netty/netty-common/4.0.23.Final</bundle>
@@ -86,6 +87,7 @@
              description="ONOS core components">
         <feature>onos-api</feature>
         <bundle>mvn:org.onosproject/onos-core-net/@ONOS-VERSION</bundle>
+        <bundle>mvn:org.onosproject/onos-core-common/@ONOS-VERSION</bundle>
         <bundle>mvn:org.onosproject/onos-core-dist/@ONOS-VERSION</bundle>
         <bundle>mvn:org.onosproject/onos-core-serializers/@ONOS-VERSION</bundle>
         <bundle>mvn:org.onosproject/onlab-netty/@ONOS-VERSION</bundle>
@@ -95,6 +97,7 @@
              description="ONOS core components">
         <feature>onos-api</feature>
         <bundle>mvn:org.onosproject/onos-core-net/@ONOS-VERSION</bundle>
+        <bundle>mvn:org.onosproject/onos-core-common/@ONOS-VERSION</bundle>
         <bundle>mvn:org.onosproject/onos-core-trivial/@ONOS-VERSION</bundle>
     </feature>
 
diff --git a/pom.xml b/pom.xml
index 062b049..ad4ae60 100644
--- a/pom.xml
+++ b/pom.xml
@@ -161,6 +161,12 @@
             </dependency>
 
             <dependency>
+                <groupId>commons-configuration</groupId>
+                <artifactId>commons-configuration</artifactId>
+                <version>1.10</version>
+            </dependency>
+
+            <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-collections4</artifactId>
                 <version>4.0</version>
@@ -315,6 +321,13 @@
                 <scope>test</scope>
             </dependency>
 
+
+            <dependency>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-core-common</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.onosproject</groupId>
                 <artifactId>onos-of-api</artifactId>
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index ac46a26..c048b8a 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -133,8 +133,8 @@
     private CoreService coreService;
     private TestHostProviderService providerService;
 
-    private ApplicationId appId = new DefaultApplicationId((short) 100,
-                "org.onosproject.provider.host");
+    private ApplicationId appId =
+            new DefaultApplicationId(100, "org.onosproject.provider.host");
 
     @Before
     public void setUp() {
diff --git a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java
index ce5e048..b48af6a 100644
--- a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java
+++ b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java
@@ -97,8 +97,8 @@
     private PacketProcessor testProcessor;
     private DeviceListener deviceListener;
 
-    private ApplicationId appId = new DefaultApplicationId((short) 100,
-                "org.onosproject.provider.lldp");
+    private ApplicationId appId =
+            new DefaultApplicationId(100, "org.onosproject.provider.lldp");
 
     @Before
     public void setUp() {
diff --git a/tools/test/bin/onos-app b/tools/test/bin/onos-app
new file mode 100755
index 0000000..d474ef3
--- /dev/null
+++ b/tools/test/bin/onos-app
@@ -0,0 +1,20 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Tool to manage ONOS applications using REST API.
+# -----------------------------------------------------------------------------
+
+node=${1:-$OCI}
+cmd=${2:-list}
+app=${3}
+
+export URL=http://$node:8181/onos/v1/applications
+export HDR="-HContent-Type:application/octet-stream"
+export curl="curl -sS"
+
+case $cmd in
+    list) $curl -X GET $URL;;
+    install) $curl -X POST $HDR $URL --data-binary @$app;;
+    uninstall) $curl -X DELETE $URL/$app;;
+    activate) $curl -X POST $URL/$app/active;;
+    deactivate) $curl -X DELETE $URL/$app/active;;
+esac
diff --git a/tools/test/topos/chordal.py b/tools/test/topos/chordal.py
new file mode 100644
index 0000000..22f9ca1
--- /dev/null
+++ b/tools/test/topos/chordal.py
@@ -0,0 +1,402 @@
+"""
+"""
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import RemoteController
+from mininet.node import Node
+from mininet.node import CPULimitedHost
+from mininet.link import TCLink
+from mininet.cli import CLI
+from mininet.log import setLogLevel
+from mininet.util import dumpNodeConnections
+
+class chordalTopo( Topo ):
+
+    def __init__( self, **opts ):
+        "Create a topology."
+
+        # Initialize Topology
+        Topo.__init__( self, **opts )
+
+        # add nodes, switches first...
+        s1 = self.addSwitch( 's1' )
+        s2 = self.addSwitch( 's2' )
+        s3 = self.addSwitch( 's3' )
+        s4 = self.addSwitch( 's4' )
+        s5 = self.addSwitch( 's5' )
+        s6 = self.addSwitch( 's6' )
+        s7 = self.addSwitch( 's7' )
+        s8 = self.addSwitch( 's8' )
+        s9 = self.addSwitch( 's9' )
+        s10 = self.addSwitch( 's10' )
+        s11 = self.addSwitch( 's11' )
+        s12 = self.addSwitch( 's12' )
+        s13 = self.addSwitch( 's13' )
+        s14 = self.addSwitch( 's14' )
+        s15 = self.addSwitch( 's15' )
+        s16 = self.addSwitch( 's16' )
+        s17 = self.addSwitch( 's17' )
+        s18 = self.addSwitch( 's18' )
+        s19 = self.addSwitch( 's19' )
+        s20 = self.addSwitch( 's20' )
+        s21 = self.addSwitch( 's21' )
+        s22 = self.addSwitch( 's22' )
+        s23 = self.addSwitch( 's23' )
+        s24 = self.addSwitch( 's24' )
+        s25 = self.addSwitch( 's25' )
+
+        # ... and now hosts
+        s1_host = self.addHost( 'h1' )
+        s2_host = self.addHost( 'h2' )
+        s3_host = self.addHost( 'h3' )
+        s4_host = self.addHost( 'h4' )
+        s5_host = self.addHost( 'h5' )
+        s6_host = self.addHost( 'h6' )
+        s7_host = self.addHost( 'h7' )
+        s8_host = self.addHost( 'h8' )
+        s9_host = self.addHost( 'h9' )
+        s10_host = self.addHost( 'h10' )
+        s11_host = self.addHost( 'h11' )
+        s12_host = self.addHost( 'h12' )
+        s13_host = self.addHost( 'h13' )
+        s14_host = self.addHost( 'h14' )
+        s15_host = self.addHost( 'h15' )
+        s16_host = self.addHost( 'h16' )
+        s17_host = self.addHost( 'h17' )
+        s18_host = self.addHost( 'h18' )
+        s19_host = self.addHost( 'h19' )
+        s20_host = self.addHost( 'h20' )
+        s21_host = self.addHost( 'h21' )
+        s22_host = self.addHost( 'h22' )
+        s23_host = self.addHost( 'h23' )
+        s24_host = self.addHost( 'h24' )
+        s25_host = self.addHost( 'h25' )
+
+        # add edges between switch and corresponding host
+        self.addLink( s1 , s1_host )
+        self.addLink( s2 , s2_host )
+        self.addLink( s3 , s3_host )
+        self.addLink( s4 , s4_host )
+        self.addLink( s5 , s5_host )
+        self.addLink( s6 , s6_host )
+        self.addLink( s7 , s7_host )
+        self.addLink( s8 , s8_host )
+        self.addLink( s9 , s9_host )
+        self.addLink( s10 , s10_host )
+        self.addLink( s11 , s11_host )
+        self.addLink( s12 , s12_host )
+        self.addLink( s13 , s13_host )
+        self.addLink( s14 , s14_host )
+        self.addLink( s15 , s15_host )
+        self.addLink( s16 , s16_host )
+        self.addLink( s17 , s17_host )
+        self.addLink( s18 , s18_host )
+        self.addLink( s19 , s19_host )
+        self.addLink( s20 , s20_host )
+        self.addLink( s21 , s21_host )
+        self.addLink( s22 , s22_host )
+        self.addLink( s23 , s23_host )
+        self.addLink( s24 , s24_host )
+        self.addLink( s25 , s25_host )
+        self.addLink(s1, s2)
+        self.addLink(s1, s3)
+        self.addLink(s1, s4)
+        self.addLink(s1, s5)
+        self.addLink(s1, s6)
+        self.addLink(s1, s7)
+        self.addLink(s1, s8)
+        self.addLink(s1, s9)
+        self.addLink(s1, s10)
+        self.addLink(s1, s11)
+        self.addLink(s1, s12)
+        self.addLink(s1, s13)
+        self.addLink(s1, s14)
+        self.addLink(s1, s15)
+        self.addLink(s1, s16)
+        self.addLink(s1, s17)
+        self.addLink(s1, s18)
+        self.addLink(s1, s19)
+        self.addLink(s1, s20)
+        self.addLink(s1, s21)
+        self.addLink(s1, s22)
+        self.addLink(s1, s23)
+        self.addLink(s1, s24)
+        self.addLink(s1, s25)
+        self.addLink(s2, s3)
+        self.addLink(s2, s4)
+        self.addLink(s2, s5)
+        self.addLink(s2, s6)
+        self.addLink(s2, s7)
+        self.addLink(s2, s8)
+        self.addLink(s2, s9)
+        self.addLink(s2, s10)
+        self.addLink(s2, s11)
+        self.addLink(s2, s12)
+        self.addLink(s2, s13)
+        self.addLink(s2, s14)
+        self.addLink(s2, s15)
+        self.addLink(s2, s16)
+        self.addLink(s2, s17)
+        self.addLink(s2, s18)
+        self.addLink(s2, s19)
+        self.addLink(s2, s20)
+        self.addLink(s2, s21)
+        self.addLink(s2, s22)
+        self.addLink(s2, s23)
+        self.addLink(s2, s24)
+        self.addLink(s2, s25)
+        self.addLink(s3, s4)
+        self.addLink(s3, s5)
+        self.addLink(s3, s6)
+        self.addLink(s3, s7)
+        self.addLink(s3, s8)
+        self.addLink(s3, s9)
+        self.addLink(s3, s10)
+        self.addLink(s3, s11)
+        self.addLink(s3, s12)
+        self.addLink(s3, s13)
+        self.addLink(s3, s14)
+        self.addLink(s3, s15)
+        self.addLink(s3, s16)
+        self.addLink(s3, s17)
+        self.addLink(s3, s18)
+        self.addLink(s3, s19)
+        self.addLink(s3, s20)
+        self.addLink(s3, s21)
+        self.addLink(s3, s22)
+        self.addLink(s3, s23)
+        self.addLink(s3, s24)
+        self.addLink(s3, s25)
+        self.addLink(s4, s5)
+        self.addLink(s4, s6)
+        self.addLink(s4, s7)
+        self.addLink(s4, s8)
+        self.addLink(s4, s9)
+        self.addLink(s4, s10)
+        self.addLink(s4, s11)
+        self.addLink(s4, s12)
+        self.addLink(s4, s13)
+        self.addLink(s4, s14)
+        self.addLink(s4, s15)
+        self.addLink(s4, s16)
+        self.addLink(s4, s17)
+        self.addLink(s4, s18)
+        self.addLink(s4, s19)
+        self.addLink(s4, s20)
+        self.addLink(s4, s21)
+        self.addLink(s4, s22)
+        self.addLink(s4, s23)
+        self.addLink(s4, s24)
+        self.addLink(s4, s25)
+        self.addLink(s5, s6)
+        self.addLink(s5, s7)
+        self.addLink(s5, s8)
+        self.addLink(s5, s9)
+        self.addLink(s5, s10)
+        self.addLink(s5, s11)
+        self.addLink(s5, s12)
+        self.addLink(s5, s13)
+        self.addLink(s5, s14)
+        self.addLink(s5, s15)
+        self.addLink(s5, s16)
+        self.addLink(s5, s17)
+        self.addLink(s5, s18)
+        self.addLink(s5, s19)
+        self.addLink(s5, s20)
+        self.addLink(s5, s21)
+        self.addLink(s5, s22)
+        self.addLink(s5, s23)
+        self.addLink(s5, s24)
+        self.addLink(s5, s25)
+        self.addLink(s6, s7)
+        self.addLink(s6, s8)
+        self.addLink(s6, s9)
+        self.addLink(s6, s10)
+        self.addLink(s6, s11)
+        self.addLink(s6, s12)
+        self.addLink(s6, s13)
+        self.addLink(s6, s14)
+        self.addLink(s6, s15)
+        self.addLink(s6, s16)
+        self.addLink(s6, s17)
+        self.addLink(s6, s18)
+        self.addLink(s6, s19)
+        self.addLink(s6, s20)
+        self.addLink(s6, s21)
+        self.addLink(s6, s22)
+        self.addLink(s6, s23)
+        self.addLink(s6, s24)
+        self.addLink(s6, s25)
+        self.addLink(s7, s8)
+        self.addLink(s7, s9)
+        self.addLink(s7, s10)
+        self.addLink(s7, s11)
+        self.addLink(s7, s12)
+        self.addLink(s7, s13)
+        self.addLink(s7, s14)
+        self.addLink(s7, s15)
+        self.addLink(s7, s16)
+        self.addLink(s7, s17)
+        self.addLink(s7, s18)
+        self.addLink(s7, s19)
+        self.addLink(s7, s20)
+        self.addLink(s7, s21)
+        self.addLink(s7, s22)
+        self.addLink(s7, s23)
+        self.addLink(s7, s24)
+        self.addLink(s7, s25)
+        self.addLink(s8, s9)
+        self.addLink(s8, s10)
+        self.addLink(s8, s11)
+        self.addLink(s8, s12)
+        self.addLink(s8, s13)
+        self.addLink(s8, s14)
+        self.addLink(s8, s15)
+        self.addLink(s8, s16)
+        self.addLink(s8, s17)
+        self.addLink(s8, s18)
+        self.addLink(s8, s19)
+        self.addLink(s8, s20)
+        self.addLink(s8, s21)
+        self.addLink(s8, s22)
+        self.addLink(s8, s23)
+        self.addLink(s8, s24)
+        self.addLink(s8, s25)
+        self.addLink(s9, s10)
+        self.addLink(s9, s11)
+        self.addLink(s9, s12)
+        self.addLink(s9, s13)
+        self.addLink(s9, s14)
+        self.addLink(s9, s15)
+        self.addLink(s9, s16)
+        self.addLink(s9, s17)
+        self.addLink(s9, s18)
+        self.addLink(s9, s19)
+        self.addLink(s9, s20)
+        self.addLink(s9, s21)
+        self.addLink(s9, s22)
+        self.addLink(s9, s23)
+        self.addLink(s9, s24)
+        self.addLink(s9, s25)
+        self.addLink(s10, s11)
+        self.addLink(s10, s12)
+        self.addLink(s10, s13)
+        self.addLink(s10, s14)
+        self.addLink(s10, s15)
+        self.addLink(s10, s16)
+        self.addLink(s10, s17)
+        self.addLink(s10, s18)
+        self.addLink(s10, s19)
+        self.addLink(s10, s20)
+        self.addLink(s10, s21)
+        self.addLink(s10, s22)
+        self.addLink(s10, s23)
+        self.addLink(s10, s24)
+        self.addLink(s10, s25)
+        self.addLink(s11, s12)
+        self.addLink(s11, s13)
+        self.addLink(s11, s14)
+        self.addLink(s11, s15)
+        self.addLink(s11, s16)
+        self.addLink(s11, s17)
+        self.addLink(s11, s18)
+        self.addLink(s11, s19)
+        self.addLink(s11, s20)
+        self.addLink(s11, s21)
+        self.addLink(s11, s22)
+        self.addLink(s11, s23)
+        self.addLink(s11, s24)
+        self.addLink(s11, s25)
+        self.addLink(s12, s13)
+        self.addLink(s12, s14)
+        self.addLink(s12, s15)
+        self.addLink(s12, s16)
+        self.addLink(s12, s17)
+        self.addLink(s12, s18)
+        self.addLink(s12, s19)
+        self.addLink(s12, s20)
+        self.addLink(s12, s21)
+        self.addLink(s12, s22)
+        self.addLink(s12, s23)
+        self.addLink(s12, s24)
+        self.addLink(s12, s25)
+        self.addLink(s13, s14)
+        self.addLink(s13, s15)
+        self.addLink(s13, s16)
+        self.addLink(s13, s17)
+        self.addLink(s13, s18)
+        self.addLink(s13, s19)
+        self.addLink(s13, s20)
+        self.addLink(s13, s21)
+        self.addLink(s13, s22)
+        self.addLink(s13, s23)
+        self.addLink(s13, s24)
+        self.addLink(s13, s25)
+        self.addLink(s14, s15)
+        self.addLink(s14, s16)
+        self.addLink(s14, s17)
+        self.addLink(s14, s18)
+        self.addLink(s14, s19)
+        self.addLink(s14, s20)
+        self.addLink(s14, s21)
+        self.addLink(s14, s22)
+        self.addLink(s14, s23)
+        self.addLink(s14, s24)
+        self.addLink(s14, s25)
+        self.addLink(s15, s16)
+        self.addLink(s15, s17)
+        self.addLink(s15, s18)
+        self.addLink(s15, s19)
+        self.addLink(s15, s20)
+        self.addLink(s15, s21)
+        self.addLink(s15, s22)
+        self.addLink(s15, s23)
+        self.addLink(s15, s24)
+        self.addLink(s15, s25)
+        self.addLink(s16, s17)
+        self.addLink(s16, s18)
+        self.addLink(s16, s19)
+        self.addLink(s16, s20)
+        self.addLink(s16, s21)
+        self.addLink(s16, s22)
+        self.addLink(s16, s23)
+        self.addLink(s16, s24)
+        self.addLink(s16, s25)
+        self.addLink(s17, s18)
+        self.addLink(s17, s19)
+        self.addLink(s17, s20)
+        self.addLink(s17, s21)
+        self.addLink(s17, s22)
+        self.addLink(s17, s23)
+        self.addLink(s17, s24)
+        self.addLink(s17, s25)
+        self.addLink(s18, s19)
+        self.addLink(s18, s20)
+        self.addLink(s18, s21)
+        self.addLink(s18, s22)
+        self.addLink(s18, s23)
+        self.addLink(s18, s24)
+        self.addLink(s18, s25)
+        self.addLink(s19, s20)
+        self.addLink(s19, s21)
+        self.addLink(s19, s22)
+        self.addLink(s19, s23)
+        self.addLink(s19, s24)
+        self.addLink(s19, s25)
+        self.addLink(s20, s21)
+        self.addLink(s20, s22)
+        self.addLink(s20, s23)
+        self.addLink(s20, s24)
+        self.addLink(s20, s25)
+        self.addLink(s21, s22)
+        self.addLink(s21, s23)
+        self.addLink(s21, s24)
+        self.addLink(s21, s25)
+        self.addLink(s22, s23)
+        self.addLink(s22, s24)
+        self.addLink(s22, s25)
+        self.addLink(s23, s24)
+        self.addLink(s23, s25)
+        self.addLink(s24, s25)
+
+topos = { 'chordal': ( lambda: chordalTopo() ) }
diff --git a/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java b/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java
new file mode 100644
index 0000000..09b3fe3
--- /dev/null
+++ b/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 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.onlab.junit;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Base for exception tests.
+ */
+public abstract class ExceptionTest {
+
+    protected static final Throwable CAUSE = new RuntimeException("boom");
+    protected static final String MESSAGE = "Uh oh.... boom";
+
+    protected abstract Exception getDefault();
+    protected abstract Exception getWithMessage();
+    protected abstract Exception getWithMessageAndCause();
+
+    @Test
+    public void noMessageNoCause() {
+        Exception e = getDefault();
+        assertEquals("incorrect message", null, e.getMessage());
+        assertEquals("incorrect cause", null, e.getCause());
+    }
+
+    @Test
+    public void withMessage() {
+        Exception e = getWithMessage();
+        assertEquals("incorrect message", MESSAGE, e.getMessage());
+        assertEquals("incorrect cause", null, e.getCause());
+    }
+
+    @Test
+    public void withCause() {
+        Exception e = getWithMessageAndCause();
+        assertEquals("incorrect message", MESSAGE, e.getMessage());
+        assertSame("incorrect cause", CAUSE, e.getCause());
+    }
+}
diff --git a/utils/junit/src/main/java/org/onlab/junit/TestTools.java b/utils/junit/src/main/java/org/onlab/junit/TestTools.java
index 550a028..400f710 100644
--- a/utils/junit/src/main/java/org/onlab/junit/TestTools.java
+++ b/utils/junit/src/main/java/org/onlab/junit/TestTools.java
@@ -15,6 +15,14 @@
  */
 package org.onlab.junit;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Random;
+
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.junit.Assert.fail;
 
@@ -23,6 +31,8 @@
  */
 public final class TestTools {
 
+    private static final Random RANDOM = new Random();
+
     // Prohibit construction
     private TestTools() {
     }
@@ -109,4 +119,92 @@
         assertAfter(0, duration, assertions);
     }
 
+
+    /**
+     * Creates a directory tree of test files. To signify creating a directory
+     * file path should end with '/'.
+     *
+     * @param paths list of file paths
+     * @return list of created files
+     * @throws java.io.IOException if there is an issue
+     */
+    public static List<File> createTestFiles(List<String> paths) throws IOException {
+        return createTestFiles(paths, 32, 1024);
+    }
+
+    /**
+     * Creates a directory tree of test files. To signify creating a directory
+     * file path should end with '/'.
+     *
+     * @param paths   list of file paths
+     * @param minSize minimum file size in bytes
+     * @param maxSize maximum file size in bytes
+     * @return list of created files
+     * @throws java.io.IOException if there is an issue
+     */
+    public static List<File> createTestFiles(List<String> paths,
+                                             int minSize, int maxSize) throws IOException {
+        ImmutableList.Builder<File> files = ImmutableList.builder();
+        for (String p : paths) {
+            File f = new File(p);
+            if (p.endsWith("/")) {
+                if (f.mkdirs()) {
+                    files.add(f);
+                }
+            } else {
+                Files.createParentDirs(f);
+                if (f.createNewFile()) {
+                    writeRandomFile(f, minSize, maxSize);
+                    files.add(f);
+                }
+            }
+        }
+        return files.build();
+    }
+
+    /**
+     * Writes random binary content into the specified file. The number of
+     * bytes will be random between the given minimum and maximum.
+     *
+     * @param file    file to write data to
+     * @param minSize minimum number of bytes to write
+     * @param maxSize maximum number of bytes to write
+     * @throws IOException if there is an issue
+     */
+    public static void writeRandomFile(File file, int minSize, int maxSize) throws IOException {
+        int size = minSize + (minSize == maxSize ? 0 : RANDOM.nextInt(maxSize - minSize));
+        byte[] data = new byte[size];
+        tweakBytes(RANDOM, data, size / 4);
+        Files.write(data, file);
+    }
+
+
+    /**
+     * Tweaks the given number of bytes in a byte array.
+     *
+     * @param random random number generator
+     * @param data   byte array to be tweaked
+     * @param count  number of bytes to tweak
+     */
+    public static void tweakBytes(Random random, byte[] data, int count) {
+        tweakBytes(random, data, count, 0, data.length);
+    }
+
+    /**
+     * Tweaks the given number of bytes in the specified range of a byte array.
+     *
+     * @param random random number generator
+     * @param data   byte array to be tweaked
+     * @param count  number of bytes to tweak
+     * @param start  index at beginning of range (inclusive)
+     * @param end    index at end of range (exclusive)
+     */
+    public static void tweakBytes(Random random, byte[] data, int count,
+                                  int start, int end) {
+        int len = end - start;
+        for (int i = 0; i < count; i++) {
+            data[start + random.nextInt(len)] = (byte) random.nextInt();
+        }
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/util/Tools.java b/utils/misc/src/main/java/org/onlab/util/Tools.java
index e908300..498f3b1 100644
--- a/utils/misc/src/main/java/org/onlab/util/Tools.java
+++ b/utils/misc/src/main/java/org/onlab/util/Tools.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.util;
 
+import static java.nio.file.Files.delete;
+import static java.nio.file.Files.walkFileTree;
 import static org.slf4j.LoggerFactory.getLogger;
 
 import java.io.BufferedReader;
@@ -24,6 +26,11 @@
 import java.io.InputStreamReader;
 import java.lang.Thread.UncaughtExceptionHandler;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ThreadFactory;
@@ -39,7 +46,7 @@
     private Tools() {
     }
 
-    private static final Logger TOOLS_LOG = getLogger(Tools.class);
+    private static final Logger log = getLogger(Tools.class);
 
     /**
      * Returns a thread factory that produces threads named according to the
@@ -51,12 +58,12 @@
     public static ThreadFactory namedThreads(String pattern) {
         return new ThreadFactoryBuilder()
                 .setNameFormat(pattern)
-                // FIXME remove UncaughtExceptionHandler before release
+                        // FIXME remove UncaughtExceptionHandler before release
                 .setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
 
                     @Override
                     public void uncaughtException(Thread t, Throwable e) {
-                        TOOLS_LOG.error("Uncaught exception on {}", t.getName(), e);
+                        log.error("Uncaught exception on {}", t.getName(), e);
                     }
                 }).build();
     }
@@ -69,9 +76,9 @@
      */
     public static ThreadFactory minPriority(ThreadFactory factory) {
         return new ThreadFactoryBuilder()
-                    .setThreadFactory(factory)
-                    .setPriority(Thread.MIN_PRIORITY)
-                    .build();
+                .setThreadFactory(factory)
+                .setPriority(Thread.MIN_PRIORITY)
+                .build();
     }
 
     /**
@@ -127,7 +134,7 @@
     public static List<String> slurp(File path) {
         try {
             BufferedReader br = new BufferedReader(
-                new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
+                    new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
 
             List<String> lines = new ArrayList<>();
             String line;
@@ -141,4 +148,56 @@
         }
     }
 
+
+    /**
+     * Purges the specified directory path.&nbsp;Use with great caution since
+     * no attempt is made to check for symbolic links, which could result in
+     * deletion of unintended files.
+     *
+     * @param path directory to be removed
+     * @throws java.io.IOException if unable to remove contents
+     */
+    public static void removeDirectory(String path) throws IOException {
+        walkFileTree(Paths.get(path), new DirectoryDeleter());
+    }
+
+    /**
+     * Purges the specified directory path.&nbsp;Use with great caution since
+     * no attempt is made to check for symbolic links, which could result in
+     * deletion of unintended files.
+     *
+     * @param dir directory to be removed
+     * @throws java.io.IOException if unable to remove contents
+     */
+    public static void removeDirectory(File dir) throws IOException {
+        walkFileTree(Paths.get(dir.getAbsolutePath()), new DirectoryDeleter());
+    }
+
+
+    private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
+                throws IOException {
+            if (attributes.isRegularFile()) {
+                delete(file);
+            }
+            return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
+                throws IOException {
+            delete(directory);
+            return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult visitFileFailed(Path file, IOException ioe)
+                throws IOException {
+            log.warn("Unable to delete file {}", file);
+            log.warn("Boom", ioe);
+            return FileVisitResult.CONTINUE;
+        }
+    }
+
 }
diff --git a/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java b/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
new file mode 100644
index 0000000..4cf0b7c
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 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.app.ApplicationService;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.Application;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Application JSON codec.
+ */
+public class ApplicationCodec extends JsonCodec<Application> {
+
+    @Override
+    public ObjectNode encode(Application app, CodecContext context) {
+        checkNotNull(app, "Application cannot be null");
+        ApplicationService service = context.get(ApplicationService.class);
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("name", app.id().name())
+                .put("id", app.id().id())
+                .put("version", app.version().toString())
+                .put("description", app.description())
+                .put("origin", app.origin())
+                .put("permissions", app.permissions().toString())
+                .put("featuresRepo", app.featuresRepo().isPresent() ?
+                        app.featuresRepo().toString() : "")
+                .put("features", app.features().toString())
+                .put("state", service.getState(app.id()).toString());
+        return result;
+    }
+
+}
diff --git a/web/api/src/main/java/org/onosproject/codec/impl/CodecManager.java b/web/api/src/main/java/org/onosproject/codec/impl/CodecManager.java
index d176077..88fb8dd 100644
--- a/web/api/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/web/api/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -26,6 +26,7 @@
 import org.onlab.packet.Ethernet;
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.Application;
 import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -64,6 +65,7 @@
     @Activate
     public void activate() {
         codecs.clear();
+        registerCodec(Application.class, new ApplicationCodec());
         registerCodec(Annotations.class, new AnnotationsCodec());
         registerCodec(Device.class, new DeviceCodec());
         registerCodec(Port.class, new PortCodec());
diff --git a/web/api/src/main/java/org/onosproject/rest/ApplicationsWebResource.java b/web/api/src/main/java/org/onosproject/rest/ApplicationsWebResource.java
new file mode 100644
index 0000000..6177bab
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/rest/ApplicationsWebResource.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 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.rest;
+
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+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.InputStream;
+import java.util.Set;
+
+/**
+ * REST resource for interacting with the inventory of applications.
+ */
+@Path("applications")
+public class ApplicationsWebResource extends AbstractWebResource {
+
+    @GET
+    public Response getApplications() {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        Set<Application> apps = service.getApplications();
+        return ok(encodeArray(Application.class, "applications", apps)).build();
+    }
+
+    @GET
+    @Path("{name}")
+    public Response getApplication(@PathParam("id") String name) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = service.getId(name);
+        return response(service, appId);
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response installApplication(InputStream stream) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        Application app = service.install(stream);
+        return ok(codec(Application.class).encode(app, this)).build();
+    }
+
+    @DELETE
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{name}")
+    public Response uninstallApplication(@PathParam("name") String name) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = service.getId(name);
+        service.uninstall(appId);
+        return Response.ok().build();
+    }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{name}/active")
+    public Response activateApplication(@PathParam("name") String name) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = service.getId(name);
+        service.activate(appId);
+        return response(service, appId);
+    }
+
+    @DELETE
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{name}/active")
+    public Response deactivateApplication(@PathParam("name") String name) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = service.getId(name);
+        service.deactivate(appId);
+        return response(service, appId);
+    }
+
+    private Response response(ApplicationAdminService service, ApplicationId appId) {
+        Application app = service.getApplication(appId);
+        return ok(codec(Application.class).encode(app, this)).build();
+    }
+
+}
diff --git a/web/api/src/main/webapp/WEB-INF/web.xml b/web/api/src/main/webapp/WEB-INF/web.xml
index d8fb9e7..e0e4737 100644
--- a/web/api/src/main/webapp/WEB-INF/web.xml
+++ b/web/api/src/main/webapp/WEB-INF/web.xml
@@ -35,6 +35,7 @@
                 org.onosproject.rest.exceptions.ServerErrorMapper,
                 org.onosproject.rest.JsonBodyWriter,
 
+                org.onosproject.rest.ApplicationsWebResource,
                 org.onosproject.rest.DevicesWebResource,
                 org.onosproject.rest.LinksWebResource,
                 org.onosproject.rest.HostsWebResource,
diff --git a/web/api/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java b/web/api/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java
index a230273..d96f481 100644
--- a/web/api/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java
+++ b/web/api/src/test/java/org/onosproject/codec/impl/IntentCodecTest.java
@@ -66,8 +66,7 @@
 
     private final HostId id1 = hid("12:34:56:78:91:ab/1");
     private final HostId id2 = hid("12:34:56:78:92:ab/1");
-    private final ApplicationId appId =
-            new DefaultApplicationId((short) 3, "test");
+    private final ApplicationId appId = new DefaultApplicationId(3, "test");
     final TrafficSelector emptySelector =
             DefaultTrafficSelector.builder().build();
     final TrafficTreatment emptyTreatment =
diff --git a/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java
index 2d650a5..b2eee81 100644
--- a/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java
@@ -62,8 +62,7 @@
 public class IntentsResourceTest extends ResourceTest {
     final IntentService mockIntentService = createMock(IntentService.class);
     final HashSet<Intent> intents = new HashSet<>();
-    private static final ApplicationId APP_ID =
-            new DefaultApplicationId((short) 1, "test");
+    private static final ApplicationId APP_ID = new DefaultApplicationId(1, "test");
     private IdGenerator mockGenerator;
 
     /**