"""
SOAP View for Django
Originally at: http://code.djangoproject.com/ticket/552

Updated 2009-06-16:
Rewrote wsdl generator
-Added convertTypeToXml function to keep DRY
-Only parses int or strings; user will want to add others depending on their data
beth.mcnany@yahoo.com
"""
from __future__ import nested_scopes

import re
import sys
import thread
from types import *

# SOAPpy modules
from SOAPpy.version     import __version__
from SOAPpy.Parser      import parseSOAPRPC
from SOAPpy.Config      import Config
from SOAPpy.Types       import faultType, voidType, simplify
from SOAPpy.NS          import NS
from SOAPpy.SOAPBuilder import buildSOAP
from SOAPpy.Utilities   import debugHeader, debugFooter
from SOAPpy.Server      import SOAPServerBase, HeaderHandler, SOAPContext, MethodSig

try: from M2Crypto import SSL
except: pass

from django.http import HttpResponseServerError, HttpResponse

_contexts = dict()

class SimpleSOAPView(SOAPServerBase):
    def __init__(self, encoding = 'UTF-8', config = Config, namespace = None):
        
        # Test the encoding, raising an exception if it's not known
        if encoding != None:
            ''.encode(encoding)
        
        self.namespace           = namespace
        self.objmap              = {}
        self.funcmap             = {}
        self.encoding            = encoding
        self.config              = config
        
        self.allow_reuse_address = 1

    def dispatch(self, data):
        global _contexts
        try:
            (r, header, body, attrs) = \
                parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
            print (r, header, body, attrs)
            method = r._name
            args   = r._aslist()
            kw     = r._asdict()
            
            if self.config.simplify_objects:
                args = simplify(args)
                kw = simplify(kw)
            
            if self.config.specialArgs: 
                ordered_args = {}
                named_args   = {}
                
                for (k,v) in  kw.items():
                    if k[0]=="v":
                        try:
                            i = int(k[1:])
                            ordered_args[i] = v
                        except ValueError:
                            named_args[str(k)] = v
                        
                    else:
                        named_args[str(k)] = v
                    
            ns = r._ns
                
            if len(self.path) > 1 and not ns:
                ns = self.path.replace("/", ":")
                if ns[0] == ":": ns = ns[1:]
                
            # authorization method
            a = None
                
            keylist = ordered_args.keys()
            keylist.sort()
                
            # create list in proper order w/o names
            tmp = map( lambda x: ordered_args[x], keylist)
            ordered_args = tmp
            
            resp = ""
            
            # For fault messages
            if ns:
                nsmethod = "%s:%s" % (ns, method)
            else:
                nsmethod = method
            
            try:
                # First look for registered functions
                if self.funcmap.has_key(ns) and \
                    self.funcmap[ns].has_key(method):
                    f = self.funcmap[ns][method]
                    
                    # look for the authorization method
                    if self.config.authMethod != None:
                        authmethod = self.config.authMethod
                        if self.funcmap.has_key(ns) and \
                               self.funcmap[ns].has_key(authmethod):
                            a = self.funcmap[ns][authmethod]
                else:
                    # Now look at registered objects
                    # Check for nested attributes. This works even if
                    # there are none, because the split will return
                    # [method]
                    f = self.objmap[ns]
                    
                    # Look for the authorization method
                    if self.config.authMethod != None:
                        authmethod = self.config.authMethod
                        if hasattr(f, authmethod):
                            a = getattr(f, authmethod)
                        
                    # then continue looking for the method
                    l = method.split(".")
                    for i in l:
                        f = getattr(f, i)
            except Exception, e:
                info = sys.exc_info()
                resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
                                           "Method Not Found",
                                           "%s : %s %s %s" % (nsmethod,
                                                              info[0],
                                                              info[1],
                                                              info[2])),
                                 encoding = self.encoding,
                                 config = self.config)
                del info
                #print e
                return resp
            else:
                try:
                    if header:
                        x = HeaderHandler(header, attrs)
                    
                    fr = 1
                    
                    # call context book keeping
                    # We're stuffing the method into the soapaction if there
                    # isn't one, someday, we'll set that on the client
                    # and it won't be necessary here
                    # for now we're doing both
                    
                    if "SOAPAction".lower() not in self.headers.keys() or \
                       self.headers["SOAPAction"] == "\"\"":
                        self.headers["SOAPAction"] = method
                        
                    thread_id = thread.get_ident()
                    _contexts[thread_id] = SOAPContext(header, body,
                                                       attrs, data,
                                                       None,
                                                       self.headers,
                                                       self.headers["SOAPAction"])
                    
                    # Do an authorization check
                    if a != None:
                        if not apply(a, (), {"_SOAPContext" :
                                             _contexts[thread_id] }):
                            raise faultType("%s:Server" % NS.ENV_T,
                                            "Authorization failed.",
                                            "%s" % nsmethod)
                    
                    # If it's wrapped, some special action may be needed
                    if isinstance(f, MethodSig):
                        c = None
                    
                        if f.context:  # retrieve context object
                            c = _contexts[thread_id]
                        
                        if self.config.specialArgs:
                            if c:
                                named_args["_SOAPContext"] = c
                            fr = apply(f, ordered_args, named_args)
                        elif f.keywords:
                            # This is lame, but have to de-unicode
                            # keywords
                            
                            strkw = {}
                            
                            for (k, v) in kw.items():
                                strkw[str(k)] = v
                            if c:
                                strkw["_SOAPContext"] = c
                            fr = apply(f, (), strkw)
                        elif c:
                            fr = apply(f, args, {'_SOAPContext':c})
                        else:
                            fr = apply(f, args, {})
                        
                    else:
                        if self.config.specialArgs:
                            fr = apply(f, ordered_args, named_args)
                        else:
                            fr = apply(f, args, {})
                        
                        
                    if type(fr) == type(self) and \
                        isinstance(fr, voidType):
                        resp = buildSOAP(kw = {'%sResponse' % method: fr},
                            encoding = self.encoding,
                            config = self.config)
                    else:
                        resp = buildSOAP(kw =
                            {'%sResponse' % method: {'Result': fr}},
                            encoding = self.encoding,
                            config = self.config)
                    
                    # Clean up _contexts
                    if _contexts.has_key(thread_id):
                        del _contexts[thread_id]
                    
                except Exception, e:
                    import traceback
                    info = sys.exc_info()
                    
                    if isinstance(e, faultType):
                        f = e
                    else:
                        f = faultType("%s:Server" % NS.ENV_T,
                                      "Method Failed",
                                      "%s" % nsmethod)
                        
                    if self.config.returnFaultInfo:
                        f._setDetail("".join(traceback.format_exception(
                            info[0], info[1], info[2])))
                    elif not hasattr(f, 'detail'):
                        f._setDetail("%s %s" % (info[0], info[1]))
                    del info
                    print e   
                    resp = buildSOAP(f, encoding = self.encoding,
                       config = self.config)
                    return resp
                else:
                    return resp
        except faultType, e:
            import traceback
            info = sys.exc_info()
            if self.config.returnFaultInfo:
                e._setDetail("".join(traceback.format_exception(
                        info[0], info[1], info[2])))
            elif not hasattr(e, 'detail'):
                e._setDetail("%s %s" % (info[0], info[1]))
            del info

            resp = buildSOAP(e, encoding = self.encoding,
                config = self.config)
            
    #---------------------------------------------------------------------
    # 2009-06-16 - Rewrote the existing wsdl generator which didn't work
    # This assumes that:
    # - you define defaults for your function variables (how it determines i/o types)
    # - you have defined functions that either return or take a variable
    # - only one output returned
    # beth.mcnany@yahoo.com
    #---------------------------------------------------------------------
    def wsdl(self, serviceName="MySoap", serviceLoc="http://localhost/soap/"):
        """Generate a WSDL for the service"""
        import inspect
        from types import *
        funcArray = []
        
        #funcmap is such that funcmap.values()[0][example]() will call a function
        methods = self.funcmap.values()[0]
        for name, function in methods.iteritems():
            output = function() #need defaults!
            #{name:"function", input:{name:type, etc}, output:type}
            temp = {}
            temp['name'] = name
            temp['output'] = self.convertTypeToXml(type(output))
            return output, temp['output']
            args = inspect.getargspec(function)
            if len(args.args) > 0:
                inputArr = {}
                #this is where the assumption about default inputs comes in
                for arg, default in zip(args.args, args.defaults):
                    inputArr[arg] = self.convertTypeToXml(type(default))
                temp['input'] = inputArr
            else:
                temp['input'] = {}
            funcArray.append(temp)
            
        #header + definitions tag
        wsdl = """<?xml version="1.0" encoding="UTF-8"?>
<definitions name="%(name)s"
             targetNamespace="%(url)s"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:tns="%(url)s"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema">"""
        
        #message tags
        for func in funcArray:
            if len(func['input']) > 0:
                wsdl += '<message name="%sRequest">\n' % func['name']
                for k,v in func['input'].iteritems():
                    wsdl += '<part name="%s" type="xsd:%s" />\n' % (k, v)
                wsdl += '</message>\n'
            if func['output'] != "null":
                wsdl += '<message name="%sResponse">\n' % func['name']
                wsdl += '<part name="return" type="xsd:%s" />\n' % func['output']
                wsdl += '</message>\n'

        #portType tag
        wsdl += '<portType name="%(name)s_PortType">\n'
        for func in funcArray:
            wsdl += '<operation name="%s">\n' % func['name']
            if len(func['input']) > 0:
                wsdl += '<input message="tns:%sRequest" />\n' % func['name']
            if func['output'] != "null":
                wsdl += '<output message="tns:%sResponse" />\n' % func['name']
            wsdl += '</operation>\n'
        wsdl += '</portType>'
        
        #binding tag
        wsdl += """
  <binding name="%(name)s_Binding" type="tns:%(name)s_PortType">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http/" />\n"""
        for func in funcArray:
            wsdl += '<operation name="%s">' % func['name']
            if len(func['input']) > 0:
                wsdl += """                
      <input>
        <soap:body use="literal" />
      </input>"""
            if func['output'] != "null":
                wsdl += """
      <output>
        <soap:body use="literal" />
      </output>"""
            wsdl += '\n</operation>\n'
        wsdl += '</binding>'
        
        #services tag
        wsdl += """
  <service name="%(name)s">
    <port binding="tns:%(name)s_Binding" name="%(name)s_Port">
      <soap:address location="%(url)s" />
    </port>
  </service>
</definitions>"""        
        return wsdl % {'name':serviceName, 'url':serviceLoc}

    def convertTypeToXml(self, type):
        """Converts a Python type to XML type"""
        #TODO: add more types
        tStr = "null"
        if type == IntType:        tStr = "int"
        elif type == BooleanType:  tStr = "boolean"
        elif type == StringType:   tStr = "string"
        return tStr

    def __call__(self, request, path=''):
        """ SimpleXMLRPCView is callable so it can be installed as a view.

            Django calls it with 'request', which is a HttpRequest            
        """
        self.path = path
        self.headers = request.META # compatible?
        
        if request.META['REQUEST_METHOD'] == 'GET':
            if request.META['QUERY_STRING'] == 'wsdl':
                wsdl = self.wsdl()
                return HttpResponse(wsdl, mimetype='text/xml')
            else:
                return HttpResponseServerError('Use /?wsdl to get WSDL.')
        elif request.META['REQUEST_METHOD'] != 'POST':
            return HttpResponseServerError('Non POST methods not allowed.')
        
        try:
            response = self.dispatch(request.raw_post_data)
            print response
            print self.objmap, self.funcmap
        except Exception, e:
            # internal error, report as HTTP server error
            return HttpResponseServerError('internal error')
        else:
            # got a valid XML RPC response
            return HttpResponse(response, mimetype="text/xml")

