]> code.delx.au - gnu-emacs/blobdiff - lisp/net/soap-client.el
Merge from emacs-24; up to 2012-12-17T11:17:34Z!rgm@gnu.org
[gnu-emacs] / lisp / net / soap-client.el
index bad6ca1e4312c0944dcf33060ceea953437e4117..4ba8e5b58541669d4cd8bcb476292f4ec6877769 100644 (file)
@@ -1,25 +1,27 @@
 ;;;; soap-client.el -- Access SOAP web services from Emacs
 
-;; Copyright (C) 2009-2011  Alex Harsanyi <AlexHarsanyi@gmail.com>
+;; Copyright (C) 2009-2013 Free Software Foundation, Inc.
 
-;; This program is free software: you can redistribute it and/or modify
+;; Author: Alexandru Harsanyi <AlexHarsanyi@gmail.com>
+;; Created: December, 2009
+;; Keywords: soap, web-services, comm, hypermedia
+;; Package: soap-client
+;; Homepage: http://code.google.com/p/emacs-soap-client
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
 ;; the Free Software Foundation, either version 3 of the License, or
 ;; (at your option) any later version.
 
-;; This program is distributed in the hope that it will be useful,
+;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-;; Author: Alexandru Harsanyi (AlexHarsanyi@gmail.com)
-;; Created: December, 2009
-;; Keywords: soap, web-services, comm, hypermedia
-;; Homepage: http://code.google.com/p/emacs-soap-client
-;;
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 ;;
@@ -30,7 +32,7 @@
 ;; `soap-invoke' method passing it the WSDL, the service name, the operation
 ;; you wish to invoke and any required parameters.
 ;;
-;; Idealy, the service you want to access will have some documentation about
+;; Ideally, the service you want to access will have some documentation about
 ;; the operations it supports.  If it does not, you can try using
 ;; `soap-inspect' to browse the WSDL document and see the available operations
 ;; and their parameters.
@@ -53,6 +55,7 @@
 
 (defgroup soap-client nil
   "Access SOAP web services from Emacs."
+  :version "24.1"
   :group 'tools)
 
 ;;;; Support for parsing XML documents with namespaces
@@ -322,13 +325,18 @@ added to the namespace."
     ;; if name is nil, use TARGET as a name...
     (cond ((soap-element-p target)
            (setq name (soap-element-name target)))
+          ((consp target)               ; a fq name: (namespace . name)
+           (setq name (cdr target)))
           ((stringp target)
            (cond ((string-match "^\\(.*\\):\\(.*\\)$" target)
                   (setq name (match-string 2 target)))
                  (t
                   (setq name target))))))
 
-  (assert name)                         ; by now, name should be valid
+  ;; by now, name should be valid
+  (assert (and name (not (equal name "")))
+          nil
+          "Cannot determine name for namespace link")
   (push (make-soap-namespace-link :name name :target target)
         (gethash name (soap-namespace-elements ns))))
 
@@ -361,12 +369,15 @@ binding) but the same name."
   kind                              ; a symbol of: string, dateTime, long, int
   )
 
+(defstruct (soap-simple-type (:include soap-basic-type))
+  enumeration)
+
 (defstruct soap-sequence-element
   name type nillable? multiple?)
 
 (defstruct (soap-sequence-type (:include soap-element))
   parent                                ; OPTIONAL WSDL-TYPE name
-  elements                              ; LIST of SOAP-SEQUCENCE-ELEMENT
+  elements                              ; LIST of SOAP-SEQUENCE-ELEMENT
   )
 
 (defstruct (soap-array-type (:include soap-element))
@@ -407,8 +418,9 @@ binding) but the same name."
 (defun soap-default-xsd-types ()
   "Return a namespace containing some of the XMLSchema types."
   (let ((ns (make-soap-namespace :name "http://www.w3.org/2001/XMLSchema")))
-    (dolist (type '("string" "dateTime" "boolean" "long" "int" "float"
-                    "base64Binary" "anyType" "Array" "byte[]"))
+    (dolist (type '("string" "dateTime" "boolean"
+                    "long" "int" "integer" "unsignedInt" "byte" "float" "double"
+                    "base64Binary" "anyType" "anyURI" "Array" "byte[]"))
       (soap-namespace-put
        (make-soap-basic-type :name type :kind (intern type))
        ns))
@@ -417,9 +429,10 @@ binding) but the same name."
 (defun soap-default-soapenc-types ()
   "Return a namespace containing some of the SOAPEnc types."
   (let ((ns (make-soap-namespace
-            :name "http://schemas.xmlsoap.org/soap/encoding/")))
-    (dolist (type '("string" "dateTime" "boolean" "long" "int" "float"
-                    "base64Binary" "anyType" "Array" "byte[]"))
+             :name "http://schemas.xmlsoap.org/soap/encoding/")))
+    (dolist (type '("string" "dateTime" "boolean"
+                    "long" "int" "integer" "unsignedInt" "byte" "float" "double"
+                    "base64Binary" "anyType" "anyURI" "Array" "byte[]"))
       (soap-namespace-put
        (make-soap-basic-type :name type :kind (intern type))
        ns))
