ONOS-1684 Added support for app dependencies.

Change-Id: Iae318c24c3c9bd43d84318c79ac420fc85d5d599
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java
index 17cf89e..100f682 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationsListCommand.java
@@ -41,7 +41,7 @@
 
     private static final String FMT =
             "%s id=%d, name=%s, version=%s, origin=%s, description=%s, " +
-                    "features=%s, featuresRepo=%s, permissions=%s";
+                    "features=%s, featuresRepo=%s, apps=%s, permissions=%s";
 
     private static final String SHORT_FMT =
             "%s %3d %-32s %-8s %s";
@@ -76,7 +76,7 @@
                               app.id().id(), app.id().name(), app.version(), app.origin(),
                               app.description(), app.features(),
                               app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "",
-                              app.permissions());
+                              app.requiredApps(), app.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
index 2561280..e8ff9ec 100644
--- a/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationDescription.java
@@ -86,4 +86,11 @@
      * @return application features
      */
     List<String> features();
+
+    /**
+     * Returns list of required application names.
+     *
+     * @return list of application names
+     */
+    List<String> requiredApps();
 }
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationStore.java b/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
index b3cdc43..0a1f072 100644
--- a/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
@@ -76,7 +76,7 @@
     void remove(ApplicationId appId);
 
     /**
-     * Mark the application as actived.
+     * Mark the application as active.
      *
      * @param appId application identifier
      */
diff --git a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
index 710d0f9..569183a 100644
--- a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
+++ b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
@@ -41,6 +41,7 @@
     private final Set<Permission> permissions;
     private final Optional<URI> featuresRepo;
     private final List<String> features;
+    private final List<String> requiredApps;
 
     /**
      * Creates a new application descriptor using the supplied data.
@@ -53,11 +54,13 @@
      * @param permissions  requested permissions
      * @param featuresRepo optional features repo URI
      * @param features     application features
+     * @param requiredApps list of required application names
      */
     public DefaultApplicationDescription(String name, Version version,
                                          String description, String origin,
                                          ApplicationRole role, Set<Permission> permissions,
-                                         URI featuresRepo, List<String> features) {
+                                         URI featuresRepo, List<String> features,
+                                         List<String> requiredApps) {
         this.name = checkNotNull(name, "Name cannot be null");
         this.version = checkNotNull(version, "Version cannot be null");
         this.description = checkNotNull(description, "Description cannot be null");
@@ -66,6 +69,7 @@
         this.permissions = checkNotNull(permissions, "Permissions cannot be null");
         this.featuresRepo = Optional.ofNullable(featuresRepo);
         this.features = checkNotNull(features, "Features cannot be null");
+        this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null");
         checkArgument(!features.isEmpty(), "There must be at least one feature");
     }
 
@@ -110,6 +114,11 @@
     }
 
     @Override
+    public List<String> requiredApps() {
+        return requiredApps;
+    }
+
+    @Override
     public String toString() {
         return toStringHelper(this)
                 .add("name", name)
@@ -120,6 +129,7 @@
                 .add("permissions", permissions)
                 .add("featuresRepo", featuresRepo)
                 .add("features", features)
+                .add("requiredApps", requiredApps)
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/core/Application.java b/core/api/src/main/java/org/onosproject/core/Application.java
index fca5384..ea2eab9 100644
--- a/core/api/src/main/java/org/onosproject/core/Application.java
+++ b/core/api/src/main/java/org/onosproject/core/Application.java
@@ -84,4 +84,11 @@
      * @return application features
      */
     List<String> features();
+
+    /**
+     * Returns list of required application names.
+     *
+     * @return list of application names
+     */
+    List<String> requiredApps();
 }
diff --git a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
index d8062dd..c351563 100644
--- a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
+++ b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
@@ -40,6 +40,7 @@
     private final Set<Permission> permissions;
     private final Optional<URI> featuresRepo;
     private final List<String> features;
