Add support for callbacks

Callbacks are the response in the PDM 'trigger->response' model.
Add general support for implementing callbacks and implement
a log to systemd journal using that framework.

Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: I8bead5368ee5472a02b47e8bba9e9df3a1f346bc
diff --git a/src/Makefile.am b/src/Makefile.am
index 3b4d9cf..f223713 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,6 +7,7 @@
 
 phosphor_dbus_monitor_SOURCES = \
 	functor.cpp \
+	journal.cpp \
 	main.cpp \
 	monitor.cpp \
 	propertywatch.cpp
@@ -21,7 +22,8 @@
 CLEANFILES = generated.hpp
 
 TEMPLATES = \
-	templates/generated.mako.hpp
+	templates/generated.mako.hpp \
+	templates/journal.mako.cpp
 
 generated.hpp: $(PDMGEN) $(YAML_PATH) $(TEMPLATES)
 	$(AM_V_GEN)$(PYTHON) ${PDMGEN} \
diff --git a/src/callback.hpp b/src/callback.hpp
new file mode 100644
index 0000000..d7a678c
--- /dev/null
+++ b/src/callback.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "data_types.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class Callback
+ *  @brief Callback interface.
+ *
+ *  Callbacks of any type can be run.
+ */
+class Callback
+{
+    public:
+        Callback() = default;
+        Callback(const Callback&) = delete;
+        Callback(Callback&&) = default;
+        Callback& operator=(const Callback&) = delete;
+        Callback& operator=(Callback&&) = default;
+        virtual ~Callback() = default;
+
+        /** @brief Run the callback. */
+        virtual void operator()() = 0;
+};
+
+/** @class IndexedCallback
+ *  @brief Callback with an index.
+ */
+class IndexedCallback : public Callback
+{
+    public:
+        IndexedCallback() = delete;
+        IndexedCallback(const IndexedCallback&) = delete;
+        IndexedCallback(IndexedCallback&&) = default;
+        IndexedCallback& operator=(const IndexedCallback&) = delete;
+        IndexedCallback& operator=(IndexedCallback&&) = default;
+        virtual ~IndexedCallback() = default;
+        explicit IndexedCallback(const PropertyIndex& callbackIndex)
+            : Callback(), index(callbackIndex) {}
+
+        /** @brief Run the callback. */
+        virtual void operator()() override = 0;
+
+    protected:
+
+        /** @brief Property names and their associated storage. */
+        const PropertyIndex& index;
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/example/example.yaml b/src/example/example.yaml
index 4e216cd..171aeaa 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -53,3 +53,24 @@
   watch: property
   paths: example path group
   properties: example property group
+
+- name: example journal callback
+  description: >
+    'Callbacks are actions PDM should take when instructed to do so.
+
+    Some callback types refer to a group of paths and group of properties
+    in a similar fashion as the property watch directive.
+
+    The journal callback logs the specified message to the systemd journal
+    with the specified severity.
+
+    Additionally, the journal callback will add to the journal key value
+    pair metadata for each property in the specified property group with
+    the key being the property element metadata and the value being the
+    property value.'
+  class: callback
+  callback: journal
+  paths: example path group
+  properties: example property group
+  severity: INFO
+  message: Hello world from PDM!
diff --git a/src/journal.cpp b/src/journal.cpp
new file mode 100644
index 0000000..46f74e9
--- /dev/null
+++ b/src/journal.cpp
@@ -0,0 +1,46 @@
+/**
+ * Copyright © 2017 IBM Corporation
+ *
+ * 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.
+ */
+#include "journal.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+void JournalBase::operator()()
+{
+    for (const auto& n : index)
+    {
+        const auto& path = std::get<0>(n.first);
+        const auto& pathMeta = std::get<0>(n.second);
+        const auto& propertyMeta = std::get<1>(n.second);
+        const auto& value = std::get<2>(n.second);
+
+        if (!value.get().empty())
+        {
+            log(message,
+                pathMeta,
+                path,
+                propertyMeta,
+                value);
+        }
+    }
+}
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/journal.hpp b/src/journal.hpp
new file mode 100644
index 0000000..4e47670
--- /dev/null
+++ b/src/journal.hpp
@@ -0,0 +1,90 @@
+#pragma once
+
+#include <phosphor-logging/log.hpp>
+#include "callback.hpp"
+#include "format.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class JournalBase
+ *  @brief Journal callback implementation.
+ *
+ *  The journal callback logs the client message and
+ *  journal metadata key value pairs as specified by the
+ *  client supplied property index.
+ */
+class JournalBase : public IndexedCallback
+{
+    public:
+        JournalBase() = delete;
+        JournalBase(const JournalBase&) = delete;
+        JournalBase(JournalBase&&) = default;
+        JournalBase& operator=(const JournalBase&) = delete;
+        JournalBase& operator=(JournalBase&&) = default;
+        virtual ~JournalBase() = default;
+        JournalBase(const char* msg, const PropertyIndex& index) :
+            IndexedCallback(index), message(msg) {}
+
+        /** @brief Callback interface implementation. */
+        void operator()() override;
+
+    private:
+        /** @brief Delegate type specific calls to subclasses. */
+        virtual void log(
+            const char* message,
+            const std::string& pathMeta,
+            const std::string& path,
+            const std::string& propertyMeta,
+            const any_ns::any& value) const = 0;
+
+        /** @brief The client provided message to be traced.  */
+        const char* message;
+};
+
+/** @class Journal
+ *  @brief C++ type specific logic for the journal callback.
+ *
+ *  @tparam T - The C++ type of the property values being traced.
+ *  @tparam Severity - The log severity of the log entry.
+ */
+template <typename T, phosphor::logging::level Severity>
+class Journal : public JournalBase
+{
+    public:
+        Journal() = delete;
+        Journal(const Journal&) = delete;
+        Journal(Journal&&) = default;
+        Journal& operator=(const Journal&) = delete;
+        Journal& operator=(Journal&&) = default;
+        ~Journal() = default;
+        Journal(const char* msg, const PropertyIndex& index) :
+            JournalBase(msg, index) {}
+
+    private:
+        /** @brief log interface implementation. */
+        void log(
+            const char* message,
+            const std::string& pathMeta,
+            const std::string& path,
+            const std::string& propertyMeta,
+            const any_ns::any& value) const override
+        {
+            phosphor::logging::log<Severity>(
+                message,
+                phosphor::logging::entry(
+                    pathMeta + GetFormat<decltype(pathMeta)>::format,
+                    path),
+                phosphor::logging::entry(
+                    propertyMeta + GetFormat<T>::format,
+                    any_ns::any_cast<T>(value)));
+        }
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/pdmgen.py b/src/pdmgen.py
index 070c118..e4a97e7 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -413,6 +413,29 @@
         super(PropertyWatch, self).__init__(**kw)
 
 
+class Callback(HasPropertyIndex):
+    '''Interface and common logic for callbacks.'''
+
+    def __init__(self, *a, **kw):
+        super(Callback, self).__init__(**kw)
+
+
+class Journal(Callback, Renderer):
+    '''Handle the journal callback config file directive.'''
+
+    def __init__(self, *a, **kw):
+        self.severity = kw.pop('severity')
+        self.message = kw.pop('message')
+        super(Journal, self).__init__(**kw)
+
+    def construct(self, loader, indent):
+        return self.render(
+            loader,
+            'journal.mako.cpp',
+            c=self,
+            indent=indent)
+
+
 class Everything(Renderer):
     '''Parse/render entry point.'''
 
@@ -440,6 +463,9 @@
             'instance': {
                 'element': Instance,
             },
+            'callback': {
+                'journal': Journal,
+            },
         }
 
         if cls not in class_map:
@@ -529,6 +555,7 @@
         self.instances = kw.pop('instance', [])
         self.instancegroups = kw.pop('instancegroup', [])
         self.watches = kw.pop('watch', [])
+        self.callbacks = kw.pop('callback', [])
 
         super(Everything, self).__init__(**kw)
 
@@ -550,6 +577,7 @@
                     instances=self.instances,
                     watches=self.watches,
                     instancegroups=self.instancegroups,
+                    callbacks=self.callbacks,
                     indent=Indent()))
 
 if __name__ == '__main__':
