Add callback groups

Allow named collections of callbacks to be defined and used anywhere
callbacks are used.

Change-Id: I3224aa06b2250e9a055bc70d20c186caecd033af
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index f223713..c77c0a8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,7 @@
 CLEANFILES = generated.hpp
 
 TEMPLATES = \
+	templates/callbackgroup.mako.cpp \
 	templates/generated.mako.hpp \
 	templates/journal.mako.cpp
 
diff --git a/src/callback.hpp b/src/callback.hpp
index d7a678c..7c6f69b 100644
--- a/src/callback.hpp
+++ b/src/callback.hpp
@@ -52,6 +52,44 @@
         const PropertyIndex& index;
 };
 
+/** @class GroupOfCallbacks
+ *  @brief Invoke multiple callbacks.
+ *
+ *  A group of callbacks is implemented as a vector of array indicies
+ *  into an external array  of callbacks.  The group function call
+ *  operator traverses the vector of indicies, invoking each
+ *  callback.
+ *
+ *  @tparam CallbackAccess - Access to the array of callbacks.
+ */
+template <typename CallbackAccess>
+class GroupOfCallbacks : public Callback
+{
+    public:
+        GroupOfCallbacks() = delete;
+        GroupOfCallbacks(const GroupOfCallbacks&) = delete;
+        GroupOfCallbacks(GroupOfCallbacks&&) = default;
+        GroupOfCallbacks& operator=(const GroupOfCallbacks&) = delete;
+        GroupOfCallbacks& operator=(GroupOfCallbacks&&) = default;
+        ~GroupOfCallbacks() = default;
+        explicit GroupOfCallbacks(
+            const std::vector<size_t>& graphEntry)
+            : graph(graphEntry) {}
+
+        /** @brief Run the callbacks. */
+        void operator()() override
+        {
+            for (auto e : graph)
+            {
+                (*CallbackAccess::get()[e])();
+            }
+        }
+
+    private:
+        /** @brief The offsets of the callbacks in the group. */
+        const std::vector<size_t>& graph;
+};
+
 } // namespace monitoring
 } // namespace dbus
 } // namespace phosphor
diff --git a/src/example/example.yaml b/src/example/example.yaml
index 171aeaa..0f286dc 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -74,3 +74,22 @@
   properties: example property group
   severity: INFO
   message: Hello world from PDM!
+
+- name: example callback group
+  description: >
+    'Callbacks groups are simply named collections of other callbacks.
+    Configuration file directives can only refer to a single callback.
+    Through use of a group, these configuration file directives can
+    refer to more than one callback.
+
+    For example for a given event, one may wish to trace multiple
+    messages to the systemd journal.  The journal callback does not
+    support tracing multiple messages.  To do that, define a callback
+    group composed of multiple journal callbacks.
+
+    This example callback group only has one member.  To add more, add
+    additional callbacks to the members element.'
+  class: callback
+  callback: group
+  members:
+    - example journal callback
diff --git a/src/pdmgen.py b/src/pdmgen.py
index e4a97e7..b20436e 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -436,6 +436,65 @@
             indent=indent)
 
 
+class CallbackGraphEntry(Group):
+    '''An entry in a traversal list for groups of callbacks.'''
+
+    def __init__(self, *a, **kw):
+        super(CallbackGraphEntry, self).__init__(**kw)
+
+    def setup(self, objs):
+        '''Resolve group members.'''
+
+        def map_member(x):
+            return get_index(
+                objs, 'callback', x, config=self.configfile)
+
+        self.members = map(
+            map_member,
+            self.members)
+
+        super(CallbackGraphEntry, self).setup(objs)
+
+
+class GroupOfCallbacks(ConfigEntry, Renderer):
+    '''Handle the callback group config file directive.'''
+
+    def __init__(self, *a, **kw):
+        self.members = kw.pop('members')
+        super(GroupOfCallbacks, self).__init__(**kw)
+
+    def factory(self, objs):
+        '''Create a graph instance for this group of callbacks.'''
+
+        args = {
+            'configfile': self.configfile,
+            'members': self.members,
+            'class': 'callbackgroup',
+            'callbackgroup': 'callback',
+            'name': self.members
+        }
+
+        entry = CallbackGraphEntry(**args)
+        add_unique(entry, objs, config=self.configfile)
+
+        super(GroupOfCallbacks, self).factory(objs)
+
+    def setup(self, objs):
+        '''Resolve graph entry.'''
+
+        self.graph = get_index(
+            objs, 'callbackgroup', self.members, config=self.configfile)
+
+        super(GroupOfCallbacks, self).setup(objs)
+
+    def construct(self, loader, indent):
+        return self.render(
+            loader,
+            'callbackgroup.mako.cpp',
+            c=self,
+            indent=indent)
+
+
 class Everything(Renderer):
     '''Parse/render entry point.'''
 
@@ -465,6 +524,7 @@
             },
             'callback': {
                 'journal': Journal,
+                'group': GroupOfCallbacks,
             },
         }
 
@@ -556,6 +616,7 @@
         self.instancegroups = kw.pop('instancegroup', [])
         self.watches = kw.pop('watch', [])
         self.callbacks = kw.pop('callback', [])
+        self.callbackgroups = kw.pop('callbackgroup', [])
 
         super(Everything, self).__init__(**kw)
 
@@ -578,6 +639,7 @@
                     watches=self.watches,
                     instancegroups=self.instancegroups,
                     callbacks=self.callbacks,
+                    callbackgroups=self.callbackgroups,
                     indent=Indent()))
 
 if __name__ == '__main__':
diff --git a/src/templates/callbackgroup.mako.cpp b/src/templates/callbackgroup.mako.cpp
new file mode 100644
index 0000000..6cc6813
--- /dev/null
+++ b/src/templates/callbackgroup.mako.cpp
@@ -0,0 +1,2 @@
+std::make_unique<GroupOfCallbacks<ConfigPropertyCallbacks>>(
+${indent(1)}ConfigPropertyCallbackGroups::get()[${c.graph}])\
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 61c2b6f..67b9149 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -128,6 +128,23 @@
     }
 };
 
+struct ConfigPropertyCallbackGroups
+{
+    using CallbackGroups = std::array<std::vector<size_t>, ${len(callbackgroups)}>;
+    static auto& get()
+    {
+        static const CallbackGroups propertyCallbackGraph =
+        {
+            {
+% for g in callbackgroups:
+                {${', '.join([str(x) for x in g.members])}},
+% endfor
+            }
+        };
+        return propertyCallbackGraph;
+    }
+};
+
 struct ConfigPropertyCallbacks
 {
     using Callbacks = std::array<std::unique_ptr<Callback>, ${len(callbacks)}>;