ONOS-3321 Added ability for applications to register a deactivation hook.

Change-Id: I284321474dbf9e39e8b61f7b907f637ba6b80aaf
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 161659f..a9e928e 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
@@ -15,6 +15,7 @@
  */
 package org.onosproject.app.impl;
 
+import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -30,20 +31,21 @@
 import org.onosproject.app.ApplicationState;
 import org.onosproject.app.ApplicationStore;
 import org.onosproject.app.ApplicationStoreDelegate;
-import org.onosproject.event.AbstractListenerManager;
 import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.event.AbstractListenerManager;
 import org.onosproject.security.Permission;
 import org.onosproject.security.SecurityUtil;
 import org.slf4j.Logger;
 
 import java.io.InputStream;
+import java.util.Map;
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.app.ApplicationEvent.Type.*;
-import static org.onosproject.security.AppPermission.Type.*;
 import static org.onosproject.security.AppGuard.checkPermission;
+import static org.onosproject.security.AppPermission.Type.APP_READ;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -69,6 +71,9 @@
 
     private boolean initializing;
 
+    // Application supplied hooks for pre-activation processing.
+    private final Map<String, Runnable> deactivateHooks = Maps.newConcurrentMap();
+
     @Activate
     public void activate() {
         eventDispatcher.addSink(ApplicationEvent.class, listenerRegistry);
@@ -122,6 +127,14 @@
     }
 
     @Override
+    public void registerDeactivateHook(ApplicationId appId, Runnable hook) {
+        checkPermission(APP_READ);
+        checkNotNull(appId, APP_ID_NULL);
+        checkNotNull(hook, "Hook cannot be null");
+        deactivateHooks.put(appId.name(), hook);
+    }
+
+    @Override
     public Application install(InputStream appDescStream) {
         checkNotNull(appDescStream, "Application archive stream cannot be null");
         Application app = store.create(appDescStream);
@@ -235,6 +248,7 @@
 
     private synchronized boolean uninstallAppFeatures(Application app) throws Exception {
         boolean changed = false;
+        invokeHook(deactivateHooks.get(app.id().name()), app.id());
         for (String name : app.features()) {
             Feature feature = featuresService.getFeature(name);
             if (feature != null && featuresService.isInstalled(feature)) {
@@ -247,4 +261,16 @@
         return changed;
     }
 
+    // Invokes the specified function, if not null.
+    private void invokeHook(Runnable hook, ApplicationId appId) {
+        if (hook != null) {
+            try {
+                hook.run();
+            } catch (Exception e) {
+                log.warn("Deactivate hook for application {} encountered an error",
+                         appId.name(), e);
+            }
+        }
+    }
+
 }