diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java
index 84e43ee293d7dffc69876caac46914d039535644..d0b24f272a3e7d8fec9a4b6b45f24a0594372f0f 100644
--- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java
+++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java
@@ -874,7 +874,7 @@ public class Controller
 
     @Override
     public Map<Long, IOFSwitch> getSwitches() {
-        return this.switches;
+        return Collections.unmodifiableMap(this.switches);
     }
 
     @Override
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerAware.java b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerAware.java
index 7be350ea17a113c78d6c557ee6af7363cd52933b..cbd9b4512eb48ed83f001967377ba714027688ec 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerAware.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerAware.java
@@ -51,7 +51,7 @@ public interface IDeviceManagerAware {
      * 
      * @param device the device that changed
      */
-    public void deviceNetworkAddressChanged(IDevice device);
+    public void deviceIPV4AddrChanged(IDevice device);
     
     /**
      * Called when a VLAN tag for the device has been added or removed
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerService.java b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerService.java
index bc603ff1e37c9989659d8cb8e6cb9ddf9342f009..c3ff4eb00e57c5c920e453c89465152e7209eb1d 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerService.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceManagerService.java
@@ -18,6 +18,7 @@
 package net.floodlightcontroller.devicemanager;
 
 import java.util.Collection;
+import java.util.EnumSet;
 import java.util.Iterator;
 
 import net.floodlightcontroller.core.FloodlightContextStore;
@@ -121,11 +122,11 @@ public interface IDeviceManagerService extends IFloodlightService {
      * @param unique set to true if only the set of fields specified should be
      * unique.  If multiple devices would match the field, the devices will be
      * updated so that only one will match. 
-     * @param fields the set of fields on which to index
+     * @param keyFields the set of fields on which to index
      */
     public void addIndex(boolean perClass,
                          boolean unique,
-                         DeviceField... fields);
+                         EnumSet<DeviceField> keyFields);
     
     /**
      * Find devices that match the provided query.  Any fields that are
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java
index 54b0d79e35e4bdd27f1d379e0bc308b08c8ad338..5696a766b240587bc6e60ae715e77678ee555911 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java
@@ -18,9 +18,8 @@
 package net.floodlightcontroller.devicemanager;
 
 import java.util.Collection;
-import java.util.Set;
-
-import net.floodlightcontroller.devicemanager.internal.Device;
+import java.util.EnumSet;
+import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
 import net.floodlightcontroller.devicemanager.internal.Entity;
 
 /**
@@ -67,7 +66,7 @@ public interface IEntityClassifier {
     * @see {@link IEntityClass#getKeyFields()}
     * @see {@link IEntityClassifier#classifyEntity}
     */
-   Set<IDeviceManagerService.DeviceField> getKeyFields();
+   EnumSet<DeviceField> getKeyFields();
 
    /**
     * Reclassify the given entity into a class.  When reclassifying entities,
@@ -84,7 +83,7 @@ public interface IEntityClassifier {
     * @param entity the entity to reclassify
     * @return the IEntityClass resulting from the classification
     */
-   Collection<IEntityClass> reclassifyEntity(Device curDevice,
+   Collection<IEntityClass> reclassifyEntity(IDevice curDevice,
                                              Entity entity);
 
    /**
@@ -99,7 +98,8 @@ public interface IEntityClassifier {
     * @param newDevices all the new devices derived from the entities of the
     * old device.  If null, the old device was unchanged.
     */
-   void deviceUpdate(Device oldDevice, Collection<Device> newDevices);
+   void deviceUpdate(IDevice oldDevice, 
+                     Collection<? extends IDevice> newDevices);
 
 }
 
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
index 327ae99863943d0602a6e07fd6b85828d79265cf..5d178bf64ccb1c64a2b4c00c8501cee4a7883307 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
@@ -17,10 +17,11 @@
 
 package net.floodlightcontroller.devicemanager.internal;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumSet;
 
+import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IDeviceManagerService;
 import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
 import net.floodlightcontroller.devicemanager.IEntityClass;