+    private final List<String> requiredApps;
 
     /**
      * Creates a new application descriptor using the supplied data.
@@ -52,11 +53,13 @@
      * @param permissions  requested permissions
      * @param featuresRepo optional features repo URI
      * @param features     application features
+     * @param requiredApps list of required application names
      */
     public DefaultApplication(ApplicationId appId, Version version,
                               String description, String origin,
                               ApplicationRole role, Set<Permission> permissions,
-                              Optional<URI> featuresRepo, List<String> features) {
+                              Optional<URI> featuresRepo, List<String> features,
+                              List<String> requiredApps) {
         this.appId = checkNotNull(appId, "ID cannot be null");
         this.version = checkNotNull(version, "Version cannot be null");
         this.description = checkNotNull(description, "Description cannot be null");
@@ -65,6 +68,7 @@
         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");
+        this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null");
         checkArgument(!features.isEmpty(), "There must be at least one feature");
     }
 
@@ -109,9 +113,14 @@
     }
 
     @Override
+    public List<String> requiredApps() {
+        return requiredApps;
+    }
+
+    @Override
     public int hashCode() {
         return Objects.hash(appId, version, description, origin, role, permissions,
-                            featuresRepo, features);
+                            featuresRepo, features, requiredApps);
     }
 
     @Override
@@ -130,7 +139,8 @@
                 Objects.equals(this.role, other.role) &&
                 Objects.equals(this.permissions, other.permissions) &&
                 Objects.equals(this.featuresRepo, other.featuresRepo) &&
-                Objects.equals(this.features, other.features);
+                Objects.equals(this.features, other.features) &&
+                Objects.equals(this.requiredApps, other.requiredApps);
     }
 
     @Override
@@ -144,6 +154,7 @@
                 .add("permissions", permissions)
                 .add("featuresRepo", featuresRepo)
                 .add("features", features)
+                .add("requiredApps", requiredApps)
                 .toString();
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java b/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
index d31cc26..34c593c 100644
--- a/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
@@ -33,7 +33,7 @@
 
     private Application createApp() {
         return new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
-                                      PERMS, Optional.of(FURL), FEATURES);
+                                      PERMS, Optional.of(FURL), FEATURES, APPS);
     }
 
     @Test
diff --git a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
index d40d3fe..0e93c1f 100644
--- a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
+++ b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
@@ -46,12 +46,13 @@
                             new Permission(AppPermission.class.getName(), "FLOWRULE_READ"));
     public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
     public static final List<String> FEATURES = ImmutableList.of("foo", "bar");
+    public static final List<String> APPS = ImmutableList.of("fifi");
 
     @Test
     public void basics() {
         ApplicationDescription app =
                 new DefaultApplicationDescription(APP_NAME, VER, DESC, ORIGIN,
-                                                  ROLE, PERMS, FURL, FEATURES);
+                                                  ROLE, PERMS, FURL, FEATURES, APPS);
         assertEquals("incorrect id", APP_NAME, app.name());
         assertEquals("incorrect version", VER, app.version());
         assertEquals("incorrect description", DESC, app.description());
@@ -60,6 +61,7 @@
         assertEquals("incorrect permissions", PERMS, app.permissions());
         assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
         assertEquals("incorrect features", FEATURES, app.features());
+        assertEquals("incorrect apps", APPS, app.requiredApps());
         assertTrue("incorrect toString", app.toString().contains(APP_NAME));
     }
 
diff --git a/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java b/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
index cbedb79..77b3812 100644
--- a/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
+++ b/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
@@ -34,7 +34,7 @@
     @Test
     public void basics() {
         Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
-                                                 PERMS, Optional.of(FURL), FEATURES);
+                                                 PERMS, Optional.of(FURL), FEATURES, APPS);
         assertEquals("incorrect id", APP_ID, app.id());
         assertEquals("incorrect version", VER, app.version());
         assertEquals("incorrect description", DESC, app.description());
@@ -43,19 +43,20 @@
         assertEquals("incorrect permissions", PERMS, app.permissions());
         assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
         assertEquals("incorrect features", FEATURES, app.features());
+        assertEquals("incorrect apps", APPS, app.requiredApps());
         assertTrue("incorrect toString", app.toString().contains(APP_NAME));
     }
 
     @Test
     public void testEquality() {
         Application a1 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
-                                                PERMS, Optional.of(FURL), FEATURES);
+                                                PERMS, Optional.of(FURL), FEATURES, APPS);
         Application a2 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
