Update the Intent Delete API and semantics:

 * Added new Java API for adding/removing Application Intents:
   IPathCalcRuntimeService.addApplicationIntents(...)
   IPathCalcRuntimeService.removeApplicationIntents(...)
   IPathCalcRuntimeService.removeAllApplicationIntents(...)

 * Updated the corresponding REST API to use the new Java API, and
   move all API implementation complexity within the Intent framework.

 * Added a new REST API to support deleting multiple Intents together:
   REST DELETE: /wm/onos/intent/high?intent_id=1,2,3

 * Updated the semantics for deleting an Intent:
   Using the new Intent API (REST or Java) to delete an intent will
   prune the intent as well.
   For now, the pruning is asynchronous.

 * Implemented the corresponding backend support to automatically
   prune intents that have been deleted.
   See class DeleteIntentsTracker inside file PathCalcRuntimeModule.java

 * Updated test PathCalcRuntimeModuleTest.testIntentReroute() to
   reflect the old path deletion.

 * Updated the EntryForIntentMatcher.matchesSafely() to remove
   assertThat() statement.
   With the new implementation, this matcher can be used with not()
   Hamcrest wrapper.

Change-Id: I88e7de119bab9d955d71cede14eb7847c7b6afbf
diff --git a/src/main/java/net/onrc/onos/core/intent/IntentMap.java b/src/main/java/net/onrc/onos/core/intent/IntentMap.java
index e72cb13..7d87fa2 100644
--- a/src/main/java/net/onrc/onos/core/intent/IntentMap.java
+++ b/src/main/java/net/onrc/onos/core/intent/IntentMap.java
@@ -99,8 +99,13 @@
         notifyEvents();
     }
 
+    /**
+     * Purge all Intents that are in a state allowing them to be removed.
+     */
     public void purge() {
         LinkedList<String> removeIds = new LinkedList<>();
+
+        // Collect the IDs of all intents that can be removed
         for (Entry<String, Intent> entry : intents.entrySet()) {
             Intent intent = entry.getValue();
             if (intent.getState() == IntentState.DEL_ACK
@@ -108,6 +113,19 @@
                 removeIds.add(intent.getId());
             }
         }
+
+        purge(removeIds);
+    }
+
+    /**
+     * Purge a collection of Intents specified by Intent IDs.
+     *
+     * NOTE: The caller needs to make sure those intents are in a state
+     * that allows them to be removed.
+     *
+     * @param removeIds the collection of Intent IDs to purge.
+     */
+    public void purge(Collection<String> removeIds) {
         for (String intentId : removeIds) {
             removeIntent(intentId);
         }
@@ -216,6 +234,10 @@
     }
 
     protected void notifyEvents() {
+        if (events.isEmpty()) {
+            return;
+        }
+
         for (ChangedListener listener : listeners) {
             listener.intentsChange(events);
         }
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java b/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
index dd7bfd0..b81dbab 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
@@ -1,6 +1,9 @@
 package net.onrc.onos.core.intent.runtime;
 
+import java.util.Collection;
+
 import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.api.intent.ApplicationIntent;
 import net.onrc.onos.core.intent.IntentMap;
 import net.onrc.onos.core.intent.IntentOperationList;
 
@@ -8,6 +11,35 @@
  * @author Toshio Koide (t-koide@onlab.us)
  */
 public interface IPathCalcRuntimeService extends IFloodlightService {
+    /**
+     * Add Application Intents.
+     *
+     * @param appId the Application ID to use.
+     * @param appIntents the Application Intents to add.
+     * @return true on success, otherwise false.
+     */
+    public boolean addApplicationIntents(
+                final String appId,
+                Collection<ApplicationIntent> appIntents);
+
+    /**
+     * Remove Application Intents.
+     *
+     * @param appId the Application ID to use.
+     * @param intentIds the Application Intent IDs to remove.
+     * @return true on success, otherwise false.
+     */
+    public boolean removeApplicationIntents(final String appId,
+                                            Collection<String> intentIds);
+
+    /**
+     * Remove all Application Intents.
+     *
+     * @param appId the Application ID to use.
+     * @return true on success, otherwise false.
+     */
+    public boolean removeAllApplicationIntents(final String appId);
+
     public IntentOperationList executeIntentOperations(IntentOperationList list);
 
     public IntentMap getHighLevelIntents();
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
index fe8d012..8dcfd5f 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
@@ -6,6 +6,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -18,12 +19,17 @@
 import net.floodlightcontroller.core.module.IFloodlightModule;
 import net.floodlightcontroller.core.module.IFloodlightService;
 import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.api.intent.ApplicationIntent;
 import net.onrc.onos.core.datagrid.IDatagridService;
 import net.onrc.onos.core.datagrid.IEventChannel;
 import net.onrc.onos.core.datagrid.IEventChannelListener;
+import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
 import net.onrc.onos.core.intent.Intent;
 import net.onrc.onos.core.intent.Intent.IntentState;
 import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentMap.ChangedEvent;
+import net.onrc.onos.core.intent.IntentMap.ChangedListener;
 import net.onrc.onos.core.intent.IntentOperation;
 import net.onrc.onos.core.intent.IntentOperation.Operator;
 import net.onrc.onos.core.intent.IntentOperationList;
@@ -38,6 +44,7 @@
 import net.onrc.onos.core.topology.LinkEvent;
 import net.onrc.onos.core.topology.PortEvent;
 import net.onrc.onos.core.topology.SwitchEvent;
+import net.onrc.onos.core.util.Dpid;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -80,6 +87,89 @@
         }
     }
 
