Merge pull request #9 from bradbishop/pull-assoc

association support
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7c0d83f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,87 @@
+# Copyright (c) 2016 GitHub, Inc.
+#
+# Permission is hereby granted,  free of charge,  to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to  use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+# source: https://github.com/github/gitignore/blob/master/Python.gitignore
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+#Ipython Notebook
+.ipynb_checkpoints
+
+*.swo
+*.swp
diff --git a/OpenBMCMapper.py b/OpenBMCMapper.py
deleted file mode 100644
index 518e703..0000000
--- a/OpenBMCMapper.py
+++ /dev/null
@@ -1,364 +0,0 @@
-#!/usr/bin/env python
-
-# Contributors Listed Below - COPYRIGHT 2015
-# [+] International Business Machines Corp.
-#
-#
-# 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.
-
-from xml.etree import ElementTree
-import dbus
-
-MAPPER_NAME = 'org.openbmc.objectmapper'
-MAPPER_IFACE = MAPPER_NAME + '.ObjectMapper'
-MAPPER_PATH = '/org/openbmc/objectmapper/objectmapper'
-ENUMERATE_IFACE = 'org.openbmc.Object.Enumerate'
-MAPPER_NOT_FOUND = 'org.openbmc.objectmapper.Error.NotFound'
-
-
-class Path:
-    def __init__(self, path):
-        self.parts = filter(bool, path.split('/'))
-
-    def rel(self, first=None, last=None):
-        # relative
-        return self.get('', first, last)
-
-    def fq(self, first=None, last=None):
-        # fully qualified
-        return self.get('/', first, last)
-
-    def depth(self):
-        return len(self.parts)
-
-    def get(self, prefix='/', first=None, last=None):
-        if not first:
-            first = 0
-        if not last:
-            last = self.depth()
-        return prefix + '/'.join(self.parts[first:last])
-
-
-def org_dot_openbmc_match(name):
-    return 'org.openbmc' in name
-
-
-class ListMatch(object):
-    def __init__(self, l):
-        self.l = l
-
-    def __call__(self, match):
-        return match in self.l
-
-
-class IntrospectionNodeParser:
-    def __init__(self, data, tag_match=bool, intf_match=bool):
-        self.data = data
-        self.cache = {}
-        self.tag_match = tag_match
-        self.intf_match = intf_match
-
-    def parse_args(self):
-        return [x.attrib for x in self.data.findall('arg')]
-
-    def parse_children(self):
-        return [x.attrib['name'] for x in self.data.findall('node')]
-
-    def parse_method_or_signal(self):
-        name = self.data.attrib['name']
-        return name, self.parse_args()
-
-    def parse_interface(self):
-        iface = {}
-        iface['method'] = {}
-        iface['signal'] = {}
-
-        for node in self.data:
-            if node.tag not in ['method', 'signal']:
-                continue
-            if not self.tag_match(node.tag):
-                continue
-            p = IntrospectionNodeParser(
-                node, self.tag_match, self.intf_match)
-            n, element = p.parse_method_or_signal()
-            iface[node.tag][n] = element
-
-        return iface
-
-    def parse_node(self):
-        if self.cache:
-            return self.cache
-
-        self.cache['interfaces'] = {}
-        self.cache['children'] = []
-
-        for node in self.data:
-            if node.tag == 'interface':
-                p = IntrospectionNodeParser(
-                    node, self.tag_match, self.intf_match)
-                name = p.data.attrib['name']
-                if not self.intf_match(name):
-                    continue
-                self.cache['interfaces'][name] = p.parse_interface()
-            elif node.tag == 'node':
-                self.cache['children'] = self.parse_children()
-
-        return self.cache
-
-    def get_interfaces(self):
-        return self.parse_node()['interfaces']
-
-    def get_children(self):
-        return self.parse_node()['children']
-
-    def recursive_binding(self):
-        return any('/' in s for s in self.get_children())
-
-
-class IntrospectionParser:
-    def __init__(self, name, bus, tag_match=bool, intf_match=bool):
-        self.name = name
-        self.bus = bus
-        self.tag_match = tag_match
-        self.intf_match = intf_match
-
-    def _introspect(self, path):
-        try:
-            obj = self.bus.get_object(self.name, path, introspect=False)
-            iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
-            data = iface.Introspect()
-        except dbus.DBusException:
-            return None
-
-        return IntrospectionNodeParser(
-            ElementTree.fromstring(data),
-            self.tag_match,
-            self.intf_match)
-
-    def _discover_flat(self, path, parser):
-        items = {}
-        interfaces = parser.get_interfaces().keys()
-        if interfaces:
-            items[path] = {}
-            items[path]['interfaces'] = interfaces
-
-        return items
-
-    def introspect(self, path='/', parser=None):
-        items = {}
-        if not parser:
-            parser = self._introspect(path)
-        if not parser:
-            return {}
-        items.update(self._discover_flat(path, parser))
-
-        if path != '/':
-            path += '/'
-
-        if parser.recursive_binding():
-            callback = self._discover_flat
-        else:
-            callback = self.introspect
-
-        for k in parser.get_children():
-            parser = self._introspect(path + k)
-            if not parser:
-                continue
-            items.update(callback(path + k, parser))
-
-        return items
-
-
-class PathTreeItemIterator(object):
-    def __init__(self, path_tree, subtree, depth):
-        self.path_tree = path_tree
-        self.path = []
-        self.itlist = []
-        self.subtree = ['/'] + filter(bool, subtree.split('/'))
-        self.depth = depth
-        d = path_tree.root
-        for k in self.subtree:
-            try:
-                d = d[k]['children']
-            except KeyError:
-                raise KeyError(subtree)
-        self.it = d.iteritems()
-
-    def __iter__(self):
-        return self
-
-    def __next__(self):
-        return super(PathTreeItemIterator, self).next()
-
-    def next(self):
-        key, value = self._next()
-        path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path)
-        return path, value.get('data')
-
-    def _next(self):
-        try:
-            while True:
-                x = self.it.next()
-                depth_exceeded = len(self.path) + 1 > self.depth
-                if self.depth and depth_exceeded:
-                    continue
-                self.itlist.append(self.it)
-                self.path.append(x[0])
-                self.it = x[1]['children'].iteritems()
-                break
-
-        except StopIteration:
-            if not self.itlist:
-                raise StopIteration
-
-            self.it = self.itlist.pop()
-            self.path.pop()
-            x = self._next()
-
-        return x
-
-
-class PathTreeKeyIterator(PathTreeItemIterator):
-    def __init__(self, path_tree, subtree, depth):
-        super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth)
-
-    def next(self):
-        return super(PathTreeKeyIterator, self).next()[0]
-
-
-class PathTree:
-    def __init__(self):
-        self.root = {}
-
-    def _try_delete_parent(self, elements):
-        if len(elements) == 1:
-            return False
-
-        kids = 'children'
-        elements.pop()
-        d = self.root
-        for k in elements[:-1]:
-            d = d[k][kids]
-
-        if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]:
-            del d[elements[-1]]
-            self._try_delete_parent(elements)
-
-    def _get_node(self, key):
-        kids = 'children'
-        elements = ['/'] + filter(bool, key.split('/'))
-        d = self.root
-        for k in elements[:-1]:
-            try:
-                d = d[k][kids]
-            except KeyError:
-                raise KeyError(key)
-
-        return d[elements[-1]]
-
-    def __iter__(self):
-        return self
-
-    def __missing__(self, key):
-        for x in self.iterkeys():
-            if key == x:
-                return False
-        return True
-
-    def __delitem__(self, key):
-        kids = 'children'
-        elements = ['/'] + filter(bool, key.split('/'))
-        d = self.root
-        for k in elements[:-1]:
-            try:
-                d = d[k][kids]
-            except KeyError:
-                raise KeyError(key)
-
-        del d[elements[-1]]
-        self._try_delete_parent(elements)
-
-    def __setitem__(self, key, value):
-        kids = 'children'
-        elements = ['/'] + filter(bool, key.split('/'))
-        d = self.root
-        for k in elements[:-1]:
-            d = d.setdefault(k, {kids: {}})[kids]
-
-        children = d.setdefault(elements[-1], {kids: {}})[kids]
-        d[elements[-1]].update({kids: children, 'data': value})
-
-    def __getitem__(self, key):
-        return self._get_node(key).get('data')
-
-    def setdefault(self, key, default):
-        if not self.get(key):
-            self.__setitem__(key, default)
-
-        return self.__getitem__(key)
-
-    def get(self, key, default=None):
-        try:
-            x = self.__getitem__(key)
-        except KeyError:
-            x = default
-
-        return x
-
-    def get_children(self, key):
-        return [x for x in self._get_node(key)['children'].iterkeys()]
-
-    def demote(self, key):
-        n = self._get_node(key)
-        if 'data' in n:
-            del n['data']
-
-    def keys(self, subtree='/', depth=None):
-        return [x for x in self.iterkeys(subtree, depth)]
-
-    def values(self, subtree='/', depth=None):
-        return [x[1] for x in self.iteritems(subtree, depth)]
-
-    def items(self, subtree='/', depth=None):
-        return [x for x in self.iteritems(subtree, depth)]
-
-    def dataitems(self, subtree='/', depth=None):
-        return [x for x in self.iteritems(subtree, depth)
-                if x[1] is not None]
-
-    def iterkeys(self, subtree='/', depth=None):
-        if not self.root:
-            return {}.iterkeys()
-        return PathTreeKeyIterator(self, subtree, depth)
-
-    def iteritems(self, subtree='/', depth=None):
-        if not self.root:
-            return {}.iteritems()
-        return PathTreeItemIterator(self, subtree, depth)
-
-
-class Mapper:
-    def __init__(self, bus):
-        self.bus = bus
-        obj = bus.get_object(MAPPER_NAME, MAPPER_PATH, introspect=False)
-        self.iface = dbus.Interface(
-            obj, dbus_interface=MAPPER_IFACE)
-
-    def get_object(self, path):
-        return self.iface.GetObject(path)
-
-    def get_subtree_paths(self, path='/', depth=0):
-        return self.iface.GetSubTreePaths(path, depth)
-
-    def get_subtree(self, path='/', depth=0):
-        return self.iface.GetSubTree(path, depth)
diff --git a/phosphor-mapper b/phosphor-mapper
index 255897f..fa6bb60 100644
--- a/phosphor-mapper
+++ b/phosphor-mapper
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Contributors Listed Below - COPYRIGHT 2015
+# Contributors Listed Below - COPYRIGHT 2016
 # [+] International Business Machines Corp.
 #
 #