-                                                PERMS, Optional.of(FURL), FEATURES);
+                                                PERMS, Optional.of(FURL), FEATURES, APPS);
         Application a3 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE,
-                                                PERMS, Optional.empty(), FEATURES);
+                                                PERMS, Optional.empty(), FEATURES, APPS);
         Application a4 = new DefaultApplication(APP_ID, VER, DESC, ORIGIN + "asd", ROLE,
-                                                PERMS, Optional.of(FURL), FEATURES);
+                                                PERMS, Optional.of(FURL), FEATURES, APPS);
         new EqualsTester().addEqualityGroup(a1, a2)
                 .addEqualityGroup(a3).addEqualityGroup(a4).testEquals();
     }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
index b2cab09..a09c0bd 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ApplicationCodec.java
@@ -32,18 +32,18 @@
     public ObjectNode encode(Application app, CodecContext context) {
         checkNotNull(app, "Application cannot be null");
         ApplicationService service = context.getService(ApplicationService.class);
-        ObjectNode result = context.mapper().createObjectNode()
+        return 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("permissions", app.permissions().toString()) // FIXME: change to an array
                 .put("featuresRepo", app.featuresRepo().isPresent() ?
                         app.featuresRepo().get().toString() : "")
-                .put("features", app.features().toString())
+                .put("features", app.features().toString()) // FIXME: change to an array
+                .put("requiredApps", app.requiredApps().toString()) // FIXME: change to an array
                 .put("state", service.getState(app.id()).toString());
-        return result;
     }
 
 }
diff --git a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
index 54f0fb8..37cdbdf 100644
--- a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
+++ b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
@@ -17,6 +17,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Files;
 import org.apache.commons.configuration.ConfigurationException;
@@ -33,7 +34,6 @@
 import org.onosproject.security.AppPermission;
 import org.onosproject.security.Permission;
 import org.onosproject.store.AbstractStore;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -46,7 +46,6 @@
 import java.net.URI;
 import java.nio.charset.Charset;
 import java.nio.file.NoSuchFileException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -79,6 +78,7 @@
     private static final String VERSION = "[@version]";
     private static final String FEATURES_REPO = "[@featuresRepo]";
     private static final String FEATURES = "[@features]";
+    private static final String APPS = "[@apps]";
     private static final String DESCRIPTION = "description";
 
     private static final String ROLE = "security.role";
@@ -291,8 +291,13 @@
         URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
         List<String> features = ImmutableList.copyOf(cfg.getString(FEATURES).split(","));
 
+        String apps = cfg.getString(APPS, "");
+        List<String> requiredApps = apps.isEmpty() ?
+                ImmutableList.of() : ImmutableList.copyOf(apps.split(","));
+
         return new DefaultApplicationDescription(name, version, desc, origin, role,
-                                                 perms, featuresRepo, features);
+                                                 perms, featuresRepo, features,
+                                                 requiredApps);
     }
 
     // Expands the specified ZIP stream into app-specific directory.
@@ -390,7 +395,7 @@
 
     // Returns the set of Permissions specified in the app.xml file
     private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) {
-        List<Permission> permissionList = new ArrayList();
+        List<Permission> permissionList = Lists.newArrayList();
 
         for (Object o : cfg.getList(APP_PERMISSIONS)) {
             String name = (String) o;
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
index ea9a773..d9f5285 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
@@ -75,7 +75,8 @@
                     new DefaultApplication(appId, appDesc.version(),
                                            appDesc.description(), appDesc.origin(),
                                            appDesc.role(), appDesc.permissions(),
-                                           appDesc.featuresRepo(), appDesc.features());
+                                           appDesc.featuresRepo(), appDesc.features(),
+                                           appDesc.requiredApps());
             apps.put(appId, app);
             states.put(appId, isActive(name) ? INSTALLED : ACTIVE);
             // load app permissions
@@ -117,7 +118,8 @@
         DefaultApplication app =
                 new DefaultApplication(appId, appDesc.version(), appDesc.description(),
                                        appDesc.origin(), appDesc.role(), appDesc.permissions(),
-                                       appDesc.featuresRepo(), appDesc.features());
+                                       appDesc.featuresRepo(), appDesc.features(),
+                                       appDesc.requiredApps());
         apps.put(appId, app);
         states.put(appId, INSTALLED);
         delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
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
index a9e928e..d09eb1f 100644
--- a/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
+++ b/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
@@ -212,6 +212,7 @@
     // The following methods are fully synchronized to guard against remote vs.
     // locally induced feature service interactions.
 
