Add path conditions

Add support to setProperty and destroyObject to conditionally
perform their action based on the result of a condition testing
functor.

Change-Id: I67ded31f4a7ee0f7a29bb6edc06ebf9249cdc070
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/README.md b/README.md
index 08cba81..c97db6d 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,9 @@
 be called explicitly.  If omitted, the service will be obtained
 with an xyz.openbmc_project.ObjectMapper lookup.
 
+propertyIs can be used in an action condition context when the
+action operates on a dbus object path.
+
 ---
 **actions**
 
@@ -105,6 +108,11 @@
 
 Supported arguments for the destroyObject action are:
 * paths - The paths of the objects to remove from DBus.
+* conditions - An array of conditions.
+
+Conditions are tested and logically ANDed.  If the conditions do not
+pass, the object is not destroyed.  Any condition that accepts a path
+parameter is supported.
 
 ----
 **setProperty**
@@ -112,8 +120,13 @@
 Supported arguments for the setProperty action are:
 * interface - The interface hosting the property to be set.
 * property - The property to set.
-* path - The object hosting the property to be set.
+* paths - The objects hosting the property to be set.
 * value - The value to set.
+* conditions - An array of conditions.
+
+Conditions are tested and logically ANDed.  If the conditions do not
+pass, the property is not set.  Any condition that accepts a path
+parameter is supported.
 
 ----
 **createObjects**
diff --git a/example/events.d/match1.yaml b/example/events.d/match1.yaml
index 7133271..3763093 100644
--- a/example/events.d/match1.yaml
+++ b/example/events.d/match1.yaml
@@ -89,4 +89,41 @@
                     value: foo
                     type: string
 
+    - name: conditional setProperty example
+      description: >
+          Sets the ExampleProperty1 on the /changeme object when
+          the value of ExampleProperty3 on /testing/trigger7
+          changes to 10 and the value of the ExampleProperty3
+          value on /changeme is 22.
+      type: match
+      signatures:
+          - type: signal
+            path: /testing/trigger7
+            interface: org.freedesktop.DBus.Properties
+            member: PropertiesChanged
+      filters:
+          - name: propertyChangedTo
+            interface: xyz.openbmc_project.Example.Iface2
+            property: ExampleProperty3
+            value:
+              value: 10
+              type: int64
+      actions:
+          - name: setProperty
+            interface: xyz.openbmc_project.Example.Iface1
+            property: ExampleProperty1
+            paths:
+                - /changeme
+            value:
+              type: string
+              value: changed
+            conditions:
+              - name: propertyIs
+                interface: xyz.openbmc_project.Example.Iface2
+                property: ExampleProperty3
+                value:
+                  value: 22
+                  type: int64
+
+
 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/example/events.d/match2.yaml b/example/events.d/match2.yaml
index 31836b0..6fbe496 100644
--- a/example/events.d/match2.yaml
+++ b/example/events.d/match2.yaml
@@ -82,4 +82,40 @@
             paths:
               - /deleteme3
 
+    - name: conditional destroyObject example
+      description: >
+          Destroys the /deleteme3 object when the value of
+          ExampleProperty3 on /testing/trigger6
+          changes to 10 and the value of the ExampleProperty3
+          value on /deleteme3 is 22.
+          Destroys the /deleteme4 object when the value of
+          ExampleProperty3 on /testing/trigger6
+          changes to 10 and the value of the ExampleProperty3
+          value on /deleteme4 is 22.
+      type: match
+      signatures:
+          - type: signal
+            path: /testing/trigger6
+            interface: org.freedesktop.DBus.Properties
+            member: PropertiesChanged
+      filters:
+          - name: propertyChangedTo
+            interface: xyz.openbmc_project.Example.Iface2
+            property: ExampleProperty3
+            value:
+              value: 10
+              type: int64
+      actions:
+          - name: destroyObjects
+            paths:
+              - /deleteme3
+              - /deleteme4
+            conditions:
+              - name: propertyIs
+                interface: xyz.openbmc_project.Example.Iface2
+                property: ExampleProperty3
+                value:
+                  value: 22
+                  type: int64
+
 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/functor.cpp b/functor.cpp