@@ -50,8 +51,7 @@ public class DefaultEntityClassifier implements IEntityClassifier {
     
     public static Collection<IEntityClass> entityClasses;
     static {
-        entityClasses = new ArrayList<IEntityClass>(1);
-        entityClasses.add(entityClass);
+        entityClasses = Arrays.asList(entityClass);
     }
 
     @Override
@@ -60,14 +60,14 @@ public class DefaultEntityClassifier implements IEntityClassifier {
     }
 
     @Override
-    public Collection<IEntityClass> reclassifyEntity(Device curDevice,
+    public Collection<IEntityClass> reclassifyEntity(IDevice curDevice,
                                                      Entity entity) {
         return entityClasses;
     }
 
     @Override
-    public void deviceUpdate(Device oldDevice, 
-                             Collection<Device> newDevices) {
+    public void deviceUpdate(IDevice oldDevice, 
+                             Collection<? extends IDevice> newDevices) {
         // no-op
     }
 
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
index 26b1a09c1e0f9fa809f68cac5abbf5aeaefa3921..f1eb3e991aa1a8b005085d1cb46cbf74c306ffc7 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
@@ -204,10 +204,10 @@ public class Device implements IDevice {
     /**
      * Check whether the device contains the specified entity
      * @param entity the entity to search for
-     * @return true if the entity is in the device, or false otherwise
+     * @return true the index of the entity, or <0 if not found
      */
-    public boolean containsEntity(Entity entity) {
-        return (0 <= Arrays.binarySearch(entities, entity));
+    public int containsEntity(Entity entity) {
+        return Arrays.binarySearch(entities, entity);
     }
     
     // ******
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java
new file mode 100644
index 0000000000000000000000000000000000000000..418c2170333f231f23db1c9f917674d497016ba9
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java
@@ -0,0 +1,107 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    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 net.floodlightcontroller.devicemanager.internal;
+
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity to device keys
+ */
+public abstract class DeviceIndex {
+    /**
+     * The key fields for this index
+     */
+    protected EnumSet<DeviceField> keyFields;
+
+    /**
+     * Construct a new device index using the provided key fields
+     * @param keyFields the key fields to use
+     */
+    public DeviceIndex(EnumSet<DeviceField> keyFields) {
+        super();
+        this.keyFields = keyFields;
+    }
+    
+    /**
+     * Get a device index instance, which is either a multi-values or 
+     * single-valued index.
+     * @param keyFields the key fields for the index
+     * @param unique true if this index should allow just one value per 
+     * compound key
+     * @return the newly-allocated index
+     */
+    public static DeviceIndex getInstance(EnumSet<DeviceField> keyFields,
+                                          boolean unique) {
+        DeviceIndex di;
+        if (unique) {
+            di = new DeviceUniqueIndex(keyFields);
+        } else {
+            di = new DeviceMultiIndex(keyFields);
+        }
+        return di;
+    }
+    
+    /**
+     * Find all device keys in the index that match the given entity
+     * on all the key fields for this index
+     * @param e the entity to search for
+     * @return an iterator over device keys
+     */
+    public abstract Iterator<Long> queryByEntity(Entity entity);
+    
+    /**
+     * Get all device keys in the index.  If certain devices exist
+     * multiple times, then these devices may be returned multiple times
+     * @return an iterator over device keys
+     */
+    public abstract Iterator<Long> getAll();
+
+    /**
+     * Attempt to update an index with the entities in the provided
+     * {@link Device}.  If the update fails because of a concurrent update,
+     * will return false.
+     * @param device the device to update
+     * @param deviceKey the device key for the device
+     * @return true if the update succeeded, false otherwise.
+     */
+    public abstract boolean updateIndex(Device device, Long deviceKey);
+
+    /**
+     * Add a mapping from the given entity to the given device key.  This
+     * update will not fail because of a concurrent update 
+     * @param device the device to update
+     * @param deviceKey the device key for the device
+     */
+    public abstract void updateIndex(Entity entity, Long deviceKey);
+
+    /**
+     * Remove the entry for the given entity
+     * @param entity the entity to remove
+     */
+    public abstract void removeEntity(Entity entity);
+
+    /**
+     * Remove the given device key from the index for the given entity
+     * @param entity the entity to search for
+     * @param deviceKey the key to remove
+     */
+    public abstract void removeEntity(Entity entity, Long deviceKey);
+}
\ No newline at end of file
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..2015bbe60d648d86c33a582d2c9bae9b5274e5c8
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java
@@ -0,0 +1,59 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    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 net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Iterator;
+
+/**
+ * An iterator for handling device index queries
+ */
+public class DeviceIndexInterator implements Iterator<Device> {
+    private DeviceManagerImpl deviceManager;
+    private Iterator<Long> subIterator;
+
+    /**
+     * Construct a new device index iterator referring to a device manager
+     * instance and an iterator over device keys
+     * 
+     * @param deviceManager the device manager
+     * @param subIterator an iterator over device keys
+     */
+    public DeviceIndexInterator(DeviceManagerImpl deviceManager,
+                                Iterator<Long> subIterator) {
+        super();
+        this.deviceManager = deviceManager;
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return subIterator.hasNext();
+    }
+
+    @Override
+    public Device next() {
+        Long next = subIterator.next();
+        return deviceManager.deviceMap.get(next);
+    }
+
+    @Override
+    public void remove() {
+        subIterator.remove();
+    }
+
+}
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
index 730f8dfc4c8d1fc82f5cb2c5addba4f10acc5cc6..0ff058d542a049cd047313109915213bad4af14d 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
@@ -21,14 +21,13 @@ import java.util.Arrays;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
-import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IEntityClass;
 import net.floodlightcontroller.devicemanager.SwitchPort;
 
 /**
  * An iterator for handling device queries
  */
-public class DeviceIterator implements Iterator<IDevice> {
+public class DeviceIterator implements Iterator<Device> {
     private Iterator<Device> subIterator;
 
     private IEntityClass[] entityClasses;
@@ -131,9 +130,9 @@ public class DeviceIterator implements Iterator<IDevice> {
     }
 
     @Override
-    public IDevice next() {
+    public Device next() {
         if (hasNext()) {
-            IDevice cur = next;
+            Device cur = next;
             next = null;
             return cur;
         }
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
index 6cdfb61ef139c3b60ecca50b92239b1b67354d94..c2fbd0059dfb8ecdccfebed873356af5e965dbbf 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
@@ -18,6 +18,7 @@
 package net.floodlightcontroller.devicemanager.internal;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -25,7 +26,9 @@ import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -52,6 +55,7 @@ import net.floodlightcontroller.storage.IStorageSourceService;
 import net.floodlightcontroller.storage.IStorageSourceListener;
 import net.floodlightcontroller.topology.ITopologyService;
 import net.floodlightcontroller.topology.ITopologyListener;
+import net.floodlightcontroller.util.MultiIterator;
 
 import org.openflow.protocol.OFMessage;
 import org.openflow.protocol.OFPacketIn;
@@ -78,11 +82,6 @@ public class DeviceManagerImpl implements
     protected ITopologyService topology;
     protected IStorageSourceService storageSource;
     
-    /**
-     * Device manager event listeners
-     */
-    protected Set<IDeviceManagerAware> deviceManagerAware;
-    
     /**
      * This is the master device map that maps device IDs to {@link Device}
      * objects.
@@ -100,28 +99,26 @@ public class DeviceManagerImpl implements
     protected Object deviceKeyLock = new Object();
     
     /**
-     * This is the primary entity index that maps entities to device IDs.
-     */
-    protected ConcurrentHashMap<IndexedEntity, Long> primaryIndex;
-    
-    /**
-     * The primary key fields used in the primary index
-     * @see DeviceManagerImpl#primaryKeyFieldsArr
+     * This is the primary entity index that contains all entities
      */
-    protected Set<IDeviceManagerService.DeviceField> primaryKeyFields;
+    protected DeviceUniqueIndex primaryIndex;
     
     /**
-     * A serialization of the primary key fields set.
-     * @see DeviceManagerImpl#primaryKeyFields
+     * This stores secondary indices over the fields in the devices
      */
-    protected IDeviceManagerService.DeviceField[] primaryKeyFieldsArr;
-    
+    protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
+     
     /**
      * This map contains state for each of the {@ref IEntityClass} 
      * that exist
      */
     protected ConcurrentHashMap<IEntityClass, ClassState> classStateMap;
 
+    /**
+     * This is the list of indices we want on a per-class basis
+     */
+    protected Map<EnumSet<DeviceField>, Boolean> perClassIndices;
+    
     /**
      * The entity classifier currently in use
      */
@@ -131,36 +128,75 @@ public class DeviceManagerImpl implements
      * Used to cache state about specific entity classes
      */
     protected class ClassState {
+
         /**
-         * True if the key field set matches the primary key fields.
-         */
-        protected boolean keyFieldsMatchPrimary;
-        
-        /**
-         * An array version of the key fields
+         * The class index
          */
-        protected IDeviceManagerService.DeviceField[] keyFieldsArr;
+        protected DeviceUniqueIndex classIndex;
         
         /**
-         * The class index
+         * This stores secondary indices over the fields in the device for the
+         * class
          */
-        protected ConcurrentHashMap<IndexedEntity, Long> classIndex;
+        protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
 
         /**
          * Allocate a new {@link ClassState} object for the class
          * @param clazz the class to use for the state
          */
         public ClassState(IEntityClass clazz) {
-            Set<IDeviceManagerService.DeviceField> keyFields = 
-                    clazz.getKeyFields();
-            keyFieldsMatchPrimary = primaryKeyFields.equals(keyFields);
-            keyFieldsArr = 
-                    new IDeviceManagerService.DeviceField[keyFields.size()];
-            keyFieldsArr = keyFields.toArray(keyFieldsArr);
+            EnumSet<DeviceField> keyFields = clazz.getKeyFields();
+            EnumSet<DeviceField> primaryKeyFields = 
+                    entityClassifier.getKeyFields();
+            boolean keyFieldsMatchPrimary = 
+                    primaryKeyFields.equals(keyFields);
+            
             if (!keyFieldsMatchPrimary)
-                classIndex = new ConcurrentHashMap<IndexedEntity, Long>();
+                classIndex = new DeviceUniqueIndex(keyFields);
+            
+            secondaryIndexMap = 
+                    new HashMap<EnumSet<DeviceField>, DeviceIndex>();
+            for (Map.Entry<EnumSet<DeviceField>,Boolean> keys : 
+                 perClassIndices.entrySet()) {
+                secondaryIndexMap.put(keys.getKey(), 
+                                      DeviceIndex.getInstance(keys.getKey(), 
+                                                              keys.getValue()));
+            }
         }
     }
+   
+    /**
+     * Device manager event listeners
+     */
+    protected Set<IDeviceManagerAware> deviceListeners;
+
+    /**
+     * A device update event to be dispatched
+     */
+    protected static class DeviceUpdate {
+        /**
+         * The affected device
+         */
+        protected IDevice device;
+        
+        /**
+         * True if the device was added
+         */
+        protected boolean added;
+        
+        /**
+         * If not added, then this is the list of fields changed
+         */
+        protected EnumSet<DeviceField> fieldsChanged;
+        
+        public DeviceUpdate(IDevice device, boolean added,
+                            EnumSet<DeviceField> fieldsChanged) {
+            super();
+            this.device = device;
+            this.added = added;
+            this.fieldsChanged = fieldsChanged;
+        }        
+    }
     
     // *********************
     // IDeviceManagerService
@@ -207,27 +243,54 @@ public class DeviceManagerImpl implements
 
     @Override
     public void addIndex(boolean perClass, boolean unique,
-                         DeviceField... fields) {
-        // TODO Auto-generated method stub
-        
+                         EnumSet<DeviceField> keyFields) {
+        if (perClass) {
+            perClassIndices.put(keyFields, unique);
+        } else {
+            secondaryIndexMap.put(keyFields, 
+                                  DeviceIndex.getInstance(keyFields, unique));
+        }
     }
-    
+
     @Override
     public Iterator<? extends IDevice> queryDevices(Long macAddress,
                                                     Short vlan, 
                                                     Integer ipv4Address,
                                                     Long switchDPID,
                                                     Integer switchPort) {
-        // XXX - TODO ... umm, should probably actually have indices :-)
+        DeviceIndex index = null;
+        if (secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys = 
+                    getEntityKeys(macAddress, vlan, ipv4Address, 
+                                  switchDPID, switchPort);
+            index = secondaryIndexMap.get(keys);
+        }
+
+        Iterator<Device> deviceIterator = null;
+        if (index == null) {
+            // Do a full table scan
+            deviceIterator = deviceMap.values().iterator();
+        } else {
+            // index lookup
+            Entity entity = new Entity((macAddress == null ? 0 : macAddress), 
+                                       vlan, 
+                                       ipv4Address, 
+                                       switchDPID, 
+                                       switchPort,
+                                       null);
+            deviceIterator = 
+                    new DeviceIndexInterator(this, index.queryByEntity(entity));
+        }
+        
         DeviceIterator di = 
-                new DeviceIterator(deviceMap.values().iterator(),
+                new DeviceIterator(deviceIterator,
                                    null,
                                    macAddress,
                                    vlan,
                                    ipv4Address, 
                                    switchDPID, 
                                    switchPort);
-        return di;
+            return di;
     }
 
     @Override
@@ -237,29 +300,61 @@ public class DeviceManagerImpl implements
                                                          Integer ipv4Address,
                                                          Long switchDPID,
                                                          Integer switchPort) {
-        // XXX - TODO ... umm, should probably actually have indices :-)
-        DeviceIterator di = new DeviceIterator(deviceMap.values().iterator(),
-                                               reference.getEntityClasses(),
-                                               macAddress,
-                                               vlan, 
-                                               ipv4Address, 
-                                               switchDPID, 
-                                               switchPort);
-        return di;
+        IEntityClass[] entityClasses = reference.getEntityClasses();
+        ArrayList<Iterator<Device>> iterators = 
+                new ArrayList<Iterator<Device>>();
+        for (IEntityClass clazz : entityClasses) {
+            ClassState classState = getClassState(clazz);
+            
+            DeviceIndex index = null;
+            if (classState.secondaryIndexMap.size() > 0) {
+                EnumSet<DeviceField> keys = 
+                        getEntityKeys(macAddress, vlan, ipv4Address, 
+                                      switchDPID, switchPort);
+                index = classState.secondaryIndexMap.get(keys);
+            }
+         
+            Iterator<Device> iter;
+            if (index == null) {
+                index = classState.classIndex;
+                if (index == null) {
+                    // scan all devices
+                    return new DeviceIterator(deviceMap.values().iterator(), 
+                                              entityClasses, 
+                                              macAddress, vlan, ipv4Address, 
+                                              switchDPID, switchPort);
+                } else {
+                    // scan the entire class
+                    iter = new DeviceIndexInterator(this, index.getAll());
+                }
+            } else {
+                // index lookup
+                Entity entity = 
+                        new Entity((macAddress == null ? 0 : macAddress), 
+                                   vlan, 
+                                   ipv4Address, 
+                                   switchDPID, 
+                                   switchPort,
+                                   null);
+                iter = new DeviceIndexInterator(this, 
+                                                index.queryByEntity(entity));
+            }
+            iterators.add(iter);
+        }
+
+        return new MultiIterator<Device>(iterators.iterator());
     }
 
     @Override
     public void addListener(IDeviceManagerAware listener) {
-        deviceManagerAware.add(listener);
+        deviceListeners.add(listener);
     }
 
     @Override
     public void setEntityClassifier(IEntityClassifier classifier) {
         entityClassifier = classifier;
-        primaryKeyFields = classifier.getKeyFields();
-        primaryKeyFieldsArr = 
-                new IDeviceManagerService.DeviceField[primaryKeyFields.size()];
-        primaryKeyFieldsArr = primaryKeyFields.toArray(primaryKeyFieldsArr);
+        primaryIndex = new DeviceUniqueIndex(classifier.getKeyFields());
+        secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
     }
     
     @Override
@@ -268,6 +363,20 @@ public class DeviceManagerImpl implements
         // TODO Auto-generated method stub
     }
 
+    // *************
+    // IInfoProvider
+    // *************
+
+    @Override
+    public Map<String, Object> getInfo(String type) {
+        if (!"summary".equals(type))
+            return null;
+        
+        Map<String, Object> info = new HashMap<String, Object>();
+        info.put("# hosts", deviceMap.size());
+        return info;
+    }
+    
     // ******************
     // IOFMessageListener
     // ******************
@@ -385,7 +494,9 @@ public class DeviceManagerImpl implements
 
     @Override
     public void init(FloodlightModuleContext fmc) {
-        this.deviceManagerAware = new HashSet<IDeviceManagerAware>();
+        this.perClassIndices =
+                new HashMap<EnumSet<DeviceField>, Boolean>();
+        this.deviceListeners = new HashSet<IDeviceManagerAware>();
         
         this.floodlightProvider = 
                 fmc.getServiceImpl(IFloodlightProviderService.class);
@@ -399,9 +510,7 @@ public class DeviceManagerImpl implements
     public void startUp(FloodlightModuleContext fmc) {
         if (entityClassifier == null)
             setEntityClassifier(new DefaultEntityClassifier());
-        
         deviceMap = new ConcurrentHashMap<Long, Device>();
-        primaryIndex = new ConcurrentHashMap<IndexedEntity, Long>();
         classStateMap = 
                 new ConcurrentHashMap<IEntityClass, ClassState>();
         
@@ -409,7 +518,7 @@ public class DeviceManagerImpl implements
             // Register to get updates from topology
             topology.addListener(this);
         } else {
-            logger.error("Could add not toplogy listener");
+            logger.error("Could not add topology listener");
         }
         
         floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
@@ -438,56 +547,47 @@ public class DeviceManagerImpl implements
      */
     protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, 
                                              FloodlightContext cntx) {
-        try {
-            Ethernet eth = 
-                    IFloodlightProviderService.bcStore.
-                    get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
-
-            // Extract source entity information
-            Entity srcEntity = 
-                    getSourceEntityFromPacket(eth, sw, pi.getInPort());
-            if (srcEntity == null)
-                return Command.STOP;
-            
-            if (isGratArp(eth) ||
-                isBroadcastDHCPReq(eth)) {
-                // XXX - TODO - Clear attachment points from other clusters
-            }
-            
-            // Learn/lookup device information
-            Device srcDevice = learnDeviceByEntity(srcEntity);
-            if (srcDevice == null)
-                return Command.STOP;
-            
-            // Store the source device in the context
-            fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice);
-            
-            // Find the device matching the destination from the entity
-            // classes of the source.
-            Entity dstEntity = getDestEntityFromPacket(eth);
-            if (dstEntity != null) {
-                Device dstDevice = 
-                        findDestByEntity(srcDevice, dstEntity);
-                if (dstDevice != null)
-                    fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
-            }
-                
-            return Command.CONTINUE;
+        Ethernet eth = 
+                IFloodlightProviderService.bcStore.
+                get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
 
-        } finally {
-            processUpdates();
-        }        
-    }
-    
-    private void processUpdates() {
-        // XXX - TODO
+        // Extract source entity information
+        Entity srcEntity = 
+                getSourceEntityFromPacket(eth, sw, pi.getInPort());
+        if (srcEntity == null)
+            return Command.STOP;
+
+        if (isGratArp(eth) ||
+            isBroadcastDHCPReq(eth)) {
+            // XXX - TODO - Clear attachment points from other clusters
+        }
+
+        // Learn/lookup device information
+        Device srcDevice = learnDeviceByEntity(srcEntity);
+        if (srcDevice == null)
+            return Command.STOP;
+
+        // Store the source device in the context
+        fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice);
+
+        // Find the device matching the destination from the entity
+        // classes of the source.
+        Entity dstEntity = getDestEntityFromPacket(eth);
+        if (dstEntity != null) {
+            Device dstDevice = 
+                    findDestByEntity(srcDevice, dstEntity);
+            if (dstDevice != null)
+                fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
+        }
+
+        return Command.CONTINUE;
     }
     
     /**
      * Check whether the port is a physical port. We should not learn 
      * attachment points on "special" ports.
      * @param port the port to check
-     * @return
+     * @return true if the port is a valid input port
      */
     private boolean isValidInputPort(short port) {
         return ((int)port & 0xff00) != 0xff00 ||
@@ -603,13 +703,10 @@ public class DeviceManagerImpl implements
      * @return The {@link Device} object if found
      */
     protected Device findDeviceByEntity(Entity entity) {
-        IndexedEntity ie = new IndexedEntity(primaryKeyFieldsArr, entity);
-        Long deviceKey = primaryIndex.get(ie);
-        if (deviceKey == null)
-            return null;
+        Long deviceKey =  primaryIndex.findByEntity(entity);
+        if (deviceKey == null) return null;
         return deviceMap.get(deviceKey);
-    }
-    
+    }    
 
     /**
      * Get a destination device using entity fields that corresponds with
@@ -667,7 +764,7 @@ public class DeviceManagerImpl implements
         // XXX - TODO
         throw new UnsupportedOperationException();
     }
- 
+    
     /**
      * Look up a {@link Device} based on the provided {@link Entity}.  Also
      * learns based on the new entity, and will update existing devices as 
@@ -677,8 +774,8 @@ public class DeviceManagerImpl implements
      * @return The {@link Device} object if found
      */
     protected Device learnDeviceByEntity(Entity entity) {
-        IndexedEntity ie = new IndexedEntity(primaryKeyFieldsArr, entity);
         ArrayList<Long> deleteQueue = null;
+        LinkedList<DeviceUpdate> deviceUpdates = null;
         Device device = null;
         
         // we may need to restart the learning process if we detect
@@ -686,9 +783,11 @@ public class DeviceManagerImpl implements
         // one thread should always succeed so we don't get into infinite
         // starvation loops
         while (true) {
+            deviceUpdates = null;
+            
             // Look up the fully-qualified entity to see if it already
             // exists in the primary entity index.
-            Long deviceKey = primaryIndex.get(ie);
+            Long deviceKey = primaryIndex.findByEntity(entity);
             Collection<IEntityClass> classes = null;
             
             if (deviceKey == null) {
@@ -701,10 +800,8 @@ public class DeviceManagerImpl implements
                     ClassState classState = getClassState(clazz);
                         
                     if (classState.classIndex != null) {
-                        IndexedEntity sie = 
-                                new IndexedEntity(classState.keyFieldsArr, 
-                                                  entity);
-                        deviceKey = classState.classIndex.get(sie);
+                        deviceKey = 
+                                classState.classIndex.findByEntity(entity);
                     }
                 }
             }
@@ -734,11 +831,20 @@ public class DeviceManagerImpl implements
                     deleteQueue.add(deviceKey);
                     continue;
                 }
+                
+                updateSecondaryIndices(entity, classes, deviceKey);
+                
+                deviceUpdates = 
+                        updateUpdates(deviceUpdates,
+                                      new DeviceUpdate(device, true, null));
+                
                 break;
             }
             
-            if (device.containsEntity(entity)) {
-                // XXX - TODO - Update entity timestamp
+            int entityindex = -1;
+            if ((entityindex = device.containsEntity(entity)) >= 0) {
+                // update timestamp on the found entity
+                device.entities[entityindex].setLastSeenTimestamp(new Date());
                 break;
             } else {
                 // When adding an entity, any existing entities on the
@@ -752,6 +858,9 @@ public class DeviceManagerImpl implements
                 Entity[] entities = device.getEntities();
                 ArrayList<Entity> newEntities = null;
                 ArrayList<Entity> removedEntities = null;
+                EnumSet<DeviceField> changedFields = 
+                        findChangedFields(device, entity);
+                
                 // iterate backwards since we want indices for removed entities
                 // to update in reverse order
                 for (int i = entities.length - 1; i >= 0; i--) {
@@ -767,9 +876,13 @@ public class DeviceManagerImpl implements
                     if (edpid != null && cdpid != null &&
                         topology != null && 
                         topology.inSameCluster(edpid, cdpid)) {
+                        // XXX - TODO don't delete entities; we should just
+                        // filter out the attachment points on read
                         removedEntities = 
                                 updateEntityList(removedEntities, entities[i]);
 
+                        changedFields.add(DeviceField.SWITCH);
+                        
                         Entity shim = makeShimEntity(entity, entities[i], 
                                                      device.getEntityClasses());
                         newEntities = updateEntityList(newEntities, shim);
@@ -783,6 +896,20 @@ public class DeviceManagerImpl implements
                 
                 Device newDevice = new Device(device,
                                               newEntities, classes);
+
+                updateSecondaryIndices(entity, 
+                                       newDevice.getEntityClasses(), 
+                                       deviceKey);
+                for (Entity e : newEntities) {
+                    updateSecondaryIndices(e, 
+                                           newDevice.getEntityClasses(), 
+                                           deviceKey);                    
+                }
+                
+                deviceUpdates = 
+                        updateUpdates(deviceUpdates,
+                                      new DeviceUpdate(device, false, 
+                                                       changedFields));
                 
                 boolean res = deviceMap.replace(deviceKey, device, newDevice);
                 // If replace returns false, restart the process from the 
@@ -799,14 +926,86 @@ public class DeviceManagerImpl implements
                 break;
             }
         }
-        
+           
         if (deleteQueue != null) {
             for (Long l : deleteQueue) {
                 deviceMap.remove(l);
             }
         }
+        
+        processUpdates(deviceUpdates);
+
         return device;
     }
+
+    protected EnumSet<DeviceField> findChangedFields(Device device, 
+                                                     Entity newEntity) {
+        EnumSet<DeviceField> changedFields = 
+                EnumSet.of(DeviceField.IPV4, 
+                           DeviceField.VLAN, 
+                           DeviceField.SWITCH);
+        
+        if (newEntity.getIpv4Address() == null)
+            changedFields.remove(DeviceField.IPV4);
+        if (newEntity.getVlan() == null)
+            changedFields.remove(DeviceField.VLAN);
+        if (newEntity.getSwitchDPID() == null ||
+            newEntity.getSwitchPort() == null)
+            changedFields.remove(DeviceField.SWITCH);
+        
+        if (changedFields.size() == 0) return changedFields;
+        
+        for (Entity entity : device.getEntities()) {
+            if (newEntity.getIpv4Address() != null &&
+                entity.getIpv4Address() != null &&
+                entity.getIpv4Address().equals(newEntity.getIpv4Address()))
+                changedFields.remove(DeviceField.IPV4);
+            if (newEntity.getVlan() != null &&
+                entity.getVlan() != null &&
+                entity.getVlan().equals(newEntity.getVlan()))
+                changedFields.remove(DeviceField.VLAN);
+            if (newEntity.getSwitchDPID() != null &&
+                entity.getSwitchDPID() != null &&
+                newEntity.getSwitchPort() != null &&
+                entity.getSwitchPort() != null &&
+                entity.getSwitchDPID().equals(newEntity.getSwitchDPID()) &&
+                entity.getSwitchPort().equals(newEntity.getSwitchPort()))
+                changedFields.remove(DeviceField.SWITCH);            
+        }
+        
+        return changedFields;
+    }
+    
+    /**
+     * Send update notifications to listeners
+     * @param updates the updates to process.
+     */
+    protected void processUpdates(Queue<DeviceUpdate> updates) {
+        if (updates == null) return;
+        DeviceUpdate update = null;
+        while (null != (update = updates.poll())) {
+            for (IDeviceManagerAware listener : deviceListeners) {
+                if (update.added) {
+                    listener.deviceAdded(update.device);
+                } else {
+                    for (DeviceField field : update.fieldsChanged) {
+                        switch (field) {
+                            case IPV4:
+                                listener.deviceIPV4AddrChanged(update.device);
+                                break;
+                            case SWITCH:
+                            case PORT:
+                                listener.deviceMoved(update.device);
+                                break;
+                            case VLAN:
+                                listener.deviceVlanChanged(update.device);
+                                break;
+                        }
+                    }
+                }
+            }
+        }
+    }
     
     /**
      * If there's information that would be lost about a device because
@@ -873,6 +1072,16 @@ public class DeviceManagerImpl implements
         return list;
     }
     
+    private LinkedList<DeviceUpdate> 
+        updateUpdates(LinkedList<DeviceUpdate> list, DeviceUpdate update) {
+        if (update == null) return list;
+        if (list == null)
+            list = new LinkedList<DeviceUpdate>();
+        list.add(update);
+        
+        return list;
+    }
+    
     /**
      * Get the secondary index for a class.  Will return null if the 
      * secondary index was created concurrently in another thread. 
@@ -894,24 +1103,21 @@ public class DeviceManagerImpl implements
     
     /**
      * Update both the primary and class indices for the provided device.
-     * If the update fails because of a concurrent update, will return false.
+     * If the update fails because of aEn concurrent update, will return false.
      * @param device the device to update
      * @param deviceKey the device key for the device
      * @return true if the update succeeded, false otherwise.
      */
     private boolean updateIndices(Device device, Long deviceKey) {
-        if (!updateIndex(device, deviceKey, 
-                         primaryIndex, primaryKeyFieldsArr)) {
+        if (!primaryIndex.updateIndex(device, deviceKey)) {
             return false;
         }
         for (IEntityClass clazz : device.getEntityClasses()) {
             ClassState classState = getClassState(clazz); 
 
             if (classState.classIndex != null) {
-                if (!updateIndex(device, 
-                                 deviceKey, 
-                                 classState.classIndex, 
-                                 classState.keyFieldsArr))
+                if (!classState.classIndex.updateIndex(device, 
+                                                       deviceKey))
                     return false;
             }
         }
@@ -919,62 +1125,69 @@ public class DeviceManagerImpl implements
         return true;
     }
     
+    /**
+     * Update the secondary indices for the given entity and associated
+     * entity classes
+     * @param entity the entity to update
+     * @param entityClasses the entity classes for the entity
+     * @param deviceKey the device key to set up
+     */
+    private void updateSecondaryIndices(Entity entity, 
+                                        Collection<IEntityClass> entityClasses, 
+                                        Long deviceKey) {
+        for (DeviceIndex index : secondaryIndexMap.values()) {
+            index.updateIndex(entity, deviceKey);
+        }
+        for (IEntityClass clazz : entityClasses) {
+            ClassState state = getClassState(clazz);
+            for (DeviceIndex index : state.secondaryIndexMap.values()) {
+                index.updateIndex(entity, deviceKey);
+            }
+        }
+    }
+    
+    /**
+     * Update the secondary indices for the given entity and associated
+     * entity classes
+     * @param entity the entity to update
+     * @param entityClasses the entity classes for the entity
+     * @param deviceKey the device key to set up
+     */
+    private void updateSecondaryIndices(Entity entity, 
+                                        IEntityClass[] entityClasses, 
+                                        Long deviceKey) {
+        updateSecondaryIndices(entity, Arrays.asList(entityClasses), deviceKey);
+    }
+
     private void removeEntities(ArrayList<Entity> removed, 
                                 IEntityClass[] classes) {
         if (removed == null) return;
         for (Entity rem : removed) {
-            IndexedEntity ie = new IndexedEntity(primaryKeyFieldsArr, rem);
-            primaryIndex.remove(ie);
+            primaryIndex.removeEntity(rem);
             
             for (IEntityClass clazz : classes) {
                 ClassState classState = getClassState(clazz);
-                ie = new IndexedEntity(classState.keyFieldsArr, rem);
 
                 if (classState.classIndex != null) {
-                    classState.classIndex.remove(ie);
+                    classState.classIndex.removeEntity(rem);
                 }
             }
         }
         // XXX - TODO handle indexed views into data
     }
-    
-    /**
-     * Attempt to update an index with the entities in the provided
-     * {@link Device}.  If the update fails because of a concurrent update,
-     * will return false.
-     * @param device the device to update
-     * @param deviceKey the device key for the device
-     * @param index the index to update
-     * @param keyFields the key fields to use for the index
-     * @return true if the update succeeded, false otherwise.
-     */
-    private boolean updateIndex(Device device, 
-                                Long deviceKey,
-                                ConcurrentHashMap<IndexedEntity, 
-                                                  Long> index, 
-                                DeviceField[] keyFields) {
-        for (Entity e : device.entities) {
-            IndexedEntity ie = new IndexedEntity(keyFields, e);
-            Long ret = index.putIfAbsent(ie, deviceKey);
-            if (ret != null && !ret.equals(deviceKey)) {
-                // If the return value is non-null, then fail the insert 
-                // (this implies that a device using this entity has 
-                // already been created in another thread).
-                return false;
-            }
-        }
-        
-        return true;
-    }
-
-	@Override
-	public Map<String, Object> getInfo(String type) {
-		if (!"summary".equals(type))
-			return null;
-		
-		Map<String, Object> info = new HashMap<String, Object>();
-		info.put("# hosts", deviceMap.size());
-		return info;
-	}
 
+    private EnumSet<DeviceField> getEntityKeys(Long macAddress,
+                                               Short vlan, 
+                                               Integer ipv4Address,
+                                               Long switchDPID,
+                                               Integer switchPort) {
+        EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class);
+        if (macAddress != null) keys.add(DeviceField.MAC);
+        if (vlan != null) keys.add(DeviceField.VLAN);
+        if (ipv4Address != null) keys.add(DeviceField.IPV4);
+        if (switchDPID != null) keys.add(DeviceField.SWITCH);
+        if (switchPort != null) keys.add(DeviceField.PORT);
+        return keys;
+    }
+    
 }
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fd7c995d567ca6291dda9c9df231d11d9a8dece
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java
@@ -0,0 +1,108 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    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 net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
+import net.floodlightcontroller.util.IterableIterator;
+
+/**
+ * An index that maps key fields of an entity to device keys, with multiple
+ * device keys allowed per entity
+ */
+public class DeviceMultiIndex extends DeviceIndex {
+    /**
+     * The index
+     */
+    private ConcurrentHashMap<IndexedEntity, Collection<Long>> index;
+
+    /**
+     * @param keyFields
+     */
+    public DeviceMultiIndex(EnumSet<DeviceField> keyFields) {
+        super(keyFields);
+        index = new ConcurrentHashMap<IndexedEntity, Collection<Long>>();
+    }
+
+    // ***********
+    // DeviceIndex
+    // ***********
+
+    @Override
+    public Iterator<Long> queryByEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Collection<Long> devices = index.get(ie);
+        if (devices != null)
+            return devices.iterator();
+        
+        return Collections.<Long>emptySet().iterator();
+    }
+    
+    @Override
+    public Iterator<Long> getAll() {
+        Iterator<Collection<Long>> iter = index.values().iterator();
+        return new IterableIterator<Long>(iter);
+    }
+    
+    @Override
+    public boolean updateIndex(Device device, Long deviceKey) {
+        for (Entity e : device.entities) {
+            updateIndex(e, deviceKey);
+        }
+        return true;
+    }
+    
+    @Override
+    public void updateIndex(Entity entity, Long deviceKey) {
+        Collection<Long> devices = null;
+
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        if (!ie.hasNonNullKeys()) return;
+
+        devices = index.get(ie);
+        if (devices == null) {
+            Map<Long,Boolean> chm = new ConcurrentHashMap<Long,Boolean>();
+            devices = Collections.newSetFromMap(chm);
+            Collection<Long> r = index.putIfAbsent(ie, devices);
+            if (r != null)
+                devices = r;
+        }
+        
+        devices.add(deviceKey);
+    }
+
+    @Override
+    public void removeEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie);        
+    }
+
+    @Override
+    public void removeEntity(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Collection<Long> devices = index.get(ie);
+        if (devices != null)
+            devices.remove(deviceKey);
+    }
+}
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e1e049fbe0675dd4f967c76073d509e40e81d78
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java
@@ -0,0 +1,116 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    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 net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity uniquely to a device key
+ */
+public class DeviceUniqueIndex extends DeviceIndex {
+    /**
+     * The index
+     */
+    private ConcurrentHashMap<IndexedEntity, Long> index;
+
+    /**
+     * Construct a new device index using the provided key fields
+     * @param keyFields the key fields to use
+     */
+    public DeviceUniqueIndex(EnumSet<DeviceField> keyFields) {
+        super(keyFields);
+        index = new ConcurrentHashMap<IndexedEntity, Long>();
+    }
+
+    // ***********
+    // DeviceIndex
+    // ***********
+
+    @Override
+    public Iterator<Long> queryByEntity(Entity entity) {
+        final Long deviceKey = findByEntity(entity);
+        if (deviceKey != null)
+            return Collections.<Long>singleton(deviceKey).iterator();
+        
+        return Collections.<Long>emptySet().iterator();
+    }
+    
+    @Override
+    public Iterator<Long> getAll() {
+        return index.values().iterator();
+    }
+
+    @Override
+    public boolean updateIndex(Device device, Long deviceKey) {
+        for (Entity e : device.entities) {
+            IndexedEntity ie = new IndexedEntity(keyFields, e);
+            if (!ie.hasNonNullKeys()) continue;
+
+            Long ret = index.putIfAbsent(ie, deviceKey);
+            if (ret != null && !ret.equals(deviceKey)) {
+                // If the return value is non-null, then fail the insert 
+                // (this implies that a device using this entity has 
+                // already been created in another thread).
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    @Override
+    public void updateIndex(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        if (!ie.hasNonNullKeys()) return;
+        index.put(ie, deviceKey);
+    }
+
+    @Override
+    public void removeEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie);
+    }
+
+    @Override
+    public void removeEntity(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie, deviceKey);
+    }
+
+    // **************
+    // Public Methods
+    // **************
+
+    /**
+     * Look up a {@link Device} based on the provided {@link Entity}.
+     * @param entity the entity to search for
+     * @return The key for the {@link Device} object if found
+     */
+    public Long findByEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Long deviceKey = index.get(ie);
+        if (deviceKey == null)
+            return null;
+        return deviceKey;
+    }
+
+}
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
index 1ec3a44a6d9a03c685dc6ec0e03b96b3283474a5..574677f100a712ff172b335a87825af27234233a 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
@@ -211,7 +211,17 @@ public class Entity implements Comparable<Entity> {
             r = 1;
         else
             r = switchPort.compareTo(o.switchPort);
-        return r;
+        if (r != 0) return r;
+
+        if (lastSeenTimestamp == null)
+            r = o.lastSeenTimestamp == null ? 0 : -1;
+        else if (o.lastSeenTimestamp == null)
+            r = 1;
+        else
+            r = lastSeenTimestamp.compareTo(o.lastSeenTimestamp);
+        if (r != 0) return r;
+
+        return 0;
     }
     
 }
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
index 6b2f3ff190808434457cf0fa2c8a8899cc5b2904..3ca3551041c80cd8d9b9f4c667da1f617691782a 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
@@ -1,6 +1,9 @@
 package net.floodlightcontroller.devicemanager.internal;
 
