From 5afa19043d186d4741d9e98aaf5f269c4ebcc56e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Klaus=20K=C3=A4mpf?= <kkaempf@suse.de>
Date: Tue, 13 Jan 2015 11:53:43 +0100
Subject: [PATCH 8/8] Store client option properties as list to keep order

This fixes issue #49

The invoke action requires method parameters as WS-Sequence, keeping
order. The previous hash implementation did not honor order.
(Actually, this is a flaw in the WS-Management standard since method
parameter are passed by name, thus the receiver can regenerate the
correct order)

This patch changes options->properties from hash to list.

As a nice side effect, this also cleans up commit 4916a94, removing
the "<epr>" key prefix.

Ruby: Move ClientOptions#properties= (setter) to openwsman.rb

Implement ClientOptions#properties (getter) with new list_t type of
options->properties.

Conflicts:
	bindings/ruby/openwsman/openwsman.rb
	bindings/wsman-client_opt.i
	src/lib/wsman-client.c
---
 bindings/client_opt.i                |  14 +--
 bindings/ruby/helpers.c              |  12 +++
 bindings/ruby/openwsman/openwsman.rb |  82 +++++++++++++++
 include/u/list.h                     |   2 +-
 include/wsman-client-api.h           |   8 +-
 src/lib/wsman-client.c               | 188 ++++++++++++++++++++++++++++-------
 6 files changed, 259 insertions(+), 47 deletions(-)

diff --git a/bindings/client_opt.i b/bindings/client_opt.i
index 8e1f6bb82086..af18d1c1c314 100644
--- a/bindings/client_opt.i
+++ b/bindings/client_opt.i
@@ -10,7 +10,6 @@
 %nodefault client_opt_t;
 typedef struct {} client_opt_t;
 
-
 /*
  * ClientOptions control the behaviour of the Client connection
  *
@@ -93,15 +92,12 @@ typedef struct {} client_opt_t;
 #if defined(SWIGRUBY)
   void add_property(VALUE k, VALUE v)
   {
-    VALUE k_s = rb_funcall(k, rb_intern("to_s"), 0 );
-    VALUE v_s = rb_funcall(v, rb_intern("to_s"), 0 );
-    const char *key = StringValuePtr(k_s);
-    const char *value = StringValuePtr(v_s);
+    _add_property(k, v, (VALUE)$self);
 #else
   void add_property(const char *key, const char *value)
   {
-#endif
     wsmc_add_property($self, key, value);
+#endif
   }
   
 #if defined(SWIGRUBY)
@@ -111,7 +107,11 @@ typedef struct {} client_opt_t;
   %rename( "properties=" ) set_properties(VALUE hash);
   void set_properties(VALUE hash)
   {
-    $self->properties = value2hash(NULL, hash);
+    if (NIL_P(hash)) return;
+  
+    Check_Type( hash, T_HASH );
+
+    rb_hash_foreach( hash, _add_property, (VALUE)$self );
   }
 #endif
 
diff --git a/bindings/ruby/helpers.c b/bindings/ruby/helpers.c
index f945b9959604..4594b6577e43 100644
--- a/bindings/ruby/helpers.c
+++ b/bindings/ruby/helpers.c
@@ -38,6 +38,18 @@
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/
  
+/* add key/value property to (client_opt_t *)self */
+static void
+_add_property(VALUE k, VALUE v, VALUE self)
+{
+  VALUE k_s = rb_funcall(k, rb_intern("to_s"), 0 );
+  VALUE v_s = rb_funcall(v, rb_intern("to_s"), 0 );
+  const char *key = StringValuePtr(k_s);
+  const char *value = StringValuePtr(v_s);
+
+  wsmc_add_property((client_opt_t *)self, key, value);
+}
+
 /* convert char* to string VALUE */
 static VALUE
 makestring( const char *s )
diff --git a/bindings/ruby/openwsman/openwsman.rb b/bindings/ruby/openwsman/openwsman.rb
index 5f9fedfb3586..2299dfb91421 100644
--- a/bindings/ruby/openwsman/openwsman.rb
+++ b/bindings/ruby/openwsman/openwsman.rb
@@ -9,3 +9,85 @@ require 'openwsman'
 
 # this extends Openwsman::XmlNode with method_missing
 require 'openwsman/xmlnode'