@@ -21,29 +21,89 @@
 import dbus.exceptions
 import dbus.mainloop.glib
 import gobject
-from OpenBMCMapper import IntrospectionParser, PathTree
-import OpenBMCMapper
+from obmc.dbuslib.introspection import IntrospectionParser
+import obmc.utils.pathtree
+import obmc.utils.misc
+import obmc.mapper
+import obmc.dbuslib.bindings
+import obmc.dbuslib.enums
 
 
 class MapperNotFoundException(dbus.exceptions.DBusException):
-    _dbus_error_name = OpenBMCMapper.MAPPER_NOT_FOUND
+    _dbus_error_name = obmc.mapper.MAPPER_NOT_FOUND
 
     def __init__(self, path):
         super(MapperNotFoundException, self).__init__(
-            "path or object not found: %s" % (path))
+            "path or object not found: %s" % path)
+
+
+class Association(dbus.service.Object):
+    def __init__(self, bus, path, endpoints):
+        super(Association, self).__init__(bus, path)
+        self.endpoints = endpoints
+
+    def __getattr__(self, name):
+        if name == 'properties':
+            return {
+                obmc.dbuslib.enums.OBMC_ASSOC_IFACE: {
+                    'endpoints': self.endpoints}}
+        return super(Association, self).__getattr__(name)
+
+    def emit_signal(self, old):
+        if old != self.endpoints:
+            self.PropertiesChanged(
+                obmc.dbuslib.enums.OBMC_ASSOC_IFACE,
+                {'endpoints': self.endpoints}, ['endpoints'])
+
+    def append(self, endpoints):
+        old = self.endpoints
+        self.endpoints = list(set(endpoints).union(self.endpoints))
+        self.emit_signal(old)
+
+    def remove(self, endpoints):
+        old = self.endpoints
+        self.endpoints = list(set(self.endpoints).difference(endpoints))
+        self.emit_signal(old)
+
+    @dbus.service.method(dbus.PROPERTIES_IFACE, 'ss', 'as')
+    def Get(self, interface_name, property_name):
+        if property_name != 'endpoints':
+            raise dbus.exceptions.DBusException(name=DBUS_UNKNOWN_PROPERTY)
+        return self.GetAll(interface_name)[property_name]
+
+    @dbus.service.method(dbus.PROPERTIES_IFACE, 's', 'a{sas}')
+    def GetAll(self, interface_name):
+        if interface_name != obmc.dbuslib.enums.OBMC_ASSOC_IFACE:
+            raise dbus.exceptions.DBusException(DBUS_UNKNOWN_INTERFACE)
+        return {'endpoints': self.endpoints}
+
+    @dbus.service.signal(
+        dbus.PROPERTIES_IFACE, signature='sa{sas}as')
+    def PropertiesChanged(
+            self, interface_name, changed_properties, invalidated_properties):
+        pass
+
+
+class Manager(obmc.dbuslib.bindings.DbusObjectManager):
+    def __init__(self, bus, path):
+        obmc.dbuslib.bindings.DbusObjectManager.__init__(self)
+        dbus.service.Object.__init__(self, bus.dbus, path)
 
 
 class ObjectMapper(dbus.service.Object):
     def __init__(self, bus, path,
-                 name_match=OpenBMCMapper.org_dot_openbmc_match,
-                 intf_match=OpenBMCMapper.org_dot_openbmc_match):
+                 name_match=obmc.utils.misc.org_dot_openbmc_match,
+                 intf_match=obmc.utils.misc.org_dot_openbmc_match):
         super(ObjectMapper, self).__init__(bus.dbus, path)
