[ONOS-6348] Intent installer redesign

Change-Id: I9ae2e8158dc1c686eaf848f330566f9dbb78405f
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/InstallCoordinator.java b/core/net/src/main/java/org/onosproject/net/intent/impl/InstallCoordinator.java
new file mode 100644
index 0000000..d5eb1fd
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/InstallCoordinator.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.intent.impl;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentInstallationContext;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.IntentStore;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.onosproject.net.intent.IntentState.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of IntentInstallCoordinator.
+ */
+public class InstallCoordinator {
+    private static final String INSTALLER_NOT_FOUND = "Intent installer not found, Intent: {}";
+    private final Logger log = getLogger(IntentManager.class);
+
+    private InstallerRegistry installerRegistry;
+    private IntentStore intentStore;
+
+    /**
+     * Creates an InstallCoordinator.
+     *
+     * @param installerRegistry the installer registry
+     * @param intentStore the Intent store
+     */
+    public InstallCoordinator(InstallerRegistry installerRegistry,
+                              IntentStore intentStore) {
+        this.installerRegistry = installerRegistry;
+        this.intentStore = intentStore;
+    }
+
+    /**
+     * Applies Intent data to be uninstalled and to be installed.
+     *
+     * @param toUninstall Intent data to be uninstalled
+     * @param toInstall Intent data to be installed
+     */
+    public void installIntents(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
+        // If no any Intents to be uninstalled or installed, ignore it.
+        if (!toUninstall.isPresent() && !toInstall.isPresent()) {
+            return;
+        }
+
+        // Classify installable Intents to different installers.
+        ArrayListMultimap<IntentInstaller, Intent> uninstallInstallers;
+        ArrayListMultimap<IntentInstaller, Intent> installInstallers;
+        Set<IntentInstaller> allInstallers = Sets.newHashSet();
+
+        if (toUninstall.isPresent()) {
+            uninstallInstallers = getInstallers(toUninstall.get());
+            allInstallers.addAll(uninstallInstallers.keySet());
+        } else {
+            uninstallInstallers = ArrayListMultimap.create();
+        }
+
+        if (toInstall.isPresent()) {
+            installInstallers = getInstallers(toInstall.get());
+            allInstallers.addAll(installInstallers.keySet());
+        } else {
+            installInstallers = ArrayListMultimap.create();
+        }
+
+        // Generates an installation context for the high level Intent.
+        IntentInstallationContext installationContext =
+                new IntentInstallationContext(toUninstall.orElse(null), toInstall.orElse(null));
+
+        //Generates different operation context for different installable Intents.
+        Map<IntentInstaller, IntentOperationContext> contexts = Maps.newHashMap();
+        allInstallers.forEach(installer -> {
+            List<Intent> intentsToUninstall = uninstallInstallers.get(installer);
+            List<Intent> intentsToInstall = installInstallers.get(installer);
+
+            // Connect context to high level installation context
+            IntentOperationContext context =
+                    new IntentOperationContext(intentsToUninstall, intentsToInstall,
+                                               installationContext);
+            installationContext.addPendingContext(context);
+            contexts.put(installer, context);
+        });
+
+        // Apply contexts to installers
+        contexts.forEach((installer, context) -> {
+            installer.apply(context);
+        });
+    }
+
+    /**
+     * Generates a mapping for installable Intents to installers.
+     *
+     * @param intentData the Intent data which contains installable Intents
+     * @return the mapping for installable Intents to installers
+     */
+    private ArrayListMultimap<IntentInstaller, Intent> getInstallers(IntentData intentData) {
+        ArrayListMultimap<IntentInstaller, Intent> intentInstallers = ArrayListMultimap.create();
+        intentData.installables().forEach(intent -> {
+            IntentInstaller installer = installerRegistry.getInstaller(intent.getClass());
+            if (installer != null) {
+                intentInstallers.put(installer, intent);
+            } else {
+                log.warn(INSTALLER_NOT_FOUND, intent);
+            }
+        });
+        return intentInstallers;
+    }
+
+    /**
+     * Handles success operation context.
+     *
+     * @param context the operation context
+     */
+    public void success(IntentOperationContext context) {
+        IntentInstallationContext intentInstallationContext =
+                context.intentInstallationContext();
+        intentInstallationContext.removePendingContext(context);
+
+        if (intentInstallationContext.isPendingContextsEmpty()) {
+            finish(intentInstallationContext);
+        }
+    }
+
+    /**
+     * Handles failed operation context.
+     *
+     * @param context the operation context
+     */
+    public void failed(IntentOperationContext context) {
+        IntentInstallationContext intentInstallationContext =
+                context.intentInstallationContext();
+        intentInstallationContext.addErrorContext(context);
+        intentInstallationContext.removePendingContext(context);
+
+        if (intentInstallationContext.isPendingContextsEmpty()) {
+            finish(intentInstallationContext);
+        }
+    }
+
+    /**
+     * Completed the installation context and update the Intent store.
+     *
+     * @param intentInstallationContext the installation context
+     */
+    private void finish(IntentInstallationContext intentInstallationContext) {
+        Set<IntentOperationContext> errCtxs = intentInstallationContext.errorContexts();
+        Optional<IntentData> toUninstall = intentInstallationContext.toUninstall();
+        Optional<IntentData> toInstall = intentInstallationContext.toInstall();
+
+        // Intent install success
+        if (errCtxs == null || errCtxs.isEmpty()) {
+            if (toInstall.isPresent()) {
+                IntentData installData = toInstall.get();
+                log.debug("Completed installing: {}", installData.key());
+                installData.setState(INSTALLED);
+                intentStore.write(installData);
+            } else if (toUninstall.isPresent()) {
+                IntentData uninstallData = toUninstall.get();
+                log.debug("Completed withdrawing: {}", uninstallData.key());
+                switch (uninstallData.request()) {
+                    case INSTALL_REQ:
+                        log.warn("{} was requested to withdraw during installation?",
+                                 uninstallData.intent());
+                        uninstallData.setState(FAILED);
+                        break;
+                    case WITHDRAW_REQ:
+                    default: //TODO "default" case should not happen
+                        uninstallData.setState(WITHDRAWN);
+                        break;
+                }
+                // Intent has been withdrawn; we can clear the installables
+                intentStore.write(new IntentData(uninstallData, Collections.emptyList()));
+            }
+        } else {
+            // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
+            if (toInstall.isPresent()) {
+                IntentData installData = toInstall.get();
+                installData.setState(CORRUPT);
+                installData.incrementErrorCount();
+                intentStore.write(installData);
+            }
+            // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
+            if (toUninstall.isPresent()) {
+                IntentData uninstallData = toUninstall.get();
+                uninstallData.setState(CORRUPT);
+                uninstallData.incrementErrorCount();
+                intentStore.write(uninstallData);
+            }
+        }
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/InstallerRegistry.java b/core/net/src/main/java/org/onosproject/net/intent/impl/InstallerRegistry.java
new file mode 100644
index 0000000..db5becc
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/InstallerRegistry.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.intent.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentInstaller;
+
+import java.util.Map;
+
+/**
+ * The local registry for Intent installer.
+ */
+public class InstallerRegistry {
+    private final Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> installers;
+
+    public InstallerRegistry() {
+        installers = Maps.newConcurrentMap();
+    }
+
+    /**
+     * Registers the specific installer for the given intent class.
+     *
+     * @param cls intent class
+     * @param installer intent installer
+     * @param <T> the type of intent
+     */
+    public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+        installers.put(cls, installer);
+    }
+
+    /**
+     * Unregisters the installer for the specific intent class.
+     *
+     * @param cls intent class
+     * @param <T> the type of intent
+     */
+    public <T extends Intent> void unregisterInstaller(Class<T> cls) {
+        installers.remove(cls);
+    }
+
+    /**
+     * Returns immutable set of binding of currently registered intent installers.
+     *
+     * @return the set of installer bindings
+     */
+    public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
+        return ImmutableMap.copyOf(installers);
+    }
+
+    /**
+     * Get an Intent installer by given Intent type.
+     *
+     * @param cls the Intent type
+     * @param <T> the Intent type
+     * @return the Intent installer of the Intent type if exists; null otherwise
+     */
+    public <T extends Intent> IntentInstaller<T> getInstaller(Class<T> cls) {
+        return (IntentInstaller<T>) installers.get(cls);
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstaller.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstaller.java
deleted file mode 100644
index d0928ce..0000000
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentInstaller.java
+++ /dev/null
@@ -1,1051 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.onosproject.net.intent.impl;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import org.apache.commons.lang3.tuple.Pair;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription;
-import org.onosproject.net.behaviour.protection.ProtectionConfig;
-import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.domain.DomainIntent;
-import org.onosproject.net.domain.DomainIntentOperations;
-import org.onosproject.net.domain.DomainIntentOperationsContext;
-import org.onosproject.net.domain.DomainIntentService;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleOperations;
-import org.onosproject.net.flow.FlowRuleOperationsContext;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.flowobjective.FilteringObjective;
-import org.onosproject.net.flowobjective.FlowObjectiveService;
-import org.onosproject.net.flowobjective.ForwardingObjective;
-import org.onosproject.net.flowobjective.NextObjective;
-import org.onosproject.net.flowobjective.Objective;
-import org.onosproject.net.flowobjective.ObjectiveContext;
-import org.onosproject.net.flowobjective.ObjectiveError;
-import org.onosproject.net.intent.FlowObjectiveIntent;
-import org.onosproject.net.intent.FlowRuleIntent;
-import org.onosproject.net.intent.Intent;
-import org.onosproject.net.intent.IntentData;
-import org.onosproject.net.intent.IntentStore;
-import org.onosproject.net.intent.ProtectionEndpointIntent;
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static org.onosproject.net.flowobjective.ObjectiveError.INSTALLATIONTHRESHOLDEXCEEDED;
-import static org.onosproject.net.intent.IntentState.*;
-import static org.slf4j.LoggerFactory.getLogger;
-
-/**
- * Auxiliary entity responsible for installing the intents into the environment.
- */
-class IntentInstaller {
-
-    private static final Logger log = getLogger(IntentInstaller.class);
-    private static final long OBJECTIVE_RETRY_THRESHOLD = 5;
-
-    private IntentStore store;
-    private ObjectiveTrackerService trackerService;
-    private FlowRuleService flowRuleService;
-    private FlowObjectiveService flowObjectiveService;
-    private NetworkConfigService networkConfigService;
-    private DomainIntentService domainIntentService;
-
-    private enum Direction {
-        ADD,
-        REMOVE
-    }
-
-    /**
-     * Initializes the installer with references to required services.
-     *
-     * @param intentStore          intent store
-     * @param trackerService       objective tracking service
-     * @param flowRuleService      flow rule service
-     * @param flowObjectiveService flow objective service
-     * @param networkConfigService network configuration service
-     * @param domainIntentService  domain intent service
-     */
-    void init(IntentStore intentStore, ObjectiveTrackerService trackerService,
-              FlowRuleService flowRuleService, FlowObjectiveService flowObjectiveService,
-              NetworkConfigService networkConfigService, DomainIntentService domainIntentService) {
-
-        this.store = intentStore;
-        this.trackerService = trackerService;
-        //TODO Various services should be plugged to the intent installer instead of being hardcoded
-        this.flowRuleService = flowRuleService;
-        this.flowObjectiveService = flowObjectiveService;
-        this.networkConfigService = networkConfigService;
-        this.domainIntentService = domainIntentService;
-    }
-
-    // FIXME: Intent Manager should have never become dependent on a specific intent type(s).
-    // This will be addressed in intent domains work; not now.
-
-    /**
-     * Applies the specified intent updates to the environment by uninstalling
-     * and installing the intents and updating the store references appropriately.
-     *
-     * @param toUninstall optional intent to uninstall
-     * @param toInstall   optional intent to install
-     */
-    void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
-        // Hook for handling success at intent installation level.
-        Consumer<IntentInstallationContext> successConsumer = (ctx) -> {
-            if (toInstall.isPresent()) {
-                IntentData installData = toInstall.get();
-                log.debug("Completed installing: {}", installData.key());
-                installData.setState(INSTALLED);
-                store.write(installData);
-            } else if (toUninstall.isPresent()) {
-                IntentData uninstallData = toUninstall.get();
-                log.debug("Completed withdrawing: {}", uninstallData.key());
-                switch (uninstallData.request()) {
-                    case INSTALL_REQ:
-                        // illegal state?
-                        log.warn("{} was requested to withdraw during installation?",
-                                 uninstallData.intent());
-                        uninstallData.setState(FAILED);
-                        break;
-                    case WITHDRAW_REQ:
-                    default: //TODO "default" case should not happen
-                        uninstallData.setState(WITHDRAWN);
-                        break;
-                }
-                // Intent has been withdrawn; we can clear the installables
-                store.write(new IntentData(uninstallData, Collections.emptyList()));
-            }
-        };
-
-        // Hook for handling errors at intent installation level
-        Consumer<IntentInstallationContext> errorConsumer = (ctx) -> {
-            // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
-            if (toInstall.isPresent()) {
-                IntentData installData = toInstall.get();
-                installData.setState(CORRUPT);
-                installData.incrementErrorCount();
-                store.write(installData);
-            }
-            // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
-            if (toUninstall.isPresent()) {
-                IntentData uninstallData = toUninstall.get();
-                uninstallData.setState(CORRUPT);
-                uninstallData.incrementErrorCount();
-                store.write(uninstallData);
-            }
-        };
-
-        // Hooks at operation level
-        Consumer<OperationContext> successOperationConsumer = (ctx) -> {
-            ctx.intentContext.finishContext(ctx);
-        };
-        Consumer<OperationContext> errorOperationConsumer = (ctx) -> {
-            if (ctx.toInstall.isPresent()) {
-                IntentData installData = toInstall.get();
-                log.warn("Failed installation operation for: {} {} due to {}",
-                         installData.key(), installData.intent(), ctx.error());
-            }
-            if (ctx.toUninstall.isPresent()) {
-                IntentData uninstallData = toUninstall.get();
-                log.warn("Failed withdrawal operation for: {} {} due to {}",
-                         uninstallData.key(), uninstallData.intent(), ctx.error());
-            }
-            ctx.intentContext.handleError(ctx);
-        };
-
-        // Create a context for tracking the backing operations for applying
-        // the intents to the environment.
-        IntentInstallationContext intentContext =
-                new IntentInstallationContext(successConsumer, errorConsumer);
-        Set<OperationContext> contexts = createContext(intentContext, toUninstall, toInstall);
-        intentContext.pendingContexts = contexts;
-        contexts.forEach(ctx -> {
-            ctx.prepare(toUninstall, toInstall, successOperationConsumer, errorOperationConsumer);
-            ctx.apply();
-        });
-    }
-
-    // Context for applying and tracking multiple kinds of operation contexts
-    // related to specific intent data.
-    private final class IntentInstallationContext {
-        private Set<OperationContext> pendingContexts = Sets.newHashSet();
-        private Set<OperationContext> errorContexts = Sets.newHashSet();
-        private Consumer<IntentInstallationContext> successConsumer;
-        private Consumer<IntentInstallationContext> errorConsumer;
-
-        private IntentInstallationContext(Consumer<IntentInstallationContext> succesConsumer,
-                                          Consumer<IntentInstallationContext> errorConsumer) {
-            this.successConsumer = succesConsumer;
-            this.errorConsumer = errorConsumer;
-        }
-
-        private void handleError(OperationContext ctx) {
-            errorContexts.add(ctx);
-            finishContext(ctx);
-        }
-
-        private void finishContext(OperationContext ctx) {
-            synchronized (pendingContexts) {
-                pendingContexts.remove(ctx);
-                if (pendingContexts.isEmpty()) {
-                    if (errorContexts.isEmpty()) {
-                        successConsumer.accept(IntentInstallationContext.this);
-                    } else {
-                        errorConsumer.accept(IntentInstallationContext.this);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                    .add("pendingContexts", pendingContexts)
-                    .add("errorContexts", errorContexts)
-                    .toString();
-        }
-    }
-
-    // --- Utilities to support FlowRule vs. FlowObjective vs. DomainIntent behavior ----
-
-    // Creates the set of contexts appropriate for tracking operations of the
-    // the specified intents.
-    private Set<OperationContext> createContext(IntentInstallationContext intentContext,
-                                                Optional<IntentData> toUninstall,
-                                                Optional<IntentData> toInstall) {
-
-        Set<OperationContext> contexts = Sets.newConcurrentHashSet();
-        if (isInstallable(toUninstall, toInstall, FlowRuleIntent.class)) {
-            contexts.add(new FlowRuleOperationContext(intentContext));
-        }
-        if (isInstallable(toUninstall, toInstall, FlowObjectiveIntent.class)) {
-            contexts.add(new FlowObjectiveOperationContext(intentContext));
-        }
-        if (isInstallable(toUninstall, toInstall, ProtectionEndpointIntent.class)) {
-            contexts.add(new ProtectionConfigOperationContext(intentContext));
-        }
-        if (isInstallable(toUninstall, toInstall, DomainIntent.class)) {
-            contexts.add(new DomainIntentOperationContext(intentContext));
-        }
-
-        if (contexts.isEmpty()) {
-            log.warn("{} did not contain installable Intents", intentContext);
-            return ImmutableSet.of(new ErrorContext(intentContext));
-        }
-
-        return contexts;
-    }
-
-    /**
-     * Tests if one of {@code toUninstall} or {@code toInstall} contains
-     * installable Intent of type specified by {@code intentClass}.
-     *
-     * @param toUninstall IntentData to test
-     * @param toInstall   IntentData to test
-     * @param intentClass installable Intent class
-     * @return true if at least one of IntentData contains installable specified.
-     */
-    private boolean isInstallable(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
-                                  Class<? extends Intent> intentClass) {
-
-        return Stream.concat(toInstall
-                              .map(IntentData::installables)
-                              .map(Collection::stream)
-                              .orElse(Stream.empty()),
-                             toUninstall
-                              .map(IntentData::installables)
-                              .map(Collection::stream)
-                              .orElse(Stream.empty()))
-                .anyMatch(i -> intentClass.isAssignableFrom(i.getClass()));
-    }
-
-    // Base context for applying and tracking operations related to installable intents.
-    private abstract class OperationContext {
-        protected IntentInstallationContext intentContext;
-        protected Optional<IntentData> toUninstall;
-        protected Optional<IntentData> toInstall;
-        /**
-         * Implementation of {@link OperationContext} should call this on success.
-         */
-        protected Consumer<OperationContext> successConsumer;
-        /**
-         * Implementation of {@link OperationContext} should call this on error.
-         */
-        protected Consumer<OperationContext> errorConsumer;
-
-        protected OperationContext(IntentInstallationContext context) {
-            this.intentContext = context;
-        }
-
-        /**
-         * Applies the Intents specified by
-         * {@link #prepareIntents(List, Direction)} call(s) prior to this call.
-         */
-        abstract void apply();
-
-        /**
-         * Returns error state of the context.
-         * <p>
-         * Used for error logging purpose.
-         * Returned Object should have reasonable toString() implementation.
-         * @return context state, describing current error state
-         */
-        abstract Object error();
-
-        /**
-         * Prepares Intent(s) to {@link #apply() apply} in this operation.
-         * <p>
-         * Intents specified by {@code intentsToApply} in a single call
-         * can be applied to the Devices in arbitrary order.
-         * But group of Intents specified in consecutive {@link #prepareIntents(List, Direction)}
-         * calls must be applied in order. (e.g., guarded by barrier)
-         *
-         * @param intentsToApply {@link Intent}s to apply
-         * @param direction of operation
-         */
-        abstract void prepareIntents(List<Intent> intentsToApply, Direction direction);
-
-        void prepare(Optional<IntentData> toUninstall, Optional<IntentData> toInstall,
-                     Consumer<OperationContext> successConsumer,
-                     Consumer<OperationContext> errorConsumer) {
-            this.toUninstall = toUninstall;
-            this.toInstall = toInstall;
-            this.successConsumer = successConsumer;
-            this.errorConsumer = errorConsumer;
-            prepareIntentData(toUninstall, toInstall);
-        }
-
-        private void prepareIntentData(Optional<IntentData> uninstallData,
-                                       Optional<IntentData> installData) {
-            if (!installData.isPresent() && !uninstallData.isPresent()) {
-                return;
-            } else if (!installData.isPresent()) {
-                prepareIntentData(uninstallData, Direction.REMOVE);
-            } else if (!uninstallData.isPresent()) {
-                prepareIntentData(installData, Direction.ADD);
-            } else {
-                IntentData uninstall = uninstallData.get();
-                IntentData install = installData.get();
-                List<Intent> uninstallIntents = Lists.newArrayList(uninstall.installables());
-                List<Intent> installIntents = Lists.newArrayList(install.installables());
-
-                checkState(uninstallIntents.stream().allMatch(this::isSupported),
-                           "Unsupported installable intents detected: %s", uninstallIntents);
-                checkState(installIntents.stream().allMatch(this::isSupported),
-                           "Unsupported installable intents detected: %s", installIntents);
-
-                //TODO: Filter FlowObjective intents
-                // Filter out same intents and intents with same flow rules
-                Iterator<Intent> iterator = installIntents.iterator();
-                while (iterator.hasNext()) {
-                    Intent installIntent = iterator.next();
-                    uninstallIntents.stream().filter(uIntent -> {
-                        if (uIntent.equals(installIntent)) {
-                            return true;
-                        } else if (uIntent instanceof FlowRuleIntent && installIntent instanceof FlowRuleIntent) {
-                            //FIXME we can further optimize this by doing the filtering on a flow-by-flow basis
-                            //      (direction can be implied from intent state)
-                            return !flowRuleIntentChanged(((FlowRuleIntent) uIntent),
-                                                          ((FlowRuleIntent) installIntent));
-                        } else {
-                            return false;
-                        }
-                    }).findFirst().ifPresent(common -> {
-                        uninstallIntents.remove(common);
-                        if (INSTALLED.equals(uninstall.state())) {
-                            // only remove the install intent if the existing
-                            // intent (i.e. the uninstall one) is already
-                            // installed or installing
-                            iterator.remove();
-                        }
-                    });
-                }
-
-                final IntentData newUninstall = new IntentData(uninstall, uninstallIntents);
-                final IntentData newInstall = new IntentData(install, installIntents);
-
-                trackerService.removeTrackedResources(newUninstall.key(), newUninstall.intent().resources());
-                uninstallIntents.forEach(installable ->
-                                                 trackerService.removeTrackedResources(newUninstall.intent().key(),
-                                                                                       installable.resources()));
-                trackerService.addTrackedResources(newInstall.key(), newInstall.intent().resources());
-                installIntents.forEach(installable ->
-                                               trackerService.addTrackedResources(newInstall.key(),
-                                                                                  installable.resources()));
-                prepareIntents(uninstallIntents, Direction.REMOVE);
-                prepareIntents(installIntents, Direction.ADD);
-            }
-        }
-
-        /**
-         * Determines whether there is any flow rule changed
-         * (i.e., different set of flow rules or different treatments)
-         * between FlowRuleIntents to be uninstalled and to be installed.
-         *
-         * @param uninstallIntent FlowRuleIntent to uninstall
-         * @param installIntent FlowRuleIntent to install
-         * @return true if flow rules which to be uninstalled
-         * contains all flow rules which to be installed.
-         */
-        private boolean flowRuleIntentChanged(FlowRuleIntent uninstallIntent,
-                                              FlowRuleIntent installIntent) {
-            Collection<FlowRule> flowRulesToUninstall = uninstallIntent.flowRules();
-            Collection<FlowRule> flowRulesToInstall = installIntent.flowRules();
-
-            // Check if any flow rule changed
-            for (FlowRule flowRuleToInstall : flowRulesToInstall) {
-                if (flowRulesToUninstall.stream().noneMatch(flowRuleToInstall::exactMatch)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        /**
-         * Applies the specified intent data, if present, to the network using the
-         * specified context.
-         *
-         * @param intentData optional intent data; no-op if not present
-         * @param direction  indicates adding or removal
-         */
-        private void prepareIntentData(Optional<IntentData> intentData, Direction direction) {
-            if (!intentData.isPresent()) {
-                return;
-            }
-
-            IntentData data = intentData.get();
-            List<Intent> intentsToApply = data.installables();
-            checkState(intentsToApply.stream().allMatch(this::isSupported),
-                       "Unsupported installable intents detected: %s", intentsToApply);
-
-            if (direction == Direction.ADD) {
-                trackerService.addTrackedResources(data.key(), data.intent().resources());
-                intentsToApply.forEach(installable ->
-                                               trackerService.addTrackedResources(data.key(),
-                                                                                  installable.resources()));
-            } else {
-                trackerService.removeTrackedResources(data.key(), data.intent().resources());
-                intentsToApply.forEach(installable ->
-                                               trackerService.removeTrackedResources(data.intent().key(),
-                                                                                     installable.resources()));
-            }
-
-            prepareIntents(intentsToApply, direction);
-        }
-
-        private boolean isSupported(Intent intent) {
-            return intent instanceof FlowRuleIntent ||
-                   intent instanceof FlowObjectiveIntent ||
-                   intent instanceof ProtectionEndpointIntent ||
-                   intent instanceof DomainIntent;
-        }
-
-        protected ToStringHelper toStringHelper() {
-            return MoreObjects.toStringHelper(this)
-                    .add("intentContext", intentContext)
-                    .add("toUninstall", toUninstall)
-                    .add("toInstall", toInstall);
-        }
-
-        @Override
-        public String toString() {
-            return toStringHelper()
-                    .toString();
-        }
-    }
-
-
-    // Context for applying and tracking operations related to flow rule intents.
-    private class FlowRuleOperationContext extends OperationContext {
-        FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
-        FlowRuleOperationsContext flowRuleOperationsContext;
-
-        FlowRuleOperationContext(IntentInstallationContext context) {
-            super(context);
-        }
-
-        @Override
-        void apply() {
-            flowRuleOperationsContext = new FlowRuleOperationsContext() {
-                @Override
-                public void onSuccess(FlowRuleOperations ops) {
-                    successConsumer.accept(FlowRuleOperationContext.this);
-                }
-
-                @Override
-                public void onError(FlowRuleOperations ops) {
-                    errorConsumer.accept(FlowRuleOperationContext.this);
-                }
-            };
-            FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
-
-            if (log.isTraceEnabled()) {
-                log.trace("applying intent {} -> {} with {} rules: {}",
-                          toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
-                          toInstall.map(x -> x.key().toString()).orElse("<empty>"),
-                          operations.stages().stream().mapToLong(Set::size).sum(),
-                          operations.stages());
-            }
-
-            flowRuleService.apply(operations);
-        }
-
-        @Override
-        public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
-            // FIXME do FlowRuleIntents have stages??? Can we do uninstall work in parallel? I think so.
-            builder.newStage();
-
-            List<Collection<FlowRule>> stages = intentsToApply.stream()
-                    .filter(x -> x instanceof FlowRuleIntent)
-                    .map(x -> (FlowRuleIntent) x)
-                    .map(FlowRuleIntent::flowRules)
-                    .collect(Collectors.toList());
-
-            for (Collection<FlowRule> rules : stages) {
-                if (direction == Direction.ADD) {
-                    rules.forEach(builder::add);
-                } else {
-                    rules.forEach(builder::remove);
-                }
-            }
-
-        }
-
-        @Override
-        public Object error() {
-            return flowRuleOperationsContext;
-        }
-
-        @Override
-        protected ToStringHelper toStringHelper() {
-            return super.toStringHelper()
-                    .omitNullValues()
-                    .add("flowRuleOperationsContext", flowRuleOperationsContext);
-        }
-    }
-
-    // Context for applying and tracking operations related to flow objective intents.
-    private class FlowObjectiveOperationContext extends OperationContext {
-        private static final String UNSUPPORT_OBJ = "unsupported objective {}";
-        final List<ObjectiveContext> contexts = Lists.newArrayList();
-
-        final Set<ObjectiveContext> pendingContexts = Sets.newConcurrentHashSet();
-
-        // Second stage of pending contexts
-        final Set<ObjectiveContext> nextPendingContexts = Sets.newConcurrentHashSet();
-        final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
-
-        FlowObjectiveOperationContext(IntentInstallationContext context) {
-            super(context);
-        }
-
-        @Override
-        public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
-            intentsToApply
-                .stream()
-                .filter(intent -> intent instanceof FlowObjectiveIntent)
-                .map(intent -> buildObjectiveContexts((FlowObjectiveIntent) intent, direction))
-                .flatMap(Collection::stream)
-                .forEach(contexts::add);
-
-            // Two stage for different direction context
-            // We will apply REMOVE context first, and apply ADD context.
-            contexts.forEach(context -> {
-                switch (direction) {
-                    case REMOVE:
-                        pendingContexts.add(context);
-                        break;
-                    case ADD:
-                        nextPendingContexts.add(context);
-                        break;
-                    default:
-                        break;
-                }
-            });
-        }
-
-        // Builds the specified objective in the appropriate direction
-        private Set<? extends ObjectiveContext> buildObjectiveContexts(FlowObjectiveIntent intent,
-                                            Direction direction) {
-            Set<FlowObjectiveInstallationContext> contexts = Sets.newHashSet();
-            int size = intent.objectives().size();
-            List<Objective> objectives = intent.objectives();
-            List<DeviceId> deviceIds = intent.devices();
-
-            if (direction == Direction.ADD) {
-                for (int i = 0; i < size; i++) {
-                    Objective objective = objectives.get(i);
-                    DeviceId deviceId = deviceIds.get(i);
-                    FlowObjectiveInstallationContext ctx =
-                            buildObjectiveContext(objective, deviceId, direction);
-                    contexts.add(ctx);
-                }
-                return contexts;
-            } else {
-                // we need to care about ordering here
-                // basic idea is to chain objective contexts
-                for (int i = 0; i < size; i++) {
-                    Objective objective = intent.objectives().get(i);
-                    DeviceId deviceId = intent.devices().get(i);
-
-                    if (objective instanceof FilteringObjective) {
-                        // don't need to care ordering of filtering objective
-                        FlowObjectiveInstallationContext ctx =
-                                buildObjectiveContext(objective, deviceId, direction);
-                        contexts.add(ctx);
-                    } else if (objective instanceof NextObjective) {
-                        // need to removed after forwarding objective
-                        // nothing to do here
-                    } else if (objective instanceof ForwardingObjective) {
-                        // forwarding objective, also find next objective if
-                        // exist
-                        FlowObjectiveInstallationContext fwdCtx =
-                                buildObjectiveContext(objective, deviceId, direction);
-                        ForwardingObjective fwd = (ForwardingObjective) objective;
-                        NextObjective nxt = null;
-                        Integer nextId = fwd.nextId();
-                        if (nextId != null) {
-                            for (int j = 0; j < size; j++) {
-                                if (objectives.get(j).id() == nextId) {
-                                    nxt = (NextObjective) objectives.get(j);
-                                    break;
-                                }
-                            }
-                            // if a next objective exists in the Intent
-                            if (nxt != null) {
-                                FlowObjectiveInstallationContext nxtCtx =
-                                        buildObjectiveContext(nxt, deviceId, direction);
-                                fwdCtx.nextContext(nxtCtx);
-                            }
-                        }
-                        contexts.add(fwdCtx);
-                    } else {
-                        // possible here?
-                        log.warn(UNSUPPORT_OBJ, objective);
-                    }
-                }
-            }
-            return contexts;
-        }
-
-        private FlowObjectiveInstallationContext buildObjectiveContext(Objective objective,
-                                                                       DeviceId deviceId,
-                                                                       Direction direction) {
-            Objective.Builder builder = objective.copy();
-            FlowObjectiveInstallationContext ctx = new FlowObjectiveInstallationContext();
-            switch (direction) {
-                case ADD:
-                    objective = builder.add(ctx);
-                    break;
-                case REMOVE:
-                    objective = builder.remove(ctx);
-                    break;
-                default:
-                    break;
-            }
-            ctx.setObjective(objective, deviceId);
-            return ctx;
-        }
-
-        @Override
-        void apply() {
-            // If there is no pending contexts, try apply second stage
-            // pending contexts
-            if (pendingContexts.isEmpty()) {
-                pendingContexts.addAll(nextPendingContexts);
-                nextPendingContexts.clear();
-            }
-            final Set<ObjectiveContext> contextsToApply = Sets.newHashSet(pendingContexts);
-            contextsToApply.forEach(ctx -> {
-                FlowObjectiveInstallationContext foiCtx =
-                        (FlowObjectiveInstallationContext) ctx;
-
-                flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective);
-            });
-        }
-
-        @Override
-        public Object error() {
-            return errorContexts;
-        }
-
-        @Override
-        protected ToStringHelper toStringHelper() {
-            return super.toStringHelper()
-                    .add("contexts", contexts)
-                    .add("pendingContexts", pendingContexts)
-                    .add("errorContexts", errorContexts);
-        }
-
-        private class FlowObjectiveInstallationContext implements ObjectiveContext {
-            Objective objective;
-            DeviceId deviceId;
-            ObjectiveError error;
-            AtomicInteger retry;
-            FlowObjectiveInstallationContext nextContext;
-
-            void setObjective(Objective objective, DeviceId deviceId) {
-                // init function
-                this.objective = objective;
-                this.deviceId = deviceId;
-                this.error = null;
-                this.retry = new AtomicInteger(0);
-                this.nextContext = null;
-            }
-
-            int retryTimes() {
-                return this.retry.get();
-            }
-
-            void increaseRetryValue() {
-                this.retry.incrementAndGet();
-            }
-
-            private void finished(ObjectiveError error) {
-
-                synchronized (pendingContexts) {
-                    if (error != null) {
-                        this.error = error;
-                        handleObjectiveError(this, error);
-                    } else {
-                        // apply next context if exist
-                        if (nextContext != null) {
-                            pendingContexts.add(nextContext);
-                            flowObjectiveService.apply(nextContext.deviceId,
-                                                       nextContext.objective);
-                            pendingContexts.remove(this);
-                        } else {
-                            pendingContexts.remove(this);
-                        }
-                    }
-                    if (!pendingContexts.isEmpty()) {
-                        return;
-                    }
-                    // Apply second stage pending contexts if it is not empty
-                    if (!nextPendingContexts.isEmpty()) {
-                        pendingContexts.addAll(nextPendingContexts);
-                        nextPendingContexts.clear();
-                        final Set<ObjectiveContext> contextsToApply =
-                                Sets.newHashSet(pendingContexts);
-                        contextsToApply.forEach(ctx -> {
-                            FlowObjectiveInstallationContext foiCtx =
-                                    (FlowObjectiveInstallationContext) ctx;
-                            flowObjectiveService.apply(foiCtx.deviceId,
-                                                       foiCtx.objective);
-                        });
-                        return;
-                    }
-                    if (errorContexts.isEmpty()) {
-                        successConsumer.accept(FlowObjectiveOperationContext.this);
-                    } else {
-                        errorConsumer.accept(FlowObjectiveOperationContext.this);
-                    }
-                }
-            }
-
-            @Override
-            public void onSuccess(Objective objective) {
-                finished(null);
-            }
-
-            @Override
-            public void onError(Objective objective, ObjectiveError error) {
-                finished(error);
-            }
-
-            @Override
-            public String toString() {
-                return String.format("(%s on %s for %s)", error, deviceId, objective);
-            }
-
-            public void nextContext(FlowObjectiveInstallationContext nextContext) {
-                this.nextContext = nextContext;
-            }
-        }
-
-        private void handleObjectiveError(FlowObjectiveInstallationContext ctx,
-                                          ObjectiveError error) {
-            log.debug("Got error(s) when install objective: {}, error: {}, retry: {}",
-                      ctx.objective, ctx.error, ctx.retry);
-            if (ctx.retryTimes() > OBJECTIVE_RETRY_THRESHOLD) {
-                ctx.error = INSTALLATIONTHRESHOLDEXCEEDED;
-                errorContexts.add(ctx);
-                return;
-            }
-            // reset error
-            ctx.error = null;
-            // strategies for errors
-            switch (error) {
-                case GROUPEXISTS:
-                    if (ctx.objective.op() == Objective.Operation.ADD) {
-                        // Next group exists
-                        // build new objective with new op ADD_TO_EXIST
-                        NextObjective newObj =
-                                ((NextObjective.Builder) ctx.objective.copy()).addToExisting(ctx);
-                        ctx.setObjective(newObj, ctx.deviceId);
-                        ctx.increaseRetryValue();
-                        flowObjectiveService.apply(ctx.deviceId, ctx.objective);
-                    } else {
-                        pendingContexts.remove(ctx);
-                        errorContexts.add(ctx);
-                    }
-                    break;
-                case GROUPINSTALLATIONFAILED:
-                    // Group install failed, retry again
-                    ctx.increaseRetryValue();
-                    flowObjectiveService.apply(ctx.deviceId, ctx.objective);
-                    break;
-                case GROUPMISSING:
-                    if (ctx.objective.op() == Objective.Operation.ADD_TO_EXISTING) {
-                        // Next group not exist, but we want to add new buckets
-                        // build new objective with new op ADD
-                        NextObjective newObj = (NextObjective) ctx.objective.copy().add(ctx);
-                        ctx.setObjective(newObj, ctx.deviceId);
-                        ctx.increaseRetryValue();
-                        flowObjectiveService.apply(ctx.deviceId, ctx.objective);
-                    } else if (ctx.objective.op() == Objective.Operation.REMOVE ||
-                            ctx.objective.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
-                        // Already removed, no need to do anything
-                        ctx.error = null;
-                        pendingContexts.remove(ctx);
-                        return;
-                    } else {
-                        // Next chaining group missing, try again.
-                        ctx.increaseRetryValue();
-                        flowObjectiveService.apply(ctx.deviceId, ctx.objective);
-                    }
-                    break;
-                case FLOWINSTALLATIONFAILED:
-                case GROUPREMOVALFAILED:
-                case INSTALLATIONTIMEOUT:
-                    // Retry
-                    ctx.increaseRetryValue();
-                    flowObjectiveService.apply(ctx.deviceId, ctx.objective);
-                    break;
-                default:
-                    pendingContexts.remove(ctx);
-                    errorContexts.add(ctx);
-                    break;
-            }
-        }
-    }
-
-    // Context for applying and tracking operations related to domain intents.
-    private class DomainIntentOperationContext extends OperationContext {
-        DomainIntentOperations.Builder builder = DomainIntentOperations.builder();
-        DomainIntentOperationsContext domainOperationsContext;
-
-        DomainIntentOperationContext(IntentInstallationContext context) {
-            super(context);
-        }
-        @Override
-        void apply() {
-            domainOperationsContext = new DomainIntentOperationsContext() {
-                @Override
-                public void onSuccess(DomainIntentOperations ops) {
-                    successConsumer.accept(DomainIntentOperationContext.this);
-                }
-
-                @Override
-                public void onError(DomainIntentOperations ops) {
-                    errorConsumer.accept(DomainIntentOperationContext.this);
-                }
-            };
-            DomainIntentOperations operations = builder.build(domainOperationsContext);
-
-            if (log.isTraceEnabled()) {
-                log.trace("submitting domain intent {} -> {}",
-                          toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
-                          toInstall.map(x -> x.key().toString()).orElse("<empty>"));
-            }
-            domainIntentService.sumbit(operations);
-        }
-
-        @Override
-        public void prepareIntents(List<Intent> intentsToApply, Direction direction) {
-            List<DomainIntent> intents = intentsToApply.stream()
-                    .filter(x -> x instanceof DomainIntent)
-                    .map(x -> (DomainIntent) x)
-                    .collect(Collectors.toList());
-
-            for (DomainIntent intent : intents) {
-                if (direction == Direction.ADD) {
-                    builder.add(intent);
-                } else {
-                    builder.remove(intent);
-                }
-            }
-        }
-
-        @Override
-        public Object error() {
-            return domainOperationsContext;
-        }
-    }
-
-    private class ErrorContext extends OperationContext {
-        ErrorContext(IntentInstallationContext context) {
-            super(context);
-        }
-        @Override
-        void apply() {
-            throw new UnsupportedOperationException("Unsupported installable intent");
-        }
-
-        @Override
-        Object error() {
-            return null;
-        }
-
-        @Override
-        void prepareIntents(List<Intent> intentsToApply, Direction direction) {
-        }
-    }
-
-
-    /**
-     * Context for applying and tracking operations related to
-     * {@link ProtectionEndpointIntent}.
-     */
-    @Beta
-    private class ProtectionConfigOperationContext extends OperationContext {
-
-        ProtectionConfigOperationContext(IntentInstallationContext context) {
-            super(context);
-        }
-
-        /**
-         * Stage of installable Intents which can be processed in parallel.
-         */
-        private final class Stage {
-            // should it have progress state, how far it went?
-            private final Collection<Pair<ProtectionEndpointIntent, Direction>> ops;
-
-            Stage(Collection<Pair<ProtectionEndpointIntent, Direction>> ops) {
-                this.ops = checkNotNull(ops);
-            }
-
-            CompletableFuture<Void> apply() {
-                return ops.stream()
-                    .map(op -> applyOp(op.getRight(), op.getLeft()))
-                    .reduce(CompletableFuture.completedFuture(null),
-                            (l, r) -> {
-                                l.join();
-                                return r;
-                            });
-            }
-
-            private CompletableFuture<Void> applyOp(Direction dir, ProtectionEndpointIntent intent) {
-                log.trace("applying {}: {}", dir, intent);
-                if (dir == Direction.REMOVE) {
-                    networkConfigService.removeConfig(intent.deviceId(), ProtectionConfig.class);
-                } else if (dir == Direction.ADD) {
-                    ProtectedTransportEndpointDescription description = intent.description();
-
-                    // Can't do following. Will trigger empty CONFIG_ADDED
-                    //ProtectionConfig cfg = networkConfigService.addConfig(intent.deviceId(),
-                    //                                                      ProtectionConfig.class);
-                    ProtectionConfig cfg = new ProtectionConfig(intent.deviceId());
-                    cfg.fingerprint(description.fingerprint());
-                    cfg.peer(description.peer());
-                    cfg.paths(description.paths());
-                    //cfg.apply();
-
-                    networkConfigService.applyConfig(intent.deviceId(),
-                                                     ProtectionConfig.class,
-                                                     cfg.node());
-                }
-                // TODO Should monitor progress and complete only after it's
-                // actually done.
-                return CompletableFuture.completedFuture(null);
-            }
-
-            @Override
-            public String toString() {
-                return ops.toString();
-            }
-        }
-
-        /**
-         * List of Stages which must be executed in order.
-         */
-        private final List<Stage> stages = new ArrayList<>();
-
-        private final List<Stage> failed = new CopyOnWriteArrayList<>();
-
-        @Override
-        synchronized void apply() {
-            for (Stage stage : stages) {
-                log.trace("applying Stage {}", stage);
-                CompletableFuture<Void> result = stage.apply();
-                // wait for stage completion
-                result.join();
-                if (result.isCompletedExceptionally()) {
-                    log.error("Stage {} failed", stage);
-                    failed.add(stage);
-                    errorConsumer.accept(ProtectionConfigOperationContext.this);
-                    return;
-                }
-            }
-            successConsumer.accept(ProtectionConfigOperationContext.this);
-        }
-
-        @Override
-        Object error() {
-            // Something to represent error state
-            return failed;
-        }
-
-        @Override
-        synchronized void prepareIntents(List<Intent> intentsToApply,
-                                         Direction direction) {
-
-            stages.add(new Stage(intentsToApply.stream()
-                                 .filter(i -> i instanceof ProtectionEndpointIntent)
-                                 .map(i -> Pair.of((ProtectionEndpointIntent) i, direction))
-                                 .collect(Collectors.toList())));
-        }
-
-        @Override
-        protected ToStringHelper toStringHelper() {
-            return super.toStringHelper()
-                    .add("stages", stages)
-                    .add("failed", failed);
-        }
-    }
-}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
index 3b542bd..14d2bea 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
@@ -38,9 +38,12 @@
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentBatchDelegate;
 import org.onosproject.net.intent.IntentCompiler;
+import org.onosproject.net.intent.IntentInstallCoordinator;
 import org.onosproject.net.intent.IntentData;
 import org.onosproject.net.intent.IntentEvent;
 import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.onosproject.net.intent.IntentInstaller;
 import org.onosproject.net.intent.IntentListener;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.IntentState;
@@ -87,7 +90,7 @@
 @Service
 public class IntentManager
         extends AbstractListenerManager<IntentEvent, IntentListener>
-        implements IntentService, IntentExtensionService {
+        implements IntentService, IntentExtensionService, IntentInstallCoordinator {
 
     private static final Logger log = getLogger(IntentManager.class);
 
@@ -141,17 +144,17 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private NetworkConfigService networkConfigService;
 
-
     private ExecutorService batchExecutor;
     private ExecutorService workerExecutor;
 
-    private final IntentInstaller intentInstaller = new IntentInstaller();
     private final CompilerRegistry compilerRegistry = new CompilerRegistry();
+    private final InstallerRegistry installerRegistry = new InstallerRegistry();
     private final InternalIntentProcessor processor = new InternalIntentProcessor();
     private final IntentStoreDelegate delegate = new InternalStoreDelegate();
     private final IntentStoreDelegate testOnlyDelegate = new TestOnlyIntentStoreDelegate();
     private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
     private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
+    private InstallCoordinator installCoordinator;
     private IdGenerator idGenerator;
 
     private final IntentAccumulator accumulator = new IntentAccumulator(batchDelegate);
@@ -159,9 +162,6 @@
     @Activate
     public void activate() {
         configService.registerProperties(getClass());
-
-        intentInstaller.init(store, trackerService, flowRuleService, flowObjectiveService,
-                             networkConfigService, domainIntentService);
         if (skipReleaseResourcesOnWithdrawal) {
             store.setDelegate(testOnlyDelegate);
         } else {
@@ -174,12 +174,12 @@
         idGenerator = coreService.getIdGenerator("intent-ids");
         Intent.unbindIdGenerator(idGenerator);
         Intent.bindIdGenerator(idGenerator);
+        installCoordinator = new InstallCoordinator(installerRegistry, store);
         log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
-        intentInstaller.init(null, null, null, null, null, null);
         if (skipReleaseResourcesOnWithdrawal) {
             store.unsetDelegate(testOnlyDelegate);
         } else {
@@ -337,11 +337,42 @@
     }
 
     @Override
+    public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+        installerRegistry.registerInstaller(cls, installer);
+    }
+
+    @Override
+    public <T extends Intent> void unregisterInstaller(Class<T> cls) {
+        installerRegistry.unregisterInstaller(cls);
+    }
+
+    @Override
+    public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
+        return installerRegistry.getInstallers();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends Intent> IntentInstaller<T> getInstaller(Class<T> cls) {
+        return (IntentInstaller<T>) installerRegistry.getInstallers().get(cls);
+    }
+
+    @Override
     public Iterable<Intent> getPending() {
         checkPermission(INTENT_READ);
         return store.getPending();
     }
 
+    @Override
+    public void intentInstallSuccess(IntentOperationContext context) {
+        installCoordinator.success(context);
+    }
+
+    @Override
+    public void intentInstallFailed(IntentOperationContext context) {
+        installCoordinator.failed(context);
+    }
+
     // Store delegate to re-post events emitted from the store.
     private class InternalStoreDelegate implements IntentStoreDelegate {
         @Override
@@ -536,7 +567,7 @@
 
         @Override
         public void apply(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) {
-            intentInstaller.apply(toUninstall, toInstall);
+            installCoordinator.installIntents(toUninstall, toInstall);
         }
     }
 
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/installer/DomainIntentInstaller.java b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/DomainIntentInstaller.java
new file mode 100644
index 0000000..a713034
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/DomainIntentInstaller.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.intent.impl.installer;
+
+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.onosproject.net.domain.DomainIntent;
+import org.onosproject.net.domain.DomainIntentOperations;
+import org.onosproject.net.domain.DomainIntentOperationsContext;
+import org.onosproject.net.domain.DomainIntentService;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentInstallCoordinator;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.onosproject.net.intent.impl.IntentManager;
+import org.onosproject.net.intent.impl.ObjectiveTrackerService;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Installer for domain Intent.
+ */
+@Component(immediate = true)
+public class DomainIntentInstaller implements IntentInstaller<DomainIntent> {
+
+    private final Logger log = getLogger(IntentManager.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentExtensionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ObjectiveTrackerService trackerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentInstallCoordinator intentInstallCoordinator;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DomainIntentService domainIntentService;
+
+    @Activate
+    public void activated() {
+        intentExtensionService.registerInstaller(DomainIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivated() {
+        intentExtensionService.unregisterInstaller(DomainIntent.class);
+    }
+
+    @Override
+    public void apply(IntentOperationContext<DomainIntent> context) {
+        Optional<IntentData> toUninstall = context.toUninstall();
+        Optional<IntentData> toInstall = context.toInstall();
+
+        List<DomainIntent> uninstallIntents = context.intentsToUninstall();
+        List<DomainIntent> installIntents = context.intentsToInstall();
+
+        if (!toInstall.isPresent() && !toUninstall.isPresent()) {
+            intentInstallCoordinator.intentInstallSuccess(context);
+            return;
+        }
+
+        if (toUninstall.isPresent()) {
+            IntentData intentData = toUninstall.get();
+            trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources());
+            uninstallIntents.forEach(installable ->
+                                             trackerService.removeTrackedResources(intentData.intent().key(),
+                                                                                   installable.resources()));
+        }
+
+        if (toInstall.isPresent()) {
+            IntentData intentData = toInstall.get();
+            trackerService.addTrackedResources(intentData.key(), intentData.intent().resources());
+            installIntents.forEach(installable ->
+                                           trackerService.addTrackedResources(intentData.key(),
+                                                                              installable.resources()));
+        }
+
+        // Generate domain Intent operations
+        DomainIntentOperations.Builder builder = DomainIntentOperations.builder();
+        DomainIntentOperationsContext domainOperationsContext;
+
+        uninstallIntents.forEach(builder::remove);
+        installIntents.forEach(builder::add);
+
+        domainOperationsContext = new DomainIntentOperationsContext() {
+            @Override
+            public void onSuccess(DomainIntentOperations idops) {
+                intentInstallCoordinator.intentInstallSuccess(context);
+            }
+
+            @Override
+            public void onError(DomainIntentOperations idos) {
+                intentInstallCoordinator.intentInstallFailed(context);
+            }
+        };
+        log.debug("submitting domain intent {} -> {}",
+                  toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
+                  toInstall.map(x -> x.key().toString()).orElse("<empty>"));
+
+        // Submit domain Inten operations with domain context
+        domainIntentService.sumbit(builder.build(domainOperationsContext));
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/installer/FlowObjectiveIntentInstaller.java b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/FlowObjectiveIntentInstaller.java
new file mode 100644
index 0000000..22ff970
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/FlowObjectiveIntentInstaller.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.intent.impl.installer;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+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;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.intent.FlowObjectiveIntent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentInstallCoordinator;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.impl.IntentManager;
+import org.onosproject.net.intent.impl.ObjectiveTrackerService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.onosproject.net.flowobjective.ObjectiveError.INSTALLATIONTHRESHOLDEXCEEDED;
+import static org.onosproject.net.intent.IntentInstaller.Direction.ADD;
+import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Installer for FlowObjectiveIntent.
+ */
+@Component(immediate = true)
+public class FlowObjectiveIntentInstaller implements IntentInstaller<FlowObjectiveIntent> {
+    private static final int OBJECTIVE_RETRY_THRESHOLD = 5;
+    private static final String UNSUPPORT_OBJ = "unsupported objective {}";
+    private final Logger log = getLogger(IntentManager.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentExtensionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ObjectiveTrackerService trackerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentInstallCoordinator intentInstallCoordinator;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Activate
+    public void activate() {
+        intentExtensionService.registerInstaller(FlowObjectiveIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivated() {
+        intentExtensionService.unregisterInstaller(FlowObjectiveIntent.class);
+    }
+
+    @Override
+    public void apply(IntentOperationContext<FlowObjectiveIntent> intentOperationContext) {
+        Objects.requireNonNull(intentOperationContext);
+        Optional<IntentData> toUninstall = intentOperationContext.toUninstall();
+        Optional<IntentData> toInstall = intentOperationContext.toInstall();
+
+        List<FlowObjectiveIntent> uninstallIntents = intentOperationContext.intentsToUninstall();
+        List<FlowObjectiveIntent> installIntents = intentOperationContext.intentsToInstall();
+
+        if (!toInstall.isPresent() && !toUninstall.isPresent()) {
+            intentInstallCoordinator.intentInstallSuccess(intentOperationContext);
+            return;
+        }
+
+        if (toUninstall.isPresent()) {
+            IntentData intentData = toUninstall.get();
+            trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources());
+            uninstallIntents.forEach(installable ->
+                                             trackerService.removeTrackedResources(intentData.intent().key(),
+                                                                                   installable.resources()));
+        }
+
+        if (toInstall.isPresent()) {
+            IntentData intentData = toInstall.get();
+            trackerService.addTrackedResources(intentData.key(), intentData.intent().resources());
+            installIntents.forEach(installable ->
+                                           trackerService.addTrackedResources(intentData.key(),
+                                                                              installable.resources()));
+        }
+
+        FlowObjectiveIntentInstallationContext intentInstallationContext =
+                new FlowObjectiveIntentInstallationContext(intentOperationContext);
+
+        uninstallIntents.stream()
+                .map(intent -> buildObjectiveContexts(intent, REMOVE))
+                .flatMap(Collection::stream)
+                .forEach(context -> {
+                    context.intentInstallationContext(intentInstallationContext);
+                    intentInstallationContext.addContext(context);
+                    intentInstallationContext.addPendingContext(context);
+                });
+
+        installIntents.stream()
+                .map(intent -> buildObjectiveContexts(intent, ADD))
+                .flatMap(Collection::stream)
+                .forEach(context -> {
+                    context.intentInstallationContext(intentInstallationContext);
+                    intentInstallationContext.addContext(context);
+                    intentInstallationContext.addNextPendingContext(context);
+                });
+
+        intentInstallationContext.apply();
+    }
+
+    /**
+     * Builds all objective contexts for a given flow objective Intent with given
+     * operation.
+     *
+     * @param intent the flow objective Intent
+     * @param direction the operation of this Intent
+     * @return all objective context of the Intent with given operation
+     */
+    private Set<FlowObjectiveInstallationContext> buildObjectiveContexts(FlowObjectiveIntent intent,
+                                                                         Direction direction) {
+        Objects.requireNonNull(intent);
+        Objects.requireNonNull(direction);
+        Set<FlowObjectiveInstallationContext> contexts = Sets.newHashSet();
+        int size = intent.objectives().size();
+        List<Objective> objectives = intent.objectives();
+        List<DeviceId> deviceIds = intent.devices();
+
+        if (direction == ADD) {
+            // Install objectives
+            // The flow objective system will handle the installation order
+            for (int i = 0; i < size; i++) {
+                Objective objective = objectives.get(i);
+                DeviceId deviceId = deviceIds.get(i);
+                FlowObjectiveInstallationContext ctx = buildObjectiveContext(objective, deviceId, direction);
+                contexts.add(ctx);
+            }
+            return contexts;
+        } else {
+            // Uninstall objecitves
+            // we need to care about ordering here
+            // basic idea is to chain objective contexts
+            for (int i = 0; i < size; i++) {
+                Objective objective = intent.objectives().get(i);
+                DeviceId deviceId = intent.devices().get(i);
+                if (objective instanceof FilteringObjective) {
+                    // don't need to care ordering of filtering objective
+                    FlowObjectiveInstallationContext ctx =
+                            buildObjectiveContext(objective, deviceId, direction);
+                    contexts.add(ctx);
+                } else if (objective instanceof NextObjective) {
+                    // need to removed after forwarding objective
+                    // nothing to do here
+                } else if (objective instanceof ForwardingObjective) {
+                    // forwarding objective, also find next objective if
+                    // exist
+                    FlowObjectiveInstallationContext fwdCtx =
+                            buildObjectiveContext(objective, deviceId, direction);
+                    ForwardingObjective fwd = (ForwardingObjective) objective;
+                    NextObjective nxt = null;
+                    Integer nextId = fwd.nextId();
+                    if (nextId != null) {
+                        for (int j = 0; j < size; j++) {
+                            if (objectives.get(j).id() == nextId) {
+                                nxt = (NextObjective) objectives.get(j);
+                                break;
+                            }
+                        }
+                        // if a next objective exists in the Intent
+                        if (nxt != null) {
+                            FlowObjectiveInstallationContext nxtCtx =
+                                    buildObjectiveContext(nxt, deviceId, direction);
+                            fwdCtx.nextContext(nxtCtx);
+                        }
+                    }
+                    contexts.add(fwdCtx);
+                } else {
+                    // possible here?
+                    log.warn(UNSUPPORT_OBJ, objective);
+                }
+            }
+        }
+        return contexts;
+    }
+
+    private FlowObjectiveInstallationContext buildObjectiveContext(Objective objective,
+                                                                   DeviceId deviceId,
+                                                                   Direction direction) {
+        Objects.requireNonNull(objective);
+        Objects.requireNonNull(deviceId);
+        Objects.requireNonNull(direction);
+        Objective.Builder builder = objective.copy();
+        FlowObjectiveInstallationContext ctx = new FlowObjectiveInstallationContext();
+        switch (direction) {
+            case ADD:
+                objective = builder.add(ctx);
+                break;
+            case REMOVE:
+                objective = builder.remove(ctx);
+                break;
+            default:
+                break;
+        }
+        ctx.setObjective(objective, deviceId);
+        return ctx;
+    }
+
+    /**
+     * Installation context for flow objective.
+     * Manages installation state of a flow objective.
+     */
+    class FlowObjectiveInstallationContext implements ObjectiveContext {
+        private Objective objective;
+        private DeviceId deviceId;
+        private ObjectiveError error;
+        private AtomicInteger retry;
+        private FlowObjectiveInstallationContext nextContext;
+        private FlowObjectiveIntentInstallationContext intentInstallationContext;
+
+        /**
+         * Set flow objective Intent installation context to this context.
+         *
+         * @param intentInstallationContext the Intent installation context
+         */
+        public void intentInstallationContext(FlowObjectiveIntentInstallationContext intentInstallationContext) {
+            Objects.requireNonNull(intentInstallationContext);
+            this.intentInstallationContext = intentInstallationContext;
+
+            // Set Intent installation context to the next context if exists.
+            if (nextContext != null) {
+                nextContext.intentInstallationContext(intentInstallationContext);
+            }
+        }
+
+        /**
+         * Sets next flow objective installation context.
+         *
+         * @param nextContext the next flow objective installation context
+         */
+        public void nextContext(FlowObjectiveInstallationContext nextContext) {
+            Objects.requireNonNull(nextContext);
+            this.nextContext = nextContext;
+        }
+
+        /**
+         * Sets objective and device id to this context; reset error states.
+         *
+         * @param objective the objective
+         * @param deviceId the device id
+         */
+        void setObjective(Objective objective, DeviceId deviceId) {
+            Objects.requireNonNull(objective);
+            Objects.requireNonNull(deviceId);
+            this.objective = objective;
+            this.deviceId = deviceId;
+            this.error = null;
+            this.retry = new AtomicInteger(0);
+        }
+
+        /**
+         * Gets the number of retries.
+         *
+         * @return the retry count
+         */
+        int retryTimes() {
+            return this.retry.get();
+        }
+
+        /**
+         * Increases the number of retries.
+         */
+        void increaseRetryValue() {
+            this.retry.incrementAndGet();
+        }
+
+        /**
+         * Completed this context.
+         *
+         * @param error the error of this context if exist; null otherwise
+         */
+        private void finished(ObjectiveError error) {
+            synchronized (intentInstallationContext) {
+                if (error != null) {
+                    this.error = error;
+                    intentInstallationContext.handleObjectiveError(this, error);
+                } else {
+                    // apply next context if exist
+                    if (nextContext != null) {
+                        intentInstallationContext.addPendingContext(nextContext);
+                        flowObjectiveService.apply(nextContext.deviceId,
+                                                   nextContext.objective);
+                        intentInstallationContext.removePendingContext(this);
+                    } else {
+                        intentInstallationContext.removePendingContext(this);
+                    }
+                }
+                if (!intentInstallationContext.pendingContexts().isEmpty()) {
+                    return;
+                }
+                // Apply second stage pending contexts if it is not empty
+                if (!intentInstallationContext.nextPendingContexts().isEmpty()) {
+                    intentInstallationContext.moveNextPendingToPending();
+                    final Set<ObjectiveContext> contextsToApply =
+                            Sets.newHashSet(intentInstallationContext.pendingContexts());
+                    contextsToApply.forEach(ctx -> {
+                        FlowObjectiveInstallationContext foiCtx = (FlowObjectiveInstallationContext) ctx;
+                        flowObjectiveService.apply(foiCtx.deviceId,
+                                                   foiCtx.objective);
+                    });
+                    return;
+                }
+                if (intentInstallationContext.errorContexts().isEmpty()) {
+                    intentInstallCoordinator.intentInstallSuccess(intentInstallationContext.intentOperationContext());
+                } else {
+                    intentInstallCoordinator.intentInstallFailed(intentInstallationContext.intentOperationContext());
+                }
+            }
+        }
+
+        @Override
+        public void onSuccess(Objective objective) {
+            finished(null);
+        }
+
+        @Override
+        public void onError(Objective objective, ObjectiveError error) {
+            finished(error);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s on %s for %s)", error, deviceId, objective);
+        }
+    }
+
+    /**
+     * Installation context for FlowObjective Intent.
+     * Manages states of pending and error flow objective contexts.
+     */
+    class FlowObjectiveIntentInstallationContext {
+        private final IntentOperationContext<FlowObjectiveIntent> intentOperationContext;
+        final List<ObjectiveContext> contexts = Lists.newArrayList();
+        final Set<ObjectiveContext> errorContexts = Sets.newConcurrentHashSet();
+        final Set<ObjectiveContext> pendingContexts = Sets.newConcurrentHashSet();
+
+        // Second stage of pending contexts
+        final Set<ObjectiveContext> nextPendingContexts = Sets.newConcurrentHashSet();
+
+        /**
+         * Creates a flow objective installation context.
+         *
+         * @param intentOperationContext the flow objective installation context
+         */
+        public FlowObjectiveIntentInstallationContext(
+                IntentOperationContext<FlowObjectiveIntent> intentOperationContext) {
+            Objects.requireNonNull(intentOperationContext);
+            this.intentOperationContext = intentOperationContext;
+        }
+
+        /**
+         * Gets Intent operation context of this context.
+         *
+         * @return the Intent operation context
+         */
+        public IntentOperationContext<FlowObjectiveIntent> intentOperationContext() {
+            return intentOperationContext;
+        }
+
+        /**
+         * Applies all contexts to flow objective service.
+         */
+        public void apply() {
+            if (pendingContexts.isEmpty()) {
+                moveNextPendingToPending();
+            }
+            final Set<ObjectiveContext> contextsToApply = pendingContexts();
+            contextsToApply.forEach(ctx -> {
+                FlowObjectiveInstallationContext foiCtx =
+                        (FlowObjectiveInstallationContext) ctx;
+                flowObjectiveService.apply(foiCtx.deviceId, foiCtx.objective);
+            });
+        }
+
+        /**
+         * Gets all error contexts.
+         *
+         * @return the error contexts
+         */
+        public Set<ObjectiveContext> errorContexts() {
+            return ImmutableSet.copyOf(errorContexts);
+        }
+
+        /**
+         * Gets all pending contexts.
+         *
+         * @return the pending contexts
+         */
+        public Set<ObjectiveContext> pendingContexts() {
+            return ImmutableSet.copyOf(pendingContexts);
+        }
+
+        /**
+         * Gets all pending contexts of next stage.
+         *
+         * @return the pending contexts for next stage
+         */
+        public Set<ObjectiveContext> nextPendingContexts() {
+            return ImmutableSet.copyOf(nextPendingContexts);
+        }
+
+        /**
+         * Adds a context.
+         *
+         * @param context the context
+         */
+        public void addContext(ObjectiveContext context) {
+            Objects.requireNonNull(context);
+            contexts.add(context);
+        }
+
+        /**
+         * Adds a context to pending context of next stage.
+         *
+         * @param context the context
+         */
+        public void addNextPendingContext(ObjectiveContext context) {
+            Objects.requireNonNull(context);
+            nextPendingContexts.add(context);
+        }
+
+        /**
+         * Adds a context to pending context.
+         *
+         * @param context the context
+         */
+        public void addPendingContext(ObjectiveContext context) {
+            Objects.requireNonNull(context);
+            pendingContexts.add(context);
+        }
+
+        /**
+         * Removes the pending context.
+         *
+         * @param context the context
+         */
+        public void removePendingContext(ObjectiveContext context) {
+            Objects.requireNonNull(context);
+            pendingContexts.remove(context);
+        }
+
+        /**
+         * Moves pending context from next stage to current stage.
+         */
+        public void moveNextPendingToPending() {
+            pendingContexts.addAll(nextPendingContexts);
+            nextPendingContexts.clear();
+        }
+
+        /**
+         * Handles error of objective context.
+         *
+         * @param ctx the objective context
+         * @param error the error
+         */
+        public void handleObjectiveError(FlowObjectiveInstallationContext ctx,
+                                         ObjectiveError error) {
+            Objects.requireNonNull(ctx);
+            Objects.requireNonNull(error);
+            log.debug("Got error(s) when install objective: {}, error: {}, retry: {}",
+                      ctx.objective, ctx.error, ctx.retry);
+            if (ctx.retryTimes() > OBJECTIVE_RETRY_THRESHOLD) {
+                ctx.error = INSTALLATIONTHRESHOLDEXCEEDED;
+                pendingContexts.remove(ctx);
+                errorContexts.add(ctx);
+                return;
+            }
+            // reset error
+            ctx.error = null;
+            // strategies for errors
+            switch (error) {
+                case GROUPEXISTS:
+                    if (ctx.objective.op() == Objective.Operation.ADD &&
+                            ctx.objective instanceof NextObjective) {
+                        // Next group exists
+                        // build new objective with new op ADD_TO_EXIST
+                        NextObjective newObj =
+                                ((NextObjective.Builder) ctx.objective.copy()).addToExisting(ctx);
+                        ctx.setObjective(newObj, ctx.deviceId);
+                        ctx.increaseRetryValue();
+                        flowObjectiveService.apply(ctx.deviceId, ctx.objective);
+                    } else {
+                        pendingContexts.remove(ctx);
+                        errorContexts.add(ctx);
+                    }
+                    break;
+                case GROUPINSTALLATIONFAILED:
+                    // Group install failed, retry again
+                    ctx.increaseRetryValue();
+                    flowObjectiveService.apply(ctx.deviceId, ctx.objective);
+                    break;
+                case GROUPMISSING:
+                    if (ctx.objective.op() == Objective.Operation.ADD_TO_EXISTING) {
+                        // Next group not exist, but we want to add new buckets
+                        // build new objective with new op ADD
+                        NextObjective newObj = (NextObjective) ctx.objective.copy().add(ctx);
+                        ctx.setObjective(newObj, ctx.deviceId);
+                        ctx.increaseRetryValue();
+                        flowObjectiveService.apply(ctx.deviceId, ctx.objective);
+                    } else if (ctx.objective.op() == Objective.Operation.REMOVE ||
+                            ctx.objective.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
+                        // Already removed, no need to do anything
+                        ctx.error = null;
+                        pendingContexts.remove(ctx);
+                        return;
+                    } else {
+                        // Next chaining group missing, try again.
+                        ctx.increaseRetryValue();
+                        flowObjectiveService.apply(ctx.deviceId, ctx.objective);
+                    }
+                    break;
+                case FLOWINSTALLATIONFAILED:
+                case GROUPREMOVALFAILED:
+                case INSTALLATIONTIMEOUT:
+                    // Retry
+                    ctx.increaseRetryValue();
+                    flowObjectiveService.apply(ctx.deviceId, ctx.objective);
+                    break;
+                default:
+                    pendingContexts.remove(ctx);
+                    errorContexts.add(ctx);
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/installer/FlowRuleIntentInstaller.java b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/FlowRuleIntentInstaller.java
new file mode 100644
index 0000000..f29ceec
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/FlowRuleIntentInstaller.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.intent.impl.installer;
+
+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.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.IntentInstallCoordinator;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.impl.IntentManager;
+import org.onosproject.net.intent.impl.ObjectiveTrackerService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import static org.onosproject.net.intent.IntentInstaller.Direction.ADD;
+import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE;
+import static org.onosproject.net.intent.IntentState.INSTALLED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Installer for FlowRuleIntent.
+ */
+@Component(immediate = true)
+public class FlowRuleIntentInstaller implements IntentInstaller<FlowRuleIntent> {
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentExtensionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ObjectiveTrackerService trackerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentInstallCoordinator intentInstallCoordinator;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Activate
+    public void activate() {
+        intentExtensionService.registerInstaller(FlowRuleIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivated() {
+        intentExtensionService.unregisterInstaller(FlowRuleIntent.class);
+    }
+
+    protected final Logger log = getLogger(IntentManager.class);
+
+    @Override
+    public void apply(IntentOperationContext<FlowRuleIntent> context) {
+        Optional<IntentData> toUninstall = context.toUninstall();
+        Optional<IntentData> toInstall = context.toInstall();
+
+        List<FlowRuleIntent> uninstallIntents = context.intentsToUninstall();
+        List<FlowRuleIntent> installIntents = context.intentsToInstall();
+
+        if (!toInstall.isPresent() && !toUninstall.isPresent()) {
+            intentInstallCoordinator.intentInstallSuccess(context);
+            return;
+        } else if (!toInstall.isPresent()) {
+            // Uninstall only
+            trackIntentResources(toUninstall.get(), uninstallIntents, REMOVE);
+        } else if (!toUninstall.isPresent()) {
+            // Install only
+            trackIntentResources(toInstall.get(), installIntents, ADD);
+        } else {
+            IntentData uninstall = toUninstall.get();
+            IntentData install = toInstall.get();
+
+            // Filter out same intents and intents with same flow rules
+            Iterator<FlowRuleIntent> iterator = installIntents.iterator();
+            while (iterator.hasNext()) {
+                FlowRuleIntent installIntent = iterator.next();
+                uninstallIntents.stream().filter(uIntent -> {
+                    if (uIntent.equals(installIntent)) {
+                        return true;
+                    } else {
+                        return !flowRuleIntentChanged(uIntent, installIntent);
+                    }
+                }).findFirst().ifPresent(common -> {
+                    uninstallIntents.remove(common);
+                    if (INSTALLED.equals(uninstall.state())) {
+                        // only remove the install intent if the existing
+                        // intent (i.e. the uninstall one) is already
+                        // installed or installing
+                        iterator.remove();
+                    }
+                });
+            }
+            trackIntentResources(uninstall, uninstallIntents, REMOVE);
+            trackIntentResources(install, installIntents, ADD);
+        }
+
+        FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+        builder.newStage();
+
+        toUninstall.ifPresent(intentData -> {
+            uninstallIntents.stream().map(FlowRuleIntent::flowRules)
+                    .flatMap(Collection::stream).forEach(builder::remove);
+        });
+
+        toInstall.ifPresent(intentData -> {
+            installIntents.stream().map(FlowRuleIntent::flowRules)
+                    .flatMap(Collection::stream).forEach(builder::add);
+        });
+
+        FlowRuleOperationsContext flowRuleOperationsContext = new FlowRuleOperationsContext() {
+            @Override
+            public void onSuccess(FlowRuleOperations ops) {
+                intentInstallCoordinator.intentInstallSuccess(context);
+            }
+
+            @Override
+            public void onError(FlowRuleOperations ops) {
+                intentInstallCoordinator.intentInstallFailed(context);
+            }
+        };
+
+        FlowRuleOperations operations = builder.build(flowRuleOperationsContext);
+
+
+        log.debug("applying intent {} -> {} with {} rules: {}",
+                  toUninstall.map(x -> x.key().toString()).orElse("<empty>"),
+                  toInstall.map(x -> x.key().toString()).orElse("<empty>"),
+                  operations.stages().stream().mapToLong(Set::size).sum(),
+                  operations.stages());
+        flowRuleService.apply(operations);
+    }
+
+    /**
+     * Track or un-track network resource of a Intent and it's installable
+     * Intents.
+     *
+     * @param intentData the Intent data
+     * @param intentsToApply the list of flow rule Intents from the Intent
+     * @param direction the direction to determine track or un-track
+     */
+    private void trackIntentResources(IntentData intentData, List<FlowRuleIntent> intentsToApply, Direction direction) {
+        switch (direction) {
+            case ADD:
+                trackerService.addTrackedResources(intentData.key(), intentData.intent().resources());
+                intentsToApply.forEach(installable ->
+                                               trackerService.addTrackedResources(intentData.key(),
+                                                                                  installable.resources()));
+                break;
+            default:
+                trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources());
+                intentsToApply.forEach(installable ->
+                                               trackerService.removeTrackedResources(intentData.intent().key(),
+                                                                                     installable.resources()));
+                break;
+        }
+    }
+
+    /**
+     * Determines whether there is any flow rule changed
+     * (i.e., different set of flow rules or different treatments)
+     * between FlowRuleIntents to be uninstalled and to be installed.
+     *
+     * @param uninstallIntent FlowRuleIntent to uninstall
+     * @param installIntent   FlowRuleIntent to install
+     * @return true if flow rules which to be uninstalled contains all flow
+     *         rules which to be installed; false otherwise
+     */
+    private boolean flowRuleIntentChanged(FlowRuleIntent uninstallIntent,
+                                          FlowRuleIntent installIntent) {
+        Collection<FlowRule> flowRulesToUninstall = uninstallIntent.flowRules();
+        Collection<FlowRule> flowRulesToInstall = installIntent.flowRules();
+
+        // Check if any flow rule changed
+        for (FlowRule flowRuleToInstall : flowRulesToInstall) {
+            if (flowRulesToUninstall.stream().noneMatch(flowRuleToInstall::exactMatch)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/installer/ProtectionEndpointIntentInstaller.java b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/ProtectionEndpointIntentInstaller.java
new file mode 100644
index 0000000..58c24e6
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/ProtectionEndpointIntentInstaller.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.intent.impl.installer;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.tuple.Pair;
+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.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointDescription;
+import org.onosproject.net.behaviour.protection.ProtectionConfig;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentException;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentInstallCoordinator;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.ProtectionEndpointIntent;
+import org.onosproject.net.intent.impl.IntentManager;
+import org.onosproject.net.intent.impl.ObjectiveTrackerService;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.config.NetworkConfigEvent.Type.*;
+import static org.onosproject.net.intent.IntentInstaller.Direction.ADD;
+import static org.onosproject.net.intent.IntentInstaller.Direction.REMOVE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Installer for ProtectionEndpointIntent.
+ */
+@Component(immediate = true)
+public class ProtectionEndpointIntentInstaller implements IntentInstaller<ProtectionEndpointIntent> {
+    private static final String CONFIG_FAILED = "Config operation unsuccessful, expected %s, actual %s.";
+    private final Logger log = getLogger(IntentManager.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentExtensionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    NetworkConfigService networkConfigService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    IntentInstallCoordinator intentInstallCoordinator;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    ObjectiveTrackerService trackerService;
+
+    @Activate
+    public void activate() {
+        intentExtensionService.registerInstaller(ProtectionEndpointIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivated() {
+        intentExtensionService.unregisterInstaller(ProtectionEndpointIntent.class);
+    }
+
+    @Override
+    public void apply(IntentOperationContext<ProtectionEndpointIntent> context) {
+        Optional<IntentData> toUninstall = context.toUninstall();
+        Optional<IntentData> toInstall = context.toInstall();
+
+        List<ProtectionEndpointIntent> uninstallIntents = context.intentsToUninstall();
+        List<ProtectionEndpointIntent> installIntents = context.intentsToInstall();
+
+        if (!toInstall.isPresent() && !toUninstall.isPresent()) {
+            intentInstallCoordinator.intentInstallSuccess(context);
+            return;
+        }
+
+        if (toUninstall.isPresent()) {
+            IntentData intentData = toUninstall.get();
+            trackerService.removeTrackedResources(intentData.key(), intentData.intent().resources());
+            uninstallIntents.forEach(installable ->
+                                             trackerService.removeTrackedResources(intentData.intent().key(),
+                                                                                   installable.resources()));
+        }
+
+        if (toInstall.isPresent()) {
+            IntentData intentData = toInstall.get();
+            trackerService.addTrackedResources(intentData.key(), intentData.intent().resources());
+            installIntents.forEach(installable ->
+                                           trackerService.addTrackedResources(intentData.key(),
+                                                                              installable.resources()));
+        }
+
+        List<Stage> stages = new ArrayList<>();
+
+        stages.add(new Stage(uninstallIntents.stream()
+                                     .map(i -> Pair.of(i, REMOVE))
+                                     .collect(Collectors.toList())));
+
+        stages.add(new Stage(installIntents.stream()
+                                     .map(i -> Pair.of(i, ADD))
+                                     .collect(Collectors.toList())));
+        for (Stage stage : stages) {
+            log.debug("applying Stage {}", stage);
+            try {
+                // wait for stage completion
+                stage.apply();
+                stage.listeners().forEach(networkConfigService::removeListener);
+            } catch (IntentException e) {
+                log.error("Stage {} failed, reason: {}", stage, e.toString());
+                intentInstallCoordinator.intentInstallFailed(context);
+                return;
+            }
+        }
+        // All stage success
+        intentInstallCoordinator.intentInstallSuccess(context);
+    }
+
+    /**
+     * Stage of installable Intents which can be processed in parallel.
+     */
+    private final class Stage {
+        // should it have progress state, how far it went?
+        private final Collection<Pair<ProtectionEndpointIntent, Direction>> ops;
+        private final Set<NetworkConfigListener> listeners = Sets.newHashSet();
+
+        /**
+         * Create a stage with given operations.
+         *
+         * @param ops the operations
+         */
+        Stage(Collection<Pair<ProtectionEndpointIntent, Direction>> ops) {
+            this.ops = checkNotNull(ops);
+        }
+
+        /**
+         * Applies all operations for this stage.
+         *
+         * @return the CompletableFuture object for this operation
+         */
+        void apply() {
+            ops.stream()
+                    .map(op -> applyOp(op.getRight(), op.getLeft()))
+                    .forEach(future -> {
+                        try {
+                            future.get(100, TimeUnit.MILLISECONDS);
+                        } catch (TimeoutException | InterruptedException | ExecutionException e) {
+                            throw new IntentException(e.toString());
+                        }
+                    });
+        }
+
+        /**
+         * Applies the protection endpoint Intent with a given direction.
+         *
+         * @param dir the direction
+         * @param intent the protection endpoint Intent
+         * @return the CompletableFuture object for this operation
+         */
+        private CompletableFuture<Void> applyOp(Direction dir, ProtectionEndpointIntent intent) {
+            log.trace("applying {}: {}", dir, intent);
+            if (dir == REMOVE) {
+                ProtectionConfigListener listener =
+                        new ProtectionConfigListener(ImmutableSet.of(CONFIG_REMOVED),
+                                                     intent.deviceId());
+                networkConfigService.addListener(listener);
+                listeners.add(listener);
+                networkConfigService.removeConfig(intent.deviceId(), ProtectionConfig.class);
+                return listener.completableFuture();
+            } else {
+                ProtectedTransportEndpointDescription description = intent.description();
+
+                // Can't do following. Will trigger empty CONFIG_ADDED
+                //ProtectionConfig cfg = networkConfigService.addConfig(intent.deviceId(),
+                //                                                      ProtectionConfig.class);
+                ProtectionConfig cfg = new ProtectionConfig(intent.deviceId());
+                cfg.fingerprint(description.fingerprint());
+                cfg.peer(description.peer());
+                cfg.paths(description.paths());
+                ProtectionConfigListener listener =
+                        new ProtectionConfigListener(ImmutableSet.of(CONFIG_ADDED, CONFIG_UPDATED),
+                                                     intent.deviceId());
+
+                networkConfigService.addListener(listener);
+                listeners.add(listener);
+                networkConfigService.applyConfig(intent.deviceId(),
+                                                 ProtectionConfig.class,
+                                                 cfg.node());
+                return listener.completableFuture();
+            }
+        }
+
+        @Override
+        public String toString() {
+            return ops.toString();
+        }
+
+        public Set<NetworkConfigListener> listeners() {
+            return listeners;
+        }
+
+        /**
+         * Listener for protection config for specific config event and device.
+         */
+        class ProtectionConfigListener implements NetworkConfigListener {
+            private CompletableFuture<Void> completableFuture;
+            private Set<NetworkConfigEvent.Type> listenTypes;
+            private DeviceId listenDevice;
+
+            public ProtectionConfigListener(Set<NetworkConfigEvent.Type> listenTypes, DeviceId listenDevice) {
+                completableFuture = new CompletableFuture<>();
+                this.listenTypes = listenTypes;
+                this.listenDevice = listenDevice;
+            }
+
+            @Override
+            public void event(NetworkConfigEvent event) {
+                if (!event.subject().equals(listenDevice)) {
+                    return;
+                }
+                if (!listenTypes.contains(event.type())) {
+                    String errorMsg = String.format(CONFIG_FAILED, listenTypes.toString(), event.type());
+                    completableFuture.completeExceptionally(new IntentException(errorMsg));
+                } else {
+                    completableFuture.complete(null);
+                }
+            }
+
+            public CompletableFuture<Void> completableFuture() {
+                return completableFuture;
+            }
+        }
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/installer/package-info.java b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/package-info.java
new file mode 100644
index 0000000..b971165
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/installer/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Installers for different installable Intents.
+ */
+package org.onosproject.net.intent.impl.installer;
\ No newline at end of file