+
+# this extends Openwsman::XmlDoc with method_missing
+require 'openwsman/xmldoc'
+
+# = About openwsman
+# Openwsman (http://www.openwsman.org) is a project intended to provide an open-source
+# implementation of the Web Services Management specification
+# (WS-Management) and to expose system management information on the
+# Linux operating system using the WS-Management protocol. WS-Management
+# is based on a suite of web services specifications and usage
+# requirements that exposes a set of operations focused on and covers
+# all system management aspects. 
+#
+# = Using the bindings
+# The bindings provide access to the client-side API of openwsman.
+# You start by creating a Client instance and set up ClientOptions
+# to control the communication.
+#
+# The Client instance now provides the WS-Management operations, like
+# enumerate, get, invoke, etc.
+#
+# All client operations return a XmlDoc representing the SOAP response
+# from the system.
+# # You can then use XmlDoc methods to extract SOAP elements from the
+# response and dig down through its XmlNode and XmlAttr objects.
+
+module Openwsman
+  class ClientOption
+    # assign hash to properties
+    def properties= value
+      value.each do |k,v|
+        self.add_property k.to_s, v.to_s
+      end
+    end
+  end
+  class Transport
+    # called when authentication credentials missing or wrong
+    def Transport.auth_request_callback client, auth_type
+      # override in client code
+      # return Array of [ username, password ]
+      # return nil to abort authentication
+    end
+  end
+  #
+  # return endpoint-reference (EPR) prefix for given classname and namespace
+  #
+  # * +classname+ - classname (using the <schema>_<name> format)
+  # * +namespace+ - optional namespace, required for Windows WMI which embeds the namespace in the EPR
+  #
+  # ==== Examples
+  #   prefix = Openwsman.epr_prefix_for "CIM_Managed_Element"
+  #   => "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2"
+  #   prefix = Openwsman.epr_prefix_for "Win32_Foo", "root/cimv2"
+  #   => "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2"
+  #
+  def self.epr_prefix_for classname, namespace = nil
+    prefix = Openwsman::uri_prefix classname
+    prefix += "/#{namespace}" if namespace && !namespace.empty?
+    prefix
+  end
+  
+  # create full endpoint reference URI for namespace and classname
+  #
+  # * +classname+ - classname (using the <schema>_<name> format)
+  # * +namespace+ - optional namespace, required for Windows WMI which embeds the namespace in the EPR
+  #
+  # ==== Examples
+  #   Openwsman.epr_uri_for "root/cimv2", "Win32_Foo"
+  #   => "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Foo"
+  def self.epr_uri_for namespace, classname
+    raise "Namespace must not be nil" unless namespace
+    raise "Classname must not be nil" unless classname
+    epr = epr_prefix_for(classname,namespace) + "/#{classname}"
+  end
+  
+  class EndPointReference
+    def method_missing name, *args # :nodoc:
+      selector(name)
+    end
+  end
+end
+
diff --git a/include/u/list.h b/include/u/list.h
index 69c3ae10845f..c2f609683d98 100644
--- a/include/u/list.h
+++ b/include/u/list.h
@@ -162,7 +162,7 @@ int ow_list_verify(list_t *);
 #if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
 #define lnode_pool_isempty(P)	((P)->list_free == 0)
 #define list_count(L)		((L)->list_nodecount)
-#define list_isempty(L)		((L)->list_nodecount == 0)
+#define list_isempty(L)		((L == NULL) || ((L)->list_nodecount == 0))
 #define list_isfull(L)		(LIST_SFX_CHECK(L)->list_nodecount == (L)->list_maxcount)
 #define list_next(L, N)		(LIST_SFX_CHECK(N)->list_next == &(L)->list_nilnode ? NULL : (N)->list_next)
 #define list_prev(L, N)		(LIST_SFX_CHECK(N)->list_prev == &(L)->list_nilnode ? NULL : (N)->list_prev)
diff --git a/include/wsman-client-api.h b/include/wsman-client-api.h
index c8aa935e1751..9d1fff5a2b7a 100644
--- a/include/wsman-client-api.h
+++ b/include/wsman-client-api.h
@@ -47,6 +47,7 @@ extern "C" {
 #include "wsman-xml-serializer.h"
 #include "wsman-epr.h"
 #include "wsman-filter.h"
+#include "u/list.h"
 
 /**
  * @defgroup Client Client
@@ -186,13 +187,16 @@ typedef enum {
 		float heartbeat_interval;
 		float expires;
 		hash_t *selectors;
-		hash_t *properties;
+		list_t *properties; /* keep properties sorted */
 		unsigned int timeout;
 		unsigned int max_envelope_size;
 		unsigned int max_elements;
 	} client_opt_t;
 
