Add support for createObjects action

Change-Id: I999a5e506a236eac8ca0944b0e2b003c57612e54
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/actions.hpp b/actions.hpp
index dfa1d00..634cbc9 100644
--- a/actions.hpp
+++ b/actions.hpp
@@ -3,6 +3,7 @@
 #include <utility>
 #include <memory>
 #include "utils.hpp"
+#include "types.hpp"
 
 namespace phosphor
 {
@@ -48,6 +49,16 @@
     };
 }
 
+/** @brief Create objects action.  */
+inline auto createObjects(
+    const std::map<sdbusplus::message::object_path, Object>& objs)
+{
+    return [&objs](auto&, auto & m)
+    {
+        m.createObjects(objs);
+    };
+}
+
 /** @brief Set a property action.
  *
  *  Invoke the requested method with a reference to the requested
diff --git a/example/events.d/match1.yaml b/example/events.d/match1.yaml
index 2219c9f..0d53657 100644
--- a/example/events.d/match1.yaml
+++ b/example/events.d/match1.yaml
@@ -2,7 +2,7 @@
     An example inventory match rule.
 
 events:
-    - name: Example Match(1)
+    - name: Example Match
       description: >
           Sets the value of ExampleProperty1 on /changeme
           when the value of ExampleProperty2 on
@@ -29,4 +29,49 @@
               type: string
               value: changed
 
+    - name: Example Match
+      description: >
+          Create /createme1 and /createme2 when the value of
+          ExampleProperty2 on /testing/trigger5 changes to abc123.
+      type: match
+      signatures:
+          - type: signal
+            interface: org.freedesktop.DBus.Properties
+            path: /testing/trigger5
+            member: PropertiesChanged
+      filters:
+          - name: propertyChangedTo
+            interface: xyz.openbmc_project.Example.Iface2
+            property: ExampleProperty2
+            value:
+              type: string
+              value: abc123
+      actions:
+          - name: createObjects
+            objs:
+              /createme1:
+                xyz.openbmc_project.Example.Iface1:
+                  ExampleProperty1:
+                    value: foo
+                    type: string
+                xyz.openbmc_project.Example.Iface2:
+                  ExampleProperty2:
+                    value: bar
+                    type: string
+                  ExampleProperty3:
+                    value: 999
+                    type: int64
+              /createme2:
+                xyz.openbmc_project.Example.Iface1:
+                  ExampleProperty1:
+                    value: bar
+                    type: string
+                xyz.openbmc_project.Example.Iface2:
+                  ExampleProperty2:
+                    value: foo
+                    type: string
+                  ExampleProperty3:
+                    value: 888
+                    type: int64
+
 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/manager.cpp b/manager.cpp
index 83ca75f..1ebcb7f 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -132,52 +132,7 @@
 {
     try
     {
-
-        if (object.cbegin() == object.cend())
-            throw std::runtime_error(
-                "No interfaces in " + path.str);
-
-        path.str.insert(0, _root);
-
-        auto obj = _refs.find(path);
-        if (obj != _refs.end())
-            throw std::runtime_error(
-                obj->first + " already exists");
-
-        // Create an interface holder for each interface
-        // provided by the client and group them into
-        // a container.
-        InterfaceComposite ref;
-
-        auto i = object.size();
-        for (auto& x : object)
-        {
-            // Defer sending any signals until the last interface.
-            auto deferSignals = --i != 0;
-            auto pMakers = _makers.find(x.first.c_str());
-
-            if (pMakers == _makers.end())
-                throw std::runtime_error(
-                    "Unimplemented interface: " + x.first);
-
-            auto& maker = std::get<MakerType>(pMakers->second);
-
-            auto& props = x.second;
-            ref.emplace(x.first, maker(
-                            _bus,
-                            path.str.c_str(),
-                            props,
-                            deferSignals));
-        }
-
-        if (!ref.empty())
-        {
-            // Hang on to a reference to the object (interfaces)
-            // so it stays on the bus, and so we can make calls
-            // to it if needed.
-            _refs.emplace(
-                path, std::move(ref));
-        }
+        createObjects({std::make_pair(path, object)});
     }
     catch (const std::exception& e)
     {
@@ -215,6 +170,68 @@
     }
 }
 
+void Manager::createObjects(
+    const std::map<sdbusplus::message::object_path, Object>& objs)
+{
+    std::string absPath;
+
+    for (auto& o : objs)
+    {
+        auto& relPath = o.first;
+        auto& ifaces = o.second;
+
+        absPath.assign(_root);
+        absPath.append(relPath);
+
+        auto obj = _refs.find(absPath);
+        if (obj != _refs.end())
+        {
+            // This object already exists...skip.
+            continue;
+        }
+
+        // Create an interface holder for each interface
+        // provided by the client and group them into
+        // a container.
+        InterfaceComposite ref;
+
+        auto i = ifaces.size();
+        for (auto& iface : ifaces)
+        {
+            auto& props = iface.second;
+
+            // Defer sending any signals until the last interface.
+            auto deferSignals = --i != 0;
+            auto pMakers = _makers.find(iface.first.c_str());
+
+            if (pMakers == _makers.end())
+            {
+                // This interface is not known.
+                continue;
+            }
+
+            auto& maker = std::get<MakerType>(pMakers->second);
+
+            ref.emplace(
+                iface.first,
+                maker(
+                    _bus,
+                    absPath.c_str(),
+                    props,
+                    deferSignals));
+        }
+
+        if (!ref.empty())
+        {
+            // Hang on to a reference to the object (interfaces)
+            // so it stays on the bus, and so we can make calls
+            // to it if needed.
+            _refs.emplace(
+                absPath, std::move(ref));
+        }
+    }
+}
+
 details::holder::Base& Manager::getInterfaceHolder(
     const char* path, const char* interface)
 {
diff --git a/manager.hpp b/manager.hpp
index 7a01bee..e6c08bf 100644
--- a/manager.hpp
+++ b/manager.hpp
@@ -103,6 +103,10 @@
         void destroyObjects(
             const std::vector<const char*>& paths);
 
+        /** @brief Add objects to DBus. */
+        void createObjects(
+            const std::map<sdbusplus::message::object_path, Object>& objs);
+
         /** @brief Invoke an sdbusplus server binding method.
          *
          *  Invoke the requested method with a reference to the requested
diff --git a/test/test.cpp b/test/test.cpp
index 21f89e2..816d471 100644
--- a/test/test.cpp
+++ b/test/test.cpp
@@ -40,7 +40,8 @@
                       "/trigger3"s);
 const auto trigger4 = sdbusplus::message::object_path(EXAMPLE_ROOT +
                       "/trigger4"s);
-
+const auto trigger5 = sdbusplus::message::object_path(EXAMPLE_ROOT +
+                      "/trigger5"s);
 
 const sdbusplus::message::object_path relDeleteMeOne{"/deleteme1"};
 const sdbusplus::message::object_path relDeleteMeTwo{"/deleteme2"};
@@ -78,6 +79,8 @@
         ExampleIface1, ExampleIface2 > t3(bus, trigger3.str.c_str());
         sdbusplus::server::object::object <
         ExampleIface1, ExampleIface2 > t4(bus, trigger4.str.c_str());
+        sdbusplus::server::object::object <
+        ExampleIface1, ExampleIface2 > t5(bus, trigger5.str.c_str());
 
         while (!shutdown)
         {
@@ -456,6 +459,46 @@
             }
         }
     }
+
+    // Validate the create object action.
+    {
+        sdbusplus::message::object_path relCreateMe1{"/createme1"};
+        sdbusplus::message::object_path relCreateMe2{"/createme2"};
+        std::string createMe1{root + relCreateMe1.str};
+        std::string createMe2{root + relCreateMe2.str};
+
+        // Trigger the action.
+        {
+            sdbusplus::message::object_path signalPath;
+            Object signalObject;
+
+            SignalQueue queue(
+                "path='" + root + "',member='InterfacesAdded'");
+
+            auto m = set(trigger5.str);
+            m.append("xyz.openbmc_project.Example.Iface2");
+            m.append("ExampleProperty2");
+            m.append(sdbusplus::message::variant<std::string>("abc123"));
+            b.call(m);
+            {
+                auto sig{queue.pop()};
+                assert(sig);
+                sig.read(signalPath);
+                assert(createMe1 == signalPath.str);
+                sig.read(signalObject);
+            }
+            {
+                auto sig{queue.pop()};
+                assert(sig);
+                sig.read(signalPath);
+                assert(createMe2 == signalPath.str);
+                sig.read(signalObject);
+            }
+
+            auto moreSignals{queue.pop()};
+            assert(!moreSignals);
+        }
+    }
 }
 
 int main()