@@ -547,6 +560,15 @@ updated."
     (when resolver
       (funcall resolver element wsdl))))
 
+(defun soap-resolve-references-for-simple-type (type wsdl)
+  "Resolve the base type for the simple TYPE using the WSDL
+  document."
+  (let ((kind (soap-basic-type-kind type)))
+    (unless (symbolp kind)
+      (let ((basic-type (soap-wsdl-get kind wsdl 'soap-basic-type-p)))
+        (setf (soap-basic-type-kind type)
+              (soap-basic-type-kind basic-type))))))
+
 (defun soap-resolve-references-for-sequence-type (type wsdl)
   "Resolve references for a sequence TYPE using WSDL document.
 See also `soap-resolve-references-for-element' and
@@ -554,12 +576,18 @@ See also `soap-resolve-references-for-element' and
   (let ((parent (soap-sequence-type-parent type)))
     (when (or (consp parent) (stringp parent))
       (setf (soap-sequence-type-parent type)
-            (soap-wsdl-get parent wsdl 'soap-type-p))))
+            (soap-wsdl-get
+             parent wsdl
+             ;; Prevent self references, see Bug#9
+             (lambda (e) (and (not (eq e type)) (soap-type-p e)))))))
   (dolist (element (soap-sequence-type-elements type))
     (let ((element-type (soap-sequence-element-type element)))
       (cond ((or (consp element-type) (stringp element-type))
              (setf (soap-sequence-element-type element)
-                   (soap-wsdl-get element-type wsdl 'soap-type-p)))
+                   (soap-wsdl-get
+                    element-type wsdl
+                    ;; Prevent self references, see Bug#9
+                    (lambda (e) (and (not (eq e type)) (soap-type-p e))))))
             ((soap-element-p element-type)
              ;; since the element already has a child element, it
              ;; could be an inline structure.  we must resolve
@@ -574,7 +602,10 @@ See also `soap-resolve-references-for-element' and
   (let ((element-type (soap-array-type-element-type type)))
     (when (or (consp element-type) (stringp element-type))
       (setf (soap-array-type-element-type type)
-            (soap-wsdl-get element-type wsdl 'soap-type-p)))))
+            (soap-wsdl-get
+             element-type wsdl
+             ;; Prevent self references, see Bug#9
+             (lambda (e) (and (not (eq e type)) (soap-type-p e))))))))
 
 (defun soap-resolve-references-for-message (message wsdl)
   "Resolve references for a MESSAGE type using the WSDL document.
@@ -671,6 +702,8 @@ See also `soap-resolve-references-for-element' and
 
 ;; Install resolvers for our types
 (progn
+  (put (aref (make-soap-simple-type) 0) 'soap-resolve-references
+       'soap-resolve-references-for-simple-type)
   (put (aref (make-soap-sequence-type) 0) 'soap-resolve-references
        'soap-resolve-references-for-sequence-type)
   (put (aref (make-soap-array-type) 0) 'soap-resolve-references
@@ -722,9 +755,7 @@ traverse an element tree."
                               (incf nprocessed)
                               (soap-resolve-references-for-element e wsdl)
                               (setf (soap-element-namespace-tag e) nstag))))))
-                 (soap-namespace-elements ns))))
-
-    (message "Processed %d" nprocessed))
+                 (soap-namespace-elements ns)))))
     wsdl)
 
 ;;;;; Loading WSDL from XML documents