-
+        typedef struct {
+          char *key;
+          selector_entry value; /* either char* or epr_t */
+        } client_property_t;
 
 	struct _WsManFault {
 		const char *code;
diff --git a/src/lib/wsman-client.c b/src/lib/wsman-client.c
index eca65ad06ab9..5f4bb3deffb0 100644
--- a/src/lib/wsman-client.c
+++ b/src/lib/wsman-client.c
@@ -308,6 +308,29 @@ wsmc_options_init(void)
 	return op;
 }
 
+static void
+_wsmc_properties_destroy(list_t *properties)
+{
+  while (!list_isempty(properties)) {
+    lnode_t *node;
+    client_property_t *prop;
+    
+    node = list_del_last(properties);
+    if (!node)
+      break;
+    prop = (client_property_t *)node->list_data;
+    lnode_destroy(node);
+    u_free(prop->key);
+    if (prop->value.type == 0) {
+      u_free(prop->value.entry.text);
+    }
+    else {
+      epr_destroy(prop->value.entry.eprp);
+    }
+    u_free(prop);
+  }
+  list_destroy(properties);
+}
 
 void
 wsmc_options_destroy(client_opt_t * op)
@@ -315,10 +338,7 @@ wsmc_options_destroy(client_opt_t * op)
 	if (op->selectors) {
 		hash_free(op->selectors);
 	}
-	if (op->properties) {
-		hash_free(op->properties);
-	}
-
+        _wsmc_properties_destroy(op->properties);
 	u_free(op->fragment);
 	u_free(op->cim_ns);
 	u_free(op->delivery_uri);
@@ -343,21 +363,97 @@ wsmc_clear_action_option(client_opt_t * options, unsigned int flag)
 }
 
 
+/*
+ * compare two property list entries by key
+ */
+static int
+_property_key_compare(const void *node1, const void *node2)
+{
+  const char *key1 = ((client_property_t *)node1)->key;
+  const char *key2 = ((client_property_t *)node2)->key;
+  return strcmp(key1, key2);
+}
+
+/*
+ * (static function)
+ * Add property to options
+ * either as char* (string set, epr == NULL)
+ * or as epr_t (string == NULL, epr set)
+ */
+
+static void
+_wsmc_add_property(client_opt_t *options,
+		const char *key,
+		const char *string,
+		epr_t *epr)
+{
+  client_property_t *prop;
+  lnode_t *lnode;
+  if ((string != NULL) && (epr != NULL)) {
+    error("Ambiguous call to add_property");
+    return;
+  }
+  if (key == NULL) {
+    error("Can't add property with NULL key");
+    return;
+  }
+  if (options->properties == NULL) {
+    options->properties = list_create(LISTCOUNT_T_MAX);
+  }
+  if (list_find(options->properties, key, _property_key_compare)) {
+    error("duplicate key not added to properties");
+    return;
+  }
+  prop = u_malloc(sizeof(client_property_t));
+  if (!prop) {
+    error("No memory for property");
+    return;
+  }
+  prop->key = u_strdup(key);
+  if (string != NULL) {
+    prop->value.type = 0;
+    prop->value.entry.text = u_strdup(string);
+  } else if (epr != NULL) {
+    prop->value.type = 1;
+    prop->value.entry.eprp = epr_copy(epr);
+  } else {
+    error("Can't add NULL as property value");
+    return;
+  }
+  lnode = lnode_create(prop);
+  if (!lnode) {
+    error("No memory for property node");
+    return;
+  }
+  list_append(options->properties, lnode);
+}
+
+
+/*
+ * add a char* as property
+ *
+ */
+
 void
 wsmc_add_property(client_opt_t * options,
 		const char *key,
 		const char *value)
 {
-	if (options->properties == NULL)
-		options->properties = hash_create(HASHCOUNT_T_MAX, 0, 0);
-	if (!hash_lookup(options->properties, key)) {
-		if (!hash_alloc_insert(options->properties,
-					(char *)key, (char *)value)) {
-			error("hash_alloc_insert failed");
-		}
-	} else {
-		error("duplicate not added to hash");
-	}
+  _wsmc_add_property(options, key, value, NULL);
+}
+
+
+/*
+ * add an EndpointReference as property
+ *
+ */
+
+void
+wsmc_add_property_epr(client_opt_t * options,
+		const char *key,
+		epr_t *value)
+{
+  _wsmc_add_property(options, key, NULL, value);
 }
 
 void
