Add string attributes to self-containd objects.

- Add common super class to *Event classes, which handles attributes.
- Add string attributes to *Event classes (ONOS-1564)
- Populate string attributes.
  Picked random attribute obtained from OF, just to initially populate attrs.
  Attributes to be used for each elements should be revisited later.
- *Impl class to use/reference self-contained objects
   prep-work for snapshot in mind.
- unified equals implementations
- Add unfrozen Copy constructor.
- Add freeze check to fixed attributes.
- Remove get*Impl which was not really adding value.

Change-Id: I10f9538f87d133a22237bd8ab97b8de421d3930b
diff --git a/src/main/java/net/onrc/onos/core/topology/Device.java b/src/main/java/net/onrc/onos/core/topology/Device.java
index 9c4833e..a3eaf8b 100644
--- a/src/main/java/net/onrc/onos/core/topology/Device.java
+++ b/src/main/java/net/onrc/onos/core/topology/Device.java
@@ -25,7 +25,8 @@
     /**
      * Get the device attachment points.
      * <p/>
-     * Add requirement for Iteration order? Latest observed port first.
+     * TODO: There is only 1 attachment point right now.
+     * TODO: Add requirement for Iteration order? Latest observed port first.
      *
      * @return the device attachment points.
      */
@@ -34,9 +35,8 @@
     /**
      * Get the device last seen time.
      * <p/>
-     * TODO: what is the time definition?
      *
-     * @return the device last seen time.
+     * @return the device last seen time. (UTC in ms)
      */
     public long getLastSeenTime();
 }
diff --git a/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java b/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java
index 02041eb..110aef5 100644
--- a/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java
+++ b/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java
@@ -1,8 +1,11 @@
 package net.onrc.onos.core.topology;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.core.topology.web.serializers.DeviceEventSerializer;
@@ -20,19 +23,22 @@
  * Multiple attachmentPoints can be specified to batch events into 1 object.
  * Each should be treated as independent events.
  * <p/>
- * TODO: We probably want common base class/interface for Self-Contained Event Object
+ * TODO: Rename to match what it is. (Switch/Port/Link/Device)Snapshot?
+ * FIXME: Current implementation directly use this object as
+ *        Replication message, but should be sending update operation info.
  */
 @JsonSerialize(using = DeviceEventSerializer.class)
