Fixed "Intent" problem: Add flow removed event by using "DEL_ACK".
Fixed "Fowarding" to make it work on changed "Intent".
Did cleanup some of "Intent" measurement code.
Deleted unused receiver in FlowProgrammer.java.

Memo:
ReactiveForwarding can set idle timeout.
You can specify it from 1 ~ second.
Out of this range, the application set it to 5 sec (default).

Sample: (in onos.properties file)
net.onrc.onos.apps.forwarding.Forwarding.idletimeout = 5

When you test, please be careful we only set the value to the first switch and the other switches has +2 second idle timeout values.

Change-Id: I1b261a8c771356a412af004c7c0cd93b539ebba7
diff --git a/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java b/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java
index 9fd8870..e59b33c 100644
--- a/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java
+++ b/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java
@@ -68,7 +68,8 @@
     private ITopologyService topologyService;
     private Topology topology;
     private IPathCalcRuntimeService pathRuntime;
-    private IntentMap intentMap;
+    private IntentMap pathIntentMap;
+    private IntentMap highLevelIntentMap;
 
     // TODO it seems there is a Guava collection that will time out entries.
     // We should see if this will work here.
@@ -191,8 +192,9 @@
         packetService.registerPacketListener(this);
 
         topology = topologyService.getTopology();
-        intentMap = pathRuntime.getPathIntents();
-        intentMap.addChangeListener(this);
+        highLevelIntentMap = pathRuntime.getHighLevelIntents();
+        pathIntentMap = pathRuntime.getPathIntents();
+        pathIntentMap.addChangeListener(this);
     }
 
     @Override