@@ -399,7 +495,19 @@ wsmc_add_prop_from_str(client_opt_t * options,
 
         query = u_parse_query(query_string);
 	if (query) {
-		options->properties = query;
+          /* convert query hash to property list */
+          hscan_t hs;
+          hnode_t *hn;
+          _wsmc_properties_destroy(options->properties);
+          options->properties = NULL;
+          hash_scan_begin(&hs, query);
+          while ((hn = hash_scan_next(&hs))) {
+            _wsmc_add_property(options,
+                               (char *)hnode_getkey(hn),
+                               (char *)hnode_get(hn),
+                               NULL);
+          }
+          hash_free(query);
 	}
 }
 
@@ -752,8 +860,6 @@ wsmc_set_put_prop(WsXmlDocH get_response,
 {
 	WsXmlNodeH      resource_node;
 	char           *ns_uri;
-	hscan_t         hs;
-	hnode_t        *hn;
 	WsXmlNodeH      get_body = ws_xml_get_soap_body(get_response);
 	WsXmlNodeH      put_body = ws_xml_get_soap_body(put_request);
 
@@ -761,14 +867,20 @@ wsmc_set_put_prop(WsXmlDocH get_response,
 	resource_node = ws_xml_get_child(put_body, 0, NULL, NULL);
 	ns_uri = ws_xml_get_node_name_ns_uri(resource_node);
 
-	if (!options->properties) {
-		return;
-	}
-	hash_scan_begin(&hs, options->properties);
-	while ((hn = hash_scan_next(&hs))) {
-		WsXmlNodeH      n = ws_xml_get_child(resource_node, 0,
-				ns_uri, (char *) hnode_getkey(hn));
-		ws_xml_set_node_text(n, (char *) hnode_get(hn));
+	if (!list_isempty(options->properties)) {
+          lnode_t *node = list_first(options->properties);
+          while (node) {
+            client_property_t *property = (client_property_t *)node->list_data;
+            WsXmlNodeH n = ws_xml_get_child(resource_node, 0,
+                                            ns_uri, property->key);
+            if (property->value.type == 0) {
+              ws_xml_set_node_text(n, property->value.entry.text);
+            }
+            else {
+              epr_serialize(n, ns_uri, property->key, property->value.entry.eprp, 1);
+            }
+            node = list_next(options->properties, node);
+          }
 	}
 }
 
@@ -1235,24 +1347,26 @@ wsmc_action_invoke(WsManClient * cl,
 
 	body = ws_xml_get_soap_body(request);
 
-	if ((!options->properties ||
-                    hash_count(options->properties) == 0) &&
+	if (list_isempty(options->properties) &&
                 data != NULL) {
 
 		WsXmlNodeH n = ws_xml_get_doc_root(data);
 		ws_xml_duplicate_tree(ws_xml_get_soap_body(request), n);
-        } else if (options->properties &&
-                hash_count(options->properties) > 0 ) {
+        } else if (!list_isempty(options->properties)) {
             if (method) {
-                WsXmlNodeH node = ws_xml_add_empty_child_format(body,
-                        (char *)resource_uri, "%s_INPUT", method);
-                hash_scan_begin(&hs, options->properties);
-                while ((hn = hash_scan_next(&hs))) {
-                    ws_xml_add_child(node,
-                            (char *)resource_uri,
-                            (char *) hnode_getkey(hn),
-                            (char *) hnode_get(hn));
+              WsXmlNodeH xnode = ws_xml_add_empty_child_format(body,
+                                  (char *)resource_uri, "%s_INPUT", method);
+              lnode_t *lnode = list_first(options->properties);
+              while (lnode) {
+                client_property_t *property = (client_property_t *)lnode->list_data;
+                if (property->value.type == 0) {
+                  ws_xml_add_child(xnode, (char *)resource_uri, (char *)property->key, (char *)property->value.entry.text);
+                }
+                else {
+                  epr_serialize(xnode, (char *)resource_uri, property->key, property->value.entry.eprp, 1);
                 }
+                lnode = list_next(options->properties, lnode);
+              }
             }
         } else {
             ws_xml_add_empty_child_format(body,
-- 
2.1.4