-        self.cache = PathTree()
+        self.cache = obmc.utils.pathtree.PathTree()
         self.bus = bus
         self.name_match = name_match
         self.intf_match = intf_match
-        self.tag_match = OpenBMCMapper.ListMatch(['children', 'interface'])
+        self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface'])
         self.service = None
+        self.index = {}
+        self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX)
+        self.unique = bus.dbus.get_unique_name()
 
         gobject.idle_add(self.discover)
         self.bus.dbus.add_signal_receiver(
@@ -56,131 +116,378 @@
             dbus_interface=
             dbus.BUS_DAEMON_IFACE + '.ObjectManager',
             signal_name='InterfacesAdded',
-            sender_keyword='sender')
+            sender_keyword='sender',
+            path_keyword='sender_path')
         self.bus.dbus.add_signal_receiver(
             self.interfaces_removed_handler,
             dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
             signal_name='InterfacesRemoved',
+            sender_keyword='sender',
+            path_keyword='sender_path')
+        self.bus.dbus.add_signal_receiver(
+            self.properties_changed_handler,
+            dbus_interface=dbus.PROPERTIES_IFACE,
+            signal_name='PropertiesChanged',
+            path_keyword='path',
             sender_keyword='sender')
 
-    def bus_match(self, name):
-        if name == OpenBMCMapper.MAPPER_NAME:
-            return False
-        return self.name_match(name)
+    def bus_match(self, owner):
+        # Ignore my own signals
+        return owner != obmc.mapper.MAPPER_NAME and \
+            self.name_match(owner)
 
     def discovery_pending(self):
         return not bool(self.service)
 