-public class DeviceEvent {
+public class DeviceEvent extends TopologyElement<DeviceEvent> {
+
     private final MACAddress mac;
-    protected List<SwitchPort> attachmentPoints;
+    private List<SwitchPort> attachmentPoints;
     private long lastSeenTime;
 
     /**
      * Default constructor for Serializer to use.
      */
     @Deprecated
-    public DeviceEvent() {
+    protected DeviceEvent() {
         mac = null;
     }
 
@@ -44,31 +50,103 @@
         this.attachmentPoints = new LinkedList<>();
     }
 
+    /**
+     * Create an unfrozen copy of given Object.
+     *
+     * @param original to make copy of.
+     */
+    public DeviceEvent(DeviceEvent original) {
+        super(original);
+        this.mac = original.mac;
+        this.attachmentPoints = new ArrayList<>(original.attachmentPoints);
+    }
+
+
     public MACAddress getMac() {
         return mac;
     }
 
     public List<SwitchPort> getAttachmentPoints() {
-        return attachmentPoints;
+        return Collections.unmodifiableList(attachmentPoints);
     }
 
     public void setAttachmentPoints(List<SwitchPort> attachmentPoints) {
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
         this.attachmentPoints = attachmentPoints;
     }
 
     public void addAttachmentPoint(SwitchPort attachmentPoint) {
-        // may need to maintain uniqness
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
+        // may need to maintain uniqueness
         this.attachmentPoints.add(0, attachmentPoint);
     }
 
+    public boolean removeAttachmentPoint(SwitchPort attachmentPoint) {
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
+        return this.attachmentPoints.remove(attachmentPoint);
+    }
+
+
+    public long getLastSeenTime() {
+        return lastSeenTime;
+    }
+
+    public void setLastSeenTime(long lastSeenTime) {
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
+        this.lastSeenTime = lastSeenTime;
+    }
+
     @Override
     public String toString() {
         return "[DeviceEvent " + mac + " attachmentPoints:" + attachmentPoints + "]";
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result
+                + ((attachmentPoints == null) ? 0 : attachmentPoints.hashCode());
+        result = prime * result + ((mac == null) ? 0 : mac.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!super.equals(obj)) {
+            return false;
+        }
+
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        DeviceEvent other = (DeviceEvent) obj;
+
+        if (!super.equals(obj)) {
+            return false;
+        }
+
+        // XXX lastSeenTime excluded from Equality condition, is it OK?
+        return Objects.equals(mac, other.mac) &&
+                Objects.equals(this.attachmentPoints, other.attachmentPoints);
+    }
+
     // Assuming mac is unique cluster-wide
     public static ByteBuffer getDeviceID(final byte[] mac) {
-        return (ByteBuffer) ByteBuffer.allocate(2 + mac.length).putChar('D').put(mac).flip();
+        return (ByteBuffer) ByteBuffer.allocate(2 + mac.length)
+                .putChar('D').put(mac).flip();
     }
 
     public byte[] getID() {
@@ -78,12 +156,4 @@
     public ByteBuffer getIDasByteBuffer() {
         return getDeviceID(mac.toBytes());
     }
-
-    public long getLastSeenTime() {
-        return lastSeenTime;
-    }
-
-    public void setLastSeenTime(long lastSeenTime) {
-        this.lastSeenTime = lastSeenTime;
-    }
 }
diff --git a/src/main/java/net/onrc/onos/core/topology/DeviceImpl.java b/src/main/java/net/onrc/onos/core/topology/DeviceImpl.java
index 19fe0a7..61de0d2 100644
--- a/src/main/java/net/onrc/onos/core/topology/DeviceImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/DeviceImpl.java
@@ -1,47 +1,99 @@
 package net.onrc.onos.core.topology;
 
-import java.util.Collections;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.Validate;
 
 import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.util.SwitchPort;
 
 /**
  * Device Object stored in In-memory Topology.
  */
 public class DeviceImpl extends TopologyObject implements Device {
 
-    private final MACAddress macAddr;
-    protected LinkedList<Port> attachmentPoints;
-    private long lastSeenTime;
+    //////////////////////////////////////////////////////
+    /// Topology element attributes
+    ///  - any changes made here needs to be replicated.
+    //////////////////////////////////////////////////////
+    private DeviceEvent deviceObj;
 
-    public DeviceImpl(Topology topology, MACAddress mac) {
+    ///////////////////
+    /// In-memory index
+    ///////////////////
+
+    // none
+
+    /**
+     * Creates a Device object based on {@link DeviceEvent}.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param scHost self contained {@link DeviceEvent}
+     */
+    public DeviceImpl(Topology topology, DeviceEvent scHost) {
         super(topology);
-        this.macAddr = mac;
-        this.attachmentPoints = new LinkedList<>();
+        Validate.notNull(scHost);
+
+        // TODO should we assume deviceObj is already frozen before this call
+        //      or expect attribute update will happen after .
+        if (scHost.isFrozen()) {
+            this.deviceObj = scHost;
+        } else {
+            this.deviceObj = new DeviceEvent(scHost);
+            this.deviceObj.freeze();
+        }
+    }
+
+    /**
+     * Creates a Device object with empty attributes.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param mac MAC address of the host
+     */
+    public DeviceImpl(Topology topology, MACAddress mac) {
+        this(topology, new DeviceEvent(mac).freeze());
     }
 
     @Override
     public MACAddress getMacAddress() {
-        return this.macAddr;
+        return this.deviceObj.getMac();
     }
 
     @Override
     public Iterable<Port> getAttachmentPoints() {
-        return Collections.unmodifiableList(this.attachmentPoints);
+        List<Port> ports = new ArrayList<>();
+        topology.acquireReadLock();
+        try {
+            for (SwitchPort swp : this.deviceObj.getAttachmentPoints()) {
+                Port p = this.topology.getPort(swp);
+                if (p != null) {
+                    ports.add(p);
+                }
+            }
+        } finally {
+            topology.releaseReadLock();
+        }
+        return ports;
     }
 
     @Override
     public long getLastSeenTime() {
-        return lastSeenTime;
+        return deviceObj.getLastSeenTime();
     }
 
     @Override
     public String toString() {
-        return macAddr.toString();
+        return getMacAddress().toString();
     }
 
+    // TODO we may no longer need this. confirm and delete later.
     void setLastSeenTime(long lastSeenTime) {
-        this.lastSeenTime = lastSeenTime;
+        // XXX Following will make this instance thread unsafe. Need to use AtomicRef.
+        DeviceEvent updated = new DeviceEvent(this.deviceObj);
+        updated.setLastSeenTime(lastSeenTime);
+        updated.freeze();
+        this.deviceObj = updated;
     }
 
     /**
@@ -50,8 +102,12 @@
      * @param port the port that the device is attached to
      */
     void addAttachmentPoint(Port port) {
-        this.attachmentPoints.remove(port);
-        this.attachmentPoints.addFirst(port);
+        // XXX Following will make this instance thread unsafe. Need to use AtomicRef.
+        DeviceEvent updated = new DeviceEvent(this.deviceObj);
+        updated.removeAttachmentPoint(port.asSwitchPort());
+        updated.addAttachmentPoint(port.asSwitchPort());
+        updated.freeze();
+        this.deviceObj = updated;
     }
 
     /**
@@ -60,7 +116,12 @@
      * @param port the port that the device is attached to
      */
     boolean removeAttachmentPoint(Port port) {
-        return this.attachmentPoints.remove(port);
+        // XXX Following will make this instance thread unsafe. Need to use AtomicRef.
+        DeviceEvent updated = new DeviceEvent(this.deviceObj);
+        final boolean result = updated.removeAttachmentPoint(port.asSwitchPort());
+        updated.freeze();
+        this.deviceObj = updated;
+        return result;
     }
 
 
diff --git a/src/main/java/net/onrc/onos/core/topology/LinkEvent.java b/src/main/java/net/onrc/onos/core/topology/LinkEvent.java
index 140e4c7..d73f80f 100644
--- a/src/main/java/net/onrc/onos/core/topology/LinkEvent.java
+++ b/src/main/java/net/onrc/onos/core/topology/LinkEvent.java
@@ -1,34 +1,65 @@
 package net.onrc.onos.core.topology;
 
 import java.nio.ByteBuffer;
+import java.util.Objects;
 
 import net.onrc.onos.core.topology.web.serializers.LinkEventSerializer;
 import net.onrc.onos.core.util.Dpid;
 import net.onrc.onos.core.util.PortNumber;
 import net.onrc.onos.core.util.SwitchPort;
 
+import org.apache.commons.lang.Validate;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 
 /**
  * Self-contained Link event Object.
  * <p/>
- * TODO: We probably want common base class/interface for Self-Contained Event Object.
+ * TODO: Rename to match what it is. (Switch/Port/Link/Device)Snapshot?
+ * FIXME: Current implementation directly use this object as
+ *        Replication message, but should be sending update operation info.
  */
 
 @JsonSerialize(using = LinkEventSerializer.class)
-public class LinkEvent {
-    protected final SwitchPort src;
-    protected final SwitchPort dst;
+public class LinkEvent extends TopologyElement<LinkEvent> {
+
+    private final SwitchPort src;
+    private final SwitchPort dst;
+    // TODO add LastSeenTime, Capacity if appropriate
 
     /**
      * Default constructor for Serializer to use.
      */
     @Deprecated
-    public LinkEvent() {
+    protected LinkEvent() {
         src = null;
         dst = null;
     }
 
+    /**
+     * Creates the Link object.
+     *
+     * @param src source SwitchPort
+     * @param dst destination SwitchPort
+     */
+    public LinkEvent(SwitchPort src, SwitchPort dst) {
+        Validate.notNull(src);
+        Validate.notNull(dst);
+
+        this.src = src;
+        this.dst = dst;
+    }
+
+    /**
+     * Create an unfrozen copy of given Object.
+     *
+     * @param original to make copy of.
+     */
+    public LinkEvent(LinkEvent original) {
+        super(original);
+        this.src = original.src;
+        this.dst = original.dst;
+    }
+
     public LinkEvent(Long srcDpid, Long srcPortNo, Long dstDpid,
                      Long dstPortNo) {
         src = new SwitchPort(srcDpid, srcPortNo);
@@ -42,16 +73,34 @@
                 link.getDstPort().getNumber());
     }
 
+    /**
+     * Creates the Link object.
+     *
+     * @param srcDpid source switch DPID
+     * @param srcPortNo source port number
+     * @param dstDpid destination switch DPID
+     * @param dstPortNo destination port number
+     */
     public LinkEvent(Dpid srcDpid, PortNumber srcPortNo,
                      Dpid dstDpid, PortNumber dstPortNo) {
         src = new SwitchPort(srcDpid, srcPortNo);
         dst = new SwitchPort(dstDpid, dstPortNo);
     }
 
+    /**
+     * Gets the source SwitchPort.
+     *
+     * @return source SwitchPort.
+     */
     public SwitchPort getSrc() {
         return src;
     }
 
+    /**
+     * Gets the destination SwitchPort.
+     *
+     * @return destination SwitchPort.
+     */
     public SwitchPort getDst() {
         return dst;
     }
@@ -71,7 +120,8 @@
 
     public static ByteBuffer getLinkID(Long srcDpid, Long srcPortNo,
                                        Long dstDpid, Long dstPortNo) {
-        return (ByteBuffer) ByteBuffer.allocate(LinkEvent.LINKID_BYTES).putChar('L')
+        return (ByteBuffer) ByteBuffer.allocate(LinkEvent.LINKID_BYTES)
+                .putChar('L')
                 .put(PortEvent.getPortID(srcDpid, srcPortNo))
                 .put(PortEvent.getPortID(dstDpid, dstPortNo)).flip();
     }
@@ -88,7 +138,7 @@
     @Override
     public int hashCode() {
         final int prime = 31;
-        int result = 1;
+        int result = super.hashCode();
         result = prime * result + ((dst == null) ? 0 : dst.hashCode());
         result = prime * result + ((src == null) ? 0 : src.hashCode());
         return result;
@@ -99,27 +149,22 @@
         if (this == obj) {
             return true;
         }
+
         if (obj == null) {
             return false;
         }
+
         if (getClass() != obj.getClass()) {
             return false;
         }
         LinkEvent other = (LinkEvent) obj;
-        if (dst == null) {
-            if (other.dst != null) {
-                return false;
-            }
-        } else if (!dst.equals(other.dst)) {
+
+        // compare attributes
+        if (!super.equals(obj)) {
             return false;
         }
-        if (src == null) {
-            if (other.src != null) {
-                return false;
-            }
-        } else if (!src.equals(other.src)) {
-            return false;
-        }
-        return true;
+
+        return Objects.equals(this.src, other.src) &&
+                Objects.equals(this.dst, other.dst);
     }
 }
diff --git a/src/main/java/net/onrc/onos/core/topology/LinkImpl.java b/src/main/java/net/onrc/onos/core/topology/LinkImpl.java
index d0276d1..aa635ef 100644
--- a/src/main/java/net/onrc/onos/core/topology/LinkImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/LinkImpl.java
@@ -2,8 +2,7 @@
 
 import java.util.Map;
 
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import net.onrc.onos.core.util.SwitchPort;
+import org.apache.commons.lang.Validate;
 
 /**
  * Link Object stored in In-memory Topology.
@@ -12,50 +11,100 @@
  * but this Object itself will not issue any read/write to the DataStore.
  */
 public class LinkImpl extends TopologyObject implements Link {
-    private SwitchPort srcPort;
-    private SwitchPort dstPort;
 
+    //////////////////////////////////////////////////////
+    /// Topology element attributes
+    ///  - any changes made here needs to be replicated.
+    //////////////////////////////////////////////////////
+    private LinkEvent linkObj;
+
+    // TODO remove?
     protected static final Double DEFAULT_CAPACITY = Double.POSITIVE_INFINITY;
     protected Double capacity = DEFAULT_CAPACITY;
 
+    ///////////////////
+    /// In-memory index
+    ///////////////////
+
+    // none
+
     /**
-     * Constructor for when a link is read from the database and the Ports
-     * already exist in the in-memory topology.
+     * Creates a Link object based on {@link LinkEvent}.
      *
-     * @param topology
-     * @param srcPort
-     * @param dstPort
+     * @param topology Topology instance this object belongs to
+     * @param scPort self contained {@link LinkEvent}
+     */
+    public LinkImpl(Topology topology, LinkEvent scPort) {
+        super(topology);
+        Validate.notNull(scPort);
+
+        // TODO should we assume linkObj is already frozen before this call
+        //      or expect attribute update will happen after .
+        if (scPort.isFrozen()) {
+            this.linkObj = scPort;
+        } else {
+            this.linkObj = new LinkEvent(scPort);
+            this.linkObj.freeze();
+        }
+    }
+
+    /**
+     * Creates a Link object with empty attributes.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param srcPort source port
+     * @param dstPort destination port
      */
     public LinkImpl(Topology topology, Port srcPort, Port dstPort) {
-        super(topology);
-        this.srcPort = srcPort.asSwitchPort();
-        this.dstPort = dstPort.asSwitchPort();
+        this(topology,
+             new LinkEvent(srcPort.asSwitchPort(),
+                           dstPort.asSwitchPort()).freeze());
     }
 
     @Override
     public Switch getSrcSwitch() {
-        return topology.getSwitch(srcPort.dpid());
+        topology.acquireReadLock();
+        try {
+            return topology.getSwitch(linkObj.getSrc().getDpid());
+        } finally {
+            topology.releaseReadLock();
+        }
     }
 
     @Override
     public Port getSrcPort() {
-        return topology.getPort(srcPort.dpid(), srcPort.port());
+        topology.acquireReadLock();
+        try {
+            return topology.getPort(linkObj.getSrc());
+        } finally {
+            topology.releaseReadLock();
+        }
     }
 
     @Override
     public Switch getDstSwitch() {
-        return topology.getSwitch(dstPort.dpid());
+        topology.acquireReadLock();
+        try {
+            return topology.getSwitch(linkObj.getDst().getDpid());
+        } finally {
+            topology.releaseReadLock();
+        }
     }
 
     @Override
     public Port getDstPort() {
-        return topology.getPort(dstPort.dpid(), dstPort.port());
+        topology.acquireReadLock();
+        try {
+            return topology.getPort(linkObj.getDst());
+        } finally {
+            topology.releaseReadLock();
+        }
     }
 
     @Override
     public long getLastSeenTime() {
         // TODO Auto-generated method stub
-        return 0;
+        throw new UnsupportedOperationException("Not implemented yet");
     }
 
     @Override
@@ -63,18 +112,31 @@
         return capacity;
     }
 
-    public void setCapacity(Double capacity) {
+    void setCapacity(Double capacity) {
         this.capacity = capacity;
     }
 
+    void replaceStringAttributes(LinkEvent updated) {
+        Validate.isTrue(this.linkObj.getSrc().equals(updated.getSrc()),
+                "Wrong LinkEvent given.");
+        Validate.isTrue(this.linkObj.getDst().equals(updated.getDst()),
+                "Wrong LinkEvent given.");
+
+        // XXX simply replacing whole self-contained object for now
+        if (updated.isFrozen()) {
+            this.linkObj = updated;
+        } else {
+            this.linkObj = new LinkEvent(updated).freeze();
+        }
+    }
+
+
     @Override
     public String getStringAttribute(String attr) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        return linkObj.getStringAttribute(attr);
     }
 
     @Override
-    @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE",
-       justification = "getStringAttribute might return null once implemented")
     public String getStringAttribute(String attr, String def) {
         final String v = getStringAttribute(attr);
         if (v == null) {
@@ -86,7 +148,7 @@
 
     @Override
     public Map<String, String> getAllStringAttributes() {
-        throw new UnsupportedOperationException("Not implemented yet");
+        return linkObj.getAllStringAttributes();
     }
 
     @Override
diff --git a/src/main/java/net/onrc/onos/core/topology/PortEvent.java b/src/main/java/net/onrc/onos/core/topology/PortEvent.java
index 296a567..222a641 100644
--- a/src/main/java/net/onrc/onos/core/topology/PortEvent.java
+++ b/src/main/java/net/onrc/onos/core/topology/PortEvent.java
@@ -7,20 +7,29 @@
 
 import org.apache.commons.lang.Validate;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
+
 import java.nio.ByteBuffer;
 import java.util.Objects;
 
 /**
  * Self-contained Port event Object.
  * <p/>
- * TODO: We probably want common base class/interface for Self-Contained Event Object.
+ * TODO: Rename to match what it is. (Switch/Port/Link/Device)Snapshot?
+ * FIXME: Current implementation directly use this object as
+ *        Replication message, but should be sending update operation info.
  */
 @JsonSerialize(using = PortEventSerializer.class)
-public class PortEvent {
+public class PortEvent extends TopologyElement<PortEvent> {
 
-    protected final SwitchPort id;
+    private final SwitchPort id;
     // TODO Add Hardware Address
-    // TODO Add Description
+
+    // TODO: Where should the attribute names be defined?
+    /**
+     * Attribute name for description.
+     */
+    public static final String DESCRIPTION = "description";
+
 
     /**
      * Default constructor for Serializer to use.
@@ -30,22 +39,64 @@
         id = null;
     }
 
+    /**
+     * Creates the port object.
+     *
+     * @param switchPort SwitchPort to identify this port
+     */
     public PortEvent(SwitchPort switchPort) {
+        Validate.notNull(switchPort);
         this.id = switchPort;
     }
 
+    /**
+     * Creates the port object.
+     *
+     * @param dpid SwitchPort to identify this port
+     * @param number PortNumber to identify this port
+     */
     public PortEvent(Dpid dpid, PortNumber number) {
         this.id = new SwitchPort(dpid, number);
     }
 
+    /**
+     * Create an unfrozen copy of given Object.
+     *
+     * @param original to make copy of.
+     */
+    public PortEvent(PortEvent original) {
+        super(original);
+        this.id = original.id;
+    }
+
+    // TODO remove me when ready
     public PortEvent(Long dpid, Long number) {
         this.id = new SwitchPort(dpid, number);
     }
 
+    /**
+     * Gets the SwitchPort identifying this port.
+     *
+     * @return SwitchPort
+     */
+    public SwitchPort getSwitchPort() {
+        return id;
+    }
+
+    /**
+     * Gets the Dpid of the switch this port belongs to.
+     *
+     * @return DPID
+     */
     public Dpid getDpid() {
         return id.getDpid();
     }
 
+    /**
+     * Gets the port number.
+     *
+     * @return port number
+     */
     public PortNumber getPortNumber() {
         return id.getPortNumber();
     }
@@ -56,17 +107,26 @@
             return true;
         }
 
-        if (!(o instanceof PortEvent)) {
+        if (o == null) {
             return false;
         }
 
-        PortEvent that = (PortEvent) o;
-        return Objects.equals(this.id, that.id);
+        if (getClass() != o.getClass()) {
+            return false;
+        }
+        PortEvent other = (PortEvent) o;
+
+        // compare attributes
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        return Objects.equals(this.id, other.id);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(id);
+        return 31 * super.hashCode() + Objects.hashCode(id);
     }
 
     @Override
@@ -89,7 +149,8 @@
         if (number == null) {
             throw new IllegalArgumentException("number cannot be null");
         }
-        return (ByteBuffer) ByteBuffer.allocate(PortEvent.PORTID_BYTES).putChar('S').putLong(dpid)
+        return (ByteBuffer) ByteBuffer.allocate(PortEvent.PORTID_BYTES)
+                .putChar('S').putLong(dpid)
                 .putChar('P').putLong(number).flip();
     }
 
diff --git a/src/main/java/net/onrc/onos/core/topology/PortImpl.java b/src/main/java/net/onrc/onos/core/topology/PortImpl.java
index 9332764..8a5e783 100644
--- a/src/main/java/net/onrc/onos/core/topology/PortImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/PortImpl.java
@@ -1,7 +1,9 @@
 package net.onrc.onos.core.topology;
 
 import java.util.Map;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import org.apache.commons.lang.Validate;
+
 import net.onrc.onos.core.util.Dpid;
 import net.onrc.onos.core.util.PortNumber;
 import net.onrc.onos.core.util.SwitchPort;
@@ -14,86 +16,166 @@
  */
 public class PortImpl extends TopologyObject implements Port {
 
-    private Switch sw;
+    //////////////////////////////////////////////////////
+    /// Topology element attributes
+    ///  - any changes made here needs to be replicated.
+    //////////////////////////////////////////////////////
+    private PortEvent portObj;
 
-    private PortNumber number;
-    private String description;
+    ///////////////////
+    /// In-memory index
+    ///////////////////
 
-    private final SwitchPort switchPort;
 
-    public PortImpl(Topology topology, Switch parentSwitch, PortNumber number) {
+    /**
+     * Creates a Port object based on {@link PortEvent}.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param scPort self contained {@link PortEvent}
+     */
+    public PortImpl(Topology topology, PortEvent scPort) {
         super(topology);
-        this.sw = parentSwitch;
-        this.number = number;
+        Validate.notNull(scPort);
 
-        switchPort = new SwitchPort(parentSwitch.getDpid(),
-                                    number);
+        // TODO should we assume portObj is already frozen before this call
+        //      or expect attribute update will happen after .
+        if (scPort.isFrozen()) {
+            this.portObj = scPort;
+        } else {
+            this.portObj = new PortEvent(scPort);
+            this.portObj.freeze();
+        }
     }
 
+    /**
+     * Creates a Port object with empty attributes.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param switchPort SwitchPort
+     */
+    public PortImpl(Topology topology, SwitchPort switchPort) {
+        this(topology, new PortEvent(switchPort).freeze());
+    }
+
+    /**
+     * Creates a Port object with empty attributes.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param dpid DPID
+     * @param number PortNumber
+     */
+    public PortImpl(Topology topology, Dpid dpid, PortNumber number) {
+        this(topology, new SwitchPort(dpid, number));
+        Validate.notNull(dpid);
+        Validate.notNull(number);
+    }
+
+    public PortImpl(Topology topology, Long dpid, Long number) {
+        this(topology, new SwitchPort(dpid, number));
+        Validate.notNull(dpid);
+        Validate.notNull(number);
+    }
+
+    @Deprecated
+    public PortImpl(Topology topology, Switch parentSwitch, PortNumber number) {
+        this(topology, new SwitchPort(parentSwitch.getDpid(), number));
+    }
+
+    @Deprecated
     public PortImpl(Topology topology, Switch parentSwitch, Long number) {
         this(topology, parentSwitch, new PortNumber(number.shortValue()));
     }
 
     @Override
     public Dpid getDpid() {
-        return sw.getDpid();
+        return asSwitchPort().getDpid();
     }
 
     @Override
     public PortNumber getNumber() {
-        return number;
+        return asSwitchPort().getPortNumber();
     }
 
     @Override
     public SwitchPort asSwitchPort() {
-        return switchPort;
+        return portObj.getSwitchPort();
     }
 
     @Override
     public String getDescription() {
-        return description;
+        return getStringAttribute(PortEvent.DESCRIPTION, "");
     }
 
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    @Override
-    public Long getHardwareAddress() {
-        // TODO Auto-generated method stub
-        return 0L;
-    }
-
-    @Override
-    public Switch getSwitch() {
-        return sw;
-    }
-
-    @Override
-    public Link getOutgoingLink() {
-        return topology.getOutgoingLink(switchPort.dpid(),
-                                        switchPort.port());
-    }
-
-    @Override
-    public Link getIncomingLink() {
-        return topology.getIncomingLink(switchPort.dpid(),
-                                        switchPort.port());
-    }
-
-    @Override
-    public Iterable<Device> getDevices() {
-        return topology.getDevices(this.asSwitchPort());
-    }
-
-    @Override
-    public String getStringAttribute(String attr) {
+    void setDescription(String description) {
+//        portObj.createStringAttribute(attr, value);
+        // TODO implement using attributes
         throw new UnsupportedOperationException("Not implemented yet");
     }
 
     @Override
-    @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE",
-       justification = "getStringAttribute might return null once implemented")
+    public Long getHardwareAddress() {
+        // TODO implement using attributes?
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public Switch getSwitch() {
+        topology.acquireReadLock();
+        try {
+            return topology.getSwitch(getDpid());
+        } finally {
+            topology.releaseReadLock();
+        }
+    }
+
+    @Override
+    public Link getOutgoingLink() {
+        topology.acquireReadLock();
+        try {
+            return topology.getOutgoingLink(asSwitchPort());
+        } finally {
+            topology.releaseReadLock();
+        }
+    }
+
+    @Override
+    public Link getIncomingLink() {
+        topology.acquireReadLock();
+        try {
+            return topology.getIncomingLink(asSwitchPort());
+        } finally {
+            topology.releaseReadLock();
+        }
+    }
+
+    @Override
+    public Iterable<Device> getDevices() {
+        topology.acquireReadLock();
+        try {
+            return topology.getDevices(this.asSwitchPort());
+        } finally {
+            topology.releaseReadLock();
+        }
+    }
+
+    void replaceStringAttributes(PortEvent updated) {
+        Validate.isTrue(this.asSwitchPort().equals(updated.getSwitchPort()),
+                "Wrong PortEvent given.");
+
+        // XXX simply replacing whole self-contained object for now
+        if (updated.isFrozen()) {
+            this.portObj = updated;
+        } else {
+            this.portObj = new PortEvent(updated).freeze();
+        }
+    }
+
+    @Override
+    public String getStringAttribute(String attr) {
+        return portObj.getStringAttribute(attr);
+    }
+
+    @Override
     public String getStringAttribute(String attr, String def) {
         final String v = getStringAttribute(attr);
         if (v == null) {
@@ -105,7 +187,7 @@
 
     @Override
     public Map<String, String> getAllStringAttributes() {
-        throw new UnsupportedOperationException("Not implemented yet");
+        return portObj.getAllStringAttributes();
     }
 
     @Override
diff --git a/src/main/java/net/onrc/onos/core/topology/StringAttributes.java b/src/main/java/net/onrc/onos/core/topology/StringAttributes.java
index 89c5b5c..16f69a8 100644
--- a/src/main/java/net/onrc/onos/core/topology/StringAttributes.java
+++ b/src/main/java/net/onrc/onos/core/topology/StringAttributes.java
@@ -2,6 +2,8 @@
 
 import java.util.Map;
 
+// TODO We may want to separate configuration attributes and
+//      running state attributes.
 /**
  * Interface for Elements with StringAttributes.
  */
diff --git a/src/main/java/net/onrc/onos/core/topology/SwitchEvent.java b/src/main/java/net/onrc/onos/core/topology/SwitchEvent.java
index de0fff6..b6246d4 100644
--- a/src/main/java/net/onrc/onos/core/topology/SwitchEvent.java
+++ b/src/main/java/net/onrc/onos/core/topology/SwitchEvent.java
@@ -7,28 +7,57 @@
 import java.util.Objects;
 
 import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.apache.commons.lang.Validate;
 
 /**
  * Self-contained Switch Object.
  * <p/>
- * TODO: We probably want common base class/interface for Self-Contained Event Object.
+ * TODO: Rename to match what it is. (Switch/Port/Link/Device)Snapshot?
+ * FIXME: Current implementation directly use this object as
+ *        Replication message, but should be sending update operation info.
  */
 @JsonSerialize(using = SwitchEventSerializer.class)
-public class SwitchEvent {
-    protected final Dpid dpid;
+public class SwitchEvent extends TopologyElement<SwitchEvent> {
+    private final Dpid dpid;
 
     /**
      * Default constructor for Serializer to use.
      */
     @Deprecated
-    public SwitchEvent() {
+    protected SwitchEvent() {
         dpid = null;
     }
 
+    /**
+     * Creates the switch object.
+     *
+     * @param dpid Dpid to identify this switch
+     */
+    public SwitchEvent(Dpid dpid) {
+        Validate.notNull(dpid);
+        this.dpid = dpid;
+    }
+
+    /**
+     * Create an unfrozen copy of given Object.
+     *
+     * @param original to make copy of.
+     */
+    public SwitchEvent(SwitchEvent original) {
+        super(original);
+        this.dpid = original.dpid;
+    }
+
+    // TODO remove me when ready
     public SwitchEvent(Long dpid) {
         this.dpid = new Dpid(dpid);
     }
 
+    /**
+     * Gets the DPID identifying this switch.
+     *
+     * @return DPID
+     */
     public Dpid getDpid() {
         return dpid;
     }
@@ -39,17 +68,26 @@
             return true;
         }
 
-        if (!(o instanceof SwitchEvent)) {
+        if (o == null) {
             return false;
         }
 
-        SwitchEvent that = (SwitchEvent) o;
-        return Objects.equals(this.dpid, that.dpid);
+        if (getClass() != o.getClass()) {
+            return false;
+        }
+        SwitchEvent other = (SwitchEvent) o;
+
+        // compare attributes
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        return Objects.equals(this.dpid, other.dpid);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(dpid);
+        return 31 * super.hashCode() + Objects.hashCode(dpid);
     }
 
     @Override
@@ -59,11 +97,16 @@
 
     public static final int SWITCHID_BYTES = 2 + 8;
 
+    public static ByteBuffer getSwitchID(Dpid dpid) {
+        return getSwitchID(dpid.value());
+    }
+
     public static ByteBuffer getSwitchID(Long dpid) {
         if (dpid == null) {
             throw new IllegalArgumentException("dpid cannot be null");
         }
-        return (ByteBuffer) ByteBuffer.allocate(SwitchEvent.SWITCHID_BYTES).putChar('S').putLong(dpid).flip();
+        return (ByteBuffer) ByteBuffer.allocate(SwitchEvent.SWITCHID_BYTES)
+                .putChar('S').putLong(dpid).flip();
     }
 
     public byte[] getID() {
diff --git a/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java b/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java
index 9457cb5..7fbc43d 100644
--- a/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java
@@ -10,7 +10,8 @@
 
 import net.onrc.onos.core.util.Dpid;
 import net.onrc.onos.core.util.PortNumber;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import org.apache.commons.lang.Validate;
 
 /**
  * Switch Object stored in In-memory Topology.
@@ -20,30 +21,76 @@
  */
 public class SwitchImpl extends TopologyObject implements Switch {
 
-    private Dpid dpid;
+    //////////////////////////////////////////////////////
+    /// Topology element attributes
+    ///  - any changes made here needs to be replicated.
+    //////////////////////////////////////////////////////
+    private SwitchEvent switchObj;
 
+    ///////////////////
+    /// In-memory index
+    ///////////////////
+
+    // none
+
+    // TODO remove when test codes are cleaned up.
     public SwitchImpl(Topology topology, Long dpid) {
         this(topology, new Dpid(dpid));
     }
 
+    /**
+     * Creates a Switch object with empty attributes.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param dpid DPID
+     */
     public SwitchImpl(Topology topology, Dpid dpid) {
+        this(topology, new SwitchEvent(dpid).freeze());
+    }
+
+    /**
+     * Creates a Switch object based on {@link SwitchEvent}.
+     *
+     * @param topology Topology instance this object belongs to
+     * @param scSw self contained {@link SwitchEvent}
+     */
+    public SwitchImpl(Topology topology, SwitchEvent scSw) {
         super(topology);
-        this.dpid = dpid;
+        Validate.notNull(scSw);
+
+        // TODO should we assume switchObj is already frozen before this call
+        //      or expect attribute update will happen after .
+        if (scSw.isFrozen()) {
+            this.switchObj = scSw;
+        } else {
+            this.switchObj = new SwitchEvent(scSw);
+            this.switchObj.freeze();
+        }
     }
 
     @Override
     public Dpid getDpid() {
-        return dpid;
+        return switchObj.getDpid();
     }
 
     @Override
     public Collection<Port> getPorts() {
-        return topology.getPorts(getDpid());
+        topology.acquireReadLock();
+        try {
+            return topology.getPorts(getDpid());
+        } finally {
+            topology.releaseReadLock();
+        }
     }
 
     @Override
     public Port getPort(PortNumber number) {
-        return topology.getPort(getDpid(), number);
+        topology.acquireReadLock();
+        try {
+            return topology.getPort(getDpid(), number);
+        } finally {
+            topology.releaseReadLock();
+        }
     }
 
     @Override
@@ -84,12 +131,26 @@
         return devices;
     }
 
-    public Port addPort(Long portNumber) {
-        PortImpl port = new PortImpl(topology, this, portNumber);
+    // FIXME this should be removed from here and moved to MockTopology
+    Port addPort(Long portNumber) {
+        PortImpl port = new PortImpl(topology, getDpid(),
+                                new PortNumber(portNumber.shortValue()));
         ((TopologyImpl) topology).putPort(port);
         return port;
     }
 
+    void replaceStringAttributes(SwitchEvent updated) {
+        Validate.isTrue(this.getDpid().equals(updated.getDpid()),
+                "Wrong SwitchEvent given.");
+
+        // XXX simply replacing whole self-contained object for now
+        if (updated.isFrozen()) {
+            this.switchObj = updated;
+        } else {
+            this.switchObj = new SwitchEvent(updated).freeze();
+        }
+    }
+
     @Override
     public Iterable<Link> getOutgoingLinks() {
         LinkedList<Link> links = new LinkedList<Link>();
@@ -116,12 +177,10 @@
 
     @Override
     public String getStringAttribute(String attr) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        return this.switchObj.getStringAttribute(attr);
     }
 
     @Override
-    @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE",
-       justification = "getStringAttribute might return null once implemented")
     public String getStringAttribute(String attr, String def) {
         final String v = getStringAttribute(attr);
         if (v == null) {
@@ -133,12 +192,12 @@
 
     @Override
     public Map<String, String> getAllStringAttributes() {
-        throw new UnsupportedOperationException("Not implemented yet");
+        return this.switchObj.getAllStringAttributes();
     }
 
     @Override
     public String toString() {
-        return dpid.toString();
+        return getDpid().toString();
     }
 
     /**
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyElement.java b/src/main/java/net/onrc/onos/core/topology/TopologyElement.java
new file mode 100644
index 0000000..3a6dad8
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyElement.java
@@ -0,0 +1,154 @@
+package net.onrc.onos.core.topology;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang.Validate;
+
+/**
+ * Base class for Topology Elements.
+ * <p/>
+ * Self-contained element, it is expected to be used as if it is an immutable
+ * object.
+ *
+ * @param <T> Sub-class' type.
+ *      (Required to define a method returning itself's type)
+ */
+public class TopologyElement<T extends TopologyElement<T>>
+        implements StringAttributes, UpdateStringAttributes {
+
+    private boolean isFrozen = false;
+
+    private ConcurrentMap<String, String> stringAttributes;
+
+
+
+    /**
+     * Default constructor for serializer.
+     */
+    protected TopologyElement() {
+        this.isFrozen = false;
+        this.stringAttributes = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * Create an unfrozen copy of given Object.
+     * <p/>
+     * Sub-classes should do a deep-copies if necessary.
+     *
+     * @param original to make copy of.
+     */
+    public TopologyElement(TopologyElement<T> original) {
+        this.isFrozen = false;
+        this.stringAttributes = new ConcurrentHashMap<>(original.stringAttributes);
+    }
+
+
+    /**
+     * Tests if this instance is frozen.
+     *
+     * @return true if frozen.
+     */
+    public boolean isFrozen() {
+        return isFrozen;
+    }
+
+    /**
+     * Freezes this instance to avoid further modifications.
+     *
+     * @return this
+     */
+    @SuppressWarnings("unchecked")
+    public T freeze() {
+        isFrozen = true;
+        return (T) this;
+    }
+
+    @Override
+    public int hashCode() {
+        return stringAttributes.hashCode();
+    }
+
+    /*
+     *  (non-Javadoc)
+     * Equality based only on string attributes.
+     *
+     * Subclasses should call super.equals().
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        @SuppressWarnings("unchecked")
+        TopologyElement<T> other = (TopologyElement<T>) obj;
+        return Objects.equals(stringAttributes, other.stringAttributes);
+    }
+
+    @Override
+    public String getStringAttribute(String attr) {
+        return this.stringAttributes.get(attr);
+    }
+
+    @Override
+    public String getStringAttribute(String attr, String def) {
+        final String v = getStringAttribute(attr);
+        if (v == null) {
+            return def;
+        } else {
+            return v;
+        }
+    }
+
+    @Override
+    public Map<String, String> getAllStringAttributes() {
+        return Collections.unmodifiableMap(this.stringAttributes);
+    }
+
+    @Override
+    public boolean createStringAttribute(String attr, String value) {
+        if (isFrozen) {
+            throw new IllegalStateException("Tried to modify frozen object: " + this);
+        }
+        Validate.notNull(value, "attribute value cannot be null");
+
+        return this.stringAttributes.putIfAbsent(attr, value) == null;
+    }
+
+    @Override
+    public boolean replaceStringAttribute(String attr, String oldValue, String value) {
+        if (isFrozen) {
+            throw new IllegalStateException("Tried to modify frozen object: " + this);
+        }
+        Validate.notNull(value, "attribute value cannot be null");
+
+        return this.stringAttributes.replace(attr, oldValue, value);
+    }
+
+    @Override
+    public boolean deleteStringAttribute(String attr, String expectedValue) {
+        if (isFrozen) {
+            throw new IllegalStateException("Tried to modify frozen object: " + this);
+        }
+
+        return this.stringAttributes.remove(attr, expectedValue);
+    }
+
+    @Override
+    public void deleteStringAttribute(String attr) {
+        if (isFrozen) {
+            throw new IllegalStateException("Tried to modify frozen object: " + this);
+        }
+
+        this.stringAttributes.remove(attr);
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyManager.java b/src/main/java/net/onrc/onos/core/topology/TopologyManager.java
index 3a07693..b079cae 100644
--- a/src/main/java/net/onrc/onos/core/topology/TopologyManager.java
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyManager.java
@@ -704,7 +704,7 @@
                     discoveredAddedLinkEvents.get(portEvent.getDpid());
             if (oldLinkEvents != null) {
                 for (LinkEvent linkEvent : new ArrayList<>(oldLinkEvents.values())) {
-                    if (linkEvent.getDst().equals(portEvent.id)) {
+                    if (linkEvent.getDst().equals(portEvent.getSwitchPort())) {
                         removeLinkDiscoveryEvent(linkEvent);
                         // XXX If we change our model to allow multiple Link on
                         // a Port, this loop must be fixed to allow continuing.
@@ -721,7 +721,7 @@
             if (oldDeviceEvents != null) {
                 for (DeviceEvent deviceEvent : new ArrayList<>(oldDeviceEvents.values())) {
                     for (SwitchPort swp : deviceEvent.getAttachmentPoints()) {
-                        if (swp.equals(portEvent.id)) {
+                        if (swp.equals(portEvent.getSwitchPort())) {
                             removedDeviceEvents.add(deviceEvent);
                         }
                     }
@@ -836,28 +836,31 @@
     }
 
     /**
-     * Add a switch to the topology.
+     * Add a switch to the topology replica.
      *
-     * @param switchEvent the Switch Event with the switch to add.
+     * @param switchEvent the SwitchEvent with the switch to add.
      */
     @GuardedBy("topology.writeLock")
     private void addSwitch(SwitchEvent switchEvent) {
         Switch sw = topology.getSwitch(switchEvent.getDpid());
         if (sw == null) {
-            sw = new SwitchImpl(topology, switchEvent.getDpid());
+            sw = new SwitchImpl(topology, switchEvent);
             topology.putSwitch(sw);
         } else {
             // TODO: Update the switch attributes
-            // TODO: Nothing to do for now
             log.debug("Update switch attributes");
+            SwitchImpl impl = (SwitchImpl) sw;
+            impl.replaceStringAttributes(switchEvent);
         }
         apiAddedSwitchEvents.add(switchEvent);
     }
 
     /**
-     * Remove a switch from the topology.
+     * Remove a switch from the topology replica.
+     * <p/>
+     * It will call {@link #removePort(PortEvent)} for each ports on this switch.
      *
-     * @param switchEvent the Switch Event with the switch to remove.
+     * @param switchEvent the SwitchEvent with the switch to remove.
      */
     @GuardedBy("topology.writeLock")
     private void removeSwitch(SwitchEvent switchEvent) {
@@ -887,9 +890,9 @@
     }
 
     /**
-     * Add a port to the topology.
+     * Add a port to the topology replica.
      *
-     * @param portEvent the Port Event with the port to add.
+     * @param portEvent the PortEvent with the port to add.
      */
     @GuardedBy("topology.writeLock")
     private void addPort(PortEvent portEvent) {
@@ -902,21 +905,27 @@
             return;
         }
 
+
         Port port = sw.getPort(portEvent.getPortNumber());
         if (port == null) {
-            port = new PortImpl(topology, sw, portEvent.getPortNumber());
+            port = new PortImpl(topology, portEvent);
             topology.putPort(port);
         } else {
             // TODO: Update the port attributes
             log.debug("Update port attributes");
+            PortImpl impl = (PortImpl) port;
+            impl.replaceStringAttributes(portEvent);
         }
         apiAddedPortEvents.add(portEvent);
     }
 
     /**
-     * Remove a port from the topology.
+     * Remove a port from the topology replica.
+     * <p/>
+     * It will call {@link #removeDevice(DeviceEvent)} for each hosts on this port
+     * and call {@link #removeLink(LinkEvent)} for each links on this port.
      *
-     * @param portEvent the Port Event with the port to remove.
+     * @param portEvent the PortEvent with the port to remove.
      */
     @GuardedBy("topology.writeLock")
     private void removePort(PortEvent portEvent) {
@@ -972,16 +981,17 @@
         }
 
         // Remove the Port from the Switch
-        SwitchImpl switchImpl = getSwitchImpl(sw);
         topology.removePort(port);
 
         apiRemovedPortEvents.add(portEvent);
     }
 
     /**
-     * Add a link to the topology.
+     * Add a link to the topology replica.
+     * <p/>
+     * It will call {@link #removeDevice(DeviceEvent)} for each hosts on both ports.
      *
-     * @param linkEvent the Link Event with the link to add.
+     * @param linkEvent the LinkEvent with the link to add.
      */
     @GuardedBy("topology.writeLock")
     private void addLink(LinkEvent linkEvent) {
@@ -993,6 +1003,7 @@
             log.debug("{} reordered because {} port is null", linkEvent,
                     (srcPort == null) ? "src" : "dst");
 
+            // XXX domain knowledge: port must be present before link.
             // Reordered event: delay the event in local cache
             ByteBuffer id = linkEvent.getIDasByteBuffer();
             reorderedAddedLinkEvents.put(id, linkEvent);
@@ -1000,10 +1011,14 @@
         }
 
         // Get the Link instance from the Destination Port Incoming Link
+        // XXX domain knowledge: Link is discovered by LLDP,
+        //      thus incoming link is likely to be more up-to-date
+        // FIXME potentially local replica may not be up-to-date yet due to HZ delay.
+        //        may need to manage local truth and use them instead.
         Link link = dstPort.getIncomingLink();
         assert (link == srcPort.getOutgoingLink());
         if (link == null) {
-            link = new LinkImpl(topology, srcPort, dstPort);
+            link = new LinkImpl(topology, linkEvent);
             topology.putLink(link);
 
             // Remove all Devices attached to the Ports
@@ -1015,11 +1030,13 @@
                 for (Device device : port.getDevices()) {
                     log.error("Device {} on Port {} should have been removed prior to adding Link {}",
                             device, port, linkEvent);
+                    // FIXME must get Device info from topology, when we add attrs.
                     DeviceEvent deviceEvent =
                             new DeviceEvent(device.getMacAddress());
                     SwitchPort switchPort =
                             new SwitchPort(port.getSwitch().getDpid(),
                                     port.getNumber());
+                    // adding attachment port which needs to be removed
                     deviceEvent.addAttachmentPoint(switchPort);
                     devicesToRemove.add(deviceEvent);
                 }
@@ -1030,15 +1047,17 @@
         } else {
             // TODO: Update the link attributes
             log.debug("Update link attributes");
+            LinkImpl impl = (LinkImpl) link;
+            impl.replaceStringAttributes(linkEvent);
         }
 
         apiAddedLinkEvents.add(linkEvent);
     }
 
     /**
-     * Remove a link from the topology.
+     * Remove a link from the topology replica.
      *
-     * @param linkEvent the Link Event with the link to remove.
+     * @param linkEvent the LinkEvent with the link to remove.
      */
     @GuardedBy("topology.writeLock")
     private void removeLink(LinkEvent linkEvent) {
@@ -1080,13 +1099,13 @@
     }
 
     /**
-     * Add a device to the topology.
+     * Add a device to the topology replica.
      * <p/>
      * TODO: Device-related work is incomplete.
      * TODO: Eventually, we might need to consider reordering
      * or addLink() and addDevice() events on the same port.
      *
-     * @param deviceEvent the Device Event with the device to add.
+     * @param deviceEvent the DeviceEvent with the device to add.
      */
     @GuardedBy("topology.writeLock")
     private void addDevice(DeviceEvent deviceEvent) {
@@ -1098,11 +1117,14 @@
             device = new DeviceImpl(topology, deviceEvent.getMac());
         }
 
-        DeviceImpl deviceImpl = getDeviceImpl(device);
+        DeviceImpl deviceImpl = (DeviceImpl) device;
 
         // Process each attachment point
         boolean attachmentFound = false;
         for (SwitchPort swp : deviceEvent.getAttachmentPoints()) {
+            // XXX domain knowledge: Port must exist before Device
+            //      but this knowledge cannot be pushed down to driver.
+
             // Attached Ports must exist
             Port port = topology.getPort(swp.getDpid(), swp.getPortNumber());
             if (port == null) {
@@ -1121,13 +1143,10 @@
             }
 
             // Add Device <-> Port attachment
-            PortImpl portImpl = getPortImpl(port);
             deviceImpl.addAttachmentPoint(port);
             attachmentFound = true;
         }
 
-        deviceImpl.setLastSeenTime(deviceEvent.getLastSeenTime());
-
         // Update the device in the topology
         if (attachmentFound) {
             log.debug("Storing the device info into the Topology: mac {}", deviceEvent.getMac());
@@ -1137,7 +1156,7 @@
     }
 
     /**
-     * Remove a device from the topology.
+     * Remove a device from the topology replica.
      * <p/>
      * TODO: Device-related work is incomplete.
      *
@@ -1157,58 +1176,6 @@
     }
 
     /**
-     * Get the SwitchImpl-casted switch implementation.
-     *
-     * @param sw the Switch to cast.
-     * @return the SwitchImpl-casted switch implementation.
-     */
-    private SwitchImpl getSwitchImpl(Switch sw) {
-        if (sw instanceof SwitchImpl) {
-            return (SwitchImpl) sw;
-        }
-        throw new ClassCastException("SwitchImpl expected, but found: " + sw);
-    }
-
-    /**
-     * Get the PortImpl-casted port implementation.
-     *
-     * @param port the Port to cast.
-     * @return the PortImpl-casted port implementation.
-     */
-    private PortImpl getPortImpl(Port port) {
-        if (port instanceof PortImpl) {
-            return (PortImpl) port;
-        }
-        throw new ClassCastException("PortImpl expected, but found: " + port);
-    }
-
-    /**
-     * Get the LinkImpl-casted link implementation.
-     *
-     * @param link the Link to cast.
-     * @return the LinkImpl-casted link implementation.
-     */
-    private LinkImpl getLinkImpl(Link link) {
-        if (link instanceof LinkImpl) {
-            return (LinkImpl) link;
-        }
-        throw new ClassCastException("LinkImpl expected, but found: " + link);
-    }
-
-    /**
-     * Get the DeviceImpl-casted device implementation.
-     *
-     * @param device the Device to cast.
-     * @return the DeviceImpl-casted device implementation.
-     */
-    private DeviceImpl getDeviceImpl(Device device) {
-        if (device instanceof DeviceImpl) {
-            return (DeviceImpl) device;
-        }
-        throw new ClassCastException("DeviceImpl expected, but found: " + device);
-    }
-
-    /**
      * Read the whole topology from the database.
      *
      * @return a collection of EventEntry-encapsulated Topology Events for
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyObject.java b/src/main/java/net/onrc/onos/core/topology/TopologyObject.java
index cdf51bd..661f081 100644
--- a/src/main/java/net/onrc/onos/core/topology/TopologyObject.java
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyObject.java
@@ -1,5 +1,7 @@
 package net.onrc.onos.core.topology;
 
+import org.apache.commons.lang.Validate;
+
 
 
 /**
@@ -18,6 +20,7 @@
      * @param topology Topology instance this object belongs to
      */
     protected TopologyObject(Topology topology) {
+        Validate.notNull(topology);
         this.topology = topology;
     }
 
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyPublisher.java b/src/main/java/net/onrc/onos/core/topology/TopologyPublisher.java
index fbd409f..60b0823 100644
--- a/src/main/java/net/onrc/onos/core/topology/TopologyPublisher.java
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyPublisher.java
@@ -24,6 +24,7 @@
 import net.onrc.onos.core.registry.IControllerRegistryService.ControlChangeCallback;
 import net.onrc.onos.core.registry.RegistryException;
 import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.PortNumber;
 import net.onrc.onos.core.util.SwitchPort;
 
 import org.openflow.protocol.OFPhysicalPort;
@@ -133,9 +134,13 @@
     @Override
     public void linkDiscoveryUpdate(LDUpdate update) {
 
-        LinkEvent linkEvent = new LinkEvent(update.getSrc(),
-                (long) update.getSrcPort(), update.getDst(),
-                (long) update.getDstPort());
+        LinkEvent linkEvent = new LinkEvent(
+                        new SwitchPort(update.getSrc(), update.getSrcPort()),
+                        new SwitchPort(update.getDst(), update.getDstPort()));
+        // FIXME should be merging, with existing attrs, etc..
+        // TODO define attr name as constant somewhere.
+        // TODO populate appropriate attributes.
+        linkEvent.freeze();
 
         switch (update.getOperation()) {
             case LINK_ADDED:
@@ -168,8 +173,15 @@
 
     @Override
     public void switchPortAdded(Long switchId, OFPhysicalPort port) {
+        final Dpid dpid = new Dpid(switchId);
+        PortEvent portEvent = new PortEvent(dpid, new PortNumber(port.getPortNumber()));
+        // FIXME should be merging, with existing attrs, etc..
+        // TODO define attr name as constant somewhere.
+        // TODO populate appropriate attributes.
+        portEvent.createStringAttribute("name", port.getName());
 
-        PortEvent portEvent = new PortEvent(switchId, (long) port.getPortNumber());
+        portEvent.freeze();
+
         if (registryService.hasControl(switchId)) {
             topologyDiscoveryInterface.putPortDiscoveryEvent(portEvent);
             linkDiscovery.removeFromSuppressLLDPs(switchId, port.getPortNumber());
@@ -193,8 +205,15 @@
 
     @Override
     public void addedSwitch(IOFSwitch sw) {
+        final Dpid dpid = new Dpid(sw.getId());
+        SwitchEvent switchEvent = new SwitchEvent(dpid);
+        // FIXME should be merging, with existing attrs, etc..
+        // TODO define attr name as constant somewhere.
+        // TODO populate appropriate attributes.
+        switchEvent.createStringAttribute("ConnectedSince",
+                sw.getConnectedSince().toString());
 
-        SwitchEvent switchEvent = new SwitchEvent(sw.getId());
+        switchEvent.freeze();
 
         // TODO Not very robust
         if (!registryService.hasControl(sw.getId())) {
@@ -205,7 +224,14 @@
 
         List<PortEvent> portEvents = new ArrayList<PortEvent>();
         for (OFPhysicalPort port : sw.getPorts()) {
-            portEvents.add(new PortEvent(sw.getId(), (long) port.getPortNumber()));
+            PortEvent portEvent = new PortEvent(dpid, new PortNumber(port.getPortNumber()));
+            // FIXME should be merging, with existing attrs, etc..
+            // TODO define attr name as constant somewhere.
+            // TODO populate appropriate attributes.
+            portEvent.createStringAttribute("name", port.getName());
+
+            portEvent.freeze();
+            portEvents.add(portEvent);
         }
         topologyDiscoveryInterface
                 .putSwitchDiscoveryEvent(switchEvent, portEvents);
@@ -314,6 +340,7 @@
         event.setAttachmentPoints(spLists);
         event.setLastSeenTime(device.getLastSeenTimestamp().getTime());
         // Does not use vlan info now.
+        event.freeze();
 
         topologyDiscoveryInterface.putDeviceDiscoveryEvent(event);
     }
@@ -322,6 +349,8 @@
     public void onosDeviceRemoved(OnosDevice device) {
         log.debug("Called onosDeviceRemoved");
         DeviceEvent event = new DeviceEvent(device.getMacAddress());
+        // XXX shouldn't we be setting attachment points?
+        event.freeze();
         topologyDiscoveryInterface.removeDeviceDiscoveryEvent(event);
     }
 }
diff --git a/src/main/java/net/onrc/onos/core/topology/UpdateStringAttributes.java b/src/main/java/net/onrc/onos/core/topology/UpdateStringAttributes.java
new file mode 100644
index 0000000..6443b14
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/topology/UpdateStringAttributes.java
@@ -0,0 +1,47 @@
+package net.onrc.onos.core.topology;
+
+// TODO Need better name
+/**
+ * Update String Attributes.
+ */
+public interface UpdateStringAttributes extends StringAttributes {
+
+    /**
+     * Creates the string attribute.
+     *
+     * @param attr attribute name
+     * @param value new value to replace with
+     * @return true if success, false if the attribute already exist
+     */
+    public boolean createStringAttribute(final String attr,
+                                         final String value);
+
+    /**
+     * Replaces the existing string attribute.
+     *
+     * @param attr attribute name
+     * @param oldValue old value to replace
+     * @param value new value to replace with
+     * @return true if success
+     */
+    public boolean replaceStringAttribute(final String attr,
+                                     final String oldValue, final String value);
+
+    /**
+     * Deletes existing string attribute.
+     *
+     * @param attr attribute name
+     * @param expectedValue value expected to be deleted
+     * @return true if success, false if an attribute already exist
+     */
+    public boolean deleteStringAttribute(final String attr,
+                                         final String expectedValue);
+
+    /**
+     * Deletes string attribute.
+     *
+     * @param attr attribute name
+     */
+    public void deleteStringAttribute(final String attr);
+
+}
diff --git a/src/main/java/net/onrc/onos/core/util/serializers/KryoFactory.java b/src/main/java/net/onrc/onos/core/util/serializers/KryoFactory.java
index a726401..ae1fdb9 100644
--- a/src/main/java/net/onrc/onos/core/util/serializers/KryoFactory.java
+++ b/src/main/java/net/onrc/onos/core/util/serializers/KryoFactory.java
@@ -7,6 +7,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.concurrent.ConcurrentHashMap;
 
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.apps.proxyarp.ArpCacheNotification;
@@ -29,6 +30,7 @@
 import net.onrc.onos.core.topology.PortEvent;
 import net.onrc.onos.core.topology.SwitchEvent;
 import net.onrc.onos.core.topology.TopologyEvent;
+import net.onrc.onos.core.topology.TopologyElement;
 import net.onrc.onos.core.util.CallerId;
 import net.onrc.onos.core.util.DataPath;
 import net.onrc.onos.core.util.Dpid;
@@ -199,6 +201,8 @@
         kryo.register(PortEvent.class);
         kryo.register(SwitchEvent.class);
         kryo.register(TopologyEvent.class);
+        kryo.register(TopologyElement.class);
+        kryo.register(ConcurrentHashMap.class);
 
         // Intent-related classes
         kryo.register(Path.class);