+    /**
+     * A class to track the deletion of intents and purge them as appropriate.
+     */
+    private class DeleteIntentsTracker implements ChangedListener {
+        @Override
+        public void intentsChange(LinkedList<ChangedEvent> events) {
+            List<String> removeIntentIds = new LinkedList<String>();
+            List<String> removePathIds = new LinkedList<String>();
+
+            //
+            // Process the events one-by-one and collect the Intent IDs of
+            // those intents that should be purged.
+            //
+            for (ChangedEvent event : events) {
+                log.debug("DeleteIntentsTracker: Intent ID {}, eventType {}",
+                          event.intent.getId() , event.eventType);
+                PathIntent pathIntent = (PathIntent) pathIntents.getIntent(event.intent.getId());
+                if (pathIntent == null) {
+                    continue;
+                }
+
+                //
+                // Test whether the new Intent state allows the Intent
+                // to be purged.
+                //
+                boolean shouldPurge = false;
+                switch (event.eventType) {
+                case ADDED:
+                    break;
+                case REMOVED:
+                    break;
+                case STATE_CHANGED:
+                    IntentState state = pathIntent.getState();
+                    switch (state) {
+                        case INST_REQ:
+                            break;
+                        case INST_ACK:
+                            break;
+                        case INST_NACK:
+                            shouldPurge = true;
+                            break;
+                        case DEL_REQ:
+                            break;
+                        case DEL_ACK:
+                            shouldPurge = true;
+                            break;
+                        case DEL_PENDING:
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                default:
+                    break;
+                }
+
+                if (shouldPurge) {
+                    removePathIds.add(pathIntent.getId());
+                    Intent parentIntent = pathIntent.getParentIntent();
+                    if (parentIntent != null) {
+                        //
+                        // Remove the High-level Intent only if it was
+                        // explicitly deleted by the user via the API.
+                        //
+                        String intentId = parentIntent.getId();
+                        if (removedApplicationIntentIds.contains(intentId)) {
+                            removeIntentIds.add(intentId);
+                            removedApplicationIntentIds.remove(intentId);
+                        }
+                    }
+                }
+            }
+
+            // Purge the intents
+            if (!removeIntentIds.isEmpty()) {
+                highLevelIntents.purge(removeIntentIds);
+            }
+            if (!removePathIds.isEmpty()) {
+                pathIntents.purge(removePathIds);
+            }
+        }
+    }
+
     private PathCalcRuntime runtime;
     private IDatagridService datagridService;
     private ITopologyService topologyService;
@@ -98,6 +188,8 @@
     private HashSet<LinkEvent> unmatchedLinkEvents = new HashSet<>();
     private ConcurrentMap<String, Set<Long>> intentInstalledMap = new ConcurrentHashMap<String, Set<Long>>();
     private ConcurrentMap<String, Intent> staleIntents = new ConcurrentHashMap<String, Intent>();
+    private DeleteIntentsTracker deleteIntentsTracker = new DeleteIntentsTracker();
+    private Set<String> removedApplicationIntentIds = new HashSet<String>();
 
     // ================================================================================
     // private methods
@@ -173,6 +265,7 @@
         highLevelIntents = new IntentMap();
         runtime = new PathCalcRuntime(topologyService.getTopology());
         pathIntents = new PathIntentMap();
+        pathIntents.addChangeListener(deleteIntentsTracker);
         opEventChannel = datagridService.createChannel(INTENT_OP_EVENT_CHANNEL_NAME, Long.class, IntentOperationList.class);
         datagridService.addListener(INTENT_STATE_EVENT_CHANNEL_NAME, this, Long.class, IntentStateList.class);
         topologyService.registerTopologyListener(this);
@@ -180,9 +273,128 @@
         restApi.addRestletRoutable(new IntentWebRoutable());
     }
 