-    def interfaces_added_handler(self, path, iprops, **kw):
-        name = self.bus.get_owned_name(self.bus_match, kw['sender'])
-        if self.discovery_pending() or \
-                not self.bus_match(name):
-            return
+    def add_new_objmgr(self, path, owner):
+        # We don't get a signal for the ObjectManager
+        # interface itself, so if we see a signal from
+        # make sure its in our cache, and add it if not.
+        cache_entry = self.cache.get(path, {})
+        cache_entry = cache_entry if cache_entry is not None else {}
+        old = self.interfaces_get(cache_entry, owner)
+        new = list(set(old).union([dbus.BUS_DAEMON_IFACE + '.ObjectManager']))
+        self.update_interfaces(path, owner, old, new)
 
-        matches = [x for x in iprops.iterkeys() if self.intf_match(x)]
-        self.add_interfaces(path, kw['sender'], matches)
+    def interfaces_added_handler(self, path, iprops, **kw):
+        path = str(path)
+        owner = str(kw['sender'])
+        self.add_new_objmgr(str(kw['sender_path']), owner)
+        interfaces = self.get_signal_interfaces(owner, iprops.iterkeys())
+        cache_entry = self.cache.get(path, {})
+        old = self.interfaces_get(cache_entry, owner)
+        new = list(set(interfaces).union(old))
+        self.update_interfaces(path, owner, old, new)
 
     def interfaces_removed_handler(self, path, interfaces, **kw):