+    // Installs all feature repositories required by the specified app.
     private synchronized boolean installAppArtifacts(Application app) throws Exception {
         if (app.featuresRepo().isPresent() &&
                 featuresService.getRepository(app.featuresRepo().get()) == null) {
@@ -221,6 +222,7 @@
         return false;
     }
 
+    // Uninstalls all the feature repositories required by the specified app.
     private synchronized boolean uninstallAppArtifacts(Application app) throws Exception {
         if (app.featuresRepo().isPresent() &&
                 featuresService.getRepository(app.featuresRepo().get()) != null) {
@@ -230,6 +232,7 @@
         return false;
     }
 
+    // Installs all features that define the specified app.
     private synchronized boolean installAppFeatures(Application app) throws Exception {
         boolean changed = false;
         for (String name : app.features()) {
@@ -246,6 +249,7 @@
         return changed;
     }
 
+    // Uninstalls all features that define the specified app.
     private synchronized boolean uninstallAppFeatures(Application app) throws Exception {
         boolean changed = false;
         invokeHook(deactivateHooks.get(app.id().name()), app.id());
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
index 5461cf0..a99fd21 100644
--- a/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.app.impl;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.junit.After;
 import org.junit.Before;
@@ -138,7 +139,7 @@
         @Override
         public Application create(InputStream appDescStream) {
             app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN, ROLE, PERMS,
-                                         Optional.of(FURL), FEATURES);
+                                         Optional.of(FURL), FEATURES, ImmutableList.of());
             state = INSTALLED;
             delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
             return app;
@@ -177,6 +178,11 @@
             state = INSTALLED;
             delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));
         }
+
+        @Override
+        public ApplicationId getId(String name) {
+            return new DefaultApplicationId(0, name);
+        }
     }
 
     private class TestFeaturesService extends FeaturesServiceAdapter {
diff --git a/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java b/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java
index 6764c22..dda820a 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/app/GossipApplicationStore.java
@@ -17,7 +17,9 @@
 
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableSet;
-
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -37,6 +39,7 @@
 import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.ApplicationIdStore;
+import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultApplication;
 import org.onosproject.security.Permission;
 import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
@@ -61,6 +64,8 @@
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.function.Function;
 
+import static com.google.common.collect.Multimaps.newSetMultimap;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
 import static com.google.common.io.ByteStreams.toByteArray;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.onlab.util.Tools.groupedThreads;
@@ -115,6 +120,14 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ApplicationIdStore idStore;
 
+    // Multimap to track which apps are required by others apps
+    // app -> { required-by, ... }
+    // Apps explicitly activated will be required by the CORE app
+    private final Multimap<ApplicationId, ApplicationId> requiredBy =
+            synchronizedSetMultimap(newSetMultimap(Maps.newHashMap(), Sets::newHashSet));
+
+    private ApplicationId coreAppId;
+
     @Activate
     public void activate() {
         KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
@@ -128,16 +141,16 @@
                 groupedThreads("onos/store/app", "message-handler"));
 
         clusterCommunicator.<String, byte[]>addSubscriber(APP_BITS_REQUEST,
-                bytes -> new String(bytes, Charsets.UTF_8),
-                name -> {
-                    try {
-                        return toByteArray(getApplicationInputStream(name));
-                    } catch (IOException e) {
-                        throw new StorageException(e);
-                    }
-                },
-                Function.identity(),
-                messageHandlingExecutor);
+                                                          bytes -> new String(bytes, Charsets.UTF_8),
+                                                          name -> {
+                                                              try {
+                                                                  return toByteArray(getApplicationInputStream(name));
+                                                              } catch (IOException e) {
+                                                                  throw new StorageException(e);
+                                                              }
+                                                          },
+                                                          Function.identity(),
+                                                          messageHandlingExecutor);
 
         // FIXME: Consider consolidating into a single map.
 
