Add timestamp offset for block header
[p2pool.git] / SOAPpy / SOAPBuilder.py
1 """
2 ################################################################################
3 # Copyright (c) 2003, Pfizer
4 # Copyright (c) 2001, Cayce Ullman.
5 # Copyright (c) 2001, Brian Matthews.
6 #
7 # All rights reserved.
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
11 # Redistributions of source code must retain the above copyright notice, this
12 # list of conditions and the following disclaimer.
13 #
14 # Redistributions in binary form must reproduce the above copyright notice,
15 # this list of conditions and the following disclaimer in the documentation
16 # and/or other materials provided with the distribution.
17 #
18 # Neither the name of actzero, inc. nor the names of its contributors may
19 # be used to endorse or promote products derived from this software without
20 # specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
26 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #
33 ################################################################################
34 """
35
36 ident = '$Id: SOAPBuilder.py 1498 2010-03-12 02:13:19Z pooryorick $'
37 from version import __version__
38
39 import cgi
40 from wstools.XMLname import toXMLname, fromXMLname
41 import fpconst
42
43 # SOAPpy modules
44 from Config import Config
45 from NS     import NS
46 from Types  import *
47
48 # Test whether this Python version has Types.BooleanType
49 # If it doesn't have it, then False and True are serialized as integers
50 try:
51     BooleanType
52     pythonHasBooleanType = 1
53 except NameError:
54     pythonHasBooleanType = 0
55
56 ################################################################################
57 # SOAP Builder
58 ################################################################################
59 class SOAPBuilder:
60     _xml_top = '<?xml version="1.0"?>\n'
61     _xml_enc_top = '<?xml version="1.0" encoding="%s"?>\n'
62     _env_top = ( '%(ENV_T)s:Envelope\n' + \
63                  '  %(ENV_T)s:encodingStyle="%(ENC)s"\n' ) % \
64                  NS.__dict__
65     _env_bot = '</%(ENV_T)s:Envelope>\n' % NS.__dict__
66
67     # Namespaces potentially defined in the Envelope tag.
68
69     _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T,
70         NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T,
71         NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T}
72
73     def __init__(self, args = (), kw = {}, method = None, namespace = None,
74         header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8',
75         use_refs = 0, config = Config, noroot = 0):
76
77         # Test the encoding, raising an exception if it's not known
78         if encoding != None:
79             ''.encode(encoding)
80
81         self.args       = args
82         self.kw         = kw
83         self.envelope   = envelope
84         self.encoding   = encoding
85         self.method     = method
86         self.namespace  = namespace
87         self.header     = header
88         self.methodattrs= methodattrs
89         self.use_refs   = use_refs
90         self.config     = config
91         self.out        = []
92         self.tcounter   = 0
93         self.ncounter   = 1
94         self.icounter   = 1
95         self.envns      = {}
96         self.ids        = {}
97         self.depth      = 0
98         self.multirefs  = []
99         self.multis     = 0
100         self.body       = not isinstance(args, bodyType)
101         self.noroot     = noroot
102
103     def build(self):
104         if Config.debug: print "In build."
105         ns_map = {}
106
107         # Cache whether typing is on or not
108         typed = self.config.typed
109
110         if self.header:
111             # Create a header.
112             self.dump(self.header, "Header", typed = typed)
113             #self.header = None # Wipe it out so no one is using it.
114
115         if self.body:
116             # Call genns to record that we've used SOAP-ENV.
117             self.depth += 1
118             body_ns = self.genns(ns_map, NS.ENV)[0]
119             self.out.append("<%sBody>\n" % body_ns)
120
121         if self.method:
122             # Save the NS map so that it can be restored when we
123             # fall out of the scope of the method definition
124             save_ns_map = ns_map.copy()
125             self.depth += 1
126             a = ''
127             if self.methodattrs:
128                 for (k, v) in self.methodattrs.items():
129                     a += ' %s="%s"' % (k, v)
130
131             if self.namespace:  # Use the namespace info handed to us
132                 methodns, n = self.genns(ns_map, self.namespace)
133             else:
134                 methodns, n = '', ''
135
136             self.out.append('<%s%s%s%s%s>\n' % (
137                 methodns, self.method, n, a, self.genroot(ns_map)))
138
139         try:
140             if type(self.args) != TupleType:
141                 args = (self.args,)
142             else:
143                 args = self.args
144
145             for i in args:
146                 self.dump(i, typed = typed, ns_map = ns_map)
147
148             if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method):
149                 for k in self.config.argsOrdering.get(self.method):
150                     self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map)                
151             else:
152                 for (k, v) in self.kw.items():
153                     self.dump(v, k, typed = typed, ns_map = ns_map)
154                 
155         except RecursionError:
156             if self.use_refs == 0:
157                 # restart
158                 b = SOAPBuilder(args = self.args, kw = self.kw,
159                     method = self.method, namespace = self.namespace,
160                     header = self.header, methodattrs = self.methodattrs,
161                     envelope = self.envelope, encoding = self.encoding,
162                     use_refs = 1, config = self.config)
163                 return b.build()
164             raise
165
166         if self.method:
167             self.out.append("</%s%s>\n" % (methodns, self.method))
168             # End of the method definition; drop any local namespaces
169             ns_map = save_ns_map
170             self.depth -= 1
171
172         if self.body:
173             # dump may add to self.multirefs, but the for loop will keep
174             # going until it has used all of self.multirefs, even those
175             # entries added while in the loop.
176
177             self.multis = 1
178
179             for obj, tag in self.multirefs:
180                 self.dump(obj, tag, typed = typed, ns_map = ns_map)
181
182             self.out.append("</%sBody>\n" % body_ns)
183             self.depth -= 1
184
185         if self.envelope:
186             e = map (lambda ns: '  xmlns:%s="%s"\n' % (ns[1], ns[0]),
187                 self.envns.items())
188
189             self.out = ['<', self._env_top] + e + ['>\n'] + \
190                        self.out + \
191                        [self._env_bot]
192
193         if self.encoding != None:
194             self.out.insert(0, self._xml_enc_top % self.encoding)
195             return ''.join(self.out).encode(self.encoding)
196
197         self.out.insert(0, self._xml_top)
198         return ''.join(self.out)
199
200     def gentag(self):
201         if Config.debug: print "In gentag."
202         self.tcounter += 1
203         return "v%d" % self.tcounter
204
205     def genns(self, ns_map, nsURI):
206         if nsURI == None:
207             return ('', '')
208
209         if type(nsURI) == TupleType: # already a tuple
210             if len(nsURI) == 2:
211                 ns, nsURI = nsURI
212             else:
213                 ns, nsURI = None, nsURI[0]
214         else:
215             ns = None
216
217         if ns_map.has_key(nsURI):
218             return (ns_map[nsURI] + ':', '')
219
220         if self._env_ns.has_key(nsURI):
221             ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI]
222             return (ns + ':', '')
223
224         if not ns:
225             ns = "ns%d" % self.ncounter
226             self.ncounter += 1
227         ns_map[nsURI] = ns
228         if self.config.buildWithNamespacePrefix:
229             return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI))
230         else:
231             return ('', ' xmlns="%s"' % (nsURI))
232
233     def genroot(self, ns_map):
234         if self.noroot:
235             return ''
236
237         if self.depth != 2:
238             return ''
239
240         ns, n = self.genns(ns_map, NS.ENC)
241         return ' %sroot="%d"%s' % (ns, not self.multis, n)
242
243     # checkref checks an element to see if it needs to be encoded as a
244     # multi-reference element or not. If it returns None, the element has
245     # been handled and the caller can continue with subsequent elements.
246     # If it returns a string, the string should be included in the opening
247     # tag of the marshaled element.
248
249     def checkref(self, obj, tag, ns_map):
250         if self.depth < 2:
251             return ''
252
253         if not self.ids.has_key(id(obj)):
254             n = self.ids[id(obj)] = self.icounter
255             self.icounter = n + 1
256
257             if self.use_refs == 0:
258                 return ''
259
260             if self.depth == 2:
261                 return ' id="i%d"' % n
262
263             self.multirefs.append((obj, tag))
264         else:
265             if self.use_refs == 0:
266                 raise RecursionError, "Cannot serialize recursive object"
267
268             n = self.ids[id(obj)]
269
270             if self.multis and self.depth == 2:
271                 return ' id="i%d"' % n
272
273         self.out.append('<%s href="#i%d"%s/>\n' %
274                         (tag, n, self.genroot(ns_map)))
275         return None
276
277     # dumpers
278
279     def dump(self, obj, tag = None, typed = 1, ns_map = {}):
280         if Config.debug: print "In dump.", "obj=", obj
281         ns_map = ns_map.copy()
282         self.depth += 1
283
284         if type(tag) not in (NoneType, StringType, UnicodeType):
285             raise KeyError, "tag must be a string or None"
286
287         self.dump_dispatch(obj, tag, typed, ns_map)
288         self.depth -= 1
289
290     # generic dumper
291     def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {},
292                rootattr = '', id = '',
293                xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s</%(tag)s>\n'):
294         if Config.debug: print "In dumper."
295
296         if nsURI == None:
297             nsURI = self.config.typesNamespaceURI
298
299         tag = tag or self.gentag()
300
301         tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
302
303         a = n = t = ''
304         if typed and obj_type:
305             ns, n = self.genns(ns_map, nsURI)
306             ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
307             t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n)
308
309         try: a = obj._marshalAttrs(ns_map, self)
310         except: pass
311
312         try: data = obj._marshalData()
313         except:
314             if (obj_type != "string"): # strings are already encoded
315                 data = cgi.escape(str(obj))
316             else:
317                 data = obj
318
319
320
321         return xml % {"tag": tag, "type": t, "data": data, "root": rootattr,
322             "id": id, "attrs": a}
323
324     def dump_float(self, obj, tag, typed = 1, ns_map = {}):
325         if Config.debug: print "In dump_float."
326         tag = tag or self.gentag()
327
328         tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
329
330         if Config.strict_range:
331             doubleType(obj)
332
333         if fpconst.isPosInf(obj):
334             obj = "INF"
335         elif fpconst.isNegInf(obj):
336             obj = "-INF"
337         elif fpconst.isNaN(obj):
338             obj = "NaN"
339         else:
340             obj = repr(obj)
341
342         # Note: python 'float' is actually a SOAP 'double'.
343         self.out.append(self.dumper(
344             None, "double", obj, tag, typed, ns_map, self.genroot(ns_map)))
345
346     def dump_int(self, obj, tag, typed = 1, ns_map = {}):
347         if Config.debug: print "In dump_int."
348         self.out.append(self.dumper(None, 'integer', obj, tag, typed,
349                                      ns_map, self.genroot(ns_map)))
350
351     def dump_bool(self, obj, tag, typed = 1, ns_map = {}):
352         if Config.debug: print "In dump_bool."
353         self.out.append(self.dumper(None, 'boolean', obj, tag, typed,
354                                      ns_map, self.genroot(ns_map)))
355         
356     def dump_string(self, obj, tag, typed = 0, ns_map = {}):
357         if Config.debug: print "In dump_string."
358         tag = tag or self.gentag()
359         tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
360
361         id = self.checkref(obj, tag, ns_map)
362         if id == None:
363             return
364
365         try: data = obj._marshalData()
366         except: data = obj
367
368         self.out.append(self.dumper(None, "string", cgi.escape(data), tag,
369                                     typed, ns_map, self.genroot(ns_map), id))
370
371     dump_str = dump_string # For Python 2.2+
372     dump_unicode = dump_string
373
374     def dump_None(self, obj, tag, typed = 0, ns_map = {}):
375         if Config.debug: print "In dump_None."
376         tag = tag or self.gentag()
377         tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
378         ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
379
380         self.out.append('<%s %snull="1"%s/>\n' %
381                         (tag, ns, self.genroot(ns_map)))
382
383     dump_NoneType = dump_None # For Python 2.2+
384
385     def dump_list(self, obj, tag, typed = 1, ns_map = {}):
386         if Config.debug: print "In dump_list.",  "obj=", obj
387         tag = tag or self.gentag()
388         tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
389
390         if type(obj) == InstanceType:
391             data = obj.data
392         else:
393             data = obj
394
395         if typed:
396             id = self.checkref(obj, tag, ns_map)
397             if id == None:
398                 return
399
400         try:
401             sample = data[0]
402             empty = 0
403         except:
404             # preserve type if present
405             if getattr(obj,"_typed",None) and getattr(obj,"_type",None):
406                 if getattr(obj, "_complexType", None):
407                     sample = typedArrayType(typed=obj._type,
408                                             complexType = obj._complexType)
409                     sample._typename = obj._type
410                     if not getattr(obj,"_ns",None): obj._ns = NS.URN
411                 else:
412                     sample = typedArrayType(typed=obj._type)
413             else:
414                 sample = structType()
415             empty = 1
416
417         # First scan list to see if all are the same type
418         same_type = 1
419
420         if not empty:
421             for i in data[1:]:
422                 if type(sample) != type(i) or \
423                     (type(sample) == InstanceType and \
424                         sample.__class__ != i.__class__):
425                     same_type = 0
426                     break
427
428         ndecl = ''
429         if same_type:
430             if (isinstance(sample, structType)) or \
431                    type(sample) == DictType or \
432                    (isinstance(sample, anyType) and \
433                     (getattr(sample, "_complexType", None) and \
434                      sample._complexType)): # force to urn struct
435                 try:
436                     tns = obj._ns or NS.URN
437                 except:
438                     tns = NS.URN
439
440                 ns, ndecl = self.genns(ns_map, tns)
441
442                 try:
443                     typename = sample._typename
444                 except:
445                     typename = "SOAPStruct"
446
447                 t = ns + typename
448                                 
449             elif isinstance(sample, anyType):
450                 ns = sample._validNamespaceURI(self.config.typesNamespaceURI,
451                                                self.config.strictNamespaces)
452                 if ns:
453                     ns, ndecl = self.genns(ns_map, ns)
454                     t = ns + str(sample._type)
455                 else:
456                     t = 'ur-type'
457             else:
458                 typename = type(sample).__name__
459
460                 # For Python 2.2+
461                 if type(sample) == StringType: typename = 'string'
462
463                 # HACK: unicode is a SOAP string
464                 if type(sample) == UnicodeType: typename = 'string'
465                 
466                 # HACK: python 'float' is actually a SOAP 'double'.
467                 if typename=="float": typename="double"  
468                 t = self.genns(
469                 ns_map, self.config.typesNamespaceURI)[0] + typename
470
471         else:
472             t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
473                 "ur-type"
474
475         try: a = obj._marshalAttrs(ns_map, self)
476         except: a = ''
477
478         ens, edecl = self.genns(ns_map, NS.ENC)
479         ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI)
480
481         if typed:
482             self.out.append(
483                 '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
484                 (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
485                  self.genroot(ns_map), id, a))
486
487         if typed:
488             try: elemsname = obj._elemsname
489             except: elemsname = "item"
490         else:
491             elemsname = tag
492             
493         if isinstance(data, (list, tuple, arrayType)):
494             should_drill = True
495         else:
496             should_drill = not same_type
497         
498         for i in data:
499             self.dump(i, elemsname, should_drill, ns_map)
500
501         if typed: self.out.append('</%s>\n' % tag)
502
503     dump_tuple = dump_list
504
505     def dump_exception(self, obj, tag, typed = 0, ns_map = {}):
506         if isinstance(obj, faultType):    # Fault
507             cns, cdecl = self.genns(ns_map, NS.ENC)
508             vns, vdecl = self.genns(ns_map, NS.ENV)
509             self.out.append('<%sFault %sroot="1"%s%s>' % (vns, cns, vdecl, cdecl))
510             self.dump(obj.faultcode, "faultcode", typed, ns_map)
511             self.dump(obj.faultstring, "faultstring", typed, ns_map)
512             if hasattr(obj, "detail"):
513                 self.dump(obj.detail, "detail", typed, ns_map)
514             self.out.append("</%sFault>\n" % vns)
515
516     def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}):
517         if Config.debug: print "In dump_dictionary."
518         tag = tag or self.gentag()
519         tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
520
521         id = self.checkref(obj, tag, ns_map)
522         if id == None:
523             return
524
525         try: a = obj._marshalAttrs(ns_map, self)
526         except: a = ''
527
528         self.out.append('<%s%s%s%s>\n' % 
529                         (tag, id, a, self.genroot(ns_map)))
530
531         for (k, v) in obj.items():
532             if k[0] != "_":
533                 self.dump(v, k, 1, ns_map)
534
535         self.out.append('</%s>\n' % tag)
536
537     dump_dict = dump_dictionary # For Python 2.2+
538
539     def dump_dispatch(self, obj, tag, typed = 1, ns_map = {}):
540         if not tag:
541             # If it has a name use it.
542             if isinstance(obj, anyType) and obj._name:
543                 tag = obj._name
544             else:
545                 tag = self.gentag()
546
547         # watch out for order! 
548         dumpmap = (
549             (Exception, self.dump_exception),
550             (arrayType, self.dump_list),
551             (basestring, self.dump_string),
552             (NoneType, self.dump_None),
553             (bool, self.dump_bool),
554             (int, self.dump_int),
555             (long, self.dump_int),
556             (list, self.dump_list),
557             (tuple, self.dump_list),
558             (dict, self.dump_dictionary),
559             (float, self.dump_float),
560         )
561         for dtype, func in dumpmap:
562             if isinstance(obj, dtype):
563                 func(obj, tag, typed, ns_map)
564                 return
565
566         r = self.genroot(ns_map)
567
568         try: a = obj._marshalAttrs(ns_map, self)
569         except: a = ''
570
571         if isinstance(obj, voidType):     # void
572             self.out.append("<%s%s%s></%s>\n" % (tag, a, r, tag))
573         else:
574             id = self.checkref(obj, tag, ns_map)
575             if id == None:
576                 return
577
578         if isinstance(obj, structType):
579             # Check for namespace
580             ndecl = ''
581             ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
582                 self.config.strictNamespaces)
583             if ns:
584                 ns, ndecl = self.genns(ns_map, ns)
585                 tag = ns + tag
586             self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r))
587
588             keylist = obj.__dict__.keys()
589
590             # first write out items with order information
591             if hasattr(obj, '_keyord'):
592                 for i in range(len(obj._keyord)):
593                     self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map)
594                     keylist.remove(obj._keyord[i])
595
596             # now write out the rest
597             for k in keylist:
598                 if (k[0] != "_"):
599                     self.dump(getattr(obj,k), k, 1, ns_map)
600
601             if isinstance(obj, bodyType):
602                 self.multis = 1
603
604                 for v, k in self.multirefs:
605                     self.dump(v, k, typed = typed, ns_map = ns_map)
606
607             self.out.append('</%s>\n' % tag)
608
609         elif isinstance(obj, anyType):
610             t = ''
611
612             if typed:
613                 ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
614                     self.config.strictNamespaces)
615                 if ns:
616                     ons, ondecl = self.genns(ns_map, ns)
617                     ins, indecl = self.genns(ns_map,
618                         self.config.schemaNamespaceURI)
619                     t = ' %stype="%s%s"%s%s' % \
620                         (ins, ons, obj._type, ondecl, indecl)
621
622             self.out.append('<%s%s%s%s%s>%s</%s>\n' %
623                             (tag, t, id, a, r, obj._marshalData(), tag))
624
625         else:                           # Some Class
626             self.out.append('<%s%s%s>\n' % (tag, id, r))
627
628             d1 = getattr(obj, '__dict__', None)
629             if d1 is not None:
630                 for (k, v) in d1:
631                     if k[0] != "_":
632                         self.dump(v, k, 1, ns_map)
633
634             self.out.append('</%s>\n' % tag)
635
636
637
638 ################################################################################
639 # SOAPBuilder's more public interface
640 ################################################################################
641
642 def buildSOAP(args=(), kw={}, method=None, namespace=None,
643               header=None, methodattrs=None, envelope=1, encoding='UTF-8',
644               config=Config, noroot = 0):
645     t = SOAPBuilder(args=args, kw=kw, method=method, namespace=namespace,
646                     header=header, methodattrs=methodattrs,envelope=envelope,
647                     encoding=encoding, config=config,noroot=noroot)
648     return t.build()