-    // ================================================================================
+    // ======================================================================
     // IPathCalcRuntimeService implementations
-    // ================================================================================
+    // ======================================================================
+
+    /**
+     * Add Application Intents.
+     *
+     * @param appId the Application ID to use.
+     * @param appIntents the Application Intents to add.
+     * @return true on success, otherwise false.
+     */
+    @Override
+    public boolean addApplicationIntents(
+                        final String appId,
+                        Collection<ApplicationIntent> appIntents) {
+        //
+        // Process all intents one-by-one
+        //
+        // TODO: The Intent Type should be enum instead of a string,
+        // and we should use a switch statement below to process the
+        // different type of intents.
+        //
+        IntentOperationList intentOperations = new IntentOperationList();
+        for (ApplicationIntent appIntent : appIntents) {
+            String appIntentId = appId + ":" + appIntent.intentId();
+
+            IntentOperation.Operator operator = IntentOperation.Operator.ADD;
+            Dpid srcSwitchDpid = new Dpid(appIntent.srcSwitchDpid());
+            Dpid dstSwitchDpid = new Dpid(appIntent.dstSwitchDpid());
+
+            if (appIntent.intentType().equals("SHORTEST_PATH")) {
+                //
+                // Process Shortest-Path Intent
+                //
+                ShortestPathIntent spi =
+                    new ShortestPathIntent(appIntentId,
+                                           srcSwitchDpid.value(),
+                                           appIntent.srcSwitchPort(),
+                                           MACAddress.valueOf(appIntent.matchSrcMac()).toLong(),
+                                           dstSwitchDpid.value(),
+                                           appIntent.dstSwitchPort(),
+                                           MACAddress.valueOf(appIntent.matchDstMac()).toLong());
+                spi.setPathFrozen(appIntent.isStaticPath());
+                intentOperations.add(operator, spi);
+            } else if (appIntent.intentType().equals("CONSTRAINED_SHORTEST_PATH")) {
+                //
+                // Process Constrained Shortest-Path Intent
+                //
+                ConstrainedShortestPathIntent cspi =
+                    new ConstrainedShortestPathIntent(appIntentId,
+                                                      srcSwitchDpid.value(),
+                                                      appIntent.srcSwitchPort(),
+                                                      MACAddress.valueOf(appIntent.matchSrcMac()).toLong(),
+                                                      dstSwitchDpid.value(),
+                                                      appIntent.dstSwitchPort(),
+                                                      MACAddress.valueOf(appIntent.matchDstMac()).toLong(),
+                                                      appIntent.bandwidth());
+                cspi.setPathFrozen(appIntent.isStaticPath());
+                intentOperations.add(operator, cspi);
+            } else {
+                log.error("Unknown Application Intent Type: {}",
+                          appIntent.intentType());
+                return false;
+            }
+            removedApplicationIntentIds.remove(appIntentId);
+        }
+        // Apply the Intent Operations
+        executeIntentOperations(intentOperations);
+        return true;
+    }
+
+    /**
+     * Remove Application Intents.
+     *
+     * @param appId the Application ID to use.
+     * @param intentIds the Application Intent IDs to remove.
+     * @return true on success, otherwise false.
+     */
+    @Override
+    public boolean removeApplicationIntents(final String appId,
+                                            Collection<String> intentIds) {
+        IntentMap intentMap = getHighLevelIntents();
+
+        //
+        // Process all intents one-by-one
+        //
+        IntentOperationList operations = new IntentOperationList();
+        for (String intentId : intentIds) {
+            String appIntentId = appId + ":" + intentId;
+            Intent intent = intentMap.getIntent(appIntentId);
+            if (intent != null) {
+                operations.add(IntentOperation.Operator.REMOVE, intent);
+                removedApplicationIntentIds.add(appIntentId);
+            }
+        }
+        executeIntentOperations(operations);
+
+        return true;
+    }
+
+    /**
+     * Remove all Application Intents.
+     *
+     * @param appId the Application ID to use.
+     * @return true on success, otherwise false.
+     */
+    @Override
+    public boolean removeAllApplicationIntents(final String appId) {
+        IntentMap intentMap = getHighLevelIntents();
+
+        //
+        // Remove all intents
+        //
+        IntentOperationList operations = new IntentOperationList();
+        for (Intent intent : intentMap.getAllIntents()) {
+            operations.add(IntentOperation.Operator.REMOVE, intent);
+            removedApplicationIntentIds.add(intent.getId());
+        }
+        executeIntentOperations(operations);
+
+        return true;
+    }
 
     @Override
     public IntentOperationList executeIntentOperations(IntentOperationList list) {
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
index fa66ea1..6f1c098 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
@@ -1,11 +1,12 @@
 package net.onrc.onos.core.intent.runtime.web;
 
+import java.util.Arrays;
+import java.util.List;
+
 import net.onrc.onos.api.rest.RestError;
 import net.onrc.onos.api.rest.RestErrorCodes;
 import net.onrc.onos.core.intent.Intent;
 import net.onrc.onos.core.intent.IntentMap;
-import net.onrc.onos.core.intent.IntentOperation;
-import net.onrc.onos.core.intent.IntentOperationList;
 import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
 
 import org.restlet.data.Status;
@@ -19,7 +20,7 @@
  */
 public class IntentHighObjectResource extends ServerResource {
     // TODO need to assign proper application id.
-    private static final String APPLN_ID = "1";
+    private static final String APPLICATION_ID = "1";
 
     /**
      * Gets a single high-level intent.
@@ -34,15 +35,14 @@
             (IPathCalcRuntimeService) getContext().getAttributes()
                 .get(IPathCalcRuntimeService.class.getCanonicalName());
 
-        Representation result;
-
         String intentId = (String) getRequestAttributes().get("intent-id");
+        Representation result;
 
         //
         // Get a single high-level Intent: use the Intent ID to find it
         //
         IntentMap intentMap = pathRuntime.getHighLevelIntents();
-        String applnIntentId = APPLN_ID + ":" + intentId;
+        String applnIntentId = APPLICATION_ID + ":" + intentId;
         Intent intent = intentMap.getIntent(applnIntentId);
         if (intent != null) {
             result = toRepresentation(intent, null);
@@ -69,22 +69,14 @@
                 .get(IPathCalcRuntimeService.class.getCanonicalName());
 
         String intentId = (String) getRequestAttributes().get("intent-id");
+        List<String> intentIds = Arrays.asList(intentId);
 
-        //
-        // Remove a single high-level Intent: use the Intent ID to find it
-        //
-        //
-        // TODO: The implementation below is broken - waiting for the Java API
-        // TODO: The deletion should use synchronous Java API?
-        IntentMap intentMap = pathRuntime.getHighLevelIntents();
-        String applnIntentId = APPLN_ID + ":" + intentId;
-        Intent intent = intentMap.getIntent(applnIntentId);
-        if (intent != null) {
-            IntentOperationList operations = new IntentOperationList();
-            operations.add(IntentOperation.Operator.REMOVE, intent);
-            pathRuntime.executeIntentOperations(operations);
+        if (pathRuntime.removeApplicationIntents(APPLICATION_ID, intentIds)) {
+            setStatus(Status.SUCCESS_NO_CONTENT);
+        } else {
+            setStatus(Status.SERVER_ERROR_INTERNAL);
         }
-        setStatus(Status.SUCCESS_NO_CONTENT);
+
         return null;
     }
 }
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
index 764c93f..2406674 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
@@ -1,20 +1,16 @@
 package net.onrc.onos.core.intent.runtime.web;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
-import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.api.intent.ApplicationIntent;
 import net.onrc.onos.api.rest.RestError;
 import net.onrc.onos.api.rest.RestErrorCodes;
-import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
 import net.onrc.onos.core.intent.Intent;
 import net.onrc.onos.core.intent.IntentMap;
-import net.onrc.onos.core.intent.IntentOperation;
-import net.onrc.onos.core.intent.IntentOperationList;
-import net.onrc.onos.core.intent.ShortestPathIntent;
 import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
-import net.onrc.onos.core.util.Dpid;
 
 import org.codehaus.jackson.map.ObjectMapper;
 import org.restlet.data.Status;
@@ -32,12 +28,12 @@
 public class IntentHighResource extends ServerResource {
     private static final Logger log = LoggerFactory.getLogger(IntentHighResource.class);
     // TODO need to assign proper application id.
-    private static final String APPLN_ID = "1";
+    private static final String APPLICATION_ID = "1";
 
     /**
      * Gets all high-level intents.
      *
-     * @return a Representation for a collection with all of the high-level intents.
+     * @return a Representation for a collection with all high-level intents.
      */
     @Get("json")
     public Representation retrieve() throws IOException {
@@ -55,19 +51,18 @@
      * Adds a collection of high-level intents.
      *
      * @param jsonIntent JSON representation of the intents to add.
-     * @return a Representation of a collection containing the intents that were
-     *         created.
+     * @return a Representation of a collection containing the intents that
+     * were added.
      */
     @Post("json")
     public Representation store(String jsonIntent) {
         IPathCalcRuntimeService pathRuntime =
             (IPathCalcRuntimeService) getContext().getAttributes()
                 .get(IPathCalcRuntimeService.class.getCanonicalName());
-        if (pathRuntime == null) {
-            log.warn("Failed to get path calc runtime");
-            return null;
-        }
 
+        //
+        // Extract the Application Intents
+        //
         ObjectMapper mapper = new ObjectMapper();
         ApplicationIntent[] addOperations = null;
         try {
@@ -77,7 +72,6 @@
         } catch (IOException ex) {
             log.error("Exception occurred parsing inbound JSON", ex);
         }
-
         if (addOperations == null) {
             setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
             final RestError error =
@@ -86,57 +80,16 @@
         }
 
         //
-        // Process all intents one-by-one
+        // Add the intents
         //
-        // TODO: The Intent Type should be enum instead of a string,
-        // and we should use a switch statement below to process the
-        // different type of intents.
-        //
-        IntentOperationList intentOperations = new IntentOperationList();
-        for (ApplicationIntent oper : addOperations) {
-            String applnIntentId = APPLN_ID + ":" + oper.intentId();
-
-            IntentOperation.Operator operator = IntentOperation.Operator.ADD;
-            Dpid srcSwitchDpid = new Dpid(oper.srcSwitchDpid());
-            Dpid dstSwitchDpid = new Dpid(oper.dstSwitchDpid());
-
-            if (oper.intentType().equals("SHORTEST_PATH")) {
-                //
-                // Process Shortest-Path Intent
-                //
-                ShortestPathIntent spi =
-                    new ShortestPathIntent(applnIntentId,
-                                           srcSwitchDpid.value(),
-                                           oper.srcSwitchPort(),
-                                           MACAddress.valueOf(oper.matchSrcMac()).toLong(),
-                                           dstSwitchDpid.value(),
-                                           oper.dstSwitchPort(),
-                                           MACAddress.valueOf(oper.matchDstMac()).toLong());
-                spi.setPathFrozen(oper.isStaticPath());
-                intentOperations.add(operator, spi);
-            } else {
-                //
-                // Process Constrained Shortest-Path Intent
-                //
-                ConstrainedShortestPathIntent cspi =
-                    new ConstrainedShortestPathIntent(applnIntentId,
-                                                      srcSwitchDpid.value(),
-                                                      oper.srcSwitchPort(),
-                                                      MACAddress.valueOf(oper.matchSrcMac()).toLong(),
-                                                      dstSwitchDpid.value(),
-                                                      oper.dstSwitchPort(),
-                                                      MACAddress.valueOf(oper.matchDstMac()).toLong(),
-                                                      oper.bandwidth());
-                cspi.setPathFrozen(oper.isStaticPath());
-                intentOperations.add(operator, cspi);
-            }
+        if (pathRuntime.addApplicationIntents(APPLICATION_ID,
+                                              Arrays.asList(addOperations))) {
+            setStatus(Status.SUCCESS_CREATED);
+        } else {
+            setStatus(Status.SERVER_ERROR_INTERNAL);
         }
-        // Apply the Intent Operations
-        pathRuntime.executeIntentOperations(intentOperations);
 
-        setStatus(Status.SUCCESS_CREATED);
-
-        return toRepresentation(intentOperations, null);
+        return toRepresentation(addOperations, null);
     }
 
     /**
@@ -150,11 +103,31 @@
             (IPathCalcRuntimeService) getContext().getAttributes()
                 .get(IPathCalcRuntimeService.class.getCanonicalName());
 
-        // Delete all intents
-        // TODO: The implementation below is broken - waiting for the Java API
-        // TODO: The deletion should use synchronous Java API?
-        pathRuntime.purgeIntents();
-        setStatus(Status.SUCCESS_NO_CONTENT);
-        return null;      // TODO no reply yet from the purge intents call
+        //
+        // Get the optional query values: comma-separated list of Intent IDs
+        //
+        String intentIdValue = getQueryValue("intent_id");
+        boolean success;
+
+        //
+        // Delete the intents
+        //
+        if (intentIdValue != null) {
+            // Delete a collection of intents, specified by Intent IDs
+            List<String> intentIds = Arrays.asList(intentIdValue.split(","));
+            success = pathRuntime.removeApplicationIntents(APPLICATION_ID,
+                                                           intentIds);
+        } else {
+            // Delete all intents
+            success = pathRuntime.removeAllApplicationIntents(APPLICATION_ID);
+        }
+
+        if (success) {
+            setStatus(Status.SUCCESS_NO_CONTENT);
+        } else {
+            setStatus(Status.SERVER_ERROR_INTERNAL);
+        }
+
+        return null;
     }
 }
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
index c2e6e95..f54463e 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
@@ -17,7 +17,7 @@
     /**
      * Gets all low-level intents.
      *
-     * @return a Representation of a collection of all of the low-level intents.
+     * @return a Representation of a collection of all low-level intents.
      */
     @Get("json")
     public Representation retrieve() {
diff --git a/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java b/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java
index c202910..e9286ad 100644
--- a/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java
+++ b/src/test/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModuleTest.java
@@ -4,6 +4,7 @@
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 
 import java.util.Collection;
@@ -78,9 +79,7 @@
 
         @Override
         public boolean matchesSafely(Collection<Intent> intents) {
-            assertThat(intents,
-                    hasItem(Matchers.<Intent>hasProperty("id", equalTo(id))));
-            return true;
+            return hasItem(Matchers.<Intent>hasProperty("id", equalTo(id))).matches(intents);
         }
 
         @Override
@@ -544,8 +543,8 @@
         assertThat(processedPathIntents, notNullValue());
 
         //  Check that the low level intent 1 was correctly processed
-        assertThat(reroutedPathIntents,
-                hasIntentWithIdAndState("1___0", IntentState.DEL_ACK));
+        assertThat(reroutedPathIntents.getAllIntents(),
+                   not(hasIntentWithId("1___0")));
         assertThat(reroutedPathIntents,
                 hasIntentWithIdAndState("1___1", IntentState.INST_ACK));