@@ -273,7 +275,7 @@
 
     private void continueHandlePacketIn(Switch sw, Port inPort, Ethernet eth, Device deviceObject) {
 
-        log.debug("Start continuehandlePacketIn");
+        log.trace("Start continuehandlePacketIn");
 
         //Iterator<IPortObject> ports = deviceObject.getAttachedPorts().iterator();
         Iterator<net.onrc.onos.core.topology.Port> ports = deviceObject.getAttachmentPoints().iterator();
@@ -300,10 +302,12 @@
 
         MACAddress srcMacAddress = MACAddress.valueOf(eth.getSourceMACAddress());
         MACAddress dstMacAddress = MACAddress.valueOf(eth.getDestinationMACAddress());
+        Path pathspec = new Path(srcMacAddress, dstMacAddress);
+        IntentOperationList operations = new IntentOperationList();
 
         synchronized (lock) {
             //TODO check concurrency
-            Path pathspec = new Path(srcMacAddress, dstMacAddress);
+
             PushedFlow existingFlow = pendingFlows.get(pathspec);
 
             //A path is installed side by side to reduce a path timeout and a wrong state.
@@ -323,23 +327,58 @@
                     // Flow has been sent to the switches so it is safe to
                     // send a packet out now
 
-                    Intent intent = intentMap.getIntent(existingFlow.intentId);
-                    PathIntent pathIntent = null;
-                    if (intent instanceof PathIntent) {
-                        pathIntent = (PathIntent) intent;
+                    // TODO Here highLevelIntentMap and pathIntentMap would be problem,
+                    // because it doesn't have global information as of May 2014.
+                    // However usually these lines here is used when we got packet-in and this class think
+                    // the path for the packet is installed already, so it is pretty rare.
+                    // I will leave it for now, and will work in the next step.
+                    Intent highLevelIntent = highLevelIntentMap.getIntent(existingFlow.intentId);
+                    if (highLevelIntent == null) {
+                        log.debug("Intent ID {} is null in HighLevelIntentMap. return.", existingFlow.intentId);
+                        return;
+                    }
+
+                    if (highLevelIntent.getState() != IntentState.INST_ACK) {
+                        log.debug("Intent ID {}'s state is not INST_ACK. return.", existingFlow.intentId);
+                        return;
+                    }
+
+                    ShortestPathIntent spfIntent = null;
+                    if (highLevelIntent instanceof ShortestPathIntent) {
+                        spfIntent = (ShortestPathIntent) highLevelIntent;
                     } else {
                         log.debug("Intent ID {} is not PathIntent or null. return.", existingFlow.intentId);
                         return;
                     }
 
-                    Boolean isflowEntryForThisSwitch = false;
+                    PathIntent pathIntent = (PathIntent) pathIntentMap.getIntent(spfIntent.getPathIntentId());
+                    if (pathIntent == null) {
+                        log.debug("PathIntent ID {} is null in PathIntentMap. return.", existingFlow.intentId);
+                        return;
+                    }
+
+                    if (pathIntent.getState() != IntentState.INST_ACK) {
+                        log.debug("Intent ID {}'s state is not INST_ACK. return.", existingFlow.intentId);
+                        return;
+                    }
+
+                    boolean isflowEntryForThisSwitch = false;
                     net.onrc.onos.core.intent.Path path = pathIntent.getPath();
+                    long outPort = -1;
+
+                    if (spfIntent.getDstSwitchDpid() == sw.getDpid()) {
+                        log.trace("The packet-in sw dpid {} is on the path.", sw.getDpid());
+                        isflowEntryForThisSwitch = true;
+                        outPort = spfIntent.getDstPortNumber();
+                    }
 
                     for (Iterator<LinkEvent> i = path.iterator(); i.hasNext();) {
                         LinkEvent le = i.next();
+
                         if (le.getSrc().dpid.equals(sw.getDpid())) {
-                            log.debug("src {} dst {}", le.getSrc(), le.getDst());
+                            log.trace("The packet-in sw dpid {} is on the path.", sw.getDpid());
                             isflowEntryForThisSwitch = true;
+                            outPort = le.getSrc().getNumber();
                             break;
                         }
                     }
@@ -352,25 +391,24 @@
                                 existingFlow.intentId,
                                 srcMacAddress, dstMacAddress);
                     } else {
-                        log.debug("Sending packet out from sw {}, outport{}", sw, existingFlow.firstOutPort);
+                        if (outPort < 0) {
+                            outPort = existingFlow.firstOutPort;
+                        }
 
+                        log.debug("Sending packet out from sw {}, outport{}", sw.getDpid(), outPort);
                         packetService.sendPacket(eth, new SwitchPort(
-                                        sw.getDpid(), existingFlow.firstOutPort));
+                                sw.getDpid(), (short) outPort));
                     }
                 } else {
                     // Flow path has not yet been installed to switches so save the
                     // packet out for later
-                    log.debug("Put a packet into the waiting list. flowId {}", existingFlow.intentId);
+                    log.trace("Put a packet into the waiting list. flowId {}", existingFlow.intentId);
                     waitingPackets.put(existingFlow.intentId, new PacketToPush(eth, sw.getDpid()));
                 }
                 return;
             }
 
-            log.debug("Adding new flow between {} at {} and {} at {}",
-                    new Object[]{srcMacAddress, srcSwitchPort, dstMacAddress, dstSwitchPort});
-
-            String intentId = callerId + ":" + controllerRegistryService.getNextUniqueId();
-            IntentOperationList operations = new IntentOperationList();
+            String intentId = Long.toString(controllerRegistryService.getNextUniqueId());
             ShortestPathIntent intent = new ShortestPathIntent(intentId,
                     sw.getDpid(), inPort.getNumber(), srcMacAddress.toLong(),
                     destinationDpid, destinationPort, dstMacAddress.toLong());
@@ -379,14 +417,16 @@
             intent.setFirstSwitchIdleTimeout(idleTimeout);
             IntentOperation.Operator operator = IntentOperation.Operator.ADD;
             operations.add(operator, intent);
