1# This program is free software; you can redistribute it and/or modify it under
2# the terms of the (LGPL) GNU Lesser General Public License as published by the
3# Free Software Foundation; either version 3 of the License, or (at your
4# option) any later version.
5#
6# This program is distributed in the hope that it will be useful, but WITHOUT
7# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
8# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
9# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
10#
11# You should have received a copy of the GNU Lesser General Public License
12# along with this program; if not, write to the Free Software Foundation, Inc.,
13# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14# written by: Jeff Ortel ( jortel@redhat.com )
15
16"""
17(WS) SOAP binding classes.
18
19"""
20
21from suds import *
22from suds.sax import Namespace
23from suds.sax.document import Document
24from suds.sax.element import Element
25from suds.sudsobject import Factory
26from suds.mx import Content
27from suds.mx.literal import Literal as MxLiteral
28from suds.umx.typed import Typed as UmxTyped
29from suds.bindings.multiref import MultiRef
30from suds.xsd.query import TypeQuery, ElementQuery
31from suds.xsd.sxbasic import Element as SchemaElement
32from suds.options import Options
33from suds.plugin import PluginContainer
34
35from copy import deepcopy
36
37
38envns = ("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/")
39
40
41class Binding(object):
42    """
43    The SOAP binding class used to process outgoing and incoming SOAP messages
44    per the WSDL port binding.
45
46    @ivar wsdl: The WSDL.
47    @type wsdl: L{suds.wsdl.Definitions}
48    @ivar schema: The collective schema contained within the WSDL.
49    @type schema: L{xsd.schema.Schema}
50    @ivar options: A dictionary options.
51    @type options: L{Options}
52
53    """
54
55    def __init__(self, wsdl):
56        """
57        @param wsdl: A WSDL.
58        @type wsdl: L{wsdl.Definitions}
59
60        """
61        self.wsdl = wsdl
62        self.multiref = MultiRef()
63
64    def schema(self):
65        return self.wsdl.schema
66
67    def options(self):
68        return self.wsdl.options
69
70    def unmarshaller(self):
71        """
72        Get the appropriate schema based XML decoder.
73
74        @return: Typed unmarshaller.
75        @rtype: L{UmxTyped}
76
77        """
78        return UmxTyped(self.schema())
79
80    def marshaller(self):
81        """
82        Get the appropriate XML encoder.
83
84        @return: An L{MxLiteral} marshaller.
85        @rtype: L{MxLiteral}
86
87        """
88        return MxLiteral(self.schema(), self.options().xstq)
89
90    def param_defs(self, method):
91        """
92        Get parameter definitions.
93
94        Each I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple.
95
96        @param method: A service method.
97        @type method: I{service.Method}
98        @return: A collection of parameter definitions
99        @rtype: [I{pdef},...]
100
101        """
102        raise Exception("not implemented")
103
104    def get_message(self, method, args, kwargs):
105        """
106        Get a SOAP message for the specified method, args and SOAP headers.
107
108        This is the entry point for creating an outbound SOAP message.
109
110        @param method: The method being invoked.
111        @type method: I{service.Method}
112        @param args: A list of args for the method invoked.
113        @type args: list
114        @param kwargs: Named (keyword) args for the method invoked.
115        @type kwargs: dict
116        @return: The SOAP envelope.
117        @rtype: L{Document}
118
119        """
120        content = self.headercontent(method)
121        header = self.header(content)
122        content = self.bodycontent(method, args, kwargs)
123        body = self.body(content)
124        env = self.envelope(header, body)
125        if self.options().prefixes:
126            body.normalizePrefixes()
127            env.promotePrefixes()
128        else:
129            env.refitPrefixes()
130        return Document(env)
131
132    def get_reply(self, method, replyroot):
133        """
134        Process the I{reply} for the specified I{method} by unmarshalling it
135        into into Python object(s).
136
137        @param method: The name of the invoked method.
138        @type method: str
139        @param replyroot: The reply XML root node received after invoking the
140            specified method.
141        @type replyroot: L{Element}
142        @return: The unmarshalled reply. The returned value is an L{Object} or
143            a I{list} depending on whether the service returns a single object
144            or a collection.
145        @rtype: L{Object} or I{list}
146
147        """
148        soapenv = replyroot.getChild("Envelope", envns)
149        soapenv.promotePrefixes()
150        soapbody = soapenv.getChild("Body", envns)
151        soapbody = self.multiref.process(soapbody)
152        nodes = self.replycontent(method, soapbody)
153        rtypes = self.returned_types(method)
154        if len(rtypes) > 1:
155            return self.replycomposite(rtypes, nodes)
156        if len(rtypes) == 0:
157            return
158        if rtypes[0].multi_occurrence():
159            return self.replylist(rtypes[0], nodes)
160        if len(nodes):
161            resolved = rtypes[0].resolve(nobuiltin=True)
162            return self.unmarshaller().process(nodes[0], resolved)
163
164    def replylist(self, rt, nodes):
165        """
166        Construct a I{list} reply.
167
168        Called for replies with possible multiple occurrences.
169
170        @param rt: The return I{type}.
171        @type rt: L{suds.xsd.sxbase.SchemaObject}
172        @param nodes: A collection of XML nodes.
173        @type nodes: [L{Element},...]
174        @return: A list of I{unmarshalled} objects.
175        @rtype: [L{Object},...]
176
177        """
178        resolved = rt.resolve(nobuiltin=True)
179        unmarshaller = self.unmarshaller()
180        return [unmarshaller.process(node, resolved) for node in nodes]
181
182    def replycomposite(self, rtypes, nodes):
183        """
184        Construct a I{composite} reply.
185
186        Called for replies with multiple output nodes.
187
188        @param rtypes: A list of known return I{types}.
189        @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...]
190        @param nodes: A collection of XML nodes.
191        @type nodes: [L{Element},...]
192        @return: The I{unmarshalled} composite object.
193        @rtype: L{Object},...
194
195        """
196        dictionary = {}
197        for rt in rtypes:
198            dictionary[rt.name] = rt
199        unmarshaller = self.unmarshaller()
200        composite = Factory.object("reply")
201        for node in nodes:
202            tag = node.name
203            rt = dictionary.get(tag)
204            if rt is None:
205                if node.get("id") is None and not self.options().allowUnknownMessageParts:
206                    message = "<%s/> not mapped to message part" % (tag,)
207                    raise Exception(message)
208                continue
209            resolved = rt.resolve(nobuiltin=True)
210            sobject = unmarshaller.process(node, resolved)
211            value = getattr(composite, tag, None)
212            if value is None:
213                if rt.multi_occurrence():
214                    value = []
215                    setattr(composite, tag, value)
216                    value.append(sobject)
217                else:
218                    setattr(composite, tag, sobject)
219            else:
220                if not isinstance(value, list):
221                    value = [value,]
222                    setattr(composite, tag, value)
223                value.append(sobject)
224        return composite
225
226    def mkparam(self, method, pdef, object):
227        """
228        Builds a parameter for the specified I{method} using the parameter
229        definition (pdef) and the specified value (object).
230
231        @param method: A method name.
232        @type method: str
233        @param pdef: A parameter definition.
234        @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
235        @param object: The parameter value.
236        @type object: any
237        @return: The parameter fragment.
238        @rtype: L{Element}
239
240        """
241        marshaller = self.marshaller()
242        content = Content(tag=pdef[0], value=object, type=pdef[1],
243            real=pdef[1].resolve())
244        return marshaller.process(content)
245
246    def mkheader(self, method, hdef, object):
247        """
248        Builds a soapheader for the specified I{method} using the header
249        definition (hdef) and the specified value (object).
250
251        @param method: A method name.
252        @type method: str
253        @param hdef: A header definition.
254        @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
255        @param object: The header value.
256        @type object: any
257        @return: The parameter fragment.
258        @rtype: L{Element}
259
260        """
261        marshaller = self.marshaller()
262        if isinstance(object, (list, tuple)):
263            return [self.mkheader(method, hdef, item) for item in object]
264        content = Content(tag=hdef[0], value=object, type=hdef[1])
265        return marshaller.process(content)
266
267    def envelope(self, header, body):
268        """
269        Build the B{<Envelope/>} for a SOAP outbound message.
270
271        @param header: The SOAP message B{header}.
272        @type header: L{Element}
273        @param body: The SOAP message B{body}.
274        @type body: L{Element}
275        @return: The SOAP envelope containing the body and header.
276        @rtype: L{Element}
277
278        """
279        env = Element("Envelope", ns=envns)
280        env.addPrefix(Namespace.xsins[0], Namespace.xsins[1])
281        env.append(header)
282        env.append(body)
283        return env
284
285    def header(self, content):
286        """
287        Build the B{<Body/>} for a SOAP outbound message.
288
289        @param content: The header content.
290        @type content: L{Element}
291        @return: The SOAP body fragment.
292        @rtype: L{Element}
293
294        """
295        header = Element("Header", ns=envns)
296        header.append(content)
297        return header
298
299    def bodycontent(self, method, args, kwargs):
300        """
301        Get the content for the SOAP I{body} node.
302
303        @param method: A service method.
304        @type method: I{service.Method}
305        @param args: method parameter values.
306        @type args: list
307        @param kwargs: Named (keyword) args for the method invoked.
308        @type kwargs: dict
309        @return: The XML content for the <body/>.
310        @rtype: [L{Element},...]
311
312        """
313        raise Exception("not implemented")
314
315    def headercontent(self, method):
316        """
317        Get the content for the SOAP I{Header} node.
318
319        @param method: A service method.
320        @type method: I{service.Method}
321        @return: The XML content for the <body/>.
322        @rtype: [L{Element},...]
323
324        """
325        content = []
326        wsse = self.options().wsse
327        if wsse is not None:
328            content.append(wsse.xml())
329        headers = self.options().soapheaders
330        if not isinstance(headers, (tuple, list, dict)):
331            headers = (headers,)
332        elif not headers:
333            return content
334        pts = self.headpart_types(method)
335        if isinstance(headers, (tuple, list)):
336            n = 0
337            for header in headers:
338                if isinstance(header, Element):
339                    content.append(deepcopy(header))
340                    continue
341                if len(pts) == n:
342                    break
343                h = self.mkheader(method, pts[n], header)
344                ns = pts[n][1].namespace("ns0")
345                h.setPrefix(ns[0], ns[1])
346                content.append(h)
347                n += 1
348        else:
349            for pt in pts:
350                header = headers.get(pt[0])
351                if header is None:
352                    continue
353                h = self.mkheader(method, pt, header)
354                ns = pt[1].namespace("ns0")
355                h.setPrefix(ns[0], ns[1])
356                content.append(h)
357        return content
358
359    def replycontent(self, method, body):
360        """
361        Get the reply body content.
362
363        @param method: A service method.
364        @type method: I{service.Method}
365        @param body: The SOAP body.
366        @type body: L{Element}
367        @return: The body content.
368        @rtype: [L{Element},...]
369
370        """
371        raise Exception("not implemented")
372
373    def body(self, content):
374        """
375        Build the B{<Body/>} for a SOAP outbound message.
376
377        @param content: The body content.
378        @type content: L{Element}
379        @return: The SOAP body fragment.
380        @rtype: L{Element}
381
382        """
383        body = Element("Body", ns=envns)
384        body.append(content)
385        return body
386
387    def bodypart_types(self, method, input=True):
388        """
389        Get a list of I{parameter definitions} (pdefs) defined for the
390        specified method.
391
392        An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple,
393        while an output I{pdef} is a L{xsd.sxbase.SchemaObject}.
394
395        @param method: A service method.
396        @type method: I{service.Method}
397        @param input: Defines input/output message.
398        @type input: boolean
399        @return:  A list of parameter definitions
400        @rtype: [I{pdef},...]
401
402        """
403        if input:
404            parts = method.soap.input.body.parts
405        else:
406            parts = method.soap.output.body.parts
407        return [self.__part_type(p, input) for p in parts]
408
409    def headpart_types(self, method, input=True):
410        """
411        Get a list of header I{parameter definitions} (pdefs) defined for the
412        specified method.
413
414        An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple,
415        while an output I{pdef} is a L{xsd.sxbase.SchemaObject}.
416
417        @param method: A service method.
418        @type method: I{service.Method}
419        @param input: Defines input/output message.
420        @type input: boolean
421        @return:  A list of parameter definitions
422        @rtype: [I{pdef},...]
423
424        """
425        if input:
426            headers = method.soap.input.headers
427        else:
428            headers = method.soap.output.headers
429        return [self.__part_type(h.part, input) for h in headers]
430
431    def returned_types(self, method):
432        """
433        Get the I{method} return value type(s).
434
435        @param method: A service method.
436        @type method: I{service.Method}
437        @return: Method return value type.
438        @rtype: [L{xsd.sxbase.SchemaObject},...]
439
440        """
441        return self.bodypart_types(method, input=False)
442
443    def __part_type(self, part, input):
444        """
445        Get a I{parameter definition} (pdef) defined for a given body or header
446        message part.
447
448        An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple,
449        while an output I{pdef} is a L{xsd.sxbase.SchemaObject}.
450
451        @param part: A service method input or output part.
452        @type part: I{suds.wsdl.Part}
453        @param input: Defines input/output message.
454        @type input: boolean
455        @return:  A list of parameter definitions
456        @rtype: [I{pdef},...]
457
458        """
459        if part.element is None:
460            query = TypeQuery(part.type)
461        else:
462            query = ElementQuery(part.element)
463        part_type = query.execute(self.schema())
464        if part_type is None:
465            raise TypeNotFound(query.ref)
466        if part.type is not None:
467            part_type = PartElement(part.name, part_type)
468        if not input:
469            return part_type
470        if part_type.name is None:
471            return part.name, part_type
472        return part_type.name, part_type
473
474
475class PartElement(SchemaElement):
476    """
477    Message part referencing an XSD type and thus acting like an XSD element.
478
479    @ivar resolved: The part type.
480    @type resolved: L{suds.xsd.sxbase.SchemaObject}
481
482    """
483
484    def __init__(self, name, resolved):
485        """
486        @param name: The part name.
487        @type name: str
488        @param resolved: The part type.
489        @type resolved: L{suds.xsd.sxbase.SchemaObject}
490
491        """
492        root = Element("element", ns=Namespace.xsdns)
493        SchemaElement.__init__(self, resolved.schema, root)
494        self.__resolved = resolved
495        self.name = name
496        self.form_qualified = False
497
498    def implany(self):
499        pass
500
501    def optional(self):
502        return True
503
504    def namespace(self, prefix=None):
505        return Namespace.default
506
507    def resolve(self, nobuiltin=False):
508        if nobuiltin and self.__resolved.builtin():
509            return self
510        return self.__resolved
511