+import java.util.EnumSet;
+
 import net.floodlightcontroller.devicemanager.IDeviceManagerService;
+import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
 
 /**
  * This is a thin wrapper around {@link Entity} that allows overriding
@@ -9,7 +12,7 @@ import net.floodlightcontroller.devicemanager.IDeviceManagerService;
  * @author readams
  */
 public class IndexedEntity {
-    protected IDeviceManagerService.DeviceField[] keyFields;
+    protected EnumSet<DeviceField> keyFields;
     protected Entity entity;
     private int hashCode = 0;
     
@@ -20,19 +23,45 @@ public class IndexedEntity {
      * {@link IndexedEntity#hashCode()} and {@link IndexedEntity#equals(Object)}
      * @param entity the entity to wrap
      */
-    public IndexedEntity(IDeviceManagerService.DeviceField[] keyFields, Entity entity) {
+    public IndexedEntity(EnumSet<DeviceField> keyFields, Entity entity) {
         super();
         this.keyFields = keyFields;
         this.entity = entity;
     }
 
+    /**
+     * Check whether this entity has non-null values in any of its key fields
+     * @return true if any key fields have a non-null value
+     */
+    public boolean hasNonNullKeys() {
+        for (DeviceField f : keyFields) {
+            switch (f) {
+                case MAC:
+                    return true;
+                case IPV4:
+                    if (entity.ipv4Address != null) return true;
+                    break;
+                case SWITCH:
+                    if (entity.switchDPID != null) return true;
+                    break;
+                case PORT:
+                    if (entity.switchPort != null) return true;
+                    break;
+                case VLAN:
+                    if (entity.vlan != null) return true;
+                    break;
+            }
+        }
+        return false;
+    }
+    
     @Override
     public int hashCode() {
         if (hashCode != 0) return hashCode;
 
         final int prime = 31;
         hashCode = 1;
-        for (IDeviceManagerService.DeviceField f : keyFields) {
+        for (DeviceField f : keyFields) {
             switch (f) {
                 case MAC:
                     hashCode = prime * hashCode
diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
index c294e35dd66ddba7c0a797b787c550b1216254c4..0d1878e8d4b2cea1f5a107b356112cbb032a882c 100644
--- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
+++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
@@ -478,7 +478,7 @@ public abstract class ForwardingBase implements
     }
 
     @Override
-    public void deviceNetworkAddressChanged(IDevice device) {
+    public void deviceIPV4AddrChanged(IDevice device) {
 
     }
 
diff --git a/src/main/java/net/floodlightcontroller/util/IterableIterator.java b/src/main/java/net/floodlightcontroller/util/IterableIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..584de0865870bf02ca889a104ed2e6a58dd45297
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/util/IterableIterator.java
@@ -0,0 +1,66 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    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 net.floodlightcontroller.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T> the type of elements returned by this iterator
+ */
+public class IterableIterator<T> implements Iterator<T> {
+    Iterator<? extends Iterable<T>> subIterator;
+    Iterator<T> current = null;
+    
+    public IterableIterator(Iterator<? extends Iterable<T>> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (current == null) {
+            if (subIterator.hasNext()) {
+                current = subIterator.next().iterator();
+            } else {
+                return false;
+            }
+        }
+        while (!current.hasNext() && subIterator.hasNext()) {
+            current = subIterator.next().iterator();
+        }
+        
+        return current.hasNext();
+    }
+
+    @Override
+    public T next() {
+        if (hasNext())
+            return current.next();
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        if (hasNext())
+            current.remove();
+        throw new NoSuchElementException();
+    }
+}
diff --git a/src/main/java/net/floodlightcontroller/util/MultiIterator.java b/src/main/java/net/floodlightcontroller/util/MultiIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..bcbc9160ac5986a4454fdca802932af5e160c247
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/util/MultiIterator.java
@@ -0,0 +1,66 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    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 net.floodlightcontroller.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T> the type of elements returned by this iterator
+ */
+public class MultiIterator<T> implements Iterator<T> {
+    Iterator<Iterator<T>> subIterator;
+    Iterator<T> current = null;
+    
+    public MultiIterator(Iterator<Iterator<T>> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (current == null) {
+            if (subIterator.hasNext()) {
+                current = subIterator.next();
+            } else {
+                return false;
+            }
+        }
+        while (!current.hasNext() && subIterator.hasNext()) {
+            current = subIterator.next();
+        }
+        
+        return current.hasNext();
+    }
+
+    @Override
+    public T next() {
+        if (hasNext())
+            return current.next();
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        if (hasNext())
+            current.remove();
+        throw new NoSuchElementException();
+    }
+}
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
index 575bf5fe76870786a2e02348b1e464056a281dff..aa615fb1d5af3a9c69b2a1f83c16206df9875a71 100644
--- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
+++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
@@ -18,31 +18,30 @@
 package net.floodlightcontroller.devicemanager.internal;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
+import java.util.List;
 
 import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.createStrictMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.reset;
 import static org.easymock.EasyMock.verify;
+import static org.easymock.EasyMock.isA;
 
 import net.floodlightcontroller.core.IFloodlightProviderService;
 import net.floodlightcontroller.core.IOFSwitch;
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.test.MockFloodlightProvider;
 import static net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField.*;
+import net.floodlightcontroller.devicemanager.IDeviceManagerAware;
 import net.floodlightcontroller.devicemanager.IDeviceManagerService.DeviceField;
 import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IEntityClass;
-import net.floodlightcontroller.devicemanager.IEntityClassifier;
 import net.floodlightcontroller.devicemanager.SwitchPort;
 import net.floodlightcontroller.devicemanager.IDeviceManagerService;
 import net.floodlightcontroller.packet.ARP;
@@ -55,15 +54,12 @@ import net.floodlightcontroller.storage.IStorageSourceService;
 import net.floodlightcontroller.storage.memory.MemoryStorageSource;
 import net.floodlightcontroller.test.FloodlightTestCase;
 import net.floodlightcontroller.topology.ITopologyService;
-import net.floodlightcontroller.topology.SwitchPortTuple;
-
 import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Test;
 import org.openflow.protocol.OFPacketIn;
 import org.openflow.protocol.OFType;
 import org.openflow.protocol.OFPacketIn.OFPacketInReason;
-import org.openflow.protocol.OFPhysicalPort;
 
 /**
  *
@@ -143,9 +139,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         @Override
         public Collection<IEntityClass> classifyEntity(Entity entity) {
             if (entity.switchDPID >= 10L) {
-                ArrayList<IEntityClass> l = new ArrayList<IEntityClass>();
-                l.add(testEC);
-                return l;
+                return Arrays.asList(testEC);
             }
             return DefaultEntityClassifier.entityClasses;
         }
@@ -159,6 +153,10 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
     
     @Test
     public void testEntityLearning() throws Exception {
+        IDeviceManagerAware mockListener = 
+                createStrictMock(IDeviceManagerAware.class);
+        
+        deviceManager.addListener(mockListener);
         deviceManager.setEntityClassifier(new TestEntityClassifier());
         
         Entity entity1 = new Entity(1L, null, null, 1L, 1, new Date());
@@ -168,6 +166,10 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         Entity entity5 = new Entity(2L, (short)4, 1, 5L, 2, new Date());
         Entity entity6 = new Entity(2L, (short)4, 1, 50L, 3, new Date());
 
+        
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+        
         Device d1 = deviceManager.learnDeviceByEntity(entity1);        
         assertSame(d1, deviceManager.learnDeviceByEntity(entity1)); 
         assertSame(d1, deviceManager.findDeviceByEntity(entity1));
@@ -175,7 +177,12 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                           d1.entityClasses);
 
         assertEquals(1, deviceManager.getAllDevices().size());
-        
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+
         Device d2 = deviceManager.learnDeviceByEntity(entity2);
         assertFalse(d1.equals(d2));
         assertNotSame(d1, d2);
@@ -183,6 +190,11 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                           d2.entityClasses);
 
         assertEquals(2, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+        replay(mockListener);
         
         Device d3 = deviceManager.learnDeviceByEntity(entity3);
         assertNotSame(d2, d3);
@@ -194,7 +206,11 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                           d3.getAttachmentPoints());
 
         assertEquals(2, deviceManager.getAllDevices().size());
-        
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+        replay(mockListener);
 
         Device d4 = deviceManager.learnDeviceByEntity(entity4);
         assertNotSame(d1, d4);
@@ -206,6 +222,11 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                           d4.getAttachmentPoints());
         
         assertEquals(2, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceAdded((isA(IDevice.class)));
+        replay(mockListener);
 
         Device d5 = deviceManager.learnDeviceByEntity(entity5);
         assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 2) },
@@ -214,6 +235,11 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                           d5.getVlanId());
         assertEquals(2L, d5.getMACAddress());
         assertEquals("00:00:00:00:00:02", d5.getMACAddressString());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
         
         Device d6 = deviceManager.learnDeviceByEntity(entity6);
         assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
@@ -222,10 +248,16 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                           d6.getVlanId());
 
         assertEquals(4, deviceManager.getAllDevices().size());
+        verify(mockListener);
     }
     
     @Test
     public void testAttachmentPointLearning() throws Exception {
+        IDeviceManagerAware mockListener = 
+                createStrictMock(IDeviceManagerAware.class);
+        
+        deviceManager.addListener(mockListener);
+
         ITopologyService mockTopology = createMock(ITopologyService.class);
         expect(mockTopology.inSameCluster(1L, 2L)).andReturn(true).anyTimes();
         expect(mockTopology.inSameCluster(2L, 1L)).andReturn(true).anyTimes();
@@ -255,20 +287,33 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         IDevice d;
         SwitchPort[] aps;
         Integer[] ips;
-        
+
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+
         d = deviceManager.learnDeviceByEntity(entity1);
         assertEquals(1, deviceManager.getAllDevices().size());
         aps = d.getAttachmentPoints(); 
         assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
         ips = d.getIPv4Addresses();
         assertArrayEquals(new Integer[] { 1 }, ips);
-        
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
         d = deviceManager.learnDeviceByEntity(entity2);
         assertEquals(1, deviceManager.getAllDevices().size());
         aps = d.getAttachmentPoints(); 
         assertArrayEquals(new SwitchPort[] { new SwitchPort(2L, 1) }, aps);
         ips = d.getIPv4Addresses();
         assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
 
         d = deviceManager.learnDeviceByEntity(entity3);
         assertEquals(1, deviceManager.getAllDevices().size());
@@ -277,6 +322,11 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                                              new SwitchPort(2L, 1) }, aps);
         ips = d.getIPv4Addresses();
         assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
 
         d = deviceManager.learnDeviceByEntity(entity4);
         assertEquals(1, deviceManager.getAllDevices().size());
@@ -284,12 +334,13 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         assertArrayEquals(new SwitchPort[] { new SwitchPort(2L, 1), 
                                              new SwitchPort(4L, 1) }, aps);
         ips = d.getIPv4Addresses();
-        assertArrayEquals(new Integer[] { 1 }, ips);        
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
     }
 
     @Test
     public void testPacketIn() throws Exception {
-        
+        deviceManager.addIndex(true, true, EnumSet.of(DeviceField.IPV4));
         byte[] dataLayerSource = ((Ethernet)this.testPacket).getSourceMACAddress();
 
         // Mock up our expected behavior