← Back to team overview

ladon-dev-team team mailing list archive

Re: [Question #219424]: soap reponse in wsdl does not match description

 

Question #219424 on ladon changed:
https://answers.launchpad.net/ladon/+question/219424

GaryS posted a new comment:
I have developed a new interface which solves the problem - this seems
to work with java-ws clients and .net clients I have tested it with. I
called it soapws:

# -*- coding: utf-8 -*-

from ladon.interfaces.base import BaseInterface,ServiceDescriptor,BaseRequestHandler,BaseResponseHandler,BaseFaultHandler
from ladon.interfaces import expose
from ladon.compat import PORTABLE_STRING,type_to_xsd,pytype_support,BytesIO
from xml.sax.handler import ContentHandler,feature_namespaces
from xml.sax import make_parser
from xml.sax.xmlreader import InputSource
import sys,re,traceback
import logging

LOG = logging.getLogger(__name__)


rx_nil_attr = re.compile(PORTABLE_STRING('^\w*[:]{0,1}nil$'),re.I)


class SOAPWSServiceDescriptor(ServiceDescriptor):
	
	xsd_type_map = type_to_xsd
	_content_type = 'text/xml'
	
	def generate(self,servicename,servicenumber,typemanager,methodlist,service_url,encoding):
		"""
		Generate WSDL file for SOAPWSInterface
		"""
		type_dict = typemanager.type_dict
		type_order = typemanager.type_order

		def map_type(typ):
			if typ in SOAPWSServiceDescriptor.xsd_type_map:
				return SOAPWSServiceDescriptor.xsd_type_map[typ]
			else:
				return typ.__name__

		def map_type2(typ):
			if typ in SOAPWSServiceDescriptor.xsd_type_map:
                                tmpval =  SOAPWSServiceDescriptor.xsd_type_map[typ]
				if tmpval == 'decimal':
					tmpval = 'float'
				return tmpval
                        else:
                                return typ.__name__

		import xml.dom.minidom as md
		doc = md.Document()
		
		# SERVICE DEFINITION
		# Create the definitions element for the service
		definitions = doc.createElement('definitions')
		definitions.setAttribute('xmlns:wsu','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd')
		definitions.setAttribute('xmlns:wsp','http://www.w3.org/ns/ws-policy')
		definitions.setAttribute('xmlns:wsp1_2','http://schemas.xmlsoap.org/ws/2004/09/policy')
		definitions.setAttribute('xmlns:wsam','http://www.w3.org/2007/05/addressing/metadata')
      		definitions.setAttribute('xmlns:soap','http://schemas.xmlsoap.org/wsdl/soap/')
		
		definitions.setAttribute('name', servicename)
		definitions.setAttribute('targetNamespace','urn:%s' % servicename)
		definitions.setAttribute('xmlns:tns','urn:%s' % servicename)
		definitions.setAttribute('xmlns','http://schemas.xmlsoap.org/wsdl/')
		definitions.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance')
		definitions.setAttribute('xmlns:xsd','http://www.w3.org/2001/XMLSchema')
		definitions.setAttribute('xmlns:ns%d' % servicenumber,'urn:%s' % servicename)
		doc.appendChild(definitions)
		
		# TYPES
		# The types element
		types = doc.createElement('types')
		definitions.appendChild(types)
		
		# Service schema for types required by the target namespace we defined in the definition element
		schema = doc.createElement('xsd:schema')
		schema.setAttribute('targetNamespace','urn:%s' % servicename)
		schema.setAttribute('xmlns:xs','http://www.w3.org/2001/XMLSchema')
		schema.setAttribute('xmlns:ns%d' % servicenumber,'urn:%s' % servicename)
		schema.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance')
                schema.setAttribute('xmlns:xsd','http://www.w3.org/2001/XMLSchema')

		types.appendChild(schema)
		
		# Define types, the type_order variable holds all that need to be defined and in the
		# correct order.
		for m in methodlist:
			basictype = doc.createElement('xs:element')
			basictype.setAttribute('name',m.name())
			basictype.setAttribute('type','tns:%s' % m.name())
			schema.appendChild(basictype)
			basictypeR = doc.createElement('xs:element')
                        basictypeR.setAttribute('name',"%sResponse" % m.name())
                        basictypeR.setAttribute('type','tns:%sResponse' % m.name())
			schema.appendChild(basictypeR)
	
		for m in methodlist:
			complextype = doc.createElement('xs:complexType')
                        complextype.setAttribute('name',m.name())
			
                        schema.appendChild(complextype)
                        sequence = doc.createElement('xs:sequence')
                        complextype.appendChild(sequence)
			for arg in m.args():
				v = arg['type']
                        	element = doc.createElement('xs:element')
                                element.setAttribute('name',arg['name'].replace('_','-'))
                                element.setAttribute('minOccurs','0')
                                if isinstance(v, list):
                                	inner = v[0]
                                        if inner in type_dict:
                                        	element.setAttribute('type','tns:%s' % inner.__name__)
                                        else:
                                        	element.setAttribute('type','xs:%s' % map_type2(inner))
                                        element.setAttribute('maxOccurs','unbounded')
                                if not(isinstance(v, list)):
                                	if v in type_dict:
                                	        element.setAttribute('type','tns:%s' % v.__name__)
                                        else:
                                                element.setAttribute('type','xs:%s' % map_type2(v))
                                sequence.appendChild(element)
			
			complextype = doc.createElement('xs:complexType')
                        complextype.setAttribute('name',"%sResponse" % m.name())

                        schema.appendChild(complextype)
                        sequence = doc.createElement('xs:sequence')
                        complextype.appendChild(sequence)
                        v = m._rtype
                        element = doc.createElement('xs:element')
                        element.setAttribute('name','return')
                        element.setAttribute('minOccurs','0')
                        if isinstance(v, list):
                        	inner = v[0]
                                if inner in type_dict:
                                	element.setAttribute('type','tns:%s' % inner.__name__)
                                	element.setAttribute('maxOccurs','unbounded')
				else:
                                        element.setAttribute('type','xs:%s' % map_type2(inner))
                                	element.setAttribute('maxOccurs','unbounded')
				element.setAttribute('minOccurs','0')
                        if not(isinstance(v, list)):
                                if v in type_dict:
                                        element.setAttribute('type','tns:%s' % v.__name__)
                                else:
                                        element.setAttribute('type','xs:%s' % map_type2(v))
                        sequence.appendChild(element)


		#special types
		for typ in type_order:
			if not(isinstance(typ, list)):
				complextype = doc.createElement('xs:complexType')
				complextype.setAttribute('name',typ['name'])
				schema.appendChild(complextype)
				sequence = doc.createElement('xs:sequence')
				complextype.appendChild(sequence)
				for k,v,props in typ['attributes']:
					element = doc.createElement('xs:element')
					element.setAttribute('name',k.replace('_','-'))
					element.setAttribute('maxOccurs','1')
					element.setAttribute('minOccurs','1')
					if props.get('nullable')==True:
						element.setAttribute('minOccurs','0')
						element.setAttribute('nillable','true')
					if isinstance(v, list):
						inner = v[0]
						#element.setAttribute('type','tns:%s' % map_type2(inner))
						if inner in type_dict:
                                                        element.setAttribute('type','tns:%s' % inner.__name__)
                                                else:
                                                        element.setAttribute('type','xs:%s' % map_type2(inner))

						#inner.__name__)
						element.setAttribute('minOccurs','0')
						element.setAttribute('maxOccurs','unbounded')
						element.setAttribute('nillable','true')
						#element.setAttribute('type','tns:%s' % type(v[0]))
					if not(isinstance(v, list)):
						if v in type_dict:
							element.setAttribute('type','tns:%s' % v.__name__)
						else:
							element.setAttribute('type','xs:%s' % map_type2(v))
					sequence.appendChild(element)

		#messages
		for m in methodlist:
			message = doc.createElement('message')
			message.setAttribute('name',m.name())
			definitions.appendChild(message)
			part = doc.createElement('part')
			part.setAttribute('name','parameters')
			part.setAttribute('element',"tns:%s"  % m.name())
			message.appendChild(part)
			message = doc.createElement('message')
			message.setAttribute('name',"%sResponse" % m.name())
			definitions.appendChild(message)
			part2 = doc.createElement('part')
			part2.setAttribute('name','parameters')
			part2.setAttribute('element',"tns:%sResponse" % m.name())
			message.appendChild(part2)
		porttype = doc.createElement('portType')
		porttype.setAttribute('name','%sPortType' % servicename)
		definitions.appendChild(porttype)

		#operations
		for m in methodlist:
			operation = doc.createElement('operation')
			operation.setAttribute('name',m.name())
			porttype.appendChild(operation)
			if m.__doc__:
				documentation = doc.createElement('documentation')
				documentation.appendChild(doc.createTextNode(m.__doc__))
				operation.appendChild(documentation)
			input_tag = doc.createElement('input')
			input_tag.setAttribute('wsam:Action',"%s/%s" % (service_url,m.name()))
			input_tag.setAttribute('message','tns:%s' % m.name())
			operation.appendChild(input_tag)
			output_tag = doc.createElement('output')
			output_tag.setAttribute('wsam:Action',"%s/%sResponse" % (service_url,m.name()))
			output_tag.setAttribute('message','tns:%sResponse' % m.name())
			operation.appendChild(output_tag)


		#binding
		binding = doc.createElement('binding')
		binding.setAttribute('name',servicename)
		binding.setAttribute('type',"tns:%sPortType" % servicename)
		transport = doc.createElement('soap:binding')
		transport.setAttribute('transport','http://schemas.xmlsoap.org/soap/http')
		transport.setAttribute('style','document')
		binding.appendChild(transport)
		definitions.appendChild(binding)

		for m in methodlist:
			operation = doc.createElement('operation')
			operation.setAttribute('name',m.name())
			binding.appendChild(operation)
			soapaction = doc.createElement('soap:operation')
			soapaction.setAttribute('soapAction','')
			operation.appendChild(soapaction)
			
			m._multipart_response_required
			input_tag = doc.createElement('input')
			body_parent = input_tag
			if m._multipart_request_required:
				multipart_related = doc.createElement('mime:multipartRelated')
				mime_body_part = doc.createElement('mime:part')
				body_parent = mime_body_part
				mime_content_part = doc.createElement('mime:part')
				mime_content = doc.createElement('mime:content')
				mime_content.setAttribute('type','*/*')
				input_tag.appendChild(multipart_related)
				multipart_related.appendChild(mime_body_part)
				multipart_related.appendChild(mime_content_part)
				mime_content_part.appendChild(mime_content)
				
			input_soapbody = doc.createElement('soap:body')
			input_soapbody.setAttribute('use','literal')
			body_parent.appendChild(input_soapbody)
			operation.appendChild(input_tag)
			output_tag = doc.createElement('output')
			body_parent = output_tag
			if m._multipart_request_required:
				multipart_related = doc.createElement('mime:multipartRelated')
				mime_body_part = doc.createElement('mime:part')
				body_parent = mime_body_part
				mime_content_part = doc.createElement('mime:part')
				mime_content = doc.createElement('content:part')
				mime_content.setAttribute('type','*/*')
				output_tag.appendChild(multipart_related)
				multipart_related.appendChild(mime_body_part)
				multipart_related.appendChild(mime_content_part)
				mime_content_part.appendChild(mime_content)

			output_soapbody = doc.createElement('soap:body')
			output_soapbody.setAttribute('use','literal')
			body_parent.appendChild(output_soapbody)
			operation.appendChild(output_tag)


		service = doc.createElement('service')
		service.setAttribute('name',servicename)
		documentation = doc.createElement('documentation')
		documentation.appendChild(doc.createTextNode('Ladon generated service definition'))
		service.appendChild(documentation)
		port = doc.createElement('port')
		port.setAttribute('name',servicename)
		port.setAttribute('binding','tns:%s' % servicename)
		service.appendChild(port)
		address = doc.createElement('soap:address')
		address.setAttribute('location',service_url)
		port.appendChild(address)
		definitions.appendChild(service)
		if sys.version_info[0]>=3:
			return doc.toxml()
		return doc.toxml(encoding)


def u(instring):
	if sys.version_info[0]==2:
		return PORTABLE_STRING(instring,'utf-8')
	else:
		return PORTABLE_STRING(instring)

class ContainerSetRef(object):
	def __init__(self,c,refval):
		self.c = c
		self.refval = refval
	
	def set(self,val):
		self.c[self.refval] = val

class SOAPWSRequestHandler(BaseRequestHandler):

	def parse_request(self,soap_body,sinfo,encoding):
		import xml.dom.minidom as md
		self.sinfo = sinfo
		doc = md.parseString(soap_body)
		soap_envelope = doc.getElementsByTagNameNS('*','Envelope')[0]
		soap_body = doc.getElementsByTagNameNS('*','Body')[0]
		EN = soap_body.ELEMENT_NODE
		soap_method = (node for node in soap_body.childNodes
				if node.nodeType == EN).next()
		soap_methodprefix = soap_method.prefix
		try:
			m = re.match("^ns(\d+)$",soap_methodprefix)
		except:
			m = None
		servicenumber = None
		if m: servicenumber = int(m.groups()[0])
		self.soap_methodname = soap_method.localName
		soap_args = {'methodname': self.soap_methodname,'servicenumber':servicenumber}
		#for typ in self.sinfo.typemanager.type_order:
                #        if not(isinstance(typ, list)):
		#		self.appendToMyLog(typ)
		TN = soap_method.TEXT_NODE
		soap_args['args'] = self.getDictForNode(soap_method)
		#self.appendToMyLog('soap_args is  %s' % soap_args)
		return soap_args

	def getDictForNode(self, node):
        	localDict={}
        	for n in node.childNodes:
            		if n.nodeType == n.ELEMENT_NODE and not n.hasAttribute('xsi:nil'):
                    		if self.isTagList(n.nodeName):
                        		lon = self.getListItems(n,node)
                        		tList = []
                        		for nn in lon:
                             			tList.append(self.getDictForNode(nn))
                             			node.removeChild(nn)
                        		localDict[n.nodeName]= tList
                    		else:
                        		localDict[n.nodeName]=self.getDictForNode(n)
            		elif n.nodeType==n.TEXT_NODE and not n.hasChildNodes() and node.childNodes.length==1:
                		return n.nodeValue
			elif n.nodeType==n.CDATA_SECTION_NODE:
		                return n.nodeValue                                                                             
   		return localDict

	def isTagList(self, tagnm):
                for typ in self.sinfo.typemanager.type_order:
                        #self.appendToMyLog(typ)
			a=1
                        try:
                                a=1
                                if not isinstance(typ, list):
                                        for k,v,props in typ['attributes']:
                                                if tagnm == k and isinstance(v,list):
                                                        return True
                        except:
                                a=2
                for m in self.sinfo.method_list():
			#self.appendToMyLog(m)
			#self.appendToMyLog(m.name())
			for arg in m.args():
                                v = arg['type']
                                if self.soap_methodname == m.name() and arg['name'] == tagnm and isinstance(v,list):
                                        #self.appendToMyLog("%s is list!!" % m.name())
					return True
			#if isinstance(m['rtype'], list):
			#	self.appendToMyLog(m['rtype'])
			#	return True
		return False

   	def getListItems(self, node, parent):
         	tagList=[]
        	for n in parent.childNodes:
            		if n.nodeName==node.nodeName:
                		tagList.append(n)
        	return tagList

	def appendToMyLog(self, s):
                f1=open('/home/ye91009/logs/testfile', 'a')
                print >> f1, s
                f1.close()
	
class SOAPWSResponseHandler(BaseResponseHandler):
	
	_content_type = 'text/xml'
	_stringify_res_dict = True

	def dictToList(self,dd):
		#convert dictionary to list i fpossible, to ensure order in type_order is preserved to giev valid xml export format
		retList=[]
		for typ in self.sinfo.typemanager.type_order:
                	if not(isinstance(typ, list)):
				ld={}
				for k,v,props in typ['attributes']:
					ld[k]=None
				if set(ld)==set(dd):
					for k,v,props in typ['attributes']:
						retList.append(k)
					self.appendToMyLog(retList)
					return retList
		for k,v in dd.items():
			retList.append(k)
		return retList
					
	def value_to_soapxml(self,value,parent,doc,is_toplevel=False):
		if isinstance(value, dict):
			locList = self.dictToList(value)
			self.appendToMyLog(value)
			for aname in locList:
			#for attr_name,attr_val in value.items():
				#self.appendToMyLog(aname)
				attr_name = aname
				attr_val = value[aname]
				xml_attr_name = attr_name.replace('_','-')
                                if isinstance(attr_val, (list,tuple)):
					for item in attr_val:
						attr_elem = doc.createElement(xml_attr_name)
						if is_toplevel:
	                                                attr_elem = doc.createElement('return')
						parent.appendChild(attr_elem)
						self.value_to_soapxml(item,attr_elem,doc)
				else:
					attr_elem = doc.createElement(xml_attr_name)
					if is_toplevel:
						attr_elem = doc.createElement('return')
					parent.appendChild(attr_elem)
					self.value_to_soapxml(attr_val,attr_elem,doc)
		else:
			if is_toplevel:
				value_parent = doc.createElement('return')
				value_parent.setAttribute('dodo','http://schemas.xmlsoap.org/soap/encoding/')
				parent.appendChild(value_parent)
			else:
				value_parent = parent

			if isinstance(value, (list, tuple)):
				if not len(value):
					# Translate empty response arrays to SOAP Null (xsi:nil) value
					value_parent.setAttribute('xsi:nil','true')
				else:
					for item in value:
						item_element = doc.createElement(value_parent.nodeName)
						self.value_to_soapxml(item,item_element,doc)
						value_parent.appendChild(item_element)
			else:
				if value==None:
					# Set xsi:nil to true if value is None
					value_parent.setAttribute('xsi:nil','true')
				else:
					value_parent.appendChild(doc.createTextNode(value))

	def build_response(self,res_dict,sinfo,encoding):
		import xml.dom.minidom as md
		self.sinfo=sinfo
		doc = md.Document()
		envelope = doc.createElement('SOAP-ENV:Envelope')
		envelope.setAttribute('xmlns:SOAP-ENV','http://schemas.xmlsoap.org/soap/envelope/')
		envelope.setAttribute('xmlns:SOAP-ENC','http://schemas.xmlsoap.org/soap/encoding/')
		envelope.setAttribute('xmlns:xsd','http://www.w3.org/2001/XMLSchema')
		envelope.setAttribute('xmlns:ns','urn:%s' % res_dict['servicename'])
		envelope.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance')
		doc.appendChild(envelope)
		body_elem = doc.createElement('SOAP-ENV:Body')
		body_elem.setAttribute('SOAP-ENV:encodingStyle','http://schemas.xmlsoap.org/soap/encoding/')
		envelope.appendChild(body_elem)
		method_elem = doc.createElement("tns:%sResponse" % res_dict['method'])
		method_elem.setAttribute('xmlns:tns','urn:%s' % res_dict['servicename'])
		#self.appendToMyLog(res_dict)
		if 'result' in res_dict['result']:
			self.value_to_soapxml(res_dict['result'],method_elem,doc,is_toplevel=True)
		else:
			self.value_to_soapxml({'result':res_dict['result']},method_elem,doc,is_toplevel=True)
		body_elem.appendChild(method_elem)
		return doc.toxml(encoding=encoding)

	def appendToMyLog(self, s):
                f1=open('/home/ye91009/logs/testfile', 'a')
                print >> f1, s
                f1.close()

class SOAPWSFaultHandler(BaseFaultHandler):
	
	_content_type = 'text/xml'
	_stringify_res_dict = True
	soapfault_template = """<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/";
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance";
xmlns:xsd="http://www.w3.org/1999/XMLSchema";>
	<SOAP-ENV:Body>
		<SOAP-ENV:Fault>
			<faultcode xsi:type="xsd:string"></faultcode>
			<faultstring xsi:type="xsd:string"></faultstring>
			<detail></detail>
		</SOAP-ENV:Fault>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""

	def build_fault_response(self,service_exc,sinfo,methodname,encoding):
		import xml.dom.minidom as md
		if service_exc.detail:
			detail = service_exc.detail
		else:
			detail = traceback.format_exc()
		d = md.parseString(self.soapfault_template)
		# Extract fault DOM elements
		faultcode_elem = d.getElementsByTagName('faultcode')[0]
		faultstring_elem = d.getElementsByTagName('faultstring')[0]
		detail_elem = d.getElementsByTagName('detail')[0]
		# Set the fault values
		faultcode_elem.appendChild(d.createTextNode(service_exc.faultcode))
		faultstring_elem.appendChild(d.createTextNode(service_exc.faultstring))
		detail_elem.appendChild(d.createTextNode(detail))
		# Return the SoapFault XML object
		return d.toxml(encoding=encoding)

@expose
class SOAPWSInterface(BaseInterface):

	def __init__(self,sinfo,**kw):
		def_kw = {
			'service_descriptor': SOAPWSServiceDescriptor,
			'request_handler': SOAPWSRequestHandler,
			'response_handler': SOAPWSResponseHandler,
			'fault_handler': SOAPWSFaultHandler}
		def_kw.update(kw)
		BaseInterface.__init__(self,sinfo,**def_kw)

	@staticmethod
	def _interface_name():
		return 'soapws'

	@staticmethod
	def _accept_basetype(typ):
		return pytype_support.count(typ)>0

	@staticmethod
	def _accept_list():
		return True

	@staticmethod
	def _accept_dict():
		return False

-- 
You received this question notification because you are a member of
Ladon Developer, which is an answer contact for ladon.