@@ -848,6 +879,9 @@ Return a SOAP-NAMESPACE containing the elements."
     (let ((ns (make-soap-namespace :name (soap-get-target-namespace node))))
       ;; NOTE: we only extract the complexTypes from the schema, we wouldn't
       ;; know how to handle basic types beyond the built in ones anyway.
+      (dolist (node (soap-xml-get-children1 node 'xsd:simpleType))
+        (soap-namespace-put (soap-parse-simple-type node) ns))
+
       (dolist (node (soap-xml-get-children1 node 'xsd:complexType))
         (soap-namespace-put (soap-parse-complex-type node) ns))
 
@@ -856,6 +890,26 @@ Return a SOAP-NAMESPACE containing the elements."
 
       ns)))
 
+(defun soap-parse-simple-type (node)
+  "Parse NODE and construct a simple type from it."
+  (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:simpleType)
+          nil
+          "soap-parse-complex-type: expecting xsd:simpleType node, got %s"
+          (soap-l2wk (xml-node-name node)))
+  (let ((name (xml-get-attribute-or-nil node 'name))
+        type
+        enumeration
+        (restriction (car-safe
+                      (soap-xml-get-children1 node 'xsd:restriction))))
+    (unless restriction
+      (error "simpleType %s has no base type" name))
+
+    (setq type (xml-get-attribute-or-nil restriction 'base))
+    (dolist (e (soap-xml-get-children1 restriction 'xsd:enumeration))
+      (push (xml-get-attribute e 'value) enumeration))
+
+    (make-soap-simple-type :name name :kind type :enumeration enumeration)))
+
 (defun soap-parse-schema-element (node)
   "Parse NODE and construct a schema element from it."
   (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:element)
@@ -889,7 +943,11 @@ Return a SOAP-NAMESPACE containing the elements."
       (when (consp c)               ; skip string nodes, which are whitespace
         (let ((node-name (soap-l2wk (xml-node-name c))))
           (cond
-            ((eq node-name 'xsd:sequence)
+            ;; The difference between xsd:all and xsd:sequence is that fields
+            ;; in xsd:all are not ordered and they can occur only once.  We
+            ;; don't care about that difference in soap-client.el
+            ((or (eq node-name 'xsd:sequence)
+                 (eq node-name 'xsd:all))
              (setq type (soap-parse-complex-type-sequence c)))
             ((eq node-name 'xsd:complexContent)
              (setq type (soap-parse-complex-type-complex-content c)))
@@ -908,9 +966,10 @@ NODE is assumed to be an xsd:sequence node.  In that case, each
 of its children is assumed to be a sequence element.  Each
 sequence element is parsed constructing the corresponding type.
 A list of these types is returned."
-  (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:sequence)
+  (assert (let ((n (soap-l2wk (xml-node-name node))))
+            (memq n '(xsd:sequence xsd:all)))
           nil
-          "soap-parse-sequence: expecting xsd:sequence node, got %s"
+          "soap-parse-sequence: expecting xsd:sequence or xsd:all node, got %s"
           (soap-l2wk (xml-node-name node)))
   (let (elements)
     (dolist (e (soap-xml-get-children1 node 'xsd:element))
@@ -964,7 +1023,7 @@ contents."
                                   extension 'xsd:sequence)))))
             (restriction
              (let ((base (xml-get-attribute-or-nil restriction 'base)))
-               (assert (equal base "soapenc:Array")
+               (assert (equal base (soap-wk2l "soapenc:Array"))
                        nil
                        "restrictions supported only for soapenc:Array types, this is a %s"
                        base))
@@ -1234,9 +1293,9 @@ type-info stored in TYPE."
     (if (null contents)
         nil
         (ecase type-kind
-          (string (car contents))
+          ((string anyURI) (car contents))
           (dateTime (car contents))     ; TODO: convert to a date time
-          ((long int float) (string-to-number (car contents)))
+          ((long int integer unsignedInt byte float double) (string-to-number (car contents)))
           (boolean (string= (downcase (car contents)) "true"))
           (base64Binary (base64-decode-string (car contents)))
           (anyType (soap-decode-any-type node))
@@ -1282,6 +1341,10 @@ This is because it is easier to work with list results in LISP."
 (progn
   (put (aref (make-soap-basic-type) 0)
        'soap-decoder 'soap-decode-basic-type)
+  ;; just use the basic type decoder for the simple type -- we accept any
+  ;; value and don't do any validation on it.
+  (put (aref (make-soap-simple-type) 0)
+       'soap-decoder 'soap-decode-basic-type)
   (put (aref (make-soap-sequence-type) 0)
        'soap-decoder 'soap-decode-sequence-type)
   (put (aref (make-soap-array-type) 0)
@@ -1311,10 +1374,11 @@ WSDL is used to decode the NODE"
                                           fault 'faultcode))))
                               (car-safe (xml-node-children n))))
                 (fault-string (let ((n (car (xml-get-children
-                                            fault 'faultstring))))
-                                (car-safe (xml-node-children n)))))
+                                             fault 'faultstring))))
+                                (car-safe (xml-node-children n))))
+                (detail (xml-get-children fault 'detail)))
           (while t
-            (signal 'soap-error (list fault-code fault-string))))))
+            (signal 'soap-error (list fault-code fault-string detail))))))
 
       ;; First (non string) element of the body is the root node of he
       ;; response
@@ -1446,7 +1510,7 @@ instead."
         (progn
           (insert ">")
           (case basic-type
-            (string
+            ((string anyURI)
              (unless (stringp value)
                (error "Soap-encode-basic-type(%s, %s, %s): not a string value"
                       xml-tag value xsi-type))
@@ -1473,10 +1537,19 @@ instead."
                       xml-tag value xsi-type))
              (insert (if value "true" "false")))
 
-            ((long int)
+            ((long int integer byte unsignedInt)
              (unless (integerp value)
                (error "Soap-encode-basic-type(%s, %s, %s): not an integer value"
                       xml-tag value xsi-type))
+             (when (and (eq basic-type 'unsignedInt) (< value 0))
+               (error "Soap-encode-basic-type(%s, %s, %s): not a positive integer"
+                      xml-tag value xsi-type))
+             (insert (number-to-string value)))
+
+            ((float double)
+             (unless (numberp value)
+               (error "Soap-encode-basic-type(%s, %s, %s): not a number"
+                      xml-tag value xsi-type))
              (insert (number-to-string value)))
 
             (base64Binary
@@ -1493,6 +1566,20 @@ instead."
         (insert " xsi:nil=\"true\">"))
     (insert "</" xml-tag ">\n")))
 
+(defun soap-encode-simple-type (xml-tag value type)
+  "Encode inside XML-TAG the LISP VALUE according to TYPE."
+
+  ;; Validate VALUE against the simple type's enumeration, than just encode it
+  ;; using `soap-encode-basic-type'
+
+  (let ((enumeration (soap-simple-type-enumeration type)))
+    (unless (and (> (length enumeration) 1)
+                 (member value enumeration))
+      (error "soap-encode-simple-type(%s, %s, %s): bad value, should be one of %s"
+             xml-tag value (soap-element-fq-name type) enumeration)))
+
+  (soap-encode-basic-type xml-tag value type))
+
 (defun soap-encode-sequence-type (xml-tag value type)
   "Encode inside XML-TAG the LISP VALUE according to TYPE.
 Do not call this function directly, use `soap-encode-value'
@@ -1553,6 +1640,8 @@ instead."
 (progn
   (put (aref (make-soap-basic-type) 0)
        'soap-encoder 'soap-encode-basic-type)
+  (put (aref (make-soap-simple-type) 0)
+       'soap-encoder 'soap-encode-simple-type)
   (put (aref (make-soap-sequence-type) 0)
        'soap-encoder 'soap-encode-sequence-type)
   (put (aref (make-soap-array-type) 0)
@@ -1679,7 +1768,11 @@ operations in a WSDL document."
             (url-package-name "soap-client.el")
             (url-package-version "1.0")
             (url-http-version "1.0")
-            (url-request-data (soap-create-envelope operation parameters wsdl))
+           (url-request-data
+            ;; url-request-data expects a unibyte string already encoded...
+            (encode-coding-string
+             (soap-create-envelope operation parameters wsdl)
+             'utf-8))
             (url-mime-charset-string "utf-8;q=1, iso-8859-1;q=0.5")
             (url-request-coding-system 'utf-8)
             (url-http-attempt-keepalives t)
@@ -1702,10 +1795,6 @@ operations in a WSDL document."
                     ;; error)
                     (warn "Error in SOAP response: HTTP code %s"
                          url-http-response-status))
-                                 (when (> (buffer-size) 1000000)
-                                   (soap-warning
-                                   "Received large message: %s bytes"
-                                   (buffer-size)))
                 (let ((mime-part (mm-dissect-buffer t t)))
                   (unless mime-part
                     (error "Failed to decode response from server"))
@@ -1733,8 +1822,7 @@ operations in a WSDL document."
 
 \f
 ;;; Local Variables:
-;;; mode: emacs-lisp
-;;; mode: outline-minor
+;;; eval: (outline-minor-mode 1)
 ;;; outline-regexp: ";;;;+"
 ;;; End: