1#!/usr/bin/env python
2import inspect
3from cStringIO import StringIO
4import ZSI, string, sys, getopt, urlparse, types, warnings
5from ZSI.wstools import WSDLTools
6from ZSI.ServiceContainer import ServiceSOAPBinding, SimpleWSResource, WSAResource
7
8from ZSI.generate import WsdlGeneratorError, Wsdl2PythonError
9from utility import TextProtect, GetModuleBaseNameFromWSDL, \
10    NCName_to_ClassName, GetPartsSubNames, TextProtectAttributeName
11from containers import BindingDescription
12from wsdl2python import MessageWriter, WriteServiceModule,\
13    MessageTypecodeContainer, SchemaDescription
14
15# Split last token
16rsplit = lambda x,sep,: (x[:x.rfind(sep)], x[x.rfind(sep)+1:],)
17if sys.version_info[0:2] == (2, 4, 0, 'final', 0)[0:2]:
18    rsplit = lambda x,sep,: x.rsplit(sep, 1)
19
20
21class SOAPService:
22    def __init__(self, service):
23        self.classdef = StringIO()
24        self.initdef  = StringIO()
25        self.location = ''
26        self.methods  = []
27
28    def newMethod(self):
29        '''name -- operation name
30        '''
31        self.methods.append(StringIO())
32        return self.methods[-1]
33
34
35class ServiceModuleWriter:
36    '''Creates a skeleton for a SOAP service instance.
37    '''
38    indent = ' '*4
39    server_module_suffix = '_server'
40    func_aname = TextProtectAttributeName
41    func_aname = staticmethod(func_aname)
42    separate_messages = False
43
44    def __init__(self, base=ServiceSOAPBinding, prefix='soap',
45                 service_class=SOAPService):
46        '''
47        parameters:
48            base -- either a class definition, or a str representing a qualified
49                class name (eg. module.name.classname)
50            prefix -- method prefix.
51        '''
52        if inspect.isclass(base):
53            self.base_class_name = base.__name__
54            self.base_module_name = inspect.getmodule(base).__name__
55        else:
56            self.base_module_name, self.base_class_name  = base.rsplit('.', 1)
57
58        self.wsdl = None
59        self.method_prefix = prefix
60        self._service_class = SOAPService
61
62        self.header  = None
63        self.imports  = None
64        self.messages = []
65        self._services = None
66        self.types_module_path = None
67        self.types_module_name = None
68        self.messages_module_name = None
69
70    def reset(self):
71        self.header  = StringIO()
72        self.imports  = StringIO()
73        self.message = []
74        self._services = {}
75
76    def getIndent(self, level=1):
77        '''return indent.
78        '''
79        assert 0 < level < 10, 'bad indent level %d' %level
80        return self.indent*level
81
82    def getMethodName(self, method):
83        '''return method name.
84        '''
85        return '%s_%s' %(self.method_prefix, TextProtect(method))
86
87    def getClassName(self, name):
88        '''return class name.
89        '''
90        return NCName_to_ClassName(name)
91
92    def setTypesModuleName(self, name):
93        self.types_module_name = name
94
95    # Backwards compatibility
96    setClientModuleName = setTypesModuleName
97
98    def getTypesModuleName(self):
99        '''return module name.
100        '''
101        assert self.wsdl is not None, 'initialize, call fromWSDL'
102        if self.types_module_name is not None:
103            return self.types_module_name
104
105        wsm = WriteServiceModule(self.wsdl)
106        return wsm.getTypesModuleName()
107
108    def getServiceModuleName(self):
109        '''return module name.
110        '''
111        name = GetModuleBaseNameFromWSDL(self.wsdl)
112        if not name:
113            raise WsdlGeneratorError, 'could not determine a service name'
114
115        if self.server_module_suffix is None:
116            return name
117        return '%s%s' %(name, self.server_module_suffix)
118
119    def getTypesModulePath(self):
120        return self.types_module_path
121    getClientModulePath = getTypesModulePath
122
123    def setTypesModulePath(self, path):
124        '''setup module path to where client module before calling fromWSDL.
125        '''
126        self.types_module_path = path
127    setClientModulePath = setTypesModulePath
128
129    def setUpClassDef(self, service):
130        '''set class definition and class variables.
131        service -- ServiceDescription instance
132        '''
133        assert isinstance(service, WSDLTools.Service) is True,\
134            'expecting WSDLTools.Service instance.'
135
136        s = self._services[service.name].classdef
137
138        print >>s, 'class %s(%s):' %(self.getClassName(service.name), self.base_class_name)
139
140        print >>s, '%ssoapAction = {}' % self.getIndent(level=1)
141        print >>s, '%sroot = {}' % self.getIndent(level=1)
142
143    def setUpImports(self):
144        '''set import statements
145        '''
146        i = self.imports
147        print >>i, 'from ZSI.schema import GED, GTD'
148        print >>i, 'from ZSI.TCcompound import ComplexType, Struct'
149
150        module = self.getTypesModuleName()
151        package = self.getTypesModulePath()
152        if package:
153            module = '%s.%s' %(package, module)
154
155        print >>i, 'from %s import *' %(module)
156
157        print >>i, 'from %s import %s' %(self.base_module_name, self.base_class_name)
158
159    def setUpInitDef(self, service):
160        '''set __init__ function
161        '''
162        assert isinstance(service, WSDLTools.Service), \
163            'expecting WSDLTools.Service instance.'
164
165        sd = self._services[service.name]
166        d = sd.initdef
167
168        if sd.location is not None:
169            scheme,netloc,path,params,query,fragment = urlparse.urlparse(sd.location)
170            print >>d, '%sdef __init__(self, post=\'%s\', **kw):' %(self.getIndent(level=1), path)
171        else:
172            print >>d, '%sdef __init__(self, post, **kw):' %self.getIndent(level=1)
173
174        # Require POST initialization value for test implementation
175        if self.base_module_name == inspect.getmodule(ServiceSOAPBinding).__name__:
176            print >>d, '%s%s.__init__(self, post)' %(self.getIndent(level=2), self.base_class_name)
177            return
178
179        # No POST initialization value, obtained from HTTP Request in twisted or wsgi
180        print >>d, '%s%s.__init__(self)' %(self.getIndent(level=2), self.base_class_name)
181
182    def mangle(self, name):
183        return TextProtect(name)
184
185    def getAttributeName(self, name):
186        return self.func_aname(name)
187
188    def setUpMethods(self, port):
189        '''set up all methods representing the port operations.
190        Parameters:
191            port -- Port that defines the operations.
192        '''
193        assert isinstance(port, WSDLTools.Port), \
194            'expecting WSDLTools.Port not: ' %type(port)
195
196        sd = self._services.get(port.getService().name)
197        assert sd is not None, 'failed to initialize.'
198
199        binding = port.getBinding()
200        portType = port.getPortType()
201        action_in = ''
202        for bop in binding.operations:
203            try:
204                op = portType.operations[bop.name]
205            except KeyError, ex:
206                raise WsdlGeneratorError,\
207                    'Port(%s) PortType(%s) missing operation(%s) defined in Binding(%s)' \
208                    %(port.name,portType.name,bop.name,binding.name)
209
210            for ext in bop.extensions:
211                 if isinstance(ext, WSDLTools.SoapOperationBinding):
212                     action_in = ext.soapAction
213                     break
214            else:
215                warnings.warn('Port(%s) operation(%s) defined in Binding(%s) missing soapAction' \
216                    %(port.name,op.name,binding.name)
217                )
218
219            msgin = op.getInputMessage()
220            msgin_name = TextProtect(msgin.name)
221            method_name = self.getMethodName(op.name)
222
223            m = sd.newMethod()
224            print >>m, '%sdef %s(self, ps, **kw):' %(self.getIndent(level=1), method_name)
225            if msgin is not None:
226                print >>m, '%srequest = ps.Parse(%s.typecode)' %(self.getIndent(level=2), msgin_name)
227            else:
228                print >>m, '%s# NO input' %self.getIndent(level=2)
229
230            msgout = op.getOutputMessage()
231            if msgout is not None:
232                msgout_name = TextProtect(msgout.name)
233                print >>m, '%sreturn request,%s()' %(self.getIndent(level=2), msgout_name)
234            else:
235                print >>m, '%s# NO output' % self.getIndent(level=2)
236                print >>m, '%sreturn request,None' % self.getIndent(level=2)
237
238            print >>m, ''
239            print >>m, '%ssoapAction[\'%s\'] = \'%s\'' %(self.getIndent(level=1), action_in, method_name)
240            print >>m, '%sroot[(%s.typecode.nspname,%s.typecode.pname)] = \'%s\'' \
241                     %(self.getIndent(level=1), msgin_name, msgin_name, method_name)
242
243        return
244
245    def setUpHeader(self):
246        print >>self.header, '#'*50
247        print >>self.header, '# file: %s.py' %self.getServiceModuleName()
248        print >>self.header, '#'
249        print >>self.header, '# skeleton generated by "%s"' %self.__class__
250        print >>self.header, '#      %s' %' '.join(sys.argv)
251        print >>self.header, '#'
252        print >>self.header, '#'*50
253
254    def write(self, fd=sys.stdout):
255        '''write out to file descriptor,
256        should not need to override.
257        '''
258        print >>fd, self.header.getvalue()
259        print >>fd, self.imports.getvalue()
260
261        print >>fd, '# Messages ',
262        for m in self.messages:
263            print >>fd, m
264
265        print >>fd, ''
266        print >>fd, ''
267        print >>fd, '# Service Skeletons'
268        for k,v in self._services.items():
269            print >>fd, v.classdef.getvalue()
270            print >>fd, v.initdef.getvalue()
271            for s in v.methods:
272                print >>fd, s.getvalue()
273
274    def fromWSDL(self, wsdl):
275        '''setup the service description from WSDL,
276        should not need to override.
277        '''
278        assert isinstance(wsdl, WSDLTools.WSDL), 'expecting WSDL instance'
279
280        if len(wsdl.services) == 0:
281            raise WsdlGeneratorError, 'No service defined'
282
283        self.reset()
284        self.wsdl = wsdl
285        self.setUpHeader()
286        self.setUpImports()
287
288        for service in wsdl.services:
289            sd = self._service_class(service.name)
290            self._services[service.name] = sd
291
292            for port in service.ports:
293                desc = BindingDescription(wsdl=wsdl)
294                try:
295                    desc.setUp(port.getBinding())
296                except Wsdl2PythonError, ex:
297                    continue
298
299                for soc in desc.operations:
300                    if not soc.hasInput(): continue
301
302                    self.messages.append(MessageWriter())
303                    self.messages[-1].setUp(soc, port, input=True)
304                    if soc.hasOutput():
305                        self.messages.append(MessageWriter())
306                        self.messages[-1].setUp(soc, port, input=False)
307
308                for e in port.extensions:
309                    if isinstance(e, WSDLTools.SoapAddressBinding):
310                        sd.location = e.location
311
312                self.setUpMethods(port)
313
314            self.setUpClassDef(service)
315            self.setUpInitDef(service)
316
317
318class WSAServiceModuleWriter(ServiceModuleWriter):
319    '''Creates a skeleton for a WS-Address service instance.
320    '''
321    def __init__(self, base=WSAResource, prefix='wsa', service_class=SOAPService,
322                 strict=True):
323        '''
324        Parameters:
325            strict -- check that soapAction and input ws-action do not collide.
326        '''
327        ServiceModuleWriter.__init__(self, base, prefix, service_class)
328        self.strict = strict
329
330    def createMethodBody(msgInName, msgOutName, **kw):
331        '''return a tuple of strings containing the body of a method.
332        msgInName -- None or a str
333        msgOutName --  None or a str
334        '''
335        body = []
336        if msgInName is not None:
337            body.append('request = ps.Parse(%s.typecode)' %msgInName)
338
339        if msgOutName is not None:
340            body.append('return request,%s()' %msgOutName)
341        else:
342            body.append('return request,None')
343
344        return tuple(body)
345    createMethodBody = staticmethod(createMethodBody)
346
347    def setUpClassDef(self, service):
348        '''use soapAction dict for WS-Action input, setup wsAction
349        dict for grabbing WS-Action output values.
350        '''
351        assert isinstance(service, WSDLTools.Service), \
352            'expecting WSDLTools.Service instance'
353
354        s = self._services[service.name].classdef
355        print >>s, 'class %s(%s):' %(self.getClassName(service.name), self.base_class_name)
356        print >>s, '%ssoapAction = {}' % self.getIndent(level=1)
357        print >>s, '%swsAction = {}' % self.getIndent(level=1)
358        print >>s, '%sroot = {}' % self.getIndent(level=1)
359
360    def setUpMethods(self, port):
361        '''set up all methods representing the port operations.
362        Parameters:
363            port -- Port that defines the operations.
364        '''
365        assert isinstance(port, WSDLTools.Port), \
366            'expecting WSDLTools.Port not: ' %type(port)
367
368        binding = port.getBinding()
369        portType = port.getPortType()
370        service = port.getService()
371        s = self._services[service.name]
372        for bop in binding.operations:
373            try:
374                op = portType.operations[bop.name]
375            except KeyError, ex:
376                raise WsdlGeneratorError,\
377                    'Port(%s) PortType(%s) missing operation(%s) defined in Binding(%s)' \
378                    %(port.name, portType.name, op.name, binding.name)
379
380            soap_action = wsaction_in = wsaction_out = None
381            if op.input is not None:
382                wsaction_in = op.getInputAction()
383            if op.output is not None:
384                wsaction_out = op.getOutputAction()
385
386            for ext in bop.extensions:
387                if isinstance(ext, WSDLTools.SoapOperationBinding) is False: continue
388                soap_action = ext.soapAction
389                if not soap_action: break
390                if wsaction_in is None: break
391                if wsaction_in == soap_action: break
392                if self.strict is False:
393                    warnings.warn(\
394                        'Port(%s) operation(%s) in Binding(%s) soapAction(%s) != WS-Action(%s)' \
395                         %(port.name, op.name, binding.name, soap_action, wsaction_in),
396                    )
397                    break
398                raise WsdlGeneratorError,\
399                    'Port(%s) operation(%s) in Binding(%s) soapAction(%s) MUST match WS-Action(%s)' \
400                     %(port.name, op.name, binding.name, soap_action, wsaction_in)
401
402            method_name = self.getMethodName(op.name)
403
404            m = s.newMethod()
405            print >>m, '%sdef %s(self, ps, address):' %(self.getIndent(level=1), method_name)
406
407            msgin_name = msgout_name = None
408            msgin,msgout = op.getInputMessage(),op.getOutputMessage()
409            if msgin is not None:
410                msgin_name = TextProtect(msgin.name)
411            if msgout is not None:
412                msgout_name = TextProtect(msgout.name)
413
414            indent = self.getIndent(level=2)
415            for l in self.createMethodBody(msgin_name, msgout_name):
416                print >>m, indent + l
417
418            print >>m, ''
419            print >>m, '%ssoapAction[\'%s\'] = \'%s\'' %(self.getIndent(level=1), wsaction_in, method_name)
420            print >>m, '%swsAction[\'%s\'] = \'%s\'' %(self.getIndent(level=1), method_name, wsaction_out)
421            print >>m, '%sroot[(%s.typecode.nspname,%s.typecode.pname)] = \'%s\'' \
422                     %(self.getIndent(level=1), msgin_name, msgin_name, method_name)
423
424