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