index a657257..253f0e9 100644
--- a/functor.cpp
+++ b/functor.cpp
@@ -24,10 +24,18 @@
 {
 namespace functor
 {
-
 bool PropertyConditionBase::operator()(
     sdbusplus::bus::bus& bus,
     sdbusplus::message::message&,
+    Manager& mgr) const
+{
+    std::string path(_path);
+    return (*this)(path, bus, mgr);
+}
+
+bool PropertyConditionBase::operator()(
+    const std::string& path,
+    sdbusplus::bus::bus& bus,
     Manager&) const
 {
     std::string host;
@@ -43,7 +51,7 @@
                               "/xyz/openbmc_project/ObjectMapper",
                               "xyz.openbmc_project.ObjectMapper",
                               "GetObject");
-        mapperCall.append(_path);
+        mapperCall.append(path);
         mapperCall.append(std::vector<std::string>({_iface}));
 
         auto mapperResponseMsg = bus.call(mapperCall);
@@ -70,7 +78,7 @@
     }
     auto hostCall = bus.new_method_call(
                         host.c_str(),
-                        _path.c_str(),
+                        path.c_str(),
                         "org.freedesktop.DBus.Properties",
                         "Get");
     hostCall.append(_iface);
diff --git a/functor.hpp b/functor.hpp
index 265e467..50fdcfa 100644
--- a/functor.hpp
+++ b/functor.hpp
@@ -45,15 +45,51 @@
     return Filter(std::forward<T>(filter));
 }
 
+/** @brief make_path_condition
+ *
+ *  Adapt a path_condition function object.
+ *
+ *  @param[in] filter - The functor being adapted.
+ *  @returns - The adapted functor.
+ *
+ *  @tparam T - The type of the functor being adapted.
+ */
+template <typename T>
+auto make_path_condition(T&& condition)
+{
+    return PathCondition(std::forward<T>(condition));
+}
+
+template <typename T, typename ...Args>
+auto callArrayWithStatus(T&& container, Args&& ...args)
+{
+    for (auto f : container)
+    {
+        if (!f(std::forward<Args>(args)...))
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
 namespace functor
 {
 
 /** @brief Destroy objects action.  */
-inline auto destroyObjects(std::vector<const char*>&& paths)
+inline auto destroyObjects(
+    std::vector<const char*>&& paths,
+    std::vector<PathCondition>&& conditions)
 {
-    return [ = ](auto&, auto & m)
+    return [ = ](auto & b, auto & m)
     {
-        m.destroyObjects(paths);
+        for (const auto& p : paths)
+        {
+            if (callArrayWithStatus(conditions, p, b, m))
+            {
+                m.destroyObjects({p});
+            }
+        }
     };
 }
 
@@ -88,20 +124,27 @@
  */
 template <typename T, typename U, typename V>
 auto setProperty(
-    std::vector<const char*>&& paths, const char* iface,
-    U&& member, V&& value)
+    std::vector<const char*>&& paths,
+    std::vector<PathCondition>&& conditions,
+    const char* iface,
+    U&& member,
+    V&& value)
 {
     // The manager is the only parameter passed to actions.
     // Bind the path, interface, interface member function pointer,
     // and value to a lambda.  When it is called, forward the
     // path, interface and value on to the manager member function.
-    return [paths, iface, member,
-                  value = std::forward<V>(value)](auto&, auto & m)
+    return [paths, conditions = conditions, iface,
+                  member,
+                  value = std::forward<V>(value)](auto & b, auto & m)
     {
         for (auto p : paths)
         {
-            m.template invokeMethod<T>(
-                p, iface, member, value);
+            if (callArrayWithStatus(conditions, p, b, m))
+            {
+                m.template invokeMethod<T>(
+                    p, iface, member, value);
+            }
         }
     };
 }
@@ -199,7 +242,7 @@
             const char* iface,
             const char* property,
             const char* service) :
-            _path(path),
+            _path(path ? path : std::string()),
             _iface(iface),
             _property(property),
             _service(service) {}
@@ -216,6 +259,15 @@
             sdbusplus::message::message&,
             Manager&) const;
 