-        name = self.bus.get_owned_name(self.bus_match, kw['sender'])
-        if self.discovery_pending() or \
-                not self.bus_match(name):
+        path = str(path)
+        owner = str(kw['sender'])
+        self.add_new_objmgr(str(kw['sender_path']), owner)
+        interfaces = self.get_signal_interfaces(owner, interfaces)
+        cache_entry = self.cache.get(path, {})
+        old = self.interfaces_get(cache_entry, owner)
+        new = list(set(old).difference(interfaces))
+        self.update_interfaces(path, owner, old, new)
+
+    def properties_changed_handler(self, interface, new, old, **kw):
+        owner = str(kw['sender'])
+        path = str(kw['path'])
+        interfaces = self.get_signal_interfaces(owner, [interface])
+        if not self.is_association(interfaces):
             return
-        item = self.cache[path]
-        sender = kw['sender']
-        for x in interfaces:
-            if self.intf_match(x):
-                try:
-                    item[sender].remove(x)
-                except ValueError:
-                    pass
+        associations = new.get('associations', None)
+        if associations is None:
+            return
 
-        # remove the owner if there aren't any interfaces left
-        if not item[sender]:
-            del item[sender]
+        associations = [
+            (str(x), str(y), str(z)) for x, y, z in associations]
+        self.update_associations(
+            path, owner,
+            self.index_get_associations(path, [owner]),
+            associations)
 
-        # update if interfaces remain
-        if item:
-            self.cache[path] = item
-        # mark for removal if no interfaces remain
-        elif self.cache.get_children(path):
-            self.cache.demote(path)
-        # delete the entire path if everything is gone
-        else:
-            del self.cache[path]
-
-    def process_new_owner(self, name):
+    def process_new_owner(self, owner):
         # unique name
-        return self.discover([IntrospectionParser(name,
+        return self.discover([IntrospectionParser(owner,
                              self.bus.dbus,
                              self.tag_match,
                              self.intf_match)])
 
-    def process_old_owner(self, name):
-        for x, y in self.cache.dataitems():
-            if name not in y:
-                continue
-            del y[name]
-            if y:
-                self.cache[x] = y
-            elif self.cache.get_children(x):
-                self.cache.demote(x)
-            else:
-                del self.cache[x]
+    def process_old_owner(self, owner):
+        for path, item in self.cache.dataitems():
+            old = self.interfaces_get(item, owner)
+            # remove all interfaces for this service
+            self.update_interfaces(
+                path, owner, old=old, new=[])
 
-    def bus_handler(self, service, old, new):
-        if self.discovery_pending() or \
-                not self.bus_match(service):
-            return
+    def bus_handler(self, owner, old, new):
+        valid = False
+        if not obmc.dbuslib.bindings.is_unique(owner):
+            valid = self.valid_signal(owner)
 
-        if new:
+        if valid and new:
             self.process_new_owner(new)
-        if old:
+        if valid and old:
             self.process_old_owner(old)
 
-    def add_interfaces(self, path, owner, interfaces):
-        d = self.cache.setdefault(path, {})
-        d = d.setdefault(owner, [])
-        self.cache[path][owner] = list(set(d + interfaces))
+    def update_interfaces(self, path, owner, old, new):
+        cache_entry = self.cache.setdefault(path, {})
+        created = [] if self.has_interfaces(cache_entry) else [path]
+        added = list(set(new).difference(old))
+        removed = list(set(old).difference(new))
+        self.interfaces_append(cache_entry, owner, added)
+        self.interfaces_remove(cache_entry, owner, removed, path)
+        destroyed = [] if self.has_interfaces(cache_entry) else [path]
+
+        # react to anything that requires association updates
+        new_assoc = []
+        old_assoc = []
+        if self.is_association(added):
+            new_assoc = self.dbus_get_associations(path, owner)
+        if self.is_association(removed):
+            old_assoc = self.index_get_associations(path, [owner])
+        self.update_associations(
+            path, owner, old_assoc, new_assoc, created, destroyed)
 
     def add_items(self, owner, bus_items):
-        for x, y in bus_items.iteritems():
-            self.add_interfaces(x, owner, y['interfaces'])
+        for path, items in bus_items.iteritems():
+            # convert dbus types to native.
+            interfaces = [str(i) for i in items.get('interfaces', [])]
+            self.update_interfaces(path, str(owner), old=[], new=interfaces)
 
-    def discover(self, owners=None):
+    def discover(self, owners=[]):
+        def match(iface):
+            return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' or \
+                self.intf_match(iface)
         if not owners:
-            owners = [IntrospectionParser(x, self.bus.dbus,
-                                          self.tag_match,
-                                          self.intf_match)
-                      for x in self.bus.get_owner_names(self.bus_match)]
+            owners = [
+                IntrospectionParser(
+                    x, self.bus.dbus,
+                    self.tag_match,
+                    match)
+                for x in self.bus.get_owner_names(self.bus_match)]
         for o in owners:
             self.add_items(o.name, o.introspect())
 
         if self.discovery_pending():
+            # add my object mananger instance
+            self.add_items(
+                self.unique,
+                {obmc.dbuslib.bindings.OBJ_PREFIX:
+                    {'interfaces':
+                        [dbus.BUS_DAEMON_IFACE + '.ObjectManager']}})
+
             print "ObjectMapper discovery complete..."
             self.service = dbus.service.BusName(
-                OpenBMCMapper.MAPPER_NAME, self.bus.dbus)
+                obmc.mapper.MAPPER_NAME, self.bus.dbus)
 
-    @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 's', 'a{sas}')
+    def valid_signal(self, owner):
+        if obmc.dbuslib.bindings.is_unique(owner):
+            owner = self.bus.get_owned_name(self.bus_match, owner)
+
+        return owner is not None and not self.discovery_pending() and \
+            self.bus_match(owner)
+
+    def get_signal_interfaces(self, owner, interfaces):
+        filtered = []
+        if self.valid_signal(owner):
+            filtered = [str(x) for x in interfaces if self.intf_match(x)]
+
+        return filtered
+
+    @staticmethod
+    def interfaces_get(item, owner, default=[]):
+        return item.get(owner, default)
+
+    @staticmethod
+    def interfaces_append(item, owner, append):
+        interfaces = item.setdefault(owner, [])
+        item[owner] = list(set(append).union(interfaces))
+
+    def interfaces_remove(self, item, owner, remove, path):
+        interfaces = item.get(owner, [])
+        item[owner] = list(set(interfaces).difference(remove))
+
+        if not item[owner]:
+            # remove the owner if there aren't any interfaces left
+            del item[owner]
+
+        if item:
+            # other owners remain
+            return
+
+        if self.cache.get_children(path):
+            # there are still references to this path
+            # from objects further down the tree.
+            # mark it for removal if that changes
+            self.cache.demote(path)
+        else:
+            # delete the entire path if everything is gone
+            del self.cache[path]
+
+    @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
     def GetObject(self, path):
         o = self.cache.get(path)
         if not o:
             raise MapperNotFoundException(path)
         return o
 
-    @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'si', 'as')
+    @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'as')
     def GetSubTreePaths(self, path, depth):
         try:
             return self.cache.iterkeys(path, depth)
         except KeyError:
             raise MapperNotFoundException(path)
 