@@ -161,6 +174,7 @@
                 .withTimestampProvider((k, v) -> clockService.getTimestamp())
                 .build();
 
+        coreAppId = getId(CoreService.CORE_APP_NAME);
         log.info("Started");
     }
 
@@ -174,6 +188,7 @@
                 try {
                     Application app = create(getApplicationDescription(name), false);
                     if (app != null && isActive(app.id().name())) {
+                        requiredBy.put(app.id(), coreAppId);
                         activate(app.id(), false);
                         // load app permissions
                     }
@@ -200,7 +215,6 @@
     public void setDelegate(ApplicationStoreDelegate delegate) {
         super.setDelegate(delegate);
         loadFromDisk();
-//        executor.schedule(this::pruneUninstalledApps, LOAD_TIMEOUT_MS, MILLISECONDS);
     }
 
     @Override
@@ -229,7 +243,15 @@
     @Override
     public Application create(InputStream appDescStream) {
         ApplicationDescription appDesc = saveApplication(appDescStream);
-        return create(appDesc, true);
+        if (hasPrerequisites(appDesc)) {
+            return create(appDesc, true);
+        }
+        throw new ApplicationException("Missing dependencies for app " + appDesc.name());
+    }
+
+    private boolean hasPrerequisites(ApplicationDescription app) {
+        return !app.requiredApps().stream().map(n -> getId(n))
+                .anyMatch(id -> id == null || getApplication(id) == null);
     }
 
     private Application create(ApplicationDescription appDesc, boolean updateTime) {
@@ -246,36 +268,80 @@
     public void remove(ApplicationId appId) {
         Application app = apps.get(appId);
         if (app != null) {
+            uninstallDependentApps(app);
             apps.remove(appId);
             states.remove(app);
             permissions.remove(app);
         }
     }
 
+    // Uninstalls all apps that depend on the given app.
+    private void uninstallDependentApps(Application app) {
+        getApplications().stream()
+                .filter(a -> a.requiredApps().contains(app.id().name()))
+                .forEach(a -> remove(a.id()));
+    }
+
     @Override
     public void activate(ApplicationId appId) {
+        activate(appId, coreAppId);
+    }
+
+    private void activate(ApplicationId appId, ApplicationId forAppId) {
+        requiredBy.put(appId, forAppId);
         activate(appId, true);
     }
 
+
     private void activate(ApplicationId appId, boolean updateTime) {
         Application app = apps.get(appId);
         if (app != null) {
             if (updateTime) {
                 updateTime(appId.name());
             }
+            activateRequiredApps(app);
             states.put(app, ACTIVATED);
         }
     }
 
+    // Activates all apps required by this application.
+    private void activateRequiredApps(Application app) {
+        app.requiredApps().stream().map(this::getId).forEach(id -> activate(id, app.id()));
+    }
+
     @Override
     public void deactivate(ApplicationId appId) {
-        Application app = apps.get(appId);
-        if (app != null) {
-            updateTime(appId.name());
-            states.put(app, DEACTIVATED);
+        deactivateDependentApps(getApplication(appId));
+        deactivate(appId, coreAppId);
+    }
+
+    private void deactivate(ApplicationId appId, ApplicationId forAppId) {
+        requiredBy.remove(appId, forAppId);
+        if (requiredBy.get(appId).isEmpty()) {
+            Application app = apps.get(appId);
+            if (app != null) {
+                updateTime(appId.name());
+                states.put(app, DEACTIVATED);
+                deactivateRequiredApps(app);
+            }
         }
     }
 
+    // Deactivates all apps that require this application.
+    private void deactivateDependentApps(Application app) {
+        getApplications().stream()
+                .filter(a -> states.get(a) == ACTIVATED)
+                .filter(a -> a.requiredApps().contains(app.id().name()))
+                .forEach(a -> deactivate(a.id()));
+    }
+
+    // Deactivates all apps required by this application.
+    private void deactivateRequiredApps(Application app) {
+        app.requiredApps().stream().map(this::getId).map(this::getApplication)
+                .filter(a -> states.get(a) == ACTIVATED)
+                .forEach(a -> deactivate(a.id(), app.id()));
+    }
+
     @Override
     public Set<Permission> getPermissions(ApplicationId appId) {
         Application app = apps.get(appId);
@@ -424,6 +490,7 @@
         ApplicationId appId = idStore.registerApplication(appDesc.name());
         return new DefaultApplication(appId, appDesc.version(), appDesc.description(),
                                       appDesc.origin(), appDesc.role(), appDesc.permissions(),
-                                      appDesc.featuresRepo(), appDesc.features());
+                                      appDesc.featuresRepo(), appDesc.features(),
+                                      appDesc.requiredApps());
     }
 }
diff --git a/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java b/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
index bfc6127..5558b13 100644
--- a/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
+++ b/tools/package/maven-plugin/src/main/java/org/onosproject/maven/OnosAppMojo.java
@@ -62,6 +62,7 @@
 
     private static final String ONOS_APP_NAME = "onos.app.name";
     private static final String ONOS_APP_ORIGIN = "onos.app.origin";
+    private static final String ONOS_APP_REQUIRES = "onos.app.requires";
 
     private static final String JAR = "jar";
     private static final String XML = "xml";
@@ -80,6 +81,7 @@
 
     private String name;
     private String origin;
+    private String requiredApps;
     private String version = DEFAULT_VERSION;
     private String featuresRepo = DEFAULT_FEATURES_REPO;
     private List<String> artifacts;
@@ -160,6 +162,9 @@
         origin = (String) project.getProperties().get(ONOS_APP_ORIGIN);
         origin = origin != null ? origin : DEFAULT_ORIGIN;
 
+        requiredApps = (String) project.getProperties().get(ONOS_APP_REQUIRES);
+        requiredApps = requiredApps == null ? "" : requiredApps;
+
         if (appFile.exists()) {
             loadAppFile(appFile);
         } else {
@@ -338,6 +343,7 @@
         return string == null ? null :
                 string.replaceAll("\\$\\{onos.app.name\\}", name)
                         .replaceAll("\\$\\{onos.app.origin\\}", origin)
+                        .replaceAll("\\$\\{onos.app.requires\\}", requiredApps)
                         .replaceAll("\\$\\{project.groupId\\}", projectGroupId)
                         .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
                         .replaceAll("\\$\\{project.version\\}", projectVersion)
diff --git a/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml b/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml
index 0f3133d..8499880 100644
--- a/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml
+++ b/tools/package/maven-plugin/src/main/resources/org/onosproject/maven/app.xml
@@ -16,7 +16,7 @@
   -->
 <app name="${onos.app.name}" origin="${onos.app.origin}" version="${project.version}"
         featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
-        features="${project.artifactId}">
+        features="${project.artifactId}" apps="${onos.app.requires}">
     <description>${project.description}</description>
     <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
 </app>
diff --git a/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
index 6fee43e..3e72f18 100644
--- a/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java
@@ -85,19 +85,19 @@
     private Application app1 =
             new DefaultApplication(id1, VER,
                                    "app1", "origin1", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"));
+                                   ImmutableList.of("My Feature"), ImmutableList.of());
     private Application app2 =
             new DefaultApplication(id2, VER,
                                    "app2", "origin2", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"));
+                                   ImmutableList.of("My Feature"), ImmutableList.of());
     private Application app3 =
             new DefaultApplication(id3, VER,
                                    "app3", "origin3", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"));
+                                   ImmutableList.of("My Feature"), ImmutableList.of());
     private Application app4 =
             new DefaultApplication(id4, VER,
                                    "app4", "origin4", ApplicationRole.ADMIN, ImmutableSet.of(), Optional.of(FURL),
-                                   ImmutableList.of("My Feature"));
+                                   ImmutableList.of("My Feature"), ImmutableList.of());
 
     /**
      * Hamcrest matcher to check that an application representation in JSON matches