Ensure ApplicationManager waits until features are installed/uninstalled

Change-Id: Ie86c7945d8707cac6ef869e0b28bb950744ea708
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 a6297a6..6c39b6a 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
@@ -47,6 +47,7 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.app.ApplicationEvent.Type.APP_ACTIVATED;
@@ -83,7 +84,7 @@
 
     // Application supplied hooks for pre-activation processing.
     private final Multimap<String, Runnable> deactivateHooks = HashMultimap.create();
-    private final Cache<ApplicationId, CountDownLatch> pendingUninstalls =
+    private final Cache<ApplicationId, CountDownLatch> pendingOperations =
             CacheBuilder.newBuilder()
                         .expireAfterWrite(DEFAULT_OPERATION_TIMEOUT_MILLIS * 2, TimeUnit.MILLISECONDS)
                         .build();
@@ -160,15 +161,7 @@
     public void uninstall(ApplicationId appId) {
         checkNotNull(appId, APP_ID_NULL);
         CountDownLatch latch = new CountDownLatch(1);
-        try {
-            pendingUninstalls.put(appId, latch);
-            store.remove(appId);
-        } catch (Exception e) {
-            pendingUninstalls.invalidate(appId);
-            latch.countDown();
-            log.warn("Unable to purge application directory for {}", appId.name());
-        }
-        Uninterruptibles.awaitUninterruptibly(latch, DEFAULT_OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        updateStoreAndWaitForNotificationHandling(appId, store::remove);
     }
 
     @Override
@@ -177,13 +170,13 @@
         if (!SecurityUtil.isAppSecured(appId)) {
             return;
         }
-        store.activate(appId);
+        updateStoreAndWaitForNotificationHandling(appId, store::activate);
     }
 
     @Override
     public void deactivate(ApplicationId appId) {
         checkNotNull(appId, APP_ID_NULL);
-        store.deactivate(appId);
+        updateStoreAndWaitForNotificationHandling(appId, store::deactivate);
     }
 
     @Override
@@ -193,12 +186,26 @@
         store.setPermissions(appId, permissions);
     }
 
+    private void updateStoreAndWaitForNotificationHandling(ApplicationId appId,
+                                                           Consumer<ApplicationId> storeUpdateTask) {
+        CountDownLatch latch = new CountDownLatch(1);
+        try {
+            pendingOperations.put(appId, latch);
+            storeUpdateTask.accept(appId);
+        } catch (Exception e) {
+            pendingOperations.invalidate(appId);
+            latch.countDown();
+            log.warn("Failed to update store for {}", appId.name(), e);
+        }
+        Uninterruptibles.awaitUninterruptibly(latch, DEFAULT_OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+    }
+
     private class InternalStoreDelegate implements ApplicationStoreDelegate {
         @Override
         public void notify(ApplicationEvent event) {
             ApplicationEvent.Type type = event.type();
             Application app = event.subject();
-            CountDownLatch latch = pendingUninstalls.getIfPresent(app.id());
+            CountDownLatch latch = pendingOperations.getIfPresent(app.id());
             try {
                 if (type == APP_ACTIVATED) {
                     if (installAppFeatures(app)) {
@@ -227,7 +234,7 @@
             } finally {
                 if (latch != null) {
                     latch.countDown();
-                    pendingUninstalls.invalidate(app.id());
+                    pendingOperations.invalidate(app.id());
                 }
             }
         }