-            pathRuntime.executeIntentOperations(operations);
-            // Add to waiting lists
-            pendingFlows.put(pathspec, new PushedFlow(intentId));
-            log.debug("Put a Path {} in the pending flow, intent ID {}", pathspec, intentId);
-            waitingPackets.put(intentId, new PacketToPush(eth, sw.getDpid()));
-            log.debug("Put a Packet in the wating list. related pathspec {}", pathspec);
+            log.debug("Adding new flow between {} at {} and {} at {}",
+                    new Object[]{srcMacAddress, srcSwitchPort, dstMacAddress, dstSwitchPort});
 
+             // Add to waiting lists
+            waitingPackets.put(intentId, new PacketToPush(eth, sw.getDpid()));
+            log.trace("Put a Packet in the wating list. intent ID {}, related pathspec {}", intentId, pathspec);
+            pendingFlows.put(pathspec, new PushedFlow(intentId));
+            log.trace("Put a Path {} in the pending flow, intent ID {}", pathspec, intentId);
         }
+        pathRuntime.executeIntentOperations(operations);
     }
 
     @Override
@@ -406,7 +446,6 @@
         MACAddress srcMacAddress = MACAddress.valueOf(spfIntent.getSrcMac());
         MACAddress dstMacAddress = MACAddress.valueOf(spfIntent.getDstMac());
         Path removedPath = new Path(srcMacAddress, dstMacAddress);
-
         synchronized (lock) {
             // There *shouldn't* be any packets queued if the flow has
             // just been removed.
@@ -414,6 +453,7 @@
             if (!packets.isEmpty()) {
                 log.warn("Removed flow {} has packets queued.", spfIntent.getId());
             }
+
             pendingFlows.remove(removedPath);
             log.debug("Removed from the pendingFlow: Path {}, Flow ID {}", removedPath, spfIntent.getId());
         }
