Refactored intent framework to deal with batches.

There is still work to be done, but for now, submit, withdraw and reroute are working.

Change-Id: Ib94cf8c4be03786cc070f402d1f296f5dfa6588b
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java
index 202326b..2cb402a 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java
@@ -29,6 +29,7 @@
 import org.onlab.onos.net.intent.IntentEvent;
 import org.onlab.onos.net.intent.IntentEvent.Type;
 import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentOperations;
 import org.onlab.onos.net.intent.IntentService;
 import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.packet.Ethernet;
@@ -63,9 +64,6 @@
               required = true, multiValued = false)
     String countString = null;
 
-
-    private static long id = 0x7870001;
-
     private IntentService service;
     private CountDownLatch latch;
     private long start, end;
@@ -91,15 +89,18 @@
         service.addListener(this);
         latch = new CountDownLatch(count);
 
-        start = System.currentTimeMillis();
-        for (int i = 0; i < count; i++) {
+        IntentOperations.Builder ops = IntentOperations.builder();
+        for (int i = 1; i <= count; i++) {
             TrafficSelector s = selector
                     .matchEthSrc(MacAddress.valueOf(i))
                     .build();
             Intent intent = new PointToPointIntent(appId(), s, treatment,
                                                    ingress, egress);
-            service.submit(intent);
+            ops.addSubmitOperation(intent);
         }
+        IntentOperations operations = ops.build();
+        start = System.currentTimeMillis();
+        service.execute(operations);
         try {
             if (latch.await(10, TimeUnit.SECONDS)) {
                 printResults(count);
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java
index 16ce184..6fd16b9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java
@@ -15,13 +15,13 @@
  */
 package org.onlab.onos.net.flow;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
  * A list of BatchOperationEntry.
  *
@@ -88,6 +88,16 @@
         return ops.add(entry) ? this : null;
     }
 
+    /**
+     * Add all operations from another batch to this batch.
+     *
+     * @param another another batch
+     * @return true if success
+     */
+    public boolean addAll(BatchOperation<T> another) {
+        return ops.addAll(another.getOperations());
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
index 3758ea9..b988744 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
@@ -19,12 +19,20 @@
 
 import com.google.common.collect.ImmutableSet;
 
+/**
+ * Representation of a completed flow rule batch operation.
+ */
 public class CompletedBatchOperation implements BatchOperationResult<FlowRule> {
 
-
     private final boolean success;
     private final Set<FlowRule> failures;
 
+    /**
+     * Creates a new batch completion result.
+     *
+     * @param success  indicates whether the completion is successful.
+     * @param failures set of any failures encountered
+     */
     public CompletedBatchOperation(boolean success, Set<? extends FlowRule> failures) {
         this.success = success;
         this.failures = ImmutableSet.copyOf(failures);
@@ -40,5 +48,4 @@
         return failures;
     }
 
-
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchDelegate.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchDelegate.java
new file mode 100644
index 0000000..248d82e
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchDelegate.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onlab.onos.net.intent;
+
+/**
+ * Facade for receiving notifications from the intent batch service.
+ */
+public interface IntentBatchDelegate {
+
+    /**
+     * Submits the specified batch of intent operations for processing.
+     *
+     * @param operations batch of operations
+     */
+    void execute(IntentOperations operations);
+
+    /**
+     * Cancesl the specified batch of intent operations.
+     *
+     * @param operations batch of operations to be cancelled
+     */
+    void cancel(IntentOperations operations);
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchService.java
new file mode 100644
index 0000000..37a1d4a
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onlab.onos.net.intent;
+
+import java.util.Set;
+
+/**
+ * Service for tracking and delegating batches of intent operations.
+ */
+public interface IntentBatchService {
+
+    /**
+     * Submits a batch of intent operations.
+     *
+     * @param operations batch of operations
+     */
+    void addIntentOperations(IntentOperations operations);
+
+    /**
+     * Removes the specified batch of intent operations after completion.
+     *
+     * @param operations batch of operations
+     */
+    void removeIntentOperations(IntentOperations operations);
+
+    /**
+     * Returns the set of intent batches currently being tracked.
+     * @return set of batches
+     */
+    Set<IntentOperations> getIntentOperations();
+
+    /**
+     * Sets the batch service delegate.
+     *
+     * @param delegate delegate to apply
+     */
+    void setDelegate(IntentBatchDelegate delegate);
+
+    /**
+     * Unsets the batch service delegate.
+     *
+     * @param delegate delegate to unset
+     */
+    void unsetDelegate(IntentBatchDelegate delegate);
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
index f6dcd17..2d6d94b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
@@ -15,7 +15,10 @@
  */
 package org.onlab.onos.net.intent;
 
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+
 import java.util.List;
+import java.util.Set;
 
 /**
  * Abstraction of a compiler which is capable of taking an intent
@@ -27,9 +30,13 @@
     /**
      * Compiles the specified intent into other intents.
      *
-     * @param intent intent to be compiled
+     * @param intent      intent to be compiled
+     * @param installable previously compilation result; optional
+     * @param resources   previously allocated resources; optional
      * @return list of resulting intents
      * @throws IntentException if issues are encountered while compiling the intent
      */
-    List<Intent> compile(T intent);
+    List<Intent> compile(T intent, List<Intent> installable,
+                         Set<LinkResourceAllocations> resources);
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
index 93d8a40..5ef717f 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
@@ -15,10 +15,10 @@
  */
 package org.onlab.onos.net.intent;
 
-import java.util.List;
-
 import org.onlab.onos.net.flow.FlowRuleBatchOperation;
 
+import java.util.List;
+
 /**
  * Abstraction of entity capable of installing intents to the environment.
  */
@@ -26,8 +26,8 @@
     /**
      * Installs the specified intent to the environment.
      *
-     * @param intent intent to be installed
-     * @return FlowRule operations to install
+     * @param intent    intent to be installed
+     * @return flow rule operations to complete install
      * @throws IntentException if issues are encountered while installing the intent
      */
     List<FlowRuleBatchOperation> install(T intent);
@@ -35,9 +35,20 @@
     /**
      * Uninstalls the specified intent from the environment.
      *
-     * @param intent intent to be uninstalled
-     * @return FlowRule operations to uninstall
+     * @param intent    intent to be uninstalled
+     * @return flow rule operations to complete uninstall
      * @throws IntentException if issues are encountered while uninstalling the intent
      */
     List<FlowRuleBatchOperation> uninstall(T intent);
+
+    /**
+     * Replaces the specified intent with a new one in the environment.
+     *
+     * @param oldIntent intent to be removed
+     * @param newIntent intent to be installed
+     * @return flow rule operations to complete the replace
+     * @throws IntentException if issues are encountered while uninstalling the intent
+     */
+    List<FlowRuleBatchOperation> replace(T oldIntent, T newIntent);
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java
index 902f418..28508b6 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java
@@ -15,6 +15,11 @@
  */
 package org.onlab.onos.net.intent;
 
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Abstraction of an intent-related operation, e.g. add, remove, replace.
  */
@@ -27,7 +32,7 @@
     /**
      * Operation type.
      */
-    enum Type {
+    public enum Type {
         /**
          * Indicates that an intent should be added.
          */
@@ -41,15 +46,20 @@
         /**
          * Indicates that an intent should be replaced with another.
          */
-        REPLACE
+        REPLACE,
+
+        /**
+         * Indicates that an intent should be updated (i.e. recompiled/reinstalled).
+         */
+        UPDATE,
     }
 
     /**
      * Creates an intent operation.
      *
-     * @param type operation type
+     * @param type     operation type
      * @param intentId identifier of the intent subject to the operation
-     * @param intent intent subject
+     * @param intent   intent subject
      */
     IntentOperation(Type type, IntentId intentId, Intent intent) {
         this.type = type;
@@ -85,4 +95,32 @@
         return intent;
     }
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, intentId, intent);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final IntentOperation other = (IntentOperation) obj;
+        return Objects.equals(this.type, other.type) &&
+                Objects.equals(this.intentId, other.intentId) &&
+                Objects.equals(this.intent, other.intent);
+    }
+
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("type", type)
+                .add("intentId", intentId)
+                .add("intent", intent)
+                .toString();
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
index 9e31ca3..eca51f0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
@@ -18,11 +18,11 @@
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
+import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onlab.onos.net.intent.IntentOperation.Type.REPLACE;
-import static org.onlab.onos.net.intent.IntentOperation.Type.SUBMIT;
-import static org.onlab.onos.net.intent.IntentOperation.Type.WITHDRAW;
+import static org.onlab.onos.net.intent.IntentOperation.Type.*;
 
 /**
  * Batch of intent submit/withdraw/replace operations.
@@ -58,6 +58,31 @@
         return new Builder();
     }
 
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(operations);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final IntentOperations other = (IntentOperations) obj;
+        return Objects.equals(this.operations, other.operations);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("operations", operations)
+                .toString();
+    }
+
     /**
      * Builder for batches of intent operations.
      */
@@ -108,6 +133,18 @@
         }
 
         /**
+         * Adds an intent update operation.
+         *
+         * @param intentId identifier of the intent to be updated
+         * @return self
+         */
+        public Builder addUpdateOperation(IntentId intentId) {
+            checkNotNull(intentId, "Intent ID cannot be null");
+            builder.add(new IntentOperation(UPDATE, intentId, null));
+            return this;
+        }
+
+        /**
          * Builds a batch of intent operations.
          *
          * @return immutable batch of intent operations
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
index 090af63..9abb584 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
@@ -17,7 +17,6 @@
 
 
 import java.util.List;
-import java.util.concurrent.Future;
 
 /**
  * Service for application submitting or withdrawing their intents.
@@ -59,9 +58,8 @@
      * affected at later time.
      * </p>
      * @param operations batch of intent operations
-     * @return Future to get execution result
      */
-    Future<IntentOperations> execute(IntentOperations operations);
+    void execute(IntentOperations operations);
 
     /**
      * Returns an iterable of intents currently in the system.
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
index 9f4755a..d6732c7 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
@@ -24,7 +24,6 @@
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 
 /**
  * Fake implementation of the intent service to assist in developing tests of
@@ -104,7 +103,7 @@
         try {
             // For the fake, we compile using a single level pass
             List<Intent> installable = new ArrayList<>();
-            for (Intent compiled : getCompiler(intent).compile(intent)) {
+            for (Intent compiled : getCompiler(intent).compile(intent, null, null)) {
                 installable.add((Intent) compiled);
             }
             executeInstallingPhase(intent, installable);
@@ -192,9 +191,8 @@
     }
 
     @Override
-    public Future<IntentOperations> execute(IntentOperations operations) {
+    public void execute(IntentOperations operations) {
         // TODO: implement later
-        return null;
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
index 86ebc59..a7004a2 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
@@ -29,11 +29,13 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
 
 /**
  * Suite of tests for the intent service contract.
@@ -294,7 +296,8 @@
         }
 
         @Override
-        public List<Intent> compile(TestIntent intent) {
+        public List<Intent> compile(TestIntent intent, List<Intent> installable,
+                                    Set<LinkResourceAllocations> resources) {
             if (fail) {
                 throw new IntentException("compile failed by design");
             }
@@ -326,6 +329,12 @@
             }
             return null;
         }
+
+        @Override
+        public List<FlowRuleBatchOperation> replace(TestInstallableIntent intent,
+                                                    TestInstallableIntent newIntent) {
+            return null;
+        }
     }
 
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index c1ba8af..d356d9a 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -509,13 +509,8 @@
             boolean success = true;
             Set<FlowRule> failed = Sets.newHashSet();
             CompletedBatchOperation completed;
-            long start = System.nanoTime();
-            long end = start + unit.toNanos(timeout);
-
             for (Future<CompletedBatchOperation> future : futures) {
-                long now = System.nanoTime();
-                long thisTimeout = end - now;
-                completed = future.get(thisTimeout, TimeUnit.NANOSECONDS);
+                completed = future.get(timeout, unit);
                 success = validateBatchOperation(failed, completed);
             }
             return finalizeBatchOperation(success, failed);
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
index 4d989ac..fdab18e 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
@@ -27,9 +27,11 @@
 import org.onlab.onos.net.intent.HostToHostIntent;
 import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 import static org.onlab.onos.net.flow.DefaultTrafficSelector.builder;
 
@@ -54,7 +56,8 @@
     }
 
     @Override
-    public List<Intent> compile(HostToHostIntent intent) {
+    public List<Intent> compile(HostToHostIntent intent, List<Intent> installable,
+                                Set<LinkResourceAllocations> resources) {
         Path pathOne = getPath(intent, intent.one(), intent.two());
         Path pathTwo = getPath(intent, intent.two(), intent.one());
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
index af2af24..e64fe82 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
@@ -15,31 +15,10 @@
  */
 package org.onlab.onos.net.intent.impl;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
-import static org.onlab.onos.net.intent.IntentState.COMPILING;
-import static org.onlab.onos.net.intent.IntentState.FAILED;
-import static org.onlab.onos.net.intent.IntentState.INSTALLED;
-import static org.onlab.onos.net.intent.IntentState.INSTALLING;
-import static org.onlab.onos.net.intent.IntentState.RECOMPILING;
-import static org.onlab.onos.net.intent.IntentState.WITHDRAWING;
-import static org.onlab.onos.net.intent.IntentState.WITHDRAWN;
-import static org.onlab.util.Tools.namedThreads;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -52,6 +31,8 @@
 import org.onlab.onos.net.flow.FlowRuleBatchOperation;
 import org.onlab.onos.net.flow.FlowRuleService;
 import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentBatchDelegate;
+import org.onlab.onos.net.intent.IntentBatchService;
 import org.onlab.onos.net.intent.IntentCompiler;
 import org.onlab.onos.net.intent.IntentEvent;
 import org.onlab.onos.net.intent.IntentException;
@@ -59,6 +40,7 @@
 import org.onlab.onos.net.intent.IntentId;
 import org.onlab.onos.net.intent.IntentInstaller;
 import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentOperation;
 import org.onlab.onos.net.intent.IntentOperations;
 import org.onlab.onos.net.intent.IntentService;
 import org.onlab.onos.net.intent.IntentState;
@@ -66,9 +48,24 @@
 import org.onlab.onos.net.intent.IntentStoreDelegate;
 import org.slf4j.Logger;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.onos.net.intent.IntentState.*;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * An implementation of Intent Manager.
@@ -96,11 +93,15 @@
 
     private final IntentStoreDelegate delegate = new InternalStoreDelegate();
     private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
+    private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentStore store;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentBatchService batchService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ObjectiveTrackerService trackerService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -113,6 +114,7 @@
     public void activate() {
         store.setDelegate(delegate);
         trackerService.setDelegate(topoDelegate);
+        batchService.setDelegate(batchDelegate);
         eventDispatcher.addSink(IntentEvent.class, listenerRegistry);
         executor = newSingleThreadExecutor(namedThreads("onos-intents"));
         monitorExecutor = newSingleThreadExecutor(namedThreads("onos-intent-monitor"));
@@ -123,6 +125,7 @@
     public void deactivate() {
         store.unsetDelegate(delegate);
         trackerService.unsetDelegate(topoDelegate);
+        batchService.unsetDelegate(batchDelegate);
         eventDispatcher.removeSink(IntentEvent.class);
         executor.shutdown();
         monitorExecutor.shutdown();
@@ -132,30 +135,27 @@
     @Override
     public void submit(Intent intent) {
         checkNotNull(intent, INTENT_NULL);
-        registerSubclassCompilerIfNeeded(intent);
-        IntentEvent event = store.createIntent(intent);
-        if (event != null) {
-            eventDispatcher.post(event);
-            executor.execute(new IntentTask(COMPILING, intent));
-        }
+        execute(IntentOperations.builder().addSubmitOperation(intent).build());
     }
 
     @Override
     public void withdraw(Intent intent) {
         checkNotNull(intent, INTENT_NULL);
-        executor.execute(new IntentTask(WITHDRAWING, intent));
+        execute(IntentOperations.builder().addWithdrawOperation(intent.id()).build());
     }
 
-    // FIXME: implement this method
     @Override
     public void replace(IntentId oldIntentId, Intent newIntent) {
-        throw new UnsupportedOperationException("execute() is not implemented yet");
+        checkNotNull(oldIntentId, INTENT_ID_NULL);
+        checkNotNull(newIntent, INTENT_NULL);
+        execute(IntentOperations.builder()
+                        .addReplaceOperation(oldIntentId, newIntent)
+                        .build());
     }
 
-    // FIXME: implement this method
     @Override
-    public Future<IntentOperations> execute(IntentOperations operations) {
-        throw new UnsupportedOperationException("execute() is not implemented yet");
+    public void execute(IntentOperations operations) {
+        batchService.addIntentOperations(operations);
     }
 
     @Override
@@ -261,29 +261,25 @@
     /**
      * Compiles the specified intent.
      *
-     * @param intent intent to be compiled
+     * @param update intent update
      */
-    private void executeCompilingPhase(Intent intent) {
+    private void executeCompilingPhase(IntentUpdate update) {
+        Intent intent = update.newIntent();
         // Indicate that the intent is entering the compiling phase.
-        store.setState(intent, COMPILING);
+        update.setState(intent, COMPILING);
 
         try {
             // Compile the intent into installable derivatives.
-            List<Intent> installable = compileIntent(intent);
+            List<Intent> installables = compileIntent(intent, update);
 
             // If all went well, associate the resulting list of installable
             // intents with the top-level intent and proceed to install.
-            store.setInstallableIntents(intent.id(), installable);
-            executeInstallingPhase(intent);
-
-        } catch (Exception e) {
+            update.setInstallables(installables);
+        } catch (IntentException e) {
             log.warn("Unable to compile intent {} due to:", intent.id(), e);
 
             // If compilation failed, mark the intent as failed.
-            IntentEvent event = store.setState(intent, FAILED);
-            if (event != null) {
-                eventDispatcher.post(event);
-            }
+            update.setState(intent, FAILED);
         }
     }
 
@@ -293,17 +289,18 @@
      * @param intent intent
      * @return result of compilation
      */
-    private List<Intent> compileIntent(Intent intent) {
+    private List<Intent> compileIntent(Intent intent, IntentUpdate update) {
         if (intent.isInstallable()) {
             return ImmutableList.of(intent);
         }
 
+        registerSubclassCompilerIfNeeded(intent);
+        List<Intent> previous = update.oldInstallables();
+        // FIXME: get previous resources
         List<Intent> installable = new ArrayList<>();
-        // TODO do we need to registerSubclassCompiler?
-        for (Intent compiled : getCompiler(intent).compile(intent)) {
-            installable.addAll(compileIntent(compiled));
+        for (Intent compiled : getCompiler(intent).compile(intent, previous, null)) {
+            installable.addAll(compileIntent(compiled, update));
         }
-
         return installable;
     }
 
@@ -311,63 +308,110 @@
      * Installs all installable intents associated with the specified top-level
      * intent.
      *
-     * @param intent intent to be installed
+     * @param update intent update
      */
-    private void executeInstallingPhase(Intent intent) {
-        // Indicate that the intent is entering the installing phase.
-        store.setState(intent, INSTALLING);
-
-        List<FlowRuleBatchOperation> installWork = Lists.newArrayList();
-        try {
-            List<Intent> installables = store.getInstallableIntents(intent.id());
-            if (installables != null) {
-                for (Intent installable : installables) {
-                    registerSubclassInstallerIfNeeded(installable);
-                    trackerService.addTrackedResources(intent.id(),
-                                                       installable.resources());
-                    List<FlowRuleBatchOperation> batch = getInstaller(installable).install(installable);
-                    installWork.addAll(batch);
-                }
-            }
-            // FIXME we have to wait for the installable intents
-            //eventDispatcher.post(store.setState(intent, INSTALLED));
-            monitorExecutor.execute(new IntentInstallMonitor(intent, installWork, INSTALLED));
-        } catch (Exception e) {
-            log.warn("Unable to install intent {} due to:", intent.id(), e);
-            uninstallIntent(intent, RECOMPILING);
-
-            // If compilation failed, kick off the recompiling phase.
-            // FIXME
-            //executeRecompilingPhase(intent);
+    private void executeInstallingPhase(IntentUpdate update) {
+        if (update.newInstallables() == null) {
+            //no failed intents allowed past this point...
+            return;
         }
+        // Indicate that the intent is entering the installing phase.
+        update.setState(update.newIntent(), INSTALLING);
+
+        List<FlowRuleBatchOperation> batches = Lists.newArrayList();
+        for (Intent installable : update.newInstallables()) {
+            registerSubclassInstallerIfNeeded(installable);
+            trackerService.addTrackedResources(update.newIntent().id(),
+                                               installable.resources());
+            try {
+                batches.addAll(getInstaller(installable).install(installable));
+            } catch (IntentException e) {
+                log.warn("Unable to install intent {} due to:", update.newIntent().id(), e);
+                //FIXME we failed... intent should be recompiled
+                // TODO: remove resources
+                // recompile!!!
+            }
+        }
+        update.setBatches(batches);
+    }
+
+    /**
+     * Uninstalls the specified intent by uninstalling all of its associated
+     * installable derivatives.
+     *
+     * @param update intent update
+     */
+    private void executeWithdrawingPhase(IntentUpdate update) {
+        if (!update.oldIntent().equals(update.newIntent())) {
+            update.setState(update.oldIntent(), WITHDRAWING);
+        } // else newIntent is FAILED
+        uninstallIntent(update);
+
+        // If all went well, disassociate the top-level intent with its
+        // installable derivatives and mark it as withdrawn.
+        // FIXME need to clean up
+        //store.removeInstalledIntents(intent.id());
+    }
+
+    /**
+     * Uninstalls all installable intents associated with the given intent.
+     *
+     * @param update intent update
+     */
+    //FIXME: need to handle next state properly
+    private void uninstallIntent(IntentUpdate update) {
+        if (update.oldInstallables == null) {
+            return;
+        }
+        List<FlowRuleBatchOperation> batches = Lists.newArrayList();
+        for (Intent installable : update.oldInstallables()) {
+            trackerService.removeTrackedResources(update.oldIntent().id(),
+                                                  installable.resources());
+            try {
+                batches.addAll(getInstaller(installable).uninstall(installable));
+            } catch (IntentException e) {
+                log.warn("Unable to uninstall intent {} due to:", update.oldIntent().id(), e);
+                // TODO: this should never happen. but what if it does?
+            }
+        }
+        update.setBatches(batches);
+        // FIXME: next state for old is WITHDRAWN or FAILED
     }
 
     /**
      * Recompiles the specified intent.
      *
-     * @param intent intent to be recompiled
+     * @param update intent update
      */
-    private void executeRecompilingPhase(Intent intent) {
+    // FIXME: update this to work
+    private void executeRecompilingPhase(IntentUpdate update) {
+        Intent intent = update.newIntent();
         // Indicate that the intent is entering the recompiling phase.
         store.setState(intent, RECOMPILING);
 
+        List<FlowRuleBatchOperation> batches = Lists.newArrayList();
         try {
             // Compile the intent into installable derivatives.
-            List<Intent> installable = compileIntent(intent);
+            List<Intent> installable = compileIntent(intent, update);
 
             // If all went well, compare the existing list of installable
             // intents with the newly compiled list. If they are the same,
             // bail, out since the previous approach was determined not to
             // be viable.
+            // FIXME do we need this?
             List<Intent> originalInstallable = store.getInstallableIntents(intent.id());
 
+            //FIXME let's be smarter about how we perform the update
+            //batches.addAll(uninstallIntent(intent, null));
+
             if (Objects.equals(originalInstallable, installable)) {
                 eventDispatcher.post(store.setState(intent, FAILED));
             } else {
                 // Otherwise, re-associate the newly compiled installable intents
                 // with the top-level intent and kick off installing phase.
                 store.setInstallableIntents(intent.id(), installable);
-                executeInstallingPhase(intent);
+                // FIXME commented out for now
+                //batches.addAll(executeInstallingPhase(update));
             }
         } catch (Exception e) {
             log.warn("Unable to recompile intent {} due to:", intent.id(), e);
@@ -378,45 +422,38 @@
     }
 
     /**
-     * Uninstalls the specified intent by uninstalling all of its associated
-     * installable derivatives.
+     * Withdraws the old intent and installs the new intent as one operation.
      *
-     * @param intent intent to be installed
+     * @param update intent update
      */
-    private void executeWithdrawingPhase(Intent intent) {
-        // Indicate that the intent is being withdrawn.
-        store.setState(intent, WITHDRAWING);
-        uninstallIntent(intent, WITHDRAWN);
-
-        // If all went well, disassociate the top-level intent with its
-        // installable derivatives and mark it as withdrawn.
-        // FIXME need to clean up
-        //store.removeInstalledIntents(intent.id());
-        // FIXME
-        //eventDispatcher.post(store.setState(intent, WITHDRAWN));
-    }
-
-    /**
-     * Uninstalls all installable intents associated with the given intent.
-     *
-     * @param intent intent to be uninstalled
-     */
-    private void uninstallIntent(Intent intent, IntentState nextState) {
-        List<FlowRuleBatchOperation> uninstallWork = Lists.newArrayList();
-        try {
-            List<Intent> installables = store.getInstallableIntents(intent.id());
-            if (installables != null) {
-                for (Intent installable : installables) {
-                    trackerService.removeTrackedResources(intent.id(),
-                                                          installable.resources());
-                    List<FlowRuleBatchOperation> batches = getInstaller(installable).uninstall(installable);
-                    uninstallWork.addAll(batches);
-                }
-            }
-            monitorExecutor.execute(new IntentInstallMonitor(intent, uninstallWork, nextState));
-        } catch (IntentException e) {
-            log.warn("Unable to uninstall intent {} due to:", intent.id(), e);
+    private void executeReplacementPhase(IntentUpdate update) {
+        checkArgument(update.oldInstallables().size() == update.newInstallables().size(),
+                      "Old and New Intent must have equivalent installable intents.");
+        if (!update.oldIntent().equals(update.newIntent())) {
+            // only set the old intent's state if it is different
+            update.setState(update.oldIntent(), WITHDRAWING);
         }
+        update.setState(update.newIntent(), INSTALLING);
+
+        List<FlowRuleBatchOperation> batches = Lists.newArrayList();
+        for (int i = 0; i < update.oldInstallables().size(); i++) {
+            Intent oldInstallable = update.oldInstallables().get(i);
+            Intent newInstallable = update.newInstallables().get(i);
+            if (oldInstallable.equals(newInstallable)) {
+                continue;
+            }
+            checkArgument(oldInstallable.getClass().equals(newInstallable.getClass()),
+                          "Installable Intent type mismatch.");
+            trackerService.removeTrackedResources(update.oldIntent().id(), oldInstallable.resources());
+            trackerService.addTrackedResources(update.newIntent().id(), newInstallable.resources());
+            try {
+                batches.addAll(getInstaller(newInstallable).replace(oldInstallable, newInstallable));
+            } catch (IntentException e) {
+                log.warn("Unable to update intent {} due to:", update.oldIntent().id(), e);
+                //FIXME... we failed. need to uninstall (if same) or revert (if different)
+            }
+        }
+        update.setBatches(batches);
     }
 
     /**
@@ -474,9 +511,6 @@
         @Override
         public void notify(IntentEvent event) {
             eventDispatcher.post(event);
-            if (event.type() == IntentEvent.Type.SUBMITTED) {
-                executor.execute(new IntentTask(COMPILING, event.subject()));
-            }
         }
     }
 
@@ -486,82 +520,221 @@
         public void triggerCompile(Iterable<IntentId> intentIds,
                                    boolean compileAllFailed) {
             // Attempt recompilation of the specified intents first.
-            for (IntentId intentId : intentIds) {
-                Intent intent = getIntent(intentId);
-                uninstallIntent(intent, RECOMPILING);
-
-                //FIXME
-                //executeRecompilingPhase(intent);
+            IntentOperations.Builder builder = IntentOperations.builder();
+            for (IntentId id : intentIds) {
+                builder.addUpdateOperation(id);
             }
 
             if (compileAllFailed) {
                 // If required, compile all currently failed intents.
                 for (Intent intent : getIntents()) {
                     if (getIntentState(intent.id()) == FAILED) {
-                        executeCompilingPhase(intent);
+                        builder.addUpdateOperation(intent.id());
                     }
                 }
             }
+            execute(builder.build());
         }
     }
 
+    /**
+     * TODO.
+     * @param op intent operation
+     * @return intent update
+     */
+    private IntentUpdate processIntentOperation(IntentOperation op) {
+        IntentUpdate update = new IntentUpdate(op);
+
+        if (update.newIntent() != null) {
+            executeCompilingPhase(update);
+        }
+
+        if (update.oldInstallables() != null && update.newInstallables() != null) {
+            executeReplacementPhase(update);
+        } else if (update.newInstallables() != null) {
+            executeInstallingPhase(update);
+        } else if (update.oldInstallables() != null) {
+            executeWithdrawingPhase(update);
+        } else {
+            if (update.oldIntent() != null) {
+                // TODO this shouldn't happen
+                return update; //FIXME
+            }
+            if (update.newIntent() != null) {
+                // TODO assert that next state is failed
+                return update; //FIXME
+            }
+        }
+
+        return update;
+    }
+
+    // TODO comments...
+    private class IntentUpdate {
+        private final IntentOperation op;
+        private final Intent oldIntent;
+        private final Intent newIntent;
+        private final Map<Intent, IntentState> stateMap = Maps.newHashMap();
+
+        private final List<Intent> oldInstallables;
+        private List<Intent> newInstallables;
+        private List<FlowRuleBatchOperation> batches;
+
+        IntentUpdate(IntentOperation op) {
+            this.op = op;
+            switch (op.type()) {
+                case SUBMIT:
+                    newIntent = op.intent();
+                    oldIntent = null;
+                    break;
+                case WITHDRAW:
+                    newIntent = null;
+                    oldIntent = store.getIntent(op.intentId());
+                    break;
+                case REPLACE:
+                    newIntent = op.intent();
+                    oldIntent = store.getIntent(op.intentId());
+                    break;
+                case UPDATE:
+                    oldIntent = store.getIntent(op.intentId());
+                    newIntent = oldIntent; //InnerAssignment: Inner assignments should be avoided.
+                    break;
+                default:
+                    oldIntent = null;
+                    newIntent = null;
+                    break;
+            }
+            // add new intent to store (if required)
+            if (newIntent != null) {
+                IntentEvent event = store.createIntent(newIntent);
+                if (event != null) {
+                    eventDispatcher.post(event);
+                }
+            }
+            // fetch the old intent's installables from the store
+            if (oldIntent != null) {
+                oldInstallables = store.getInstallableIntents(oldIntent.id());
+                // TODO: remove intent from store after uninstall
+            } else {
+                oldInstallables = null;
+            }
+        }
+
+        Intent oldIntent() {
+            return oldIntent;
+        }
+
+        Intent newIntent() {
+            return newIntent;
+        }
+
+        List<Intent> oldInstallables() {
+            return oldInstallables;
+        }
+
+        List<Intent> newInstallables() {
+            return newInstallables;
+        }
+
+        void setInstallables(List<Intent> installables) {
+            newInstallables = installables;
+            store.setInstallableIntents(newIntent.id(), installables);
+        }
+
+        List<FlowRuleBatchOperation> batches() {
+            return batches;
+        }
+
+        void setBatches(List<FlowRuleBatchOperation> batches) {
+            this.batches = batches;
+        }
+
+        IntentState getState(Intent intent) {
+            return stateMap.get(intent);
+        }
+
+        void setState(Intent intent, IntentState newState) {
+            // TODO: clean this up, or set to debug
+            IntentState oldState = stateMap.get(intent);
+            log.info("intent id: {}, old state: {}, new state: {}",
+                     intent.id(), oldState, newState);
+
+            stateMap.put(intent, newState);
+            IntentEvent event = store.setState(intent, newState);
+            if (event != null) {
+                eventDispatcher.post(event);
+            }
+        }
+
+        Map<Intent, IntentState> stateMap() {
+            return stateMap;
+        }
+    }
+
+    private static List<FlowRuleBatchOperation> mergeBatches(Map<IntentOperation,
+            IntentUpdate> intentUpdates) {
+        //TODO test this.
+        List<FlowRuleBatchOperation> batches = Lists.newArrayList();
+        for (IntentUpdate update : intentUpdates.values()) {
+            if (update.batches() == null) {
+                continue;
+            }
+            int i = 0;
+            for (FlowRuleBatchOperation batch : update.batches()) {
+                if (i == batches.size()) {
+                    batches.add(batch);
+                } else {
+                    FlowRuleBatchOperation existing = batches.get(i);
+                    existing.addAll(batch);
+                }
+                i++;
+            }
+        }
+        return batches;
+    }
+
     // Auxiliary runnable to perform asynchronous tasks.
     private class IntentTask implements Runnable {
-        private final IntentState state;
-        private final Intent intent;
+        private final IntentOperations operations;
 
-        public IntentTask(IntentState state, Intent intent) {
-            this.state = state;
-            this.intent = intent;
+        public IntentTask(IntentOperations operations) {
+            this.operations = operations;
         }
 
         @Override
         public void run() {
-            if (state == COMPILING) {
-                executeCompilingPhase(intent);
-            } else if (state == RECOMPILING) {
-                executeRecompilingPhase(intent);
-            } else if (state == WITHDRAWING) {
-                executeWithdrawingPhase(intent);
+            Map<IntentOperation, IntentUpdate> intentUpdates = Maps.newHashMap();
+            for (IntentOperation op : operations.operations()) {
+                intentUpdates.put(op, processIntentOperation(op));
             }
+            List<FlowRuleBatchOperation> batches = mergeBatches(intentUpdates);
+            monitorExecutor.execute(new IntentInstallMonitor(operations, intentUpdates, batches));
         }
     }
 
     private class IntentInstallMonitor implements Runnable {
 
-        private final Intent intent;
+        private static final long TIMEOUT = 5000; // ms
+        private final IntentOperations ops;
+        private final Map<IntentOperation, IntentUpdate> intentUpdateMap;
         private final List<FlowRuleBatchOperation> work;
-        private final List<Future<CompletedBatchOperation>> futures;
-        private final IntentState nextState;
+        private Future<CompletedBatchOperation> future;
+        private final long startTime = System.currentTimeMillis();
+        private final long endTime = startTime + TIMEOUT;
 
-        public IntentInstallMonitor(Intent intent,
-                                    List<FlowRuleBatchOperation> work,
-                                    IntentState nextState) {
-            this.intent = intent;
+        public IntentInstallMonitor(IntentOperations ops,
+                                    Map<IntentOperation, IntentUpdate> intentUpdateMap,
+                                    List<FlowRuleBatchOperation> work) {
+            this.ops = ops;
+            this.intentUpdateMap = intentUpdateMap;
             this.work = work;
-            // TODO how many Futures can be outstanding? one?
-            this.futures = Lists.newLinkedList();
-            this.nextState = nextState;
-
-            // TODO need to kick off the first batch sometime, why not now?
-            futures.add(applyNextBatch());
+            future = applyNextBatch();
         }
 
         /**
-         * Update the intent store with the next status for this intent.
-         */
-        private void updateIntent() {
-            if (nextState == RECOMPILING) {
-                executor.execute(new IntentTask(nextState, intent));
-            } else if (nextState == INSTALLED || nextState == WITHDRAWN) {
-                eventDispatcher.post(store.setState(intent, nextState));
-            } else {
-                log.warn("Invalid next intent state {} for intent {}", nextState, intent);
-            }
-        }
-
-        /**
-         * Applies the next batch.
+         * Applies the next batch, and returns the future.
+         *
+         * @return Future for next batch
          */
         private Future<CompletedBatchOperation> applyNextBatch() {
             if (work.isEmpty()) {
@@ -572,45 +745,124 @@
         }
 
         /**
+         * Update the intent store with the next status for this intent.
+         */
+        private void updateIntents() {
+            // FIXME we assume everything passes for now.
+            for (IntentUpdate update : intentUpdateMap.values()) {
+                for (Intent intent : update.stateMap().keySet()) {
+                    switch (update.getState(intent)) {
+                        case INSTALLING:
+                            update.setState(intent, INSTALLED);
+                            break;
+                        case WITHDRAWING:
+                            update.setState(intent, WITHDRAWN);
+                        // Fall-through
+                        case FAILED:
+                            store.removeInstalledIntents(intent.id());
+                            break;
+
+                        case SUBMITTED:
+                        case COMPILING:
+                        case RECOMPILING:
+                        case WITHDRAWN:
+                        case INSTALLED:
+                        default:
+                            //FIXME clean this up (we shouldn't ever get here)
+                            log.warn("Bad state: {} for {}", update.getState(intent), intent);
+                            break;
+                    }
+                }
+            }
+            /*
+            for (IntentOperation op : ops.operations()) {
+                switch (op.type()) {
+                    case SUBMIT:
+                        store.setState(op.intent(), INSTALLED);
+                        break;
+                    case WITHDRAW:
+                        Intent intent = store.getIntent(op.intentId());
+                        store.setState(intent, WITHDRAWN);
+                        break;
+                    case REPLACE:
+                        store.setState(op.intent(), INSTALLED);
+                        intent = store.getIntent(op.intentId());
+                        store.setState(intent, WITHDRAWN);
+                        break;
+                    case UPDATE:
+                        intent = store.getIntent(op.intentId());
+                        store.setState(intent, INSTALLED);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            */
+            /*
+            if (nextState == RECOMPILING) {
+                eventDispatcher.post(store.setState(intent, FAILED));
+                // FIXME try to recompile
+//                executor.execute(new IntentTask(nextState, intent));
+            } else if (nextState == INSTALLED || nextState == WITHDRAWN) {
+                eventDispatcher.post(store.setState(intent, nextState));
+            } else {
+                log.warn("Invalid next intent state {} for intent {}", nextState, intent);
+            }*/
+        }
+
+        /**
          * Iterate through the pending futures, and remove them when they have completed.
          */
         private void processFutures() {
-            List<Future<CompletedBatchOperation>> newFutures = Lists.newArrayList();
-            for (Iterator<Future<CompletedBatchOperation>> i = futures.iterator(); i.hasNext();) {
-                Future<CompletedBatchOperation> future = i.next();
-                try {
-                    // TODO: we may want to get the future here and go back to the future.
-                    CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS);
-                    if (completed.isSuccess()) {
-                        Future<CompletedBatchOperation> newFuture = applyNextBatch();
-                        if (newFuture != null) {
-                            // we'll add this later so that we don't get a ConcurrentModException
-                            newFutures.add(newFuture);
-                        }
-                    } else {
-                        // TODO check if future succeeded and if not report fail items
-                        log.warn("Failed items: {}", completed.failedItems());
-                        // TODO revert....
-                        //uninstallIntent(intent, RECOMPILING);
-                    }
-                    i.remove();
-                } catch (TimeoutException | InterruptedException | ExecutionException te) {
-                    log.debug("Intallations of intent {} is still pending", intent);
-                }
+            if (future == null) {
+                return; //FIXME look at this
             }
-            futures.addAll(newFutures);
+            try {
+                CompletedBatchOperation completed = future.get(100, TimeUnit.NANOSECONDS);
+                if (completed.isSuccess()) {
+                    future = applyNextBatch();
+                } else {
+                    // TODO check if future succeeded and if not report fail items
+                    log.warn("Failed items: {}", completed.failedItems());
+                    // FIXME revert.... by submitting a new batch
+                    //uninstallIntent(intent, RECOMPILING);
+                }
+            } catch (TimeoutException | InterruptedException | ExecutionException te) {
+                //TODO look into error message
+                log.debug("Intallations of intent {} is still pending", ops);
+            }
         }
 
         @Override
         public void run() {
             processFutures();
-            if (futures.isEmpty()) {
+            if (future == null) {
                 // woohoo! we are done!
-                updateIntent();
+                updateIntents();
+                batchService.removeIntentOperations(ops);
+            } else if (endTime < System.currentTimeMillis()) {
+                log.warn("Install request timed out");
+//                future.cancel(true);
+                // TODO retry and/or report the failure
             } else {
                 // resubmit ourselves if we are not done yet
                 monitorExecutor.submit(this);
             }
         }
     }
+
+    private class InternalBatchDelegate implements IntentBatchDelegate {
+        @Override
+        public void execute(IntentOperations operations) {
+            log.info("Execute operations: {}", operations);
+            //FIXME: perhaps we want to track this task so that we can cancel it.
+            executor.execute(new IntentTask(operations));
+        }
+
+        @Override
+        public void cancel(IntentOperations operations) {
+            //FIXME: implement this
+            log.warn("NOT IMPLEMENTED -- Cancel operations: {}", operations);
+        }
+    }
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
index 4a689a1..0524d76 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/LinkCollectionIntentInstaller.java
@@ -111,6 +111,13 @@
         return Lists.newArrayList(new FlowRuleBatchOperation(rules));
     }
 
+    @Override
+    public List<FlowRuleBatchOperation> replace(LinkCollectionIntent intent,
+                                                LinkCollectionIntent newIntent) {
+        // FIXME: implement
+        return null;
+    }
+
     /**
      * Creates a FlowRuleBatchEntry based on the provided parameters.
      *
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/MultiPointToSinglePointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/MultiPointToSinglePointIntentCompiler.java
index b01500a..9b8b149 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/MultiPointToSinglePointIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/MultiPointToSinglePointIntentCompiler.java
@@ -29,6 +29,7 @@
 import org.onlab.onos.net.intent.LinkCollectionIntent;
 import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
 import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
 import org.onlab.onos.net.topology.PathService;
 
 import java.util.Arrays;
@@ -61,7 +62,8 @@
     }
 
     @Override
-    public List<Intent> compile(MultiPointToSinglePointIntent intent) {
+    public List<Intent> compile(MultiPointToSinglePointIntent intent, List<Intent> installable,
+                                Set<LinkResourceAllocations> resources) {
         Set<Link> links = new HashSet<>();
 
         for (ConnectPoint ingressPoint : intent.ingressPoints()) {
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java
index 3683e8e..066d730 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalConnectivityIntentCompiler.java
@@ -29,6 +29,7 @@
 import org.onlab.onos.net.intent.IntentExtensionService;
 import org.onlab.onos.net.intent.OpticalConnectivityIntent;
 import org.onlab.onos.net.intent.OpticalPathIntent;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
 import org.onlab.onos.net.topology.LinkWeight;
 import org.onlab.onos.net.topology.Topology;
 import org.onlab.onos.net.topology.TopologyEdge;
@@ -60,7 +61,9 @@
     }
 
     @Override
-    public List<Intent> compile(OpticalConnectivityIntent intent) {
+    public List<Intent> compile(OpticalConnectivityIntent intent,
+                                List<Intent> installable,
+                                Set<LinkResourceAllocations> resources) {
         // TODO: compute multiple paths using the K-shortest path algorithm
         Path path = calculateOpticalPath(intent.getSrcConnectPoint(), intent.getDst());
         Intent newIntent = new OpticalPathIntent(intent.appId(),
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
index 27b5387..c102a6e 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/OpticalPathIntentInstaller.java
@@ -104,6 +104,13 @@
         return generateRules(intent, allocations, FlowRuleOperation.REMOVE);
     }
 
+    @Override
+    public List<FlowRuleBatchOperation> replace(OpticalPathIntent intent,
+                                                OpticalPathIntent newIntent) {
+        // FIXME: implement this
+        return null;
+    }
+
     private LinkResourceAllocations assignWavelength(OpticalPathIntent intent) {
         LinkResourceRequest.Builder request = DefaultLinkResourceRequest.builder(intent.id(),
                                                                                  intent.path().links())
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
index 2505be5..0e8abca 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
@@ -131,6 +131,15 @@
         return Lists.newArrayList(new FlowRuleBatchOperation(rules));
     }
 
+    @Override
+    public List<FlowRuleBatchOperation> replace(PathIntent oldIntent, PathIntent newIntent) {
+        // FIXME: implement this
+        List<FlowRuleBatchOperation> batches = Lists.newArrayList();
+        batches.addAll(uninstall(oldIntent));
+        batches.addAll(install(newIntent));
+        return batches;
+    }
+
     /**
      * Allocate resources required for an intent.
      *
@@ -147,7 +156,7 @@
         return request.resources().isEmpty() ? null : resourceService.requestResources(request);
     }
 
-    // TODO refactor below this line... ----------------------------
+    // FIXME refactor below this line... ----------------------------
 
     /**
      * Generates the series of MatchActionOperations from the
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
index efdc4bd..a4c8d58 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
@@ -28,9 +28,11 @@
 import org.onlab.onos.net.intent.PathIntent;
 import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import static java.util.Arrays.asList;
 import static org.onlab.onos.net.Link.Type.DIRECT;
@@ -57,7 +59,9 @@
     }
 
     @Override
-    public List<Intent> compile(PointToPointIntent intent) {
+    public List<Intent> compile(PointToPointIntent intent, List<Intent> installable,
+                Set<LinkResourceAllocations> resources) {
+
         ConnectPoint ingressPoint = intent.ingressPoint();
         ConnectPoint egressPoint = intent.egressPoint();
 
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/impl/PathConstraintCalculationTest.java b/core/net/src/test/java/org/onlab/onos/net/intent/impl/PathConstraintCalculationTest.java
index 3e8e7c1..548ea37 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/impl/PathConstraintCalculationTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/impl/PathConstraintCalculationTest.java
@@ -87,7 +87,7 @@
                                        constraints);
         final PointToPointIntentCompiler compiler = makeCompiler(resourceService);
 
-        return compiler.compile(intent);
+        return compiler.compile(intent, null, null);
     }
 
     /**
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestHostToHostIntentCompiler.java b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestHostToHostIntentCompiler.java
index 02fa4db..a0903fd 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestHostToHostIntentCompiler.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestHostToHostIntentCompiler.java
@@ -121,7 +121,7 @@
         HostToHostIntentCompiler compiler = makeCompiler(hops);
         assertThat(compiler, is(notNullValue()));
 
-        List<Intent> result = compiler.compile(intent);
+        List<Intent> result = compiler.compile(intent, null, null);
         assertThat(result, is(Matchers.notNullValue()));
         assertThat(result, hasSize(2));
         Intent forwardResultIntent = result.get(0);
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestMultiPointToSinglePointIntentCompiler.java b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestMultiPointToSinglePointIntentCompiler.java
index e85d090..4e49fab 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestMultiPointToSinglePointIntentCompiler.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestMultiPointToSinglePointIntentCompiler.java
@@ -137,7 +137,7 @@
         MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
         assertThat(compiler, is(notNullValue()));
 
-        List<Intent> result = compiler.compile(intent);
+        List<Intent> result = compiler.compile(intent, null, null);
         assertThat(result, is(Matchers.notNullValue()));
         assertThat(result, hasSize(1));
         Intent resultIntent = result.get(0);
@@ -172,7 +172,7 @@
         MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
         assertThat(compiler, is(notNullValue()));
 
-        List<Intent> result = compiler.compile(intent);
+        List<Intent> result = compiler.compile(intent, null, null);
         assertThat(result, is(notNullValue()));
         assertThat(result, hasSize(1));
         Intent resultIntent = result.get(0);
@@ -205,7 +205,7 @@
         MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
         assertThat(compiler, is(notNullValue()));
 
-        List<Intent> result = compiler.compile(intent);
+        List<Intent> result = compiler.compile(intent, null, null);
         assertThat(result, is(notNullValue()));
         assertThat(result, hasSize(1));
         Intent resultIntent = result.get(0);
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java
index 7f7e6d3..6521f24 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java
@@ -93,7 +93,7 @@
         PointToPointIntentCompiler compiler = makeCompiler(hops);
         assertThat(compiler, is(notNullValue()));
 
-        List<Intent> result = compiler.compile(intent);
+        List<Intent> result = compiler.compile(intent, null, null);
         assertThat(result, is(Matchers.notNullValue()));
         assertThat(result, hasSize(1));
         Intent forwardResultIntent = result.get(0);
@@ -126,7 +126,7 @@
         PointToPointIntentCompiler compiler = makeCompiler(hops);
         assertThat(compiler, is(notNullValue()));
 
-        List<Intent> result = compiler.compile(intent);
+        List<Intent> result = compiler.compile(intent, null, null);
         assertThat(result, is(Matchers.notNullValue()));
         assertThat(result, hasSize(1));
         Intent reverseResultIntent = result.get(0);
@@ -157,7 +157,7 @@
         String[] hops = {"1"};
         PointToPointIntentCompiler sut = makeCompiler(hops);
 
-        List<Intent> compiled = sut.compile(intent);
+        List<Intent> compiled = sut.compile(intent, null, null);
 
         assertThat(compiled, hasSize(1));
         assertThat(compiled.get(0), is(instanceOf(PathIntent.class)));
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentBatchQueue.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentBatchQueue.java
new file mode 100644
index 0000000..b8d22bf
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentBatchQueue.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onlab.onos.store.trivial.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.net.intent.IntentBatchDelegate;
+import org.onlab.onos.net.intent.IntentBatchService;
+import org.onlab.onos.net.intent.IntentOperations;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+@Service
+public class SimpleIntentBatchQueue implements IntentBatchService {
+
+    private final Logger log = getLogger(getClass());
+    private final Set<IntentOperations> pendingBatches = new HashSet<>();
+    private IntentBatchDelegate delegate;
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public void addIntentOperations(IntentOperations operations) {
+        checkState(delegate != null, "No delegate set");
+        pendingBatches.add(operations);
+        delegate.execute(operations);
+    }
+
+    @Override
+    public void removeIntentOperations(IntentOperations operations) {
+        pendingBatches.remove(operations);
+    }
+
+    @Override
+    public Set<IntentOperations> getIntentOperations() {
+        return ImmutableSet.copyOf(pendingBatches);
+    }
+
+    @Override
+    public void setDelegate(IntentBatchDelegate delegate) {
+        this.delegate = checkNotNull(delegate, "Delegate cannot be null");
+    }
+
+    @Override
+    public void unsetDelegate(IntentBatchDelegate delegate) {
+        if (this.delegate != null && this.delegate.equals(delegate)) {
+            this.delegate = null;
+        }
+    }
+}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
index c28adf5..5a91276 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
@@ -33,7 +33,7 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.onlab.onos.net.intent.IntentState.*;
+import static org.onlab.onos.net.intent.IntentState.WITHDRAWN;
 import static org.slf4j.LoggerFactory.getLogger;
 
 @Component(immediate = true)
@@ -45,8 +45,8 @@
     private final Logger log = getLogger(getClass());
     private final Map<IntentId, Intent> intents = new ConcurrentHashMap<>();
     private final Map<IntentId, IntentState> states = new ConcurrentHashMap<>();
-    private final Map<IntentId, List<Intent>> installable =
-            new ConcurrentHashMap<>();
+    private final Map<IntentId, List<Intent>> installable = new ConcurrentHashMap<>();
+
 
     @Activate
     public void activate() {
@@ -60,6 +60,9 @@
 
     @Override
     public IntentEvent createIntent(Intent intent) {
+        if (intents.containsKey(intent.id())) {
+            return null;
+        }
         intents.put(intent.id(), intent);
         return this.setState(intent, IntentState.SUBMITTED);
     }