-    @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
+    @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
     def GetSubTree(self, path, depth):
         try:
             return {x: y for x, y in self.cache.dataitems(path, depth)}
         except KeyError:
             raise MapperNotFoundException(path)
 
+    @staticmethod
+    def has_interfaces(item):
+        for owner in item.iterkeys():
+            if ObjectMapper.interfaces_get(item, owner):
+                return True
+        return False
+
+    @staticmethod
+    def is_association(interfaces):
+        return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
+
+    def index_get(self, index, path, owners):
+        items = []
+        item = self.index.get(index, {})
+        item = item.get(path, {})
+        for o in owners:
+            items.extend(item.get(o, []))
+        return items
+
+    def index_append(self, index, path, owner, assoc):
+        item = self.index.setdefault(index, {})
+        item = item.setdefault(path, {})
+        item = item.setdefault(owner, [])
+        item.append(assoc)
+
+    def index_remove(self, index, path, owner, assoc):
+        index = self.index.get(index, {})
+        owners = index.get(path, {})
+        items = owners.get(owner, [])
+        if assoc in items:
+            items.remove(assoc)
+        if not items:
+            del owners[owner]
+        if not owners:
+            del index[path]
+
+    def dbus_get_associations(self, path, owner):
+        obj = self.bus.dbus.get_object(owner, path, introspect=False)
+        iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
+        return [(str(f), str(r), str(e)) for f, r, e in iface.Get(
+            obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
+            'associations')]
+
+    def index_get_associations(self, path, owners=[], direction='forward'):
+        forward = 'forward' if direction == 'forward' else 'reverse'
+        reverse = 'reverse' if direction == 'forward' else 'forward'
+
+        associations = []
+        if not owners:
+            index = self.index.get(forward, {})
+            owners = index.get(path, {}).keys()
+
+        # f: forward
+        # r: reverse
+        for rassoc in self.index_get(forward, path, owners):
+            elements = rassoc.split('/')
+            rtype = ''.join(elements[-1:])
+            fendpoint = '/'.join(elements[:-1])
+            for fassoc in self.index_get(reverse, fendpoint, owners):
+                elements = fassoc.split('/')
+                ftype = ''.join(elements[-1:])
+                rendpoint = '/'.join(elements[:-1])
+                if rendpoint != path:
+                    continue
+                associations.append((ftype, rtype, fendpoint))
+
+        return associations
+
+    def update_association(self, path, removed, added):
+        iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
+        create = [] if self.manager.get(path, False) else [iface]
+
+        if added and create:
+            self.manager.add(
+                path, Association(self.bus.dbus, path, added))
+        elif added:
+            self.manager.get(path).append(added)
+
+        obj = self.manager.get(path, None)
+        if obj and removed:
+            obj.remove(removed)
+
+        if obj and not obj.endpoints:
+            self.manager.remove(path)
+
+        delete = [] if self.manager.get(path, False) else [iface]
+
+        if create != delete:
+            self.update_interfaces(
+                path, self.unique, delete, create)
+
+    def update_associations(
+            self, path, owner, old, new, created=[], destroyed=[]):
+        added = list(set(new).difference(old))
+        removed = list(set(old).difference(new))
+        for forward, reverse, endpoint in added:
+            # update the index
+            forward_path = str(path + '/' + forward)
+            reverse_path = str(endpoint + '/' + reverse)
+            self.index_append(
+                'forward', path, owner, reverse_path)
+            self.index_append(
+                'reverse', endpoint, owner, forward_path)
+
+            # create the association if the endpoint exists
+            if self.cache.get(endpoint, None) is None:
+                continue
+
+            self.update_association(forward_path, [], [endpoint])
+            self.update_association(reverse_path, [], [path])
+
+        for forward, reverse, endpoint in removed:
+            # update the index
+            forward_path = str(path + '/' + forward)
+            reverse_path = str(endpoint + '/' + reverse)
+            self.index_remove(
+                'forward', path, owner, reverse_path)
+            self.index_remove(
+                'reverse', endpoint, owner, forward_path)
+
+            # destroy the association if it exists
+            self.update_association(forward_path, [endpoint], [])
+            self.update_association(reverse_path, [path], [])
+
+        # If the associations interface endpoint comes
+        # or goes create or destroy the appropriate
+        # associations
+        for path in created:
+            for forward, reverse, endpoint in \
+                    self.index_get_associations(path, direction='reverse'):
+                forward_path = str(path + '/' + forward)
+                reverse_path = str(endpoint + '/' + reverse)
+                self.update_association(forward_path, [], [endpoint])
+                self.update_association(reverse_path, [], [path])
+
+        for path in destroyed:
+            for forward, reverse, endpoint in \
+                    self.index_get_associations(path, direction='reverse'):
+                forward_path = str(path + '/' + forward)
+                reverse_path = str(endpoint + '/' + reverse)
+                self.update_association(forward_path, [endpoint], [])
+                self.update_association(reverse_path, [path], [])
+
+    @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}')
+    def GetAncestors(self, path):
+        elements = filter(bool, path.split('/'))
+        paths = []
+        objs = {}
+        while elements:
+            elements.pop()
+            paths.append('/' + '/'.join(elements))
+        if path != '/':
+            paths.append('/')
+
+        for path in paths:
+            obj = self.cache.get(path, None)
+            if obj is None:
+                continue
+            objs[path] = obj
+
+        return objs
+
 
 class BusWrapper:
     def __init__(self, bus):
@@ -205,7 +512,7 @@
 if __name__ == '__main__':
     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
     bus = dbus.SystemBus()
-    o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH)
+    o = ObjectMapper(BusWrapper(bus), obmc.mapper.MAPPER_PATH)
     loop = gobject.MainLoop()
 
     loop.run()
diff --git a/setup.py b/setup.py
index bfbf3fc..7185072 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,5 @@
 from distutils.core import setup
-setup(name='OpenBMCMapper',
+setup(name='phosphor-mapper',
       version='1.0',
-      py_modules=['OpenBMCMapper'],
       scripts=['phosphor-mapper']
       )