+        /** @brief Test a property value.
+         *
+         * Make a DBus call and test the value of any property.
+         */
+        bool operator()(
+            const std::string&,
+            sdbusplus::bus::bus&,
+            Manager&) const;
+
     private:
         std::string _path;
         std::string _iface;
diff --git a/pimgen.py b/pimgen.py
index 2193c90..deaeb59 100755
--- a/pimgen.py
+++ b/pimgen.py
@@ -222,6 +222,14 @@
         super(Action, self).__init__(**kw)
 
 
+class PathCondition(MethodCall):
+    '''Convenience type for path conditions'''
+
+    def __init__(self, **kw):
+        kw['name'] = 'make_path_condition'
+        super(PathCondition, self).__init__(**kw)
+
+
 class CreateObjects(MethodCall):
     '''Assemble a createObjects functor.'''
 
@@ -262,8 +270,13 @@
 
     def __init__(self, **kw):
         values = [{'value': x, 'type': 'string'} for x in kw.pop('paths')]
+        conditions = [
+            Event.functor_map[
+                x['name']](**x) for x in kw.pop('conditions', [])]
+        conditions = [PathCondition(args=[x]) for x in conditions]
         args = [InitializerList(
             values=[TrivialArgument(**x) for x in values])]
+        args.append(InitializerList(values=conditions))
         kw['args'] = args
         kw['namespace'] = ['functor']
         super(DestroyObjects, self).__init__(**kw)
@@ -292,6 +305,12 @@
         args.append(InitializerList(
             values=[TrivialArgument(**x) for x in paths]))
 
+        conditions = [
+            Event.functor_map[
+                x['name']](**x) for x in kw.pop('conditions', [])]
+        conditions = [PathCondition(args=[x]) for x in conditions]
+
+        args.append(InitializerList(values=conditions))
         args.append(TrivialArgument(value=str(iface), type='string'))
         args.append(TrivialArgument(
             value=member, decorators=[Cast('static', member_cast)]))
@@ -323,7 +342,13 @@
 
     def __init__(self, **kw):
         args = []
-        args.append(TrivialArgument(value=kw.pop('path'), type='string'))
+        path = kw.pop('path', None)
+        if not path:
+            path = TrivialArgument(value='nullptr')
+        else:
+            path = TrivialArgument(value=path, type='string')
+
+        args.append(path)
         args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
         args.append(TrivialArgument(value=kw.pop('property'), type='string'))
         args.append(TrivialArgument(
@@ -342,22 +367,19 @@
 class Event(MethodCall):
     '''Assemble an inventory manager event.'''
 
-    action_map = {
+    functor_map = {
         'destroyObjects': DestroyObjects,
         'createObjects': CreateObjects,
-        'setProperty': SetProperty,
-    }
-
-    filter_map = {
         'propertyChangedTo': PropertyChanged,
         'propertyIs': PropertyIs,
+        'setProperty': SetProperty,
     }
 
     def __init__(self, **kw):
         self.summary = kw.pop('name')
 
         filters = [
-            self.filter_map[x['name']](**x) for x in kw.pop('filters', [])]
+            self.functor_map[x['name']](**x) for x in kw.pop('filters', [])]
         filters = [Filter(args=[x]) for x in filters]
         filters = Vector(
             templates=[Template(name='Filter', namespace=[])],
@@ -377,7 +399,7 @@
 
         action_type = Template(name='Action', namespace=[])
         action_args = [
-            self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
+            self.functor_map[x['name']](**x) for x in kw.pop('actions', [])]
         action_args = [Action(args=[x]) for x in action_args]
         actions = Vector(
             templates=[action_type],
diff --git a/types.hpp b/types.hpp
index d2105aa..52a00ee 100644
--- a/types.hpp
+++ b/types.hpp
@@ -32,7 +32,8 @@
 using Action = std::function<void (sdbusplus::bus::bus&, Manager&)>;
 using Filter = std::function <
                bool (sdbusplus::bus::bus&, sdbusplus::message::message&, Manager&) >;
-
+using PathCondition = std::function <
+                      bool (const std::string&, sdbusplus::bus::bus&, Manager&) >;
 } // namespace manager
 } // namespace inventory
 } // namespace phosphor