forked from baohaojun/org-jira
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsoap-client.el
1754 lines (1521 loc) · 71.1 KB
/
soap-client.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;;; soap-client.el -- Access SOAP web services from Emacs
;; Copyright (C) 2009-2011 Free Software Foundation, Inc.
;; Author: Alexandru Harsanyi (AlexHarsanyi@gmail.com)
;; Created: December, 2009
;; Keywords: soap, web-services, comm, hypermedia
;; 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.
;; 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; To use the SOAP client, you first need to load the WSDL document for the
;; service you want to access, using `soap-load-wsdl-from-url'. A WSDL
;; document describes the available operations of the SOAP service, how their
;; parameters and responses are encoded. To invoke operations, you use the
;; `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
;; 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.
;;
;;; Code:
(eval-when-compile (require 'cl))
(require 'xml)
(require 'warnings)
(require 'url)
(require 'url-http)
(require 'url-util)
(require 'mm-decode)
(defsubst soap-warning (message &rest args)
"Display a warning MESSAGE with ARGS, using the 'soap-client warning type."
(display-warning 'soap-client (apply 'format message args) :warning))
(defgroup soap-client nil
"Access SOAP web services from Emacs."
:group 'tools)
;;;; Support for parsing XML documents with namespaces
;; XML documents with namespaces are difficult to parse because the names of
;; the nodes depend on what "xmlns" aliases have been defined in the document.
;; To work with such documents, we introduce a translation layer between a
;; "well known" namespace tag and the local namespace tag in the document
;; being parsed.
(defconst soap-well-known-xmlns
'(("apachesoap" . "http://xml.apache.org/xml-soap")
("soapenc" . "http://schemas.xmlsoap.org/soap/encoding/")
("wsdl" . "http://schemas.xmlsoap.org/wsdl/")
("wsdlsoap" . "http://schemas.xmlsoap.org/wsdl/soap/")
("xsd" . "http://www.w3.org/2001/XMLSchema")
("xsi" . "http://www.w3.org/2001/XMLSchema-instance")
("soap" . "http://schemas.xmlsoap.org/soap/envelope/")
("soap12" . "http://schemas.xmlsoap.org/wsdl/soap12/")
("http" . "http://schemas.xmlsoap.org/wsdl/http/")
("mime" . "http://schemas.xmlsoap.org/wsdl/mime/"))
"A list of well known xml namespaces and their aliases.")
(defvar soap-local-xmlns nil
"A list of local namespace aliases.
This is a dynamically bound variable, controlled by
`soap-with-local-xmlns'.")
(defvar soap-default-xmlns nil
"The default XML namespaces.
Names in this namespace will be unqualified. This is a
dynamically bound variable, controlled by
`soap-with-local-xmlns'")
(defvar soap-target-xmlns nil
"The target XML namespace.
New XSD elements will be defined in this namespace, unless they
are fully qualified for a different namespace. This is a
dynamically bound variable, controlled by
`soap-with-local-xmlns'")
(defun soap-wk2l (well-known-name)
"Return local variant of WELL-KNOWN-NAME.
This is done by looking up the namespace in the
`soap-well-known-xmlns' table and resolving the namespace to
the local name based on the current local translation table
`soap-local-xmlns'. See also `soap-with-local-xmlns'."
(let ((wk-name-1 (if (symbolp well-known-name)
(symbol-name well-known-name)
well-known-name)))
(cond
((string-match "^\\(.*\\):\\(.*\\)$" wk-name-1)
(let ((ns (match-string 1 wk-name-1))
(name (match-string 2 wk-name-1)))
(let ((namespace (cdr (assoc ns soap-well-known-xmlns))))
(cond ((equal namespace soap-default-xmlns)
;; Name is unqualified in the default namespace
(if (symbolp well-known-name)
(intern name)
name))
(t
(let* ((local-ns (car (rassoc namespace soap-local-xmlns)))
(local-name (concat local-ns ":" name)))
(if (symbolp well-known-name)
(intern local-name)
local-name)))))))
(t well-known-name))))
(defun soap-l2wk (local-name)
"Convert LOCAL-NAME into a well known name.
The namespace of LOCAL-NAME is looked up in the
`soap-well-known-xmlns' table and a well known namespace tag is
used in the name.
nil is returned if there is no well-known namespace for the
namespace of LOCAL-NAME."
(let ((l-name-1 (if (symbolp local-name)
(symbol-name local-name)
local-name))
namespace name)
(cond
((string-match "^\\(.*\\):\\(.*\\)$" l-name-1)
(setq name (match-string 2 l-name-1))
(let ((ns (match-string 1 l-name-1)))
(setq namespace (cdr (assoc ns soap-local-xmlns)))
(unless namespace
(error "Soap-l2wk(%s): no namespace for alias %s" local-name ns))))
(t
(setq name l-name-1)
(setq namespace soap-default-xmlns)))
(if namespace
(let ((well-known-ns (car (rassoc namespace soap-well-known-xmlns))))
(if well-known-ns
(let ((well-known-name (concat well-known-ns ":" name)))
(if (symbol-name local-name)
(intern well-known-name)
well-known-name))
(progn
;; (soap-warning "soap-l2wk(%s): namespace %s has no well-known tag"
;; local-name namespace)
nil)))
;; if no namespace is defined, just return the unqualified name
name)))
(defun soap-l2fq (local-name &optional use-tns)
"Convert LOCAL-NAME into a fully qualified name.
A fully qualified name is a cons of the namespace name and the
name of the element itself. For example \"xsd:string\" is
converted to \(\"http://www.w3.org/2001/XMLSchema\" . \"string\"\).
The USE-TNS argument specifies what to do when LOCAL-NAME has no
namespace tag. If USE-TNS is non-nil, the `soap-target-xmlns'
will be used as the element's namespace, otherwise
`soap-default-xmlns' will be used.
This is needed because different parts of a WSDL document can use
different namespace aliases for the same element."
(let ((local-name-1 (if (symbolp local-name)
(symbol-name local-name)
local-name)))
(cond ((string-match "^\\(.*\\):\\(.*\\)$" local-name-1)
(let ((ns (match-string 1 local-name-1))
(name (match-string 2 local-name-1)))
(let ((namespace (cdr (assoc ns soap-local-xmlns))))
(if namespace
(cons namespace name)
(error "Soap-l2fq(%s): unknown alias %s" local-name ns)))))
(t
(cons (if use-tns
soap-target-xmlns
soap-default-xmlns)
local-name)))))
(defun soap-extract-xmlns (node &optional xmlns-table)
"Return a namespace alias table for NODE by extending XMLNS-TABLE."
(let (xmlns default-ns target-ns)
(dolist (a (xml-node-attributes node))
(let ((name (symbol-name (car a)))
(value (cdr a)))
(cond ((string= name "targetNamespace")
(setq target-ns value))
((string= name "xmlns")
(setq default-ns value))
((string-match "^xmlns:\\(.*\\)$" name)
(push (cons (match-string 1 name) value) xmlns)))))
(let ((tns (assoc "tns" xmlns)))
(cond ((and tns target-ns)
;; If a tns alias is defined for this node, it must match
;; the target namespace.
(unless (equal target-ns (cdr tns))
(soap-warning
"soap-extract-xmlns(%s): tns alias and targetNamespace mismatch"
(xml-node-name node))))
((and tns (not target-ns))
(setq target-ns (cdr tns)))
((and (not tns) target-ns)
;; a tns alias was not defined in this node. See if the node has
;; a "targetNamespace" attribute and add an alias to this. Note
;; that we might override an existing tns alias in XMLNS-TABLE,
;; but that is intended.
(push (cons "tns" target-ns) xmlns))))
(list default-ns target-ns (append xmlns xmlns-table))))
(defmacro soap-with-local-xmlns (node &rest body)
"Install a local alias table from NODE and execute BODY."
(declare (debug (form &rest form)) (indent 1))
(let ((xmlns (make-symbol "xmlns")))
`(let ((,xmlns (soap-extract-xmlns ,node soap-local-xmlns)))
(let ((soap-default-xmlns (or (nth 0 ,xmlns) soap-default-xmlns))
(soap-target-xmlns (or (nth 1 ,xmlns) soap-target-xmlns))
(soap-local-xmlns (nth 2 ,xmlns)))
,@body))))
(defun soap-get-target-namespace (node)
"Return the target namespace of NODE.
This is the namespace in which new elements will be defined."
(or (xml-get-attribute-or-nil node 'targetNamespace)
(cdr (assoc "tns" soap-local-xmlns))
soap-target-xmlns))
(defun soap-xml-get-children1 (node child-name)
"Return the children of NODE named CHILD-NAME.
This is the same as `xml-get-children', but CHILD-NAME can have
namespace tag."
(let (result)
(dolist (c (xml-node-children node))
(when (and (consp c)
(soap-with-local-xmlns c
;; We use `ignore-errors' here because we want to silently
;; skip nodes for which we cannot convert them to a
;; well-known name.
(eq (ignore-errors (soap-l2wk (xml-node-name c)))
child-name)))
(push c result)))
(nreverse result)))
(defun soap-xml-get-attribute-or-nil1 (node attribute)
"Return the NODE's ATTRIBUTE, or nil if it does not exist.
This is the same as `xml-get-attribute-or-nil', but ATTRIBUTE can
be tagged with a namespace tag."
(catch 'found
(soap-with-local-xmlns node
(dolist (a (xml-node-attributes node))
;; We use `ignore-errors' here because we want to silently skip
;; attributes for which we cannot convert them to a well-known name.
(when (eq (ignore-errors (soap-l2wk (car a))) attribute)
(throw 'found (cdr a)))))))
;;;; XML namespaces
;; An element in an XML namespace, "things" stored in soap-xml-namespaces will
;; be derived from this object.
(defstruct soap-element
name
;; The "well-known" namespace tag for the element. For example, while
;; parsing XML documents, we can have different tags for the XMLSchema
;; namespace, but internally all our XMLSchema elements will have the "xsd"
;; tag.
namespace-tag)
(defun soap-element-fq-name (element)
"Return a fully qualified name for ELEMENT.
A fq name is the concatenation of the namespace tag and the
element name."
(concat (soap-element-namespace-tag element)
":" (soap-element-name element)))
;; a namespace link stores an alias for an object in once namespace to a
;; "target" object possibly in a different namespace
(defstruct (soap-namespace-link (:include soap-element))
target)
;; A namespace is a collection of soap-element objects under a name (the name
;; of the namespace).
(defstruct soap-namespace
(name nil :read-only t) ; e.g "http://xml.apache.org/xml-soap"
(elements (make-hash-table :test 'equal) :read-only t))
(defun soap-namespace-put (element ns)
"Store ELEMENT in NS.
Multiple elements with the same name can be stored in a
namespace. When retrieving the element you can specify a
discriminant predicate to `soap-namespace-get'"
(let ((name (soap-element-name element)))
(push element (gethash name (soap-namespace-elements ns)))))
(defun soap-namespace-put-link (name target ns &optional replace)
"Store a link from NAME to TARGET in NS.
An error will be signaled if an element by the same name is
already present in NS, unless REPLACE is non nil.
TARGET can be either a SOAP-ELEMENT or a string denoting an
element name into another namespace.
If NAME is nil, an element with the same name as TARGET will be
added to the namespace."
(unless (and name (not (equal name "")))
;; 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))))))
;; 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))))
(defun soap-namespace-get (name ns &optional discriminant-predicate)
"Retrieve an element with NAME from the namespace NS.
If multiple elements with the same name exist,
DISCRIMINANT-PREDICATE is used to pick one of them. This allows
storing elements of different types (like a message type and a
binding) but the same name."
(assert (stringp name))
(let ((elements (gethash name (soap-namespace-elements ns))))
(cond (discriminant-predicate
(catch 'found
(dolist (e elements)
(when (funcall discriminant-predicate e)
(throw 'found e)))))
((= (length elements) 1) (car elements))
((> (length elements) 1)
(error
"Soap-namespace-get(%s): multiple elements, discriminant needed"
name))
(t
nil))))
;;;; WSDL documents
;;;;; WSDL document elements
(defstruct (soap-basic-type (:include soap-element))
kind ; a symbol of: string, dateTime, long, int
)
(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
)
(defstruct (soap-array-type (:include soap-element))
element-type ; WSDL-TYPE of the array elements
)
(defstruct (soap-message (:include soap-element))
parts ; ALIST of NAME => WSDL-TYPE name
)
(defstruct (soap-operation (:include soap-element))
parameter-order
input ; (NAME . MESSAGE)
output ; (NAME . MESSAGE)
faults) ; a list of (NAME . MESSAGE)
(defstruct (soap-port-type (:include soap-element))
operations) ; a namespace of operations
;; A bound operation is an operation which has a soap action and a use
;; method attached -- these are attached as part of a binding and we
;; can have different bindings for the same operations.
(defstruct soap-bound-operation
operation ; SOAP-OPERATION
soap-action ; value for SOAPAction HTTP header
use ; 'literal or 'encoded, see
; http://www.w3.org/TR/wsdl#_soap:body
)
(defstruct (soap-binding (:include soap-element))
port-type
(operations (make-hash-table :test 'equal) :readonly t))
(defstruct (soap-port (:include soap-element))
service-url
binding)
(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" "integer" "byte" "float"
"base64Binary" "anyType" "anyURI" "Array" "byte[]"))
(soap-namespace-put
(make-soap-basic-type :name type :kind (intern type))
ns))
ns))
(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" "integer" "byte" "float"
"base64Binary" "anyType" "anyURI" "Array" "byte[]"))
(soap-namespace-put
(make-soap-basic-type :name type :kind (intern type))
ns))
ns))
(defun soap-type-p (element)
"Return t if ELEMENT is a SOAP data type (basic or complex)."
(or (soap-basic-type-p element)
(soap-sequence-type-p element)
(soap-array-type-p element)))
;;;;; The WSDL document
;; The WSDL data structure used for encoding/decoding SOAP messages
(defstruct soap-wsdl
origin ; file or URL from which this wsdl was loaded
ports ; a list of SOAP-PORT instances
alias-table ; a list of namespace aliases
namespaces ; a list of namespaces
)
(defun soap-wsdl-add-alias (alias name wsdl)
"Add a namespace ALIAS for NAME to the WSDL document."
(push (cons alias name) (soap-wsdl-alias-table wsdl)))
(defun soap-wsdl-find-namespace (name wsdl)
"Find a namespace by NAME in the WSDL document."
(catch 'found
(dolist (ns (soap-wsdl-namespaces wsdl))
(when (equal name (soap-namespace-name ns))
(throw 'found ns)))))
(defun soap-wsdl-add-namespace (ns wsdl)
"Add the namespace NS to the WSDL document.
If a namespace by this name already exists in WSDL, individual
elements will be added to it."
(let ((existing (soap-wsdl-find-namespace (soap-namespace-name ns) wsdl)))
(if existing
;; Add elements from NS to EXISTING, replacing existing values.
(maphash (lambda (key value)
(dolist (v value)
(soap-namespace-put v existing)))
(soap-namespace-elements ns))
(push ns (soap-wsdl-namespaces wsdl)))))
(defun soap-wsdl-get (name wsdl &optional predicate use-local-alias-table)
"Retrieve element NAME from the WSDL document.
PREDICATE is used to differentiate between elements when NAME
refers to multiple elements. A typical value for this would be a
structure predicate for the type of element you want to retrieve.
For example, to retrieve a message named \"foo\" when other
elements named \"foo\" exist in the WSDL you could use:
(soap-wsdl-get \"foo\" WSDL 'soap-message-p)
If USE-LOCAL-ALIAS-TABLE is not nil, `soap-local-xmlns` will be
used to resolve the namespace alias."
(let ((alias-table (soap-wsdl-alias-table wsdl))
namespace element-name element)
(when (symbolp name)
(setq name (symbol-name name)))
(when use-local-alias-table
(setq alias-table (append soap-local-xmlns alias-table)))
(cond ((consp name) ; a fully qualified name, as returned by `soap-l2fq'
(setq element-name (cdr name))
(when (symbolp element-name)
(setq element-name (symbol-name element-name)))
(setq namespace (soap-wsdl-find-namespace (car name) wsdl))
(unless namespace
(error "Soap-wsdl-get(%s): unknown namespace: %s" name namespace)))
((string-match "^\\(.*\\):\\(.*\\)$" name)
(setq element-name (match-string 2 name))
(let* ((ns-alias (match-string 1 name))
(ns-name (cdr (assoc ns-alias alias-table))))
(unless ns-name
(error "Soap-wsdl-get(%s): cannot find namespace alias %s"
name ns-alias))
(setq namespace (soap-wsdl-find-namespace ns-name wsdl))
(unless namespace
(error
"Soap-wsdl-get(%s): unknown namespace %s, referenced by alias %s"
name ns-name ns-alias))))
(t
(error "Soap-wsdl-get(%s): bad name" name)))
(setq element (soap-namespace-get
element-name namespace
(if predicate
(lambda (e)
(or (funcall 'soap-namespace-link-p e)
(funcall predicate e)))
nil)))
(unless element
(error "Soap-wsdl-get(%s): cannot find element" name))
(if (soap-namespace-link-p element)
;; NOTE: don't use the local alias table here
(soap-wsdl-get (soap-namespace-link-target element) wsdl predicate)
element)))
;;;;; Resolving references for wsdl types
;; See `soap-wsdl-resolve-references', which is the main entry point for
;; resolving references
(defun soap-resolve-references-for-element (element wsdl)
"Resolve references in ELEMENT using the WSDL document.
This is a generic function which invokes a specific function
depending on the element type.
If ELEMENT has no resolver function, it is silently ignored.
All references are resolved in-place, that is the ELEMENT is
updated."
(let ((resolver (get (aref element 0) 'soap-resolve-references)))
(when resolver
(funcall resolver element wsdl))))
(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
`soap-wsdl-resolve-references'"
(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))))
(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-element-p element-type)
;; since the element already has a child element, it
;; could be an inline structure. we must resolve
;; references in it, because it might not be reached by
;; scanning the wsdl names.
(soap-resolve-references-for-element element-type wsdl))))))
(defun soap-resolve-references-for-array-type (type wsdl)
"Resolve references for an array TYPE using WSDL.
See also `soap-resolve-references-for-element' and
`soap-wsdl-resolve-references'"
(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)))))
(defun soap-resolve-references-for-message (message wsdl)
"Resolve references for a MESSAGE type using the WSDL document.
See also `soap-resolve-references-for-element' and
`soap-wsdl-resolve-references'"
(let (resolved-parts)
(dolist (part (soap-message-parts message))
(let ((name (car part))
(type (cdr part)))
(when (stringp name)
(setq name (intern name)))
(when (or (consp type) (stringp type))
(setq type (soap-wsdl-get type wsdl 'soap-type-p)))
(push (cons name type) resolved-parts)))
(setf (soap-message-parts message) (nreverse resolved-parts))))
(defun soap-resolve-references-for-operation (operation wsdl)
"Resolve references for an OPERATION type using the WSDL document.
See also `soap-resolve-references-for-element' and
`soap-wsdl-resolve-references'"
(let ((input (soap-operation-input operation))
(counter 0))
(let ((name (car input))
(message (cdr input)))
;; Name this part if it was not named
(when (or (null name) (equal name ""))
(setq name (format "in%d" (incf counter))))
(when (or (consp message) (stringp message))
(setf (soap-operation-input operation)
(cons (intern name)
(soap-wsdl-get message wsdl 'soap-message-p))))))
(let ((output (soap-operation-output operation))
(counter 0))
(let ((name (car output))
(message (cdr output)))
(when (or (null name) (equal name ""))
(setq name (format "out%d" (incf counter))))
(when (or (consp message) (stringp message))
(setf (soap-operation-output operation)
(cons (intern name)
(soap-wsdl-get message wsdl 'soap-message-p))))))
(let ((resolved-faults nil)
(counter 0))
(dolist (fault (soap-operation-faults operation))
(let ((name (car fault))
(message (cdr fault)))
(when (or (null name) (equal name ""))
(setq name (format "fault%d" (incf counter))))
(if (or (consp message) (stringp message))
(push (cons (intern name)
(soap-wsdl-get message wsdl 'soap-message-p))
resolved-faults)
(push fault resolved-faults))))
(setf (soap-operation-faults operation) resolved-faults))
(when (= (length (soap-operation-parameter-order operation)) 0)
(setf (soap-operation-parameter-order operation)
(mapcar 'car (soap-message-parts
(cdr (soap-operation-input operation))))))
(setf (soap-operation-parameter-order operation)
(mapcar (lambda (p)
(if (stringp p)
(intern p)
p))
(soap-operation-parameter-order operation))))
(defun soap-resolve-references-for-binding (binding wsdl)
"Resolve references for a BINDING type using the WSDL document.
See also `soap-resolve-references-for-element' and
`soap-wsdl-resolve-references'"
(when (or (consp (soap-binding-port-type binding))
(stringp (soap-binding-port-type binding)))
(setf (soap-binding-port-type binding)
(soap-wsdl-get (soap-binding-port-type binding)
wsdl 'soap-port-type-p)))
(let ((port-ops (soap-port-type-operations (soap-binding-port-type binding))))
(maphash (lambda (k v)
(setf (soap-bound-operation-operation v)
(soap-namespace-get k port-ops 'soap-operation-p)))
(soap-binding-operations binding))))
(defun soap-resolve-references-for-port (port wsdl)
"Resolve references for a PORT type using the WSDL document.
See also `soap-resolve-references-for-element' and
`soap-wsdl-resolve-references'"
(when (or (consp (soap-port-binding port))
(stringp (soap-port-binding port)))
(setf (soap-port-binding port)
(soap-wsdl-get (soap-port-binding port) wsdl 'soap-binding-p))))
;; Install resolvers for our types
(progn
(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
'soap-resolve-references-for-array-type)
(put (aref (make-soap-message) 0) 'soap-resolve-references
'soap-resolve-references-for-message)
(put (aref (make-soap-operation) 0) 'soap-resolve-references
'soap-resolve-references-for-operation)
(put (aref (make-soap-binding) 0) 'soap-resolve-references
'soap-resolve-references-for-binding)
(put (aref (make-soap-port) 0) 'soap-resolve-references
'soap-resolve-references-for-port))
(defun soap-wsdl-resolve-references (wsdl)
"Resolve all references inside the WSDL structure.
When the WSDL elements are created from the XML document, they
refer to each other by name. For example, the ELEMENT-TYPE slot
of an SOAP-ARRAY-TYPE will contain the name of the element and
the user would have to call `soap-wsdl-get' to obtain the actual
element.
After the entire document is loaded, we resolve all these
references to the actual elements they refer to so that at
runtime, we don't have to call `soap-wsdl-get' each time we
traverse an element tree."
(let ((nprocessed 0)
(nstag-id 0)
(alias-table (soap-wsdl-alias-table wsdl)))
(dolist (ns (soap-wsdl-namespaces wsdl))
(let ((nstag (car-safe (rassoc (soap-namespace-name ns) alias-table))))
(unless nstag
;; If this namespace does not have an alias, create one for it.
(catch 'done
(while t
(setq nstag (format "ns%d" (incf nstag-id)))
(unless (assoc nstag alias-table)
(soap-wsdl-add-alias nstag (soap-namespace-name ns) wsdl)
(throw 'done t)))))
(maphash (lambda (name element)
(cond ((soap-element-p element) ; skip links
(incf nprocessed)
(soap-resolve-references-for-element element wsdl)
(setf (soap-element-namespace-tag element) nstag))
((listp element)
(dolist (e element)
(when (soap-element-p e)
(incf nprocessed)
(soap-resolve-references-for-element e wsdl)
(setf (soap-element-namespace-tag e) nstag))))))
(soap-namespace-elements ns))))
(message "Processed %d" nprocessed))
wsdl)
;;;;; Loading WSDL from XML documents
(defun soap-load-wsdl-from-url (url)
"Load a WSDL document from URL and return it.
The returned WSDL document needs to be used for `soap-invoke'
calls."
(let ((url-request-method "GET")
(url-package-name "soap-client.el")
(url-package-version "1.0")
(url-mime-charset-string "utf-8;q=1, iso-8859-1;q=0.5")
(url-request-coding-system 'utf-8)
(url-http-attempt-keepalives nil))
(let ((buffer (url-retrieve-synchronously url)))
(with-current-buffer buffer
(declare (special url-http-response-status))
(if (> url-http-response-status 299)
(error "Error retrieving WSDL: %s" url-http-response-status))
(let ((mime-part (mm-dissect-buffer t t)))
(unless mime-part
(error "Failed to decode response from server"))
(unless (equal (car (mm-handle-type mime-part)) "text/xml")
(error "Server response is not an XML document"))
(with-temp-buffer
(mm-insert-part mime-part)
(let ((wsdl-xml (car (xml-parse-region (point-min) (point-max)))))
(prog1
(let ((wsdl (soap-parse-wsdl wsdl-xml)))
(setf (soap-wsdl-origin wsdl) url)
wsdl)
(kill-buffer buffer)))))))))
(defun soap-load-wsdl (file)
"Load a WSDL document from FILE and return it."
(with-temp-buffer
(insert-file-contents file)
(let ((xml (car (xml-parse-region (point-min) (point-max)))))
(let ((wsdl (soap-parse-wsdl xml)))
(setf (soap-wsdl-origin wsdl) file)
wsdl))))
(defun soap-parse-wsdl (node)
"Construct a WSDL structure from NODE, which is an XML document."
(soap-with-local-xmlns node
(assert (eq (soap-l2wk (xml-node-name node)) 'wsdl:definitions)
nil
"soap-parse-wsdl: expecting wsdl:definitions node, got %s"
(soap-l2wk (xml-node-name node)))
(let ((wsdl (make-soap-wsdl)))
;; Add the local alias table to the wsdl document -- it will be used for
;; all types in this document even after we finish parsing it.
(setf (soap-wsdl-alias-table wsdl) soap-local-xmlns)
;; Add the XSD types to the wsdl document
(let ((ns (soap-default-xsd-types)))
(soap-wsdl-add-namespace ns wsdl)
(soap-wsdl-add-alias "xsd" (soap-namespace-name ns) wsdl))
;; Add the soapenc types to the wsdl document
(let ((ns (soap-default-soapenc-types)))
(soap-wsdl-add-namespace ns wsdl)
(soap-wsdl-add-alias "soapenc" (soap-namespace-name ns) wsdl))
;; Find all the 'xsd:schema nodes which are children of wsdl:types nodes
;; and build our type-library
(let ((types (car (soap-xml-get-children1 node 'wsdl:types))))
(dolist (node (xml-node-children types))
;; We cannot use (xml-get-children node (soap-wk2l 'xsd:schema))
;; because each node can install its own alias type so the schema
;; nodes might have a different prefix.
(when (consp node)
(soap-with-local-xmlns node
(when (eq (soap-l2wk (xml-node-name node)) 'xsd:schema)
(soap-wsdl-add-namespace (soap-parse-schema node) wsdl))))))
(let ((ns (make-soap-namespace :name (soap-get-target-namespace node))))
(dolist (node (soap-xml-get-children1 node 'wsdl:message))
(soap-namespace-put (soap-parse-message node) ns))
(dolist (node (soap-xml-get-children1 node 'wsdl:portType))
(let ((port-type (soap-parse-port-type node)))
(soap-namespace-put port-type ns)
(soap-wsdl-add-namespace
(soap-port-type-operations port-type) wsdl)))
(dolist (node (soap-xml-get-children1 node 'wsdl:binding))
(soap-namespace-put (soap-parse-binding node) ns))
(dolist (node (soap-xml-get-children1 node 'wsdl:service))
(dolist (node (soap-xml-get-children1 node 'wsdl:port))
(let ((name (xml-get-attribute node 'name))
(binding (xml-get-attribute node 'binding))
(url (let ((n (car (soap-xml-get-children1
node 'wsdlsoap:address))))
(xml-get-attribute n 'location))))
(let ((port (make-soap-port
:name name :binding (soap-l2fq binding 'tns)
:service-url url)))
(soap-namespace-put port ns)
(push port (soap-wsdl-ports wsdl))))))
(soap-wsdl-add-namespace ns wsdl))
(soap-wsdl-resolve-references wsdl)
wsdl)))
(defun soap-parse-schema (node)
"Parse a schema NODE.
Return a SOAP-NAMESPACE containing the elements."
(soap-with-local-xmlns node
(assert (eq (soap-l2wk (xml-node-name node)) 'xsd:schema)
nil
"soap-parse-schema: expecting an xsd:schema node, got %s"
(soap-l2wk (xml-node-name node)))
(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:complexType))
(soap-namespace-put (soap-parse-complex-type node) ns))
(dolist (node (soap-xml-get-children1 node 'xsd:element))
(soap-namespace-put (soap-parse-schema-element node) ns))
ns)))
(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)
nil
"soap-parse-schema-element: expecting xsd:element node, got %s"
(soap-l2wk (xml-node-name node)))
(let ((name (xml-get-attribute-or-nil node 'name))
type)
;; A schema element that contains an inline complex type --
;; construct the actual complex type for it.
(let ((type-node (soap-xml-get-children1 node 'xsd:complexType)))
(when (> (length type-node) 0)
(assert (= (length type-node) 1)) ; only one complex type
; definition per element
(setq type (soap-parse-complex-type (car type-node)))))
(setf (soap-element-name type) name)
type))
(defun soap-parse-complex-type (node)
"Parse NODE and construct a complex type from it."
(assert (eq (soap-l2wk (xml-node-name node)) 'xsd:complexType)
nil
"soap-parse-complex-type: expecting xsd:complexType node, got %s"
(soap-l2wk (xml-node-name node)))
(let ((name (xml-get-attribute-or-nil node 'name))
;; Use a dummy type for the complex type, it will be replaced
;; with the real type below, except when the complex type node
;; is empty...
(type (make-soap-sequence-type :elements nil)))
(dolist (c (xml-node-children node))
(when (consp c) ; skip string nodes, which are whitespace
(let ((node-name (soap-l2wk (xml-node-name c))))
(cond
;; 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)))
((eq node-name 'xsd:attribute)
;; The name of this node comes from an attribute tag
(let ((n (xml-get-attribute-or-nil c 'name)))
(setq name n)))
(t
(error "Unknown node type %s" node-name))))))
(setf (soap-element-name type) name)
type))
(defun soap-parse-sequence (node)
"Parse NODE and a list of sequence elements that it defines.
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 (let ((n (soap-l2wk (xml-node-name node))))
(memq n '(xsd:sequence xsd:all)))
nil
"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))
(let ((name (xml-get-attribute-or-nil e 'name))
(type (xml-get-attribute-or-nil e 'type))
(nillable? (or (equal (xml-get-attribute-or-nil e 'nillable) "true")
(let ((e (xml-get-attribute-or-nil e 'minOccurs)))
(and e (equal e "0")))))
(multiple? (let ((e (xml-get-attribute-or-nil e 'maxOccurs)))
(and e (not (equal e "1"))))))
(if type
(setq type (soap-l2fq type 'tns))
;; The node does not have a type, maybe it has a complexType
;; defined inline...
(let ((type-node (soap-xml-get-children1 e 'xsd:complexType)))
(when (> (length type-node) 0)
(assert (= (length type-node) 1)
nil
"only one complex type definition per element supported")
(setq type (soap-parse-complex-type (car type-node))))))
(push (make-soap-sequence-element
:name (intern name) :type type :nillable? nillable?
:multiple? multiple?)
elements)))
(nreverse elements)))
(defun soap-parse-complex-type-sequence (node)
"Parse NODE as a sequence type."
(let ((elements (soap-parse-sequence node)))
(make-soap-sequence-type :elements elements)))
(defun soap-parse-complex-type-complex-content (node)
"Parse NODE as a xsd:complexContent node.
A sequence or an array type is returned depending on the actual
contents."
(assert (eq (soap-l2wk (xml-node-name node)) 'xsd:complexContent)
nil
"soap-parse-complex-type-complex-content: expecting xsd:complexContent node, got %s"
(soap-l2wk (xml-node-name node)))
(let (array? parent elements)
(let ((extension (car-safe (soap-xml-get-children1 node 'xsd:extension)))
(restriction (car-safe
(soap-xml-get-children1 node 'xsd:restriction))))
;; a complex content node is either an extension or a restriction
(cond (extension
(setq parent (xml-get-attribute-or-nil extension 'base))
(setq elements (soap-parse-sequence
(car (soap-xml-get-children1
extension 'xsd:sequence)))))
(restriction
(let ((base (xml-get-attribute-or-nil restriction 'base)))
(assert (equal base (soap-wk2l "soapenc:Array"))
nil
"restrictions supported only for soapenc:Array types, this is a %s"
base))
(setq array? t)
(let ((attribute (car (soap-xml-get-children1
restriction 'xsd:attribute))))
(let ((array-type (soap-xml-get-attribute-or-nil1
attribute 'wsdl:arrayType)))
(when (string-match "^\\(.*\\)\\[\\]$" array-type)
(setq parent (match-string 1 array-type))))))
(t
(error "Unknown complex type"))))
(if parent
(setq parent (soap-l2fq parent 'tns)))
(if array?
(make-soap-array-type :element-type parent)
(make-soap-sequence-type :parent parent :elements elements))))