@@ -484,7 +524,7 @@
     public void intentsChange(LinkedList<ChangedEvent> events) {
         for (ChangedEvent event : events) {
             log.debug("path intent ID {}, eventType {}", event.intent.getId() , event.eventType);
-            PathIntent pathIntent = (PathIntent) intentMap.getIntent(event.intent.getId());
+            PathIntent pathIntent = (PathIntent) pathIntentMap.getIntent(event.intent.getId());
             if (pathIntent == null) {
                 continue;
             }
diff --git a/src/main/java/net/onrc/onos/core/flowprogrammer/FlowProgrammer.java b/src/main/java/net/onrc/onos/core/flowprogrammer/FlowProgrammer.java
index 4f6451e..8a3739d 100644
--- a/src/main/java/net/onrc/onos/core/flowprogrammer/FlowProgrammer.java
+++ b/src/main/java/net/onrc/onos/core/flowprogrammer/FlowProgrammer.java
@@ -5,9 +5,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import net.floodlightcontroller.core.FloodlightContext;
 import net.floodlightcontroller.core.IFloodlightProviderService;
-import net.floodlightcontroller.core.IOFMessageListener;
 import net.floodlightcontroller.core.IOFSwitch;
 import net.floodlightcontroller.core.IOFSwitchListener;
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
@@ -17,11 +15,7 @@
 import net.floodlightcontroller.restserver.IRestApiService;
 import net.onrc.onos.core.flowprogrammer.web.FlowProgrammerWebRoutable;
 import net.onrc.onos.core.registry.IControllerRegistryService;
-import net.onrc.onos.core.util.FlowEntryId;
 
-import org.openflow.protocol.OFFlowRemoved;
-import org.openflow.protocol.OFMessage;
-import org.openflow.protocol.OFType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,7 +33,6 @@
  * @author Brian
  */
 public class FlowProgrammer implements IFloodlightModule,
-        IOFMessageListener,
         IOFSwitchListener {
     // flag to enable FlowSynchronizer
     private static final boolean ENABLE_FLOW_SYNC = false;
@@ -76,7 +69,6 @@
     public void startUp(FloodlightModuleContext context) {
         restApi.addRestletRoutable(new FlowProgrammerWebRoutable());
         pusher.start();
-        floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
         floodlightProvider.addOFSwitchListener(this);
     }
 
@@ -120,31 +112,6 @@
     }
 
     @Override
-    public boolean isCallbackOrderingPrereq(OFType type, String name) {
-        // TODO Auto-generated method stub
-        return false;
-    }
-
-    @Override
-    public boolean isCallbackOrderingPostreq(OFType type, String name) {
-        // TODO Auto-generated method stub
-        return false;
-    }
-
-    @Override
-    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
-        if (msg.getType().equals(OFType.FLOW_REMOVED) &&
-            (msg instanceof OFFlowRemoved)) {
-            OFFlowRemoved flowMsg = (OFFlowRemoved) msg;
-            FlowEntryId id = new FlowEntryId(flowMsg.getCookie());
-            log.debug("Got flow entry removed from {}: {}", sw.getId(), id);
-            // TODO: Inform the Forwarding module that a flow has expired
-        }
-
-        return Command.CONTINUE;
-    }
-
-    @Override
     public void addedSwitch(IOFSwitch sw) {
         log.debug("Switch added: {}", sw.getId());
 
diff --git a/src/main/java/net/onrc/onos/core/intent/FlowEntry.java b/src/main/java/net/onrc/onos/core/intent/FlowEntry.java
index bb051e2..94cf88a 100644
--- a/src/main/java/net/onrc/onos/core/intent/FlowEntry.java
+++ b/src/main/java/net/onrc/onos/core/intent/FlowEntry.java
@@ -21,6 +21,7 @@
     protected Operator operator;
     protected int hardTimeout = 0;
     protected int idleTimeout = 0;
+    protected long flowEntryId;
 
     public FlowEntry(long sw, long srcPort, long dstPort,
                      MACAddress srcMac, MACAddress dstMac,
@@ -30,13 +31,13 @@
         this.actions = new HashSet<Action>();
         this.actions.add(new ForwardAction(dstPort));
         this.operator = operator;
-
+        this.flowEntryId = hashCode();
     }
 
     /***
      * Gets hard timeout value in seconds.
      *
-     * @return hardTimeout
+     * @return the hard timeout value in seconds
      */
     public int getHardTimeout() {
         return hardTimeout;
@@ -45,7 +46,7 @@
     /***
      * Gets idle timeout value in seconds.
      *
-     * @return idleTimeout
+     * @return the idle timeout value in seconds
      */
     public int getIdleTimeout() {
         return idleTimeout;
@@ -54,7 +55,7 @@
     /***
      * Sets hard timeout value in seconds.
      *
-     * @param hardTimeout
+     * @param the hard timeout value in seconds
      */
     public void setHardTimeout(int hardTimeout) {
         this.hardTimeout = hardTimeout;
@@ -63,12 +64,30 @@
     /***
      * Sets idle timeout value in seconds.
      *
-     * @param idleTimeout
+     * @param the idle timeout value in seconds
      */
     public void setIdleTimeout(int idleTimeout) {
         this.idleTimeout = idleTimeout;
     }
 
+    /***
+     * Gets flowEntryId.
+     *
+     * @param the flowEntryId to be set in cookie
+     */
+    public long getFlowEntryId() {
+        return flowEntryId;
+    }
+
+    /***
+     * Sets flowEntryId.
+     *
+     * @param the flowEntryId to be set in cookie
+     */
+    public void setFlowEntryId(long flowEntryId) {
+        this.flowEntryId = flowEntryId;
+    }
+
     @Override
     public String toString() {
         return match + "->" + actions;
@@ -89,7 +108,7 @@
     public net.onrc.onos.core.util.FlowEntry getFlowEntry() {
         net.onrc.onos.core.util.FlowEntry entry = new net.onrc.onos.core.util.FlowEntry();
         entry.setDpid(new Dpid(sw));
-        entry.setFlowEntryId(new FlowEntryId(hashCode())); // naive, but useful for now
+        entry.setFlowEntryId(new FlowEntryId(flowEntryId));
         entry.setFlowEntryMatch(match.getFlowEntryMatch());
         FlowEntryActions flowEntryActions = new FlowEntryActions();
         for (Action action : actions) {
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 136a357..320126c 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
@@ -89,7 +89,7 @@
     private IRestApiService restApi;
 
     private IEventChannel<Long, IntentOperationList> opEventChannel;
-    private final ReentrantLock lock = new ReentrantLock();
+    private final ReentrantLock lock = new ReentrantLock(true);
     private HashSet<LinkEvent> unmatchedLinkEvents = new HashSet<>();
     private Map<String, Set<Long>> intentInstalledMap = new ConcurrentHashMap<String, Set<Long>>();
     private static final String INTENT_OP_EVENT_CHANNEL_NAME = "onos.pathintent";
@@ -173,15 +173,16 @@
 
     @Override
     public IntentOperationList executeIntentOperations(IntentOperationList list) {
+
         if (list == null || list.size() == 0) {
             return null;
         }
-        PerfLogger p = new PerfLogger("executeIntentOperations_" + list.get(0).operator);
 
         lock.lock(); // TODO optimize locking using smaller steps
         try {
+            log.trace("lock executeIntentOperations, lock obj is already locked? {}", lock.isLocked());
             // update the map of high-level intents
-            p.log("begin_updateInMemoryIntents");
+
             highLevelIntents.executeOperations(list);
 
             // change states of high-level intents
@@ -214,21 +215,15 @@
                 }
             }
             highLevelIntents.changeStates(states);
-            p.log("end_updateInMemoryIntents");
 
             // calculate path-intents (low-level operations)
-            p.log("begin_calcPathIntents");
             IntentOperationList pathIntentOperations = runtime.calcPathIntents(list, highLevelIntents, pathIntents);
-            p.log("end_calcPathIntents");
 
             // persist calculated low-level operations into data store
-            p.log("begin_persistPathIntents");
             long key = persistIntent.getKey();
             persistIntent.persistIfLeader(key, pathIntentOperations);
-            p.log("end_persistPathIntents");
 
             // remove error-intents and reflect them to high-level intents
-            p.log("begin_removeErrorIntents");
             states.clear();
             Iterator<IntentOperation> i = pathIntentOperations.iterator();
             while (i.hasNext()) {
@@ -239,15 +234,11 @@
                 }
             }
             highLevelIntents.changeStates(states);
-            p.log("end_removeErrorIntents");
 
             // update the map of path intents and publish the path operations
-            p.log("begin_updateInMemoryPathIntents");
             pathIntents.executeOperations(pathIntentOperations);
-            p.log("end_updateInMemoryPathIntents");
 
             // XXX Demo special: add a complete path to remove operation
-            p.log("begin_addPathToRemoveOperation");
             for (IntentOperation op : pathIntentOperations) {
                 if (op.operator.equals(Operator.REMOVE)) {
                     op.intent = pathIntents.getIntent(op.intent.getId());
@@ -256,19 +247,16 @@
                     log.debug("operation: {}, intent:{}", op.operator, op.intent);
                 }
             }
-            p.log("end_addPathToRemoveOperation");
 
             // send notification
-            p.log("begin_sendNotification");
             // XXX: Send notifications using the same key every time
             // and receive them by entryAdded() and entryUpdated()
             opEventChannel.addEntry(0L, pathIntentOperations);
-            p.log("end_sendNotification");
             //opEventChannel.removeEntry(key);
             return pathIntentOperations;
         } finally {
             lock.unlock();
-            p.flushLog();
+            log.trace("unlock executeIntentOperations");
         }
     }
 
@@ -380,11 +368,11 @@
     @Override
     public void entryUpdated(IntentStateList value) {
         // TODO draw state transition diagram in multiple ONOS instances and update this method
-        PerfLogger p = new PerfLogger("entryUpdated");
+
         lock.lock(); // TODO optimize locking using smaller steps
         try {
+            log.trace("lock entryUpdated, lock obj is already locked? {}", lock.isLocked());
             // reflect state changes of path-level intent into application-level intents
-            p.log("begin_changeStateByNotification");
             IntentStateList highLevelIntentStates = new IntentStateList();
             IntentStateList pathIntentStates = new IntentStateList();
             for (Entry<String, IntentState> entry : value.entrySet()) {
@@ -401,8 +389,6 @@
                 }
 
                 IntentState state = entry.getValue();
-                log.debug("put the state pathIntentStates ID {}, state {}", entry.getKey(), state);
-
                 switch (state) {
                     case INST_ACK:
                         Set<Long> installedDpids = calcInstalledDpids(pathIntent, value.domainSwitchDpids);
@@ -416,10 +402,19 @@
                         // FALLTHROUGH
                     // case DEL_REQ:
                         // FALLTHROUGH
-                    case DEL_ACK:
-                        // FALLTHROUGH
                     case DEL_PENDING:
+                        log.debug("put the state highLevelIntentStates ID {}, state {}", parentIntent.getId(), state);
                         highLevelIntentStates.put(parentIntent.getId(), state);
+                        log.debug("put the state pathIntentStates ID {}, state {}", entry.getKey(), entry.getValue());
+                        pathIntentStates.put(entry.getKey(), entry.getValue());
+                        break;
+                    case DEL_ACK:
+                        if (intentInstalledMap.containsKey(parentIntent.getId())) {
+                             intentInstalledMap.remove(parentIntent.getId());
+                        }
+                        log.debug("put the state highLevelIntentStates ID {}, state {}", parentIntent.getId(), state);
+                        highLevelIntentStates.put(parentIntent.getId(), state);
+                        log.debug("put the state pathIntentStates ID {}, state {}", entry.getKey(), entry.getValue());
                         pathIntentStates.put(entry.getKey(), entry.getValue());
                         break;
                     default:
@@ -428,15 +423,15 @@
             }
             highLevelIntents.changeStates(highLevelIntentStates);
             pathIntents.changeStates(pathIntentStates);
-            p.log("end_changeStateByNotification");
         } finally {
             lock.unlock();
-            p.flushLog();
+            log.trace("unlock entryUpdated");
         }
     }
 
     /***
      * This function is to check whether the entire path's flow entries are installed or not.
+     *
      * @param pathIntent : The pathIntent to be checked
      * @param installedDpids : The dpids installed on one ONOS instance
      * @return The result of whether a pathIntent has been installed or not.
@@ -492,7 +487,7 @@
         }
 
         if (log.isTraceEnabled()) {
-            log.trace("All switches {}, domain switch dpids {}", allSwitchesForPath, domainSwitchDpids);
+            log.trace("All switches for a path {}, domain switch dpids {}", allSwitchesForPath, domainSwitchDpids);
         }
 
         return allSwitchesForPath;
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java b/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java
index 73a31b8..bbd2555 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PlanCalcRuntime.java
@@ -61,6 +61,7 @@
             long lastDstSw = -1, lastDstPort = -1, firstSrcSw = -1;
             MACAddress srcMac, dstMac;
             int idleTimeout = 0, hardTimeout = 0, firstSwitchIdleTimeout = 0, firstSwitchHardTimeout = 0;
+            Long cookieId = null;
             if (parent instanceof ShortestPathIntent) {
                 ShortestPathIntent pathIntent = (ShortestPathIntent) parent;
 //              Switch srcSwitch = graph.getSwitch(pathIntent.getSrcSwitchDpid());
@@ -77,6 +78,11 @@
                 hardTimeout = pathIntent.getHardTimeout();
                 firstSwitchIdleTimeout = pathIntent.getFirstSwitchIdleTimeout();
                 firstSwitchHardTimeout = pathIntent.getFirstSwitchHardTimetout();
+                try {
+                    cookieId = Long.valueOf(pathIntent.getId());
+                } catch (NumberFormatException e) {
+                    log.trace("NumberFormatException : ", e);
+                }
             } else {
                 log.warn("Unsupported Intent: {}", parent);
                 continue;
@@ -99,6 +105,10 @@
                     fe.setIdleTimeout(firstSwitchIdleTimeout);
                     fe.setHardTimeout(firstSwitchHardTimeout);
                 }
+                if (cookieId != null) {
+                    log.trace("cookieId is set: {}", cookieId);
+                    fe.setFlowEntryId(cookieId);
+                }
                 entries.add(fe);
 //              srcPort = link.getDstPort();
                 srcPort = linkEvent.getDst().getNumber();
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallModule.java b/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallModule.java
index 94e5d8f..2d2dbd8 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallModule.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PlanInstallModule.java
@@ -6,9 +6,14 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.LinkedBlockingQueue;
 
+import net.floodlightcontroller.core.FloodlightContext;
 import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.module.FloodlightModuleException;
 import net.floodlightcontroller.core.module.IFloodlightModule;
@@ -18,15 +23,21 @@
 import net.onrc.onos.core.datagrid.IEventChannelListener;
 import net.onrc.onos.core.flowprogrammer.IFlowPusherService;
 import net.onrc.onos.core.intent.FlowEntry;
+import net.onrc.onos.core.intent.Intent;
 import net.onrc.onos.core.intent.Intent.IntentState;
 import net.onrc.onos.core.intent.IntentOperation;
 import net.onrc.onos.core.intent.IntentOperationList;
+import net.onrc.onos.core.intent.PathIntent;
+import net.onrc.onos.core.intent.ShortestPathIntent;
 import net.onrc.onos.core.topology.ITopologyService;
 
+import org.openflow.protocol.OFFlowRemoved;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class PlanInstallModule implements IFloodlightModule {
+public class PlanInstallModule implements IFloodlightModule, IOFMessageListener {
     protected volatile IFloodlightProviderService floodlightProvider;
     protected volatile ITopologyService topologyService;
     protected volatile IDatagridService datagridService;
@@ -39,19 +50,7 @@
 
     private static final String PATH_INTENT_CHANNEL_NAME = "onos.pathintent";
     private static final String INTENT_STATE_EVENT_CHANNEL_NAME = "onos.pathintent_state";
-
-
-    @Override
-    public void init(FloodlightModuleContext context)
-            throws FloodlightModuleException {
-        floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
-        topologyService = context.getServiceImpl(ITopologyService.class);
-        datagridService = context.getServiceImpl(IDatagridService.class);
-        flowPusher = context.getServiceImpl(IFlowPusherService.class);
-        planCalc = new PlanCalcRuntime();
-        planInstall = new PlanInstallRuntime(floodlightProvider, flowPusher);
-        eventListener = new EventListener();
-    }
+    private ConcurrentMap<String, Intent> parentIntentMap = new ConcurrentHashMap<String, Intent>();
 
     class EventListener extends Thread
             implements IEventChannelListener<Long, IntentOperationList> {
@@ -129,7 +128,8 @@
             }
 
             if (log.isTraceEnabled()) {
-                log.trace("domainSwitchDpids {}", states.domainSwitchDpids);
+                log.trace("sendNotifications, states {}, domainSwitchDpids {}",
+                       states, states.domainSwitchDpids);
             }
 
             intentStateChannel.addTransientEntry(key, states);
@@ -150,6 +150,7 @@
 
         @Override
         public void entryUpdated(IntentOperationList value) {
+            putIntentOpsInfoInParentMap(value);
             log("start_intentNotifRecv");
             log("begin_sendReceivedNotif");
             sendNotifications(value, false, false, null);
@@ -163,6 +164,23 @@
                 log.warn("Error putting to intent queue: {}", e.getMessage());
             }
         }
+
+        private void putIntentOpsInfoInParentMap(IntentOperationList intentOps) {
+            for (IntentOperation i : intentOps) {
+                if (!(i.intent instanceof PathIntent)) {
+                    log.warn("Not a path intent: {}", i);
+                    continue;
+                }
+                PathIntent intent = (PathIntent) i.intent;
+                Intent parent = intent.getParentIntent();
+                if (parent instanceof ShortestPathIntent) {
+                    parentIntentMap.put(parent.getId(), parent);
+                } else {
+                    log.warn("Unsupported Intent: {}", parent);
+                    continue;
+                }
+            }
+        }
     }
 
     public static void log(String step) {
@@ -170,6 +188,18 @@
     }
 
     @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+        topologyService = context.getServiceImpl(ITopologyService.class);
+        datagridService = context.getServiceImpl(IDatagridService.class);
+        flowPusher = context.getServiceImpl(IFlowPusherService.class);
+        planCalc = new PlanCalcRuntime();
+        planInstall = new PlanInstallRuntime(floodlightProvider, flowPusher);
+        eventListener = new EventListener();
+    }
+
+    @Override
     public void startUp(FloodlightModuleContext context) {
         // start subscriber
         datagridService.addListener(PATH_INTENT_CHANNEL_NAME,
@@ -181,6 +211,7 @@
         intentStateChannel = datagridService.createChannel(INTENT_STATE_EVENT_CHANNEL_NAME,
                 Long.class,
                 IntentStateList.class);
+        floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
     }
 
     @Override
@@ -205,4 +236,84 @@
         // no services, for now
         return null;
     }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        if (msg.getType().equals(OFType.FLOW_REMOVED) &&
+            (msg instanceof OFFlowRemoved)) {
+            OFFlowRemoved flowRemovedMsg = (OFFlowRemoved) msg;
+
+            if (log.isTraceEnabled()) {
+               log.trace("Receive flowRemoved from sw {} : Cookie {}",
+                       sw.getId(), flowRemovedMsg.getCookie());
+            }
+
+            String intentParentId = Long.toString(flowRemovedMsg.getCookie());
+            Intent intent = parentIntentMap.get(intentParentId);
+
+            //We assume if the path src sw flow entry is expired,
+            //the path is expired.
+            if (!isFlowSrcRemoved(sw.getId(), intentParentId)) {
+                return Command.CONTINUE;
+            }
+
+            ShortestPathIntent spfIntent = null;
+            if (!(intent instanceof ShortestPathIntent)) {
+                return Command.CONTINUE;
+            }
+            spfIntent = (ShortestPathIntent) intent;
+            String pathIntentId = spfIntent.getPathIntentId();
+
+            IntentStateList states = new IntentStateList();
+            IntentState newState = IntentState.DEL_ACK;
+            states.put(pathIntentId, newState);
+            Set<Long> domainSwitchDpids = floodlightProvider.getSwitches().keySet();
+            if (domainSwitchDpids != null) {
+                states.domainSwitchDpids.addAll(domainSwitchDpids);
+            }
+            parentIntentMap.remove(intentParentId);
+            log.debug("addEntry to intentStateChannel intentId {}, states {}", flowRemovedMsg.getCookie(), states);
+
+            intentStateChannel.addTransientEntry(flowRemovedMsg.getCookie(), states);
+        }
+
+        return Command.CONTINUE;
+    }
+
+    private boolean isFlowSrcRemoved(long dpid, String shortestPathIntentId) {
+        Intent intent =  parentIntentMap.get(shortestPathIntentId);
+        ShortestPathIntent spfIntent = null;
+        if (intent instanceof ShortestPathIntent) {
+            spfIntent = (ShortestPathIntent) intent;
+        }
+
+        if (spfIntent == null) {
+            return false;
+        }
+
+        long srcSwDpid = spfIntent.getSrcSwitchDpid();
+        if (srcSwDpid == dpid) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        // TODO Auto-generated method stub
+        return false;
+    }
 }