1############################################################################
2# Joshua R. Boverhof, LBNL
3# See Copyright for copyright notice!
4# $Id: $
5###########################################################################
6import os, sys, types, inspect
7from StringIO import StringIO
8
9# twisted & related imports
10from zope.interface import classProvides, implements, Interface
11
12# ZSI imports
13from ZSI import _get_element_nsuri_name, EvaluateException, ParseException,\
14    fault, ParsedSoap, SoapWriter
15from ZSI.twisted.reverse import DataHandler, ReverseHandlerChain,\
16    HandlerChainInterface
17
18"""
19EXAMPLES:
20
21     See zsi/samples/WSGI
22
23
24"""
25
26def soapmethod(requesttypecode, responsetypecode, soapaction='',
27               operation=None, **kw):
28    """@soapmethod
29    decorator function for soap methods
30    """
31    def _closure(func_cb):
32        func_cb.root = (requesttypecode.nspname,requesttypecode.pname)
33        func_cb.action = soapaction
34        func_cb.requesttypecode = requesttypecode
35        func_cb.responsetypecode = responsetypecode
36        func_cb.soapmethod = True
37        func_cb.operation = None
38        return func_cb
39
40    return _closure
41
42
43class SOAPCallbackHandler:
44    """ ps --> pyobj, pyobj --> sw
45    class variables:
46        writerClass -- ElementProxy implementation to use for SoapWriter instances.
47    """
48    classProvides(HandlerChainInterface)
49    writerClass = None
50
51    @classmethod
52    def processRequest(cls, ps, **kw):
53        """invokes callback that should return a (request,response) tuple.
54        representing the SOAP request and response respectively.
55        ps -- ParsedSoap instance representing HTTP Body.
56        request -- twisted.web.server.Request
57        """
58        resource = kw['resource']
59        request = kw['request']
60
61        root = _get_element_nsuri_name(ps.body_root)
62        for key,method in inspect.getmembers(resource, inspect.ismethod):
63            if (getattr(method, 'soapmethod', False) and method.root == root):
64                break
65        else:
66            raise RuntimeError, 'Missing soap callback method for root "%s"' %root
67
68        try:
69            req = ps.Parse(method.requesttypecode)
70        except Exception, ex:
71            raise
72        try:
73            rsp = method.responsetypecode.pyclass()
74        except Exception, ex:
75            raise
76
77        try:
78            req,rsp = method(req, rsp)
79        except Exception, ex:
80            raise
81
82        return rsp
83
84    @classmethod
85    def processResponse(cls, output, **kw):
86        sw = SoapWriter(outputclass=cls.writerClass)
87        sw.serialize(output)
88        return sw
89
90
91class SOAPHandlerChainFactory:
92    protocol = ReverseHandlerChain
93
94    @classmethod
95    def newInstance(cls):
96        return cls.protocol(DataHandler, SOAPCallbackHandler)
97
98
99class WSGIApplication(dict):
100    encoding = "UTF-8"
101
102    def __call__(self, env, start_response):
103        """do dispatching, else process
104        """
105        script = env['SCRIPT_NAME'] # consumed
106        ipath = os.path.split(env['PATH_INFO'])[1:]
107        for i in range(1, len(ipath)+1):
108            path = os.path.join(*ipath[:i])
109            print "PATH: ", path
110            application = self.get(path)
111            if application is not None:
112                env['SCRIPT_NAME'] = script + path
113                env['PATH_INFO'] =  ''
114                print "SCRIPT: ", env['SCRIPT_NAME']
115                return application(env, start_response)
116
117        return self._request_cb(env, start_response)
118
119    def _request_cb(self, env, start_response):
120        """callback method, override
121        """
122        start_response("404 ERROR", [('Content-Type','text/plain')])
123        return ['Move along people, there is nothing to see to hear']
124
125    def putChild(self, path, resource):
126        """
127        """
128        path = path.split('/')
129        lp = len(path)
130        if lp == 0:
131            raise RuntimeError, 'bad path "%s"' %path
132
133        if lp == 1:
134            self[path[0]] = resource
135
136        for i in range(len(path)):
137            if not path[i]: continue
138            break
139
140        next = self.get(path[i], None)
141        if next is None:
142            next = self[path[i]] = WSGIApplication()
143
144        next.putChild('/'.join(path[-1:]), resource)
145
146
147
148
149class SOAPApplication(WSGIApplication):
150    """
151    """
152    factory = SOAPHandlerChainFactory
153
154    def __init__(self, **kw):
155        dict.__init__(self, **kw)
156        self.delegate = None
157
158    def _request_cb(self, env, start_response):
159        """process request,
160        """
161        if env['REQUEST_METHOD'] == 'GET':
162            return self._handle_GET(env, start_response)
163
164        if env['REQUEST_METHOD'] == 'POST':
165            return self._handle_POST(env, start_response)
166
167        start_response("500 ERROR", [('Content-Type','text/plain')])
168        s = StringIO()
169        h = env.items(); h.sort()
170        for k,v in h:
171            print >>s, k,'=',`v`
172        return [s.getvalue()]
173
174    def _handle_GET(self, env, start_response):
175        if env['QUERY_STRING'].lower() == 'wsdl':
176            start_response("200 OK", [('Content-Type','text/plain')])
177            r = self.delegate or self
178            return _resourceToWSDL(r)
179
180        start_response("404 ERROR", [('Content-Type','text/plain')])
181        return ['NO RESOURCE FOR GET']
182
183    def _handle_POST(self, env, start_response):
184        """Dispatch Method called by twisted render, creates a
185        request/response handler chain.
186        request -- twisted.web.server.Request
187        """
188        input = env['wsgi.input']
189        data = input.read( int(env['CONTENT_LENGTH']) )
190        mimeType = "text/xml"
191        if self.encoding is not None:
192            mimeType = 'text/xml; charset="%s"' % self.encoding
193
194        request = None
195        resource = self.delegate or self
196        chain = self.factory.newInstance()
197        try:
198            pyobj = chain.processRequest(data, request=request, resource=resource)
199        except Exception, ex:
200            start_response("500 ERROR", [('Content-Type',mimeType)])
201            return [fault.FaultFromException(ex, False, sys.exc_info()[2]).AsSOAP()]
202
203        try:
204            soap = chain.processResponse(pyobj, request=request, resource=resource)
205        except Exception, ex:
206            start_response("500 ERROR", [('Content-Type',mimeType)])
207            return [fault.FaultFromException(ex, False, sys.exc_info()[2]).AsSOAP()]
208
209        start_response("200 OK", [('Content-Type',mimeType)])
210        return [soap]
211
212
213def test(app, port=8080, host="localhost"):
214    """
215    """
216    from twisted.internet import reactor
217    from twisted.python import log
218    from twisted.web2.channel import HTTPFactory
219    from twisted.web2.server import Site
220    from twisted.web2.wsgi import WSGIResource
221
222    log.startLogging(sys.stdout)
223    reactor.listenTCP(port,
224        HTTPFactory( Site(WSGIResource(app)) ),
225        interface=host,
226    )
227    reactor.run()
228
229
230def _issoapmethod(f):
231    return type(f) is types.MethodType and getattr(f, 'soapmethod', False)
232
233def _resourceToWSDL(resource):
234    from xml.etree import ElementTree
235    from xml.etree.ElementTree import Element, QName
236    from ZSI.wstools.Namespaces import WSDL
237
238    r = resource
239    methods = filter(_issoapmethod, map(lambda i: getattr(r, i), dir(r)))
240    tns = ''
241
242    #tree = ElementTree()
243    defs = Element("{%s}definitions" %WSDL.BASE)
244    defs.attrib['name'] = 'SampleDefs'
245    defs.attrib['targetNamespace'] = tns
246    #tree.append(defs)
247
248    porttype = Element("{%s}portType" %WSDL)
249    porttype.attrib['name'] = QName("{%s}SamplePortType" %tns)
250
251    binding = Element("{%s}binding" %WSDL)
252    defs.append(binding)
253    binding.attrib['name'] = QName("{%s}SampleBinding" %tns)
254    binding.attrib['type'] = porttype.get('name')
255
256    for m in methods:
257        m.action
258
259    service = Element("{%s}service" %WSDL.BASE)
260    defs.append(service)
261    service.attrib['name'] = 'SampleService'
262
263    port = Element("{%s}port" %WSDL.BASE)
264    service.append(port)
265    port.attrib['name'] = "SamplePort"
266    port.attrib['binding'] = binding.get('name')
267
268    soapaddress = Element("{%s}address" %WSDL.BIND_SOAP)
269    soapaddress.attrib['location'] = 'http://localhost/bla'
270    port.append(soapaddress)
271
272    return [ElementTree.tostring(defs)]
273
274
275
276"""
277<?xml version="1.0" encoding="UTF-8"?>
278<wsdl:definitions name="Counter" targetNamespace="http://counter.com/bindings" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:porttype="http://counter.com" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
279  <wsdl:import namespace="http://counter.com" location="counter_flattened.wsdl"/>
280  <wsdl:binding name="CounterPortTypeSOAPBinding" type="porttype:CounterPortType">
281    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
282    <wsdl:operation name="createCounter">
283      <soap:operation soapAction="http://counter.com/CounterPortType/createCounterRequest"/>
284      <wsdl:input>
285        <soap:body use="literal"/>
286      </wsdl:input>
287      <wsdl:output>
288        <soap:body use="literal"/>
289      </wsdl:output>
290    </wsdl:operation>
291
292
293<wsdl:definitions name="Counter" targetNamespace="http://counter.com/service"
294xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:binding="http://counter.com/bindings">
295  <wsdl:import namespace="http://counter.com/bindings" location="counter_bindings.wsdl"/>
296  <wsdl:service name="CounterService">
297    <wsdl:port name="CounterPortTypePort" binding="binding:CounterPortTypeSOAPBinding">
298      <soap:address location="http://localhost:8080/wsrf/services/"/>
299    </wsdl:port>
300  </wsdl:service>
301</wsdl:definitions>
302"""
303
304