1############################################################################
2# Monte M. Goode, LBNL
3# See LBNLCopyright for copyright notice!
4###########################################################################
5
6# main generator engine for new generation generator
7
8# $Id: wsdl2python.py 1402 2007-07-06 22:51:32Z boverhof $
9
10import os, sys, warnings
11from ZSI import _get_idstr
12from ZSI.wstools.logging import getLogger as _GetLogger
13from ZSI.wstools import WSDLTools
14from ZSI.wstools.WSDLTools import SoapAddressBinding,\
15    SoapBodyBinding, SoapBinding,MimeContentBinding,\
16    HttpUrlEncodedBinding
17from ZSI.wstools.XMLSchema import SchemaReader, ElementDeclaration, SchemaError
18from ZSI.typeinterpreter import BaseTypeInterpreter
19from ZSI.generate import WsdlGeneratorError, Wsdl2PythonError
20from containers import *
21from ZSI.generate import utility
22from ZSI.generate.utility import NamespaceAliasDict as NAD
23from ZSI.generate.utility import GetModuleBaseNameFromWSDL
24
25"""
26classes:
27    WriteServiceModule
28    -- composes/writes out client stubs and types module.
29
30    ServiceDescription
31    -- represents a single WSDL service.
32
33    MessageWriter
34    -- represents a single WSDL Message and associated bindings
35    of the port/binding.
36
37    SchemaDescription
38    -- generates classes for defs and decs in the schema instance.
39
40    TypeWriter
41    -- represents a type definition.
42
43    ElementWriter
44    -- represents a element declaration.
45
46"""
47
48class WriteServiceModule:
49    """top level driver class invoked by wsd2py
50    class variables:
51        client_module_suffix -- suffix of client module.
52        types_module_suffix -- suffix of types module.
53    """
54    client_module_suffix = '_client'
55    messages_module_suffix = '_messages'
56    types_module_suffix = '_types'
57    logger = _GetLogger("WriteServiceModule")
58
59    def __init__(self, wsdl, addressing=False, notification=False,
60                 do_extended=False, extPyClasses=None, configParser = None):
61        self._wsdl = wsdl
62        self._addressing = addressing
63        self._notification = notification
64        self._configParser = configParser
65        self.usedNamespaces = None
66        self.services = []
67        self.client_module_path = None
68        self.types_module_name = None
69        self.types_module_path = None
70        self.messages_module_path = None # used in extended generation
71        self.do_extended = do_extended
72        self.extPyClasses = extPyClasses
73
74    def getClientModuleName(self):
75        """client module name.
76        """
77        name = GetModuleBaseNameFromWSDL(self._wsdl)
78        if not name:
79            raise WsdlGeneratorError, 'could not determine a service name'
80
81        if self.client_module_suffix is None:
82            return name
83
84        return '%s%s' %(name, self.client_module_suffix)
85
86#    def getMessagesModuleName(self):
87#        name = GetModuleBaseNameFromWSDL(self._wsdl)
88#        if not name:
89#            raise WsdlGeneratorError, 'could not determine a service name'
90#
91#        if self.messages_module_suffix is None:
92#            return name
93#
94#        if len(self.messages_module_suffix) == 0:
95#            return self.getClientModuleName()
96#
97#        return '%s%s' %(name, self.messages_module_suffix)
98
99    def setTypesModuleName(self, name):
100        self.types_module_name = name
101
102    def getTypesModuleName(self):
103        """types module name.
104        """
105        if self.types_module_name is not None:
106            return self.types_module_name
107
108        name = GetModuleBaseNameFromWSDL(self._wsdl)
109        if not name:
110            raise WsdlGeneratorError, 'could not determine a service name'
111
112        if self.types_module_suffix is None:
113            return name
114
115        return '%s%s' %(name, self.types_module_suffix)
116
117    def setClientModulePath(self, path):
118        """setup module path to where client module before calling fromWsdl.
119        module path to types module eg. MyApp.client
120        """
121        self.client_module_path = path
122
123    def getTypesModulePath(self):
124        """module path to types module eg. MyApp.types
125        """
126        return self.types_module_path
127
128#    def getMessagesModulePath(self):
129#        '''module path to messages module
130#           same as types path
131#        '''
132#        return self.messages_module_path
133
134    def setTypesModulePath(self, path):
135        """setup module path to where service module before calling fromWsdl.
136        module path to types module eg. MyApp.types
137        """
138        self.types_module_path = path
139
140#    def setMessagesModulePath(self, path):
141#        """setup module path to where message module before calling fromWsdl.
142#        module path to types module eg. MyApp.types
143#        """
144#        self.messages_module_path = path
145
146    def gatherNamespaces(self):
147        '''This method must execute once..  Grab all schemas
148        representing each targetNamespace.
149        '''
150        if self.usedNamespaces is not None:
151            return
152
153        self.logger.debug('gatherNamespaces')
154        self.usedNamespaces = {}
155
156        # Add all schemas defined in wsdl
157        # to used namespace and to the Alias dict
158        for schema in self._wsdl.types.values():
159            tns = schema.getTargetNamespace()
160            self.logger.debug('Register schema(%s) -- TNS(%s)'\
161                %(_get_idstr(schema), tns),)
162            if self.usedNamespaces.has_key(tns) is False:
163                self.usedNamespaces[tns] = []
164            self.usedNamespaces[tns].append(schema)
165            NAD.add(tns)
166
167        # Add all xsd:import schema instances
168        # to used namespace and to the Alias dict
169        for k,v in SchemaReader.namespaceToSchema.items():
170            self.logger.debug('Register schema(%s) -- TNS(%s)'\
171                %(_get_idstr(v), k),)
172            if self.usedNamespaces.has_key(k) is False:
173                self.usedNamespaces[k] = []
174            self.usedNamespaces[k].append(v)
175            NAD.add(k)
176
177    def writeClient(self, fd, sdClass=None, **kw):
178        """write out client module to file descriptor.
179        Parameters and Keywords arguments:
180            fd -- file descriptor
181            sdClass -- service description class name
182            imports -- list of imports
183            readerclass -- class name of ParsedSoap reader
184            writerclass -- class name of SoapWriter writer
185        """
186        sdClass = sdClass or ServiceDescription
187        assert issubclass(sdClass, ServiceDescription), \
188            'parameter sdClass must subclass ServiceDescription'
189
190#        header = '%s \n# %s.py \n# generated by %s\n%s\n'\
191#                  %('#'*50, self.getClientModuleName(), self.__module__, '#'*50)
192        print >>fd, '#'*50
193        print >>fd, '# file: %s.py' %self.getClientModuleName()
194        print >>fd, '# '
195        print >>fd, '# client stubs generated by "%s"' %self.__class__
196        print >>fd, '#     %s' %' '.join(sys.argv)
197        print >>fd, '# '
198        print >>fd, '#'*50
199
200        self.services = []
201        for service in self._wsdl.services:
202            sd = sdClass(self._addressing, do_extended=self.do_extended,
203                         wsdl=self._wsdl)
204            if len(self._wsdl.types) > 0:
205                sd.setTypesModuleName(self.getTypesModuleName(),
206                                      self.getTypesModulePath())
207#                sd.setMessagesModuleName(self.getMessagesModuleName(),
208#                                         self.getMessagesModulePath())
209
210            self.gatherNamespaces()
211            sd.fromWsdl(service, **kw)
212            sd.write(fd)
213            self.services.append(sd)
214
215    def writeTypes(self, fd):
216        """write out types module to file descriptor.
217        """
218        print >>fd, '#'*50
219        print >>fd, '# file: %s.py' %self.getTypesModuleName()
220        print >>fd, '#'
221        print >>fd, '# schema types generated by "%s"' %self.__class__
222        print >>fd, '#    %s' %' '.join(sys.argv)
223        print >>fd, '#'
224        print >>fd, '#'*50
225
226        print >>fd, TypesHeaderContainer()
227        self.gatherNamespaces()
228        for l in self.usedNamespaces.values():
229            sd = SchemaDescription(do_extended=self.do_extended,
230                                   extPyClasses=self.extPyClasses)
231            for schema in l:
232                sd.fromSchema(schema)
233            sd.write(fd)
234
235
236class ServiceDescription:
237    """client interface - locator, port, etc classes"""
238    separate_messages = False
239    logger = _GetLogger("ServiceDescription")
240
241    def __init__(self, addressing=False, do_extended=False, wsdl=None):
242        self.typesModuleName = None
243        self.messagesModuleName = None
244        self.wsAddressing = addressing
245        self.imports   = ServiceHeaderContainer()
246        self.messagesImports   = ServiceHeaderContainer()
247        self.locator   = ServiceLocatorContainer()
248        self.bindings   = []
249        self.messages  = []
250        self.do_extended=do_extended
251        self._wsdl = wsdl # None unless do_extended == True
252
253    def setTypesModuleName(self, name, modulePath=None):
254        """The types module to be imported.
255        Parameters
256        name -- name of types module
257        modulePath -- optional path where module is located.
258        """
259        self.typesModuleName = '%s' %name
260        if modulePath is not None:
261            self.typesModuleName = '%s.%s' %(modulePath,name)
262
263#    def setMessagesModuleName(self, name, modulePath=None):
264#        '''The types module to be imported.
265#        Parameters
266#        name -- name of types module
267#        modulePath -- optional path where module is located.
268#        '''
269#        self.messagesModuleName = '%s' %name
270#        if modulePath is not None:
271#            self.messagesModuleName = '%s.%s' %(modulePath,name)
272
273    def fromWsdl(self, service, **kw):
274        self.imports.setTypesModuleName(self.typesModuleName)
275#        if self.separate_messages:
276#            self.messagesImports.setMessagesModuleName(self.messagesModuleName)
277        self.imports.appendImport(kw.get('imports', []))
278
279        self.locator.setUp(service)
280
281        try:
282            bindings =  map(lambda p: p.binding, service.ports)
283        except:
284            warnings.warn('not all ports have binding declared,')
285            bindings = ()
286
287        for port in service.ports:
288            if port.binding not in bindings:
289                continue
290            while port.binding in bindings:
291                bindings.remove(port.binding)
292
293            desc = BindingDescription(useWSA=self.wsAddressing,
294                                      do_extended=self.do_extended,
295                                      wsdl=self._wsdl)
296            try:
297                desc.setUp(port.getBinding())
298            except Wsdl2PythonError, ex:
299                self.logger.warning('Skipping port(%s)' %port.name)
300                if len(ex.args):
301                    self.logger.warning(ex.args[0])
302                continue
303
304            desc.setReaderClass(kw.get('readerclass'))
305            desc.setWriterClass(kw.get('writerclass'))
306            for soc in desc.operations:
307                if soc.hasInput() is True:
308                    mw = MessageWriter(do_extended=self.do_extended)
309                    mw.setUp(soc, port, input=True)
310                    self.messages.append(mw)
311
312                    if soc.hasOutput() is True:
313                        mw = MessageWriter(do_extended=self.do_extended)
314                        mw.setUp(soc, port, input=False)
315                        self.messages.append(mw)
316
317            self.bindings.append(desc)
318
319
320    def write(self, fd, msg_fd=None):
321        """write out module to file descriptor.
322        fd -- file descriptor to write out service description.
323        msg_fd -- optional file descriptor for messages module.
324        """
325#        if msg_fd != None:
326#            print >>fd, self.messagesImports
327#            print >>msg_fd, self.imports
328#        else:
329        print >>fd, self.imports
330
331        print >>fd, self.locator
332        for m in self.bindings:
333            print >>fd, m
334
335#        if msg_fd != None:
336#            for m in self.messages:
337#                print >>msg_fd, m
338#        else:
339        for m in self.messages:
340            print >>fd, m
341
342
343class MessageWriter:
344    logger = _GetLogger("MessageWriter")
345
346    def __init__(self, do_extended=False):
347        """Representation of a WSDL Message and associated WSDL Binding.
348        operation --
349        boperation --
350        input --
351        rpc --
352        literal --
353        simple --
354        """
355        self.content = None
356        self.do_extended = do_extended
357
358    def __str__(self):
359        if not self.content:
360            raise Wsdl2PythonError, 'Must call setUp.'
361        return self.content.getvalue()
362
363    def setUp(self, soc, port, input=False):
364        assert isinstance(soc, ServiceOperationContainer),\
365            'expecting a ServiceOperationContainer instance'
366        assert isinstance(port, WSDLTools.Port),\
367            'expecting a WSDL.Port instance'
368
369        rpc,literal = soc.isRPC(), soc.isLiteral(input)
370        kw,klass = {}, None
371
372        if rpc and literal:
373            klass = ServiceRPCLiteralMessageContainer
374        elif not rpc and literal:
375            kw['do_extended'] = self.do_extended
376            klass = ServiceDocumentLiteralMessageContainer
377        elif rpc and not literal:
378            klass = ServiceRPCEncodedMessageContainer
379        else:
380            raise WsdlGeneratorError, 'doc/enc not supported.'
381
382        self.content = klass(**kw)
383        self.content.setUp(port, soc, input)
384
385
386class SchemaDescription:
387    """generates classes for defs and decs in the schema instance.
388    """
389    logger = _GetLogger("SchemaDescription")
390
391    def __init__(self, do_extended=False, extPyClasses=None):
392        self.classHead = NamespaceClassHeaderContainer()
393        self.classFoot = NamespaceClassFooterContainer()
394        self.items = []
395        self.__types = []
396        self.__elements = []
397        self.targetNamespace = None
398        self.do_extended=do_extended
399        self.extPyClasses = extPyClasses
400
401    def fromSchema(self, schema):
402        ''' Can be called multiple times, but will not redefine a
403        previously defined type definition or element declaration.
404        '''
405        ns = schema.getTargetNamespace()
406        assert self.targetNamespace is None or self.targetNamespace == ns,\
407            'SchemaDescription instance represents %s, not %s'\
408            %(self.targetNamespace, ns)
409
410        if self.targetNamespace is None:
411            self.targetNamespace = ns
412
413        self.classHead.ns = self.classFoot.ns = ns
414        for item in [t for t in schema.types if t.getAttributeName() not in self.__types]:
415            self.__types.append(item.getAttributeName())
416            self.items.append(TypeWriter(do_extended=self.do_extended, extPyClasses=self.extPyClasses))
417            self.items[-1].fromSchemaItem(item)
418
419        for item in [e for e in schema.elements if e.getAttributeName() not in self.__elements]:
420            self.__elements.append(item.getAttributeName())
421            self.items.append(ElementWriter(do_extended=self.do_extended))
422            self.items[-1].fromSchemaItem(item)
423
424    def getTypes(self):
425        return self.__types
426
427    def getElements(self):
428        return self.__elements
429
430    def write(self, fd):
431        """write out to file descriptor.
432        """
433        print >>fd, self.classHead
434        for t in self.items:
435            print >>fd, t
436        print >>fd, self.classFoot
437
438class SchemaItemWriter:
439    """contains/generates a single declaration"""
440    logger = _GetLogger("SchemaItemWriter")
441
442    def __init__(self, do_extended=False, extPyClasses=None):
443        self.content = None
444        self.do_extended=do_extended
445        self.extPyClasses=extPyClasses
446
447    def __str__(self):
448        '''this appears to set up whatever is in self.content.localElements,
449        local elements simpleType|complexType.
450        '''
451        assert self.content is not None, 'Must call fromSchemaItem to setup.'
452        return str(self.content)
453
454    def fromSchemaItem(self, item):
455        raise NotImplementedError, ''
456
457
458class ElementWriter(SchemaItemWriter):
459    """contains/generates a single declaration"""
460    logger = _GetLogger("ElementWriter")
461
462    def fromSchemaItem(self, item):
463        """set up global elements.
464        """
465        if item.isElement() is False or item.isLocal() is True:
466            raise TypeError, 'expecting global element declaration: %s' %item.getItemTrace()
467
468        local = False
469        qName = item.getAttribute('type')
470        if not qName:
471            etp = item.content
472            local = True
473        else:
474            etp = item.getTypeDefinition('type')
475
476        if etp is None:
477            if local is True:
478                self.content = ElementLocalComplexTypeContainer(do_extended=self.do_extended)
479            else:
480                self.content = ElementSimpleTypeContainer()
481        elif etp.isLocal() is False:
482            self.content = ElementGlobalDefContainer()
483        elif etp.isSimple() is True:
484            self.content = ElementLocalSimpleTypeContainer()
485        elif etp.isComplex():
486            self.content = ElementLocalComplexTypeContainer(do_extended=self.do_extended)
487        else:
488            raise Wsdl2PythonError, "Unknown element declaration: %s" %item.getItemTrace()
489
490        self.logger.debug('ElementWriter setUp container "%r", Schema Item "%s"' %(
491            self.content, item.getItemTrace()))
492
493        self.content.setUp(item)
494
495
496class TypeWriter(SchemaItemWriter):
497    """contains/generates a single definition"""
498    logger = _GetLogger("TypeWriter")
499
500    def fromSchemaItem(self, item):
501        if item.isDefinition() is False or item.isLocal() is True:
502            raise TypeError, \
503                'expecting global type definition not: %s' %item.getItemTrace()
504
505        self.content = None
506        if item.isSimple():
507            if item.content.isRestriction():
508                self.content = RestrictionContainer()
509            elif item.content.isUnion():
510                self.content = UnionContainer()
511            elif item.content.isList():
512                self.content = ListContainer()
513            else:
514                raise Wsdl2PythonError,\
515                    'unknown simple type definition: %s' %item.getItemTrace()
516
517            self.content.setUp(item)
518            return
519
520        if item.isComplex():
521            kw = {}
522            if item.content is None or item.content.isModelGroup():
523                self.content = \
524                    ComplexTypeContainer(\
525                        do_extended=self.do_extended,
526                        extPyClasses=self.extPyClasses
527                        )
528                kw['empty'] = item.content is None
529            elif item.content.isSimple():
530                    self.content = ComplexTypeSimpleContentContainer()
531            elif item.content.isComplex():
532                    self.content = \
533                        ComplexTypeComplexContentContainer(\
534                            do_extended=self.do_extended
535                            )
536            else:
537                raise Wsdl2PythonError,\
538                    'unknown complex type definition: %s' %item.getItemTrace()
539
540            self.logger.debug('TypeWriter setUp container "%r", Schema Item "%s"' %(
541                self.content, item.getItemTrace()))
542
543            try:
544                self.content.setUp(item, **kw)
545            except Exception, ex:
546                args = ['Failure in setUp: %s' %item.getItemTrace()]
547                args += ex.args
548                ex.args = tuple(args)
549                raise
550
551            return
552
553        raise TypeError,\
554            'expecting SimpleType or ComplexType: %s' %item.getItemTrace()
555
556
557
558