diff --git a/src/propertywatchimpl.hpp b/src/propertywatchimpl.hpp
index 46799ed..6e957ae 100644
--- a/src/propertywatchimpl.hpp
+++ b/src/propertywatchimpl.hpp
@@ -32,10 +32,6 @@
     {
         return;
     }
-    else
-    {
-        alreadyRan = true;
-    }
 
     // The index has a flat layout which is not optimal here.  Nest
     // properties in a map of interface names in a map of object paths.
@@ -107,6 +103,8 @@
             }
         }
     }
+
+    alreadyRan = true;
 }
 
 template <typename T, typename DBusInterfaceType>
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 0911e3a..61c2b6f 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -5,6 +5,7 @@
 #include <array>
 #include <string>
 #include "data_types.hpp"
+#include "journal.hpp"
 #include "propertywatchimpl.hpp"
 #include "sdbusplus.hpp"
 
@@ -107,15 +108,15 @@
                     {
                         PropertyIndex::key_type
                         {
-                            ConfigPaths::get()[${i[0]}],
-                            ConfigInterfaces::get()[${i[2]}],
-                            ConfigProperties::get()[${i[3]}]
+                            std::cref(ConfigPaths::get()[${i[0]}]),
+                            std::cref(ConfigInterfaces::get()[${i[2]}]),
+                            std::cref(ConfigProperties::get()[${i[3]}])
                         },
                         PropertyIndex::mapped_type
                         {
-                            ConfigMeta::get()[${i[1]}],
-                            ConfigMeta::get()[${i[4]}],
-                            ConfigPropertyStorage::get()[${i[5]}]
+                            std::cref(ConfigMeta::get()[${i[1]}]),
+                            std::cref(ConfigMeta::get()[${i[4]}]),
+                            std::ref(ConfigPropertyStorage::get()[${i[5]}])
                         },
                     },
     % endfor
@@ -127,6 +128,22 @@
     }
 };
 
+struct ConfigPropertyCallbacks
+{
+    using Callbacks = std::array<std::unique_ptr<Callback>, ${len(callbacks)}>;
+
+    static auto& get()
+    {
+        static const Callbacks propertyCallbacks =
+        {
+% for c in callbacks:
+            ${c.construct(loader, indent=indent +3)},
+% endfor
+        };
+        return propertyCallbacks;
+    }
+};
+
 struct ConfigPropertyWatches
 {
     using PropertyWatches = std::array<std::unique_ptr<Watch>, ${len(watches)}>;
diff --git a/src/templates/journal.mako.cpp b/src/templates/journal.mako.cpp
new file mode 100644
index 0000000..787337d
--- /dev/null
+++ b/src/templates/journal.mako.cpp
@@ -0,0 +1,3 @@
+std::make_unique<Journal<${c.datatype}, phosphor::logging::level::${c.severity}>>(
+${indent(1)}"${c.message}",
+${indent(1)}ConfigPropertyIndicies::get()[${c.instances}])\