#!/usr/bin/env python

"""
Copyright 2008-2014, 2016 VMware, Inc.  All rights reserved. -- VMware Confidential

This module is the python vmomi soap handler module. This handle soap request,
call server object and returns response
"""
__author__ = "VMware, Inc"

import six
from six.moves import zip
import sys
import traceback
import threading

# For xml parsing
import xml
from XMLExpatHelper import XMLExpatHelper

from pyVmomi import VmomiSupport, SoapAdapter, Vmodl

import MoManager

import logging

import uuid
from contrib.logUtils import ThreadName

## XML header string
_STR_XML_ENCODING = SoapAdapter.XML_ENCODING
_STR_XML_HEADER = SoapAdapter.XML_HEADER

NS_SEP = SoapAdapter.NS_SEP

_STR_ENVELOPE = SoapAdapter.XMLNS_SOAPENV + NS_SEP + "Envelope"
_STR_ENVELOPE_START = SoapAdapter.SOAP_ENVELOPE_START
_STR_ENVELOPE_END ="</" + SoapAdapter.SOAP_ENVELOPE_TAG + ">"

_STR_HEADER = SoapAdapter.XMLNS_SOAPENV + NS_SEP + "Header"

_STR_BODY = SoapAdapter.XMLNS_SOAPENV + NS_SEP + "Body"
_STR_BODY_START = "<" + SoapAdapter.SOAP_BODY_TAG + ">"
_STR_BODY_END = "</" + SoapAdapter.SOAP_BODY_TAG + ">"

_STR_FAULT = SoapAdapter.XMLNS_SOAPENV + NS_SEP + "Fault"
_STR_FAULT_START = "<" + SoapAdapter.SOAP_FAULT_TAG + ">"
_STR_FAULT_END = "</" + SoapAdapter.SOAP_FAULT_TAG + ">"

# Soap accessor tag "Fetch"
_STR_FETCH = "Fetch"

# Soap request parameter tag "_this"
_STR_THIS = "_this"

# Soap accessor parameter tag "prop"
_STR_PROP = "prop"

# Soap response tag "returnval"
_STR_RETURNVAL = "returnval"
_STR_RETURNVAL_START = "<" + _STR_RETURNVAL + ">"
_STR_RETURNVAL_END = "</" + _STR_RETURNVAL + ">"

_STR_SOAPENV_MUST_UNDERSTAND = SoapAdapter.XMLNS_SOAPENV + NS_SEP + 'mustUnderstand'

_XSD_STRING = VmomiSupport.GetQualifiedWsdlName(str)

## Get property accessor parameters
#
# @return accessor parameters
def GetAccessorParams():
   """ Get property accessor parameters """

   params = tuple([VmomiSupport.Object(name="prop",
                                       type=str,
                                       version=VmomiSupport.BASE_VERSION,
                                       flags=0)])
   return params


## Get property accessor method
#
# @param  mo managed object
# @param  prop accessor property name
# @return accessor method
def GetAccessorMethod(mo, prop):
   """ Get property accessor method """

   # Technically objType should be the parent type with the prop. But don't
   # need the type yet. We are ok
   objType = type(mo)
   propInfo = VmomiSupport.GetPropertyInfo(objType, prop)
   info = VmomiSupport.Object(name=_STR_FETCH,
                              type=objType,
                              wsdlName=_STR_FETCH,
                              version=propInfo.version,
                              params=GetAccessorParams(),
                              isTask=False,
                              resultFlags=propInfo.flags,
                              result=propInfo.type,
                              methodResult=propInfo.type)
   return VmomiSupport.ManagedMethod(info)


## Get message from exception
#
# @param err the exception
# @return the error message
def ExceptionMsg(err):
   """ Get exception message """

   try:
      if err.msg is not None:
         msg = err.msg
      else:
         msg = ""
   except AttributeError:
      if len(err.args) > 0:
         msg = err.args[0]
      else:
         msg = err
   return msg


## Split xml tag with NS_SEP
#
def SplitTag(tag):
   """ Split tag into ns, name """
   tags = tag.split(NS_SEP, 1)
   if len(tags) > 1:
      return tags[0], tags[1]
   else:
      return "", tags[0]

## Authorizes method calls and property accesses.
#
# Subclasses should implement the validateMethodCall and validatePropertyAccess
# to check that the current user session has sufficient access privileges.
#
# @see SoapHandler.RegisterValidator
class Validator:
   ## Validate a method call.  An exception should be raised if the current
   # session not have sufficient privileges.
   #
   # @param soapContext The SOAP context from VmomiSupport.
   # @param methodInfo The information about the method.
   # @param mo The managed object that the method is being invoked on.
   # @param params The parameters to the method.
   def validateMethodCall(self, soapContext, methodInfo, mo, params):
      return

   ## Validate access to a property.  An exception should be raised if the
   # current session does not have sufficient privileges.
   #
   # @param soapContext The SOAP context from VmomiSupport.
   # @param propInfo The information about the property.
   # @param mo The managed object that the property belongs to.
   def validatePropertyAccess(self, soapContext, propInfo, mo):
      return

## Soap server stub class
#
class SoapServerStubAdapter:
   """ Soap server stub adapter """

   ## Constructor
   #
   # @param  version the server version
   # @param  moMgr the managed object manager
   def __init__(self, version, moMgr):
      """ SoapServerStubAdapter constructor """
      # .version is required (used by SoapDeserializer)
      self.version = version
      self._moMgr = moMgr
      self.validatorList = []

   ## Invoke a managed object method
   #
   # @param  mo this
   # @param  info method info
   # @param  args method arguments
   # @return method returned object
   def InvokeMethod(self, mo, info, args):
      """ Call method """

      # Lookup managed object
      obj = self._LookupObject(mo, info)

      # Get method from object
      method = getattr(obj, info.name)

      # Get arguments name from method info
      vmodlArgs = [param.name for param in info.params]

      # Validate method signature
      # 1. Method implementation argument list must match vmodl arguments list,
      #    in exact same order
      # 2. Method implementation can have 'self' as first argument. Validation
      #    starts after 'self'. Note: no 'self' for static class method
      # 3. Method implementation are allowed to have additional variable
      #    arguments following vmodl argument list
      #
      # TODO: Only do this in debug mode
      validateMethodSignature = getattr(obj, '_validateMethodSignature', True)
      numParams = len(info.params)
      if validateMethodSignature and numParams > 0:
         # Get method arguments
         methodArgs = method.__code__.co_varnames[:method.__code__.co_argcount]

         # See 2 above
         methodIdx = 0
         if methodArgs[0] == 'self':
            methodIdx = 1

         # See 1 & 3 above
         if vmodlArgs != list(methodArgs[methodIdx:(methodIdx + numParams)]):
            msg = "Method signature mismatch"
            reason = "Vmodl definition is %s while method %s is defined as %s" \
                     % (vmodlArgs, method.__name__, methodArgs[methodIdx:])
            logging.error("%s: %s" % (msg, reason))
            raise Vmodl.Fault.SystemError(msg=msg, reason=reason)
         del methodArgs

      # Call method
      params = dict(list(zip(vmodlArgs, args)))
      context = VmomiSupport.GetRequestContext()
      for validator in self.validatorList:
         validator.validateMethodCall(context, info, mo, params)
      response = method(**params)
      del params
      del obj

      return response

   ## Invoke a managed object accessor
   #
   # @param  mo this
   # @param  info method info
   # @return property value
   def InvokeAccessor(self, mo, info):
      """ Get property """

      obj = self._LookupObject(mo, info)
      context = VmomiSupport.GetRequestContext()
      for validator in self.validatorList:
         validator.validatePropertyAccess(context, info, mo)
      val = getattr(obj, info.name)
      return val

   ## Lookup a managed object
   #
   # @param  mo this
   # @param  info method info
   # @return managed object
   def _LookupObject(self, mo, info):
      """ Lookup managed object from object id """

      # Check version
      if not VmomiSupport.IsChildVersion(self.version, info.version):
         logging.error(self.version + " not child version of " + info.version)
         raise Vmodl.Fault.MethodNotFound(receiver=mo, method=info.name)

      try:
         # Lookup objects
         serverGuid = getattr(mo, "_serverGuid", None)
         obj = self._moMgr.LookupObject(mo._moId, serverGuid)
      except Exception:
         message = "Failed to find (" + mo._moId + ")" + \
                   " serverGuid (" + str(serverGuid) + ")"
         logging.error(message)
         raise Vmodl.Fault.ManagedObjectNotFound(msg=message, obj=mo)

      # TODO: Verify object type
      return obj


## Soap msg class (hold info for deserialized soap msg)
#
class SoapMsg:
   """ Soap message class """

   def __init__(self):
      """ Constructor """
      pass

## Soap response msg class
#
class SoapMsgResponse(SoapMsg):
   """ Soap response message class """

   ## Constructor
   #
   # @param  version the message version
   # @param  method the managed method
   # @param  retVal the return value
   def __init__(self, version, method, retVal):
      """ Constructor """
      SoapMsg.__init__(self)
      self.version = version
      self.method = method
      self.retVal = retVal

## Soap fault msg class
#
class SoapMsgFault(SoapMsg):
   """ Soap fault message class """

   def __init__(self):
      """ Constructor """
      SoapMsg.__init__(self)

## Soap request msg class
#
class SoapMsgRequest(SoapMsg):
   """ Soap request message class """

   ## Constructor
   #
   # @param  version the message version
   # @param  isTask true for task, false otherwise
   # @param  mo the managed object
   # @param  method the managed method
   # @param  params the managed method parameters
   def __init__(self, version, isTask, mo, method, params):
      """ Constructor """
      SoapMsg.__init__(self)
      self.version = version
      self.isTask = isTask
      self.mo = mo
      self.method = method
      self.params = params

## Soap accessor msg class
#
class SoapMsgAccessor(SoapMsgRequest):
   """ Soap accessor message class """

   ## Constructor
   #
   # @param  version the message version
   # @param  mo the managed object
   # @param  method the managed method
   # @param  prop the managed property name
   def __init__(self, version, mo, method, prop):
      """ Constructor """
      SoapMsgRequest.__init__(self, version, False, mo, method, prop)

## class to deserialize managed method paramters
#
class SoapMethodParamsDeserializer(SoapAdapter.ExpatDeserializerNSHandlers):
   """ Soap method parameters serializer """

   ## Constructor
   #
   # @param  helper the XMLExpatHelper
   # @param  version the server mo version
   # @param  params the method params [list of obj with attr name and type]
   # @param  nsMap a dict of ns prefix -> [xml ns stack]
   def __init__(self, helper, version, params, nsMap):
      SoapAdapter.ExpatDeserializerNSHandlers.__init__(self, nsMap)
      self.helper = helper
      self.params = params
      self.version = version
      self.soapDeserializer = None
      self.data = ""
      self.objType = None
      self.serverGuid = None
      self.allResults = {}
      self.element = 0
      self.thisTag = VmomiSupport.GetWsdlNamespace(version) + NS_SEP + _STR_THIS

   ## Get accumulated results
   #
   # @return soap parameter result. A dict of tag str mapped to parameter obj
   def GetResult(self):
      """ Get accumulated results """
      # Set default val for optional array
      if self.params:
         for param in self.params:
            if param.flags & VmomiSupport.F_OPTIONAL \
            and not param.name in self.allResults \
            and issubclass(param.type, list):
               self.allResults[param.name] = param.type()
      return self.allResults

   ## Handle an opening XML tag
   #
   # @param  tag the XML tag
   # @param  attr the XML attribute
   def StartElementHandler(self, tag, attr):
      """ Start XML element """

      if not self.soapDeserializer:
         self.data = ""
         if tag == self.thisTag or tag == _STR_THIS:
            try:
               typeAttr = attr[u'type']
               ns, name = self.GetNSAndWsdlname(typeAttr)
               self.objType = VmomiSupport.GetWsdlType(ns, name)
            except KeyError:
               try:
                  # Do a second attempt at finding the type.
                  # XXX: We need a cleaner fix for this.
                  # Problem description: If vpxd3 is a child namespace of vim25,
                  #    if we pass vpxd3 as a default namespace, resolution fails for objects
                  #    in vim25 namespace even though vpxd3 is a child namespace of
                  #    vim25. This fix is a temporary one to deal with that failure.
                  typeAttr = attr[u'type']
                  self.objType = VmomiSupport.GuessWsdlType(typeAttr)
               except KeyError:
                  message = "Unknown type (" + attr.get(u"type") + ")"
                  logging.error(message)
                  raise Vmodl.Fault.InvalidRequest(msg=message)
            self.serverGuid = attr.get(u"serverGuid")
         elif self.params:
            # Check for known function parameters
            _, name = SplitTag(tag)
            for param in self.params:
               if param.name == name:
                  # Deserialize if parameter exists in this version
                  parser = self.helper.GetParser()

                  # Save hijacked handlers as soapDeserializer overrides them
                  origHandlers = SoapAdapter.GetHandlers(parser)

                  # Setup Soap deserializer
                  self.soapDeserializer = SoapAdapter.SoapDeserializer(
                                                          version=self.version)
                  self.soapDeserializer.Deserialize(parser, param.type,
                                                    False, # isFault
                                                    self.nsMap)

                  # Restore original handlers
                  SoapAdapter.SetHandlers(parser, origHandlers)

                  # Call soap deserializer for current tag
                  self.soapDeserializer.StartElementHandler(tag, attr)
                  self.element = 1
                  break

            # Unknown paramters
      else:
         self.element += 1
         self.soapDeserializer.StartElementHandler(tag, attr)

   ## Handle text data
   #
   # @param  data the XML node data
   def CharacterDataHandler(self, data):
      """ Character handler """

      if not self.soapDeserializer:
         self.data += data
      else:
         self.soapDeserializer.CharacterDataHandler(data)

   ## Handle a closing XML tag
   #
   # @param  tag the XML tag
   def EndElementHandler(self, tag):
      """ End XML element """

      if not self.soapDeserializer:
         if tag == self.thisTag or tag == _STR_THIS:
            # Construct this object from object type
            self._SaveResult(_STR_THIS, self.objType(self.data,
                                                    serverGuid=self.serverGuid))
         else:
            # Unknown paramters
            pass
      else:
         self.soapDeserializer.EndElementHandler(tag)
         self.element -= 1
         if self.element == 0:
            _, name = SplitTag(tag)
            self._SaveResult(name, self.soapDeserializer.GetResult())
            self.soapDeserializer = None

   ## Get/Set expat handlers fn for this class
   #  Callback required by XMLExpatHelper
   #
   # @param  dst Expat parser to copy handlers to
   # @param  src Expat parser to copy handlers from
   # @return an object with overwritten handlers
   @staticmethod
   def ExpatHandlers(dst, src):
      ret = VmomiSupport.Object(StartElementHandler=dst.StartElementHandler,
                                EndElementHandler=dst.EndElementHandler,
                                CharacterDataHandler=dst.CharacterDataHandler,
                                StartNamespaceDeclHandler=dst.StartNamespaceDeclHandler,
                                EndNamespaceDeclHandler=dst.EndNamespaceDeclHandler)
      dst.StartElementHandler = src.StartElementHandler
      dst.EndElementHandler = src.EndElementHandler
      dst.CharacterDataHandler = src.CharacterDataHandler
      dst.StartNamespaceDeclHandler = src.StartNamespaceDeclHandler
      dst.EndNamespaceDeclHandler = src.EndNamespaceDeclHandler
      return ret

   ## Save result obj associated with tag
   #
   # @param  tag the XML tag (without ns info)
   # @param  obj the obj associated with the XML tag
   def _SaveResult(self, tag, obj):
      """ Save result object """
      assert(tag.find(NS_SEP) == -1)
      # Note: tag could be in unicode. Always convert it to ascii
      tag = str(tag)
      if tag in self.allResults:
         origObj = self.allResults[tag]
         if not isinstance(origObj, list) or not isinstance(obj, list):
            message = "Duplicated tag " + tag
            logging.error(message)
            raise Vmodl.Fault.InvalidRequest(msg=message)
         origObj.extend(obj)
      else:
         self.allResults[tag] = obj


## class to deserialize soap header
#
class SoapHeaderDeserializer(SoapAdapter.ExpatDeserializerNSHandlers):
   """ Soap header serializer """

   ## Constructor
   #
   # @param  helper the XMLExpatHelper
   # @param  nsMap a dict of ns prefix -> [xml ns stack]
   def __init__(self, helper, nsMap):
      """ Constructor """
      SoapAdapter.ExpatDeserializerNSHandlers.__init__(self, nsMap)
      self.helper = helper
      self.level = 0
      self.value = ''
      self.result = {}

   ## Get result string
   #
   # @return soap body result str
   def GetResult(self):
      """ Get result (request context dict) """
      return self.result

   ## Handle an opening XML tag
   #
   # @param  tag the XML tag
   # @param  attr the XML attribute
   def StartElementHandler(self, tag, attr):
      """ Start XML element """
      if self.level == 0:
         self.tag = tag
         self.mustUnderstand = attr.get(_STR_SOAPENV_MUST_UNDERSTAND)
         self.xsiType = attr.get(SoapAdapter.XSI_TYPE)
         self.complex = False
      else:
         self.complex = True
      self.level += 1

   ## Handle text data
   #
   # @param  data the XML node data
   def CharacterDataHandler(self, data):
      """ Character handler """
      if self.level == 1:
         self.value = data

   ## Handle a closing XML tag
   #
   # @param  tag the XML tag
   def EndElementHandler(self, tag):
      """ End XML element """
      self.level -= 1
      if self.level == 0:
         assert(self.tag == tag)
         # VMOMI request context (currently) only supports values of type string,
         # so if the SOAP header element was complex, or didn't have type string
         # we ignore it.  If no xsi:type was specified and it wasn't complex, we
         # just assume it was a string.  If we want to ignore an element, but it
         # had a mustUnderstand attribute with value "1", we raise an exception
         # instead.
         if not self.complex and (not self.xsiType
                                  or self.xsiType == 'string' # workaround bug 553406
                                  or self.GetNSAndWsdlname(self.xsiType) == _XSD_STRING
                                  ):
            _, name = SplitTag(tag)
            self.result[name] = self.value
         elif self.mustUnderstand == '1':
            fmt = 'Unsupported type for SOAP header %s, but mustUnderstand is specified'
            raise Exception(fmt % tag)
         self.tag = None

   ## Get/Set expat handlers fn for this class
   #  Callback required by XMLExpatHelper
   #
   # @param  dst Expat parser to copy handlers to
   # @param  src Expat parser to copy handlers from
   # @return an object with overwritten handlers
   @staticmethod
   def ExpatHandlers(dst, src):
      ret = VmomiSupport.Object(StartElementHandler=dst.StartElementHandler,
                                EndElementHandler=dst.EndElementHandler,
                                CharacterDataHandler=dst.CharacterDataHandler,
                                StartNamespaceDeclHandler=dst.StartNamespaceDeclHandler,
                                EndNamespaceDeclHandler=dst.EndNamespaceDeclHandler)
      dst.StartElementHandler = src.StartElementHandler
      dst.EndElementHandler = src.EndElementHandler
      dst.CharacterDataHandler = src.CharacterDataHandler
      dst.StartNamespaceDeclHandler = src.StartNamespaceDeclHandler
      dst.EndNamespaceDeclHandler = src.EndNamespaceDeclHandler
      return ret


## class to deserialize managed method
#
class SoapBodyDeserializer(SoapAdapter.ExpatDeserializerNSHandlers):
   """ Soap body serializer """

   ## Constructor
   #
   # @param  helper the XMLExpatHelper
   # @param  nsMap a dict of ns prefix -> [xml ns stack]
   def __init__(self, helper, nsMap):
      """ Constructor """
      SoapAdapter.ExpatDeserializerNSHandlers.__init__(self, nsMap)
      self.helper = helper
      self.data = ""
      self.method = None
      if self.helper.soapVersion:
         version = self.helper.soapVersion
      else:
         version = None
      self.SetVersion(version)
      self.soapMethodParamsDeserializer = None
      self.result = None
      self.isTask = False

   ## Get result string
   #
   # @return soap method result str
   def GetResult(self):
      """ Get result string """
      return self.result

   ## version setter
   #
   def SetVersion(self, version):
      self.version = version
      if version:
         ns = VmomiSupport.GetWsdlNamespace(version)
         self.fetchTag = ns + NS_SEP + _STR_FETCH

   ## Handle an opening XML tag
   #
   # @param  tag the XML tag
   # @param  attr the XML attribute
   def StartElementHandler(self, tag, attr):
      """ Start XML element """

      # Check method name
      if tag == _STR_FAULT:
         pass
      else:
         # Get version from default namespace if version is not known
         if not self.version:
            try:
               ns = self.GetCurrDefNS()
               self.SetVersion(ns)
            except KeyError:
               message = "Unknown namespace " + ns
               logging.error(message)
               raise Vmodl.Fault.InvalidRequest(msg=message)

         # Deserialize requests and responses
         try:
            if tag == self.fetchTag or tag == _STR_FETCH:
               params = GetAccessorParams()
            else:
               isResponse = False
               if tag.endswith("Response"):
                  name = tag[:-8]
                  isResponse = True
               elif tag.endswith("_Task"):
                  name = tag
                  self.isTask = True
               else:
                  name = tag
               ns, name = SplitTag(name)
               if not ns:
                  ns = self.GetCurrDefNS()
               try:
                  self.method = VmomiSupport.GetWsdlMethod(ns, name)
               except KeyError:
                  # Do a second attempt at finding the methodName.
                  # XXX: We need a cleaner fix for this.
                  # Problem description: If vpxd3 is a child namespace of vim25,
                  #    if we pass vpxd3 as a default namespace, resolution fails for methods
                  #    in vim25 namespace even though vpxd3 is a child namespace of
                  #    vim25. This fix is a temporary one to deal with that failure.
                  self.method = VmomiSupport.GuessWsdlMethod(name)
               if not isResponse:
                  params = self.method.info.params
               else:
                  # TODO: Make info.result same as param object
                  params = [VmomiSupport.Object(name=_STR_RETURNVAL,
                                                type=self.method.info.result,
                                                version=self.version,
                                                flags=0)]
         except Exception:
            logging.error("Unknown method " + tag)
            params = None
            # We cannot raise MethodNotFound here, as we don't know _this yet!
            # raise Vmodl.Fault.MethodNotFound(receiver=None, method=tag)

         # Start parameters parsing
         self.soapMethodParamsDeserializer = \
                 SoapMethodParamsDeserializer(self.helper, self.version,
                                              params, self.nsMap)
         self.helper.SubHandler(self.soapMethodParamsDeserializer)

   ## Handle a closing XML tag
   #
   # @param  tag the XML tag
   def EndElementHandler(self, tag):
      """ End XML element """

      if tag == _STR_FAULT:
         pass
      elif tag.endswith("Response"):
         result = self.soapMethodParamsDeserializer.GetResult()
         try:
            retVal = result[_STR_RETURNVAL]
         except KeyError:
            message = "Missing return value"
            logging.error(message)
            raise Vmodl.Fault.InvalidType(msg=message)

         self.result = SoapMsgResponse(self.version,
                                       self.method, retVal)
      else:
         # Assign to a callable method class
         _, name = SplitTag(tag)
         paramDict = self.soapMethodParamsDeserializer.GetResult()
         mo = paramDict.pop(_STR_THIS, None)
         if not mo:
            message = "Method (" + name + ") missing parameter: _this"
            logging.error(message)
            raise Vmodl.Fault.InvalidRequest(msg=message)

         # Fill in missing method info
         if tag == self.fetchTag or tag == _STR_FETCH:
            prop = ""
            try:
               prop = paramDict[_STR_PROP]
               # Fill in missing accessor info
               accessor = GetAccessorMethod(mo, prop)
            except Exception:
               message = "Property not found: " + name + " (" + prop + ")"
               logging.error(message)
               raise Vmodl.Fault.MethodNotFound(msg=message,
                                                receiver=mo, method=name)

            self.result = SoapMsgAccessor(self.version, mo, accessor, prop)
         else:
            if not self.method:
               message = "Method not found: " + name
               logging.error(message)
               raise Vmodl.Fault.MethodNotFound(msg=message,
                                                receiver=mo, method=name)

            self.result = SoapMsgRequest(self.version, self.isTask,
                                         mo, self.method, paramDict)

   ## Get/Set expat handlers fn for this class
   #  Callback required by XMLExpatHelper
   #
   # @param  dst Expat parser to copy handlers to
   # @param  src Expat parser to copy handlers from
   # @return an object with overwritten handlers
   @staticmethod
   def ExpatHandlers(dst, src):
      ret = VmomiSupport.Object(StartElementHandler=dst.StartElementHandler,
                                EndElementHandler=dst.EndElementHandler,
                                StartNamespaceDeclHandler=dst.StartNamespaceDeclHandler,
                                EndNamespaceDeclHandler=dst.EndNamespaceDeclHandler)
      dst.StartElementHandler = src.StartElementHandler
      dst.EndElementHandler = src.EndElementHandler
      dst.StartNamespaceDeclHandler = src.StartNamespaceDeclHandler
      dst.EndNamespaceDeclHandler = src.EndNamespaceDeclHandler
      return ret


## class to deserialize soap header or body
#
class SoapHeaderBodyDeserializer(SoapAdapter.ExpatDeserializerNSHandlers):
   """ Soap header and body serializer """

   ## Constructor
   #
   # @param  helper the XMLExpatHelper
   # @param  nsMap a dict of ns prefix -> [xml ns stack]
   def __init__(self, helper, nsMap):
      """ Constructor """
      SoapAdapter.ExpatDeserializerNSHandlers.__init__(self, nsMap)
      self.helper = helper
      self.soapHeaderDeserializer = None
      self.soapBodyDeserializer = None

   ## Get result string
   #
   # @return soap body result str
   def GetResult(self):
      """ Get result string """
      if self.soapBodyDeserializer:
         result = self.soapBodyDeserializer.GetResult()
         if self.soapHeaderDeserializer:
            result.context = self.soapHeaderDeserializer.GetResult()
         else:
            result.context = {}
      else:
         result = None
      return result

   ## Handle an opening XML tag
   #
   # @param  tag the XML tag
   # @param  attr the XML attribute
   def StartElementHandler(self, tag, attr):
      """ Start XML element """

      if tag == _STR_HEADER:
         if self.soapBodyDeserializer is not None:
            message = "Unexpected Header tag following Body tag"
            logging.error(message)
            raise Vmodl.Fault.InvalidRequest(msg=message)
         if self.soapHeaderDeserializer is not None:
            message = "Duplicated tag " + tag
            logging.error(message)
            raise Vmodl.Fault.InvalidRequest(msg=message)
         self.soapHeaderDeserializer = \
            self.helper.SubHandler(SoapHeaderDeserializer(self.helper,
                                                          self.nsMap))
      elif tag == _STR_BODY:
         if self.soapBodyDeserializer is not None:
            message = "Duplicated tag " + tag
            logging.error(message)
            raise Vmodl.Fault.InvalidRequest(msg=message)
         self.soapBodyDeserializer = \
            self.helper.SubHandler(SoapBodyDeserializer(self.helper,
                                                        self.nsMap))
      else:
         # Disallow unknown tag?
         # raise Vmodl.Fault.InvalidRequest(msg="Unexpected tag " + tag)
         pass

   ## Handle a closing XML tag
   #
   # @param  tag the XML tag
   def EndElementHandler(self, tag):
      """ End XML element """

      if tag == _STR_HEADER or tag == _STR_BODY:
         pass
      else:
         # Disallow unknown tag?
         # raise Vmodl.Fault.InvalidRequest(msg="Unexpected tag " + tag)
         pass

   ## Get/Set expat handlers fn for this class
   #  Callback required by XMLExpatHelper
   #
   # @param  dst Expat parser to copy handlers to
   # @param  src Expat parser to copy handlers from
   # @return an object with overwritten handlers
   @staticmethod
   def ExpatHandlers(dst, src):
      ret = VmomiSupport.Object(StartElementHandler=dst.StartElementHandler,
                                EndElementHandler=dst.EndElementHandler,
                                StartNamespaceDeclHandler=dst.StartNamespaceDeclHandler,
                                EndNamespaceDeclHandler=dst.EndNamespaceDeclHandler)
      dst.StartElementHandler = src.StartElementHandler
      dst.EndElementHandler = src.EndElementHandler
      dst.StartNamespaceDeclHandler = src.StartNamespaceDeclHandler
      dst.EndNamespaceDeclHandler = src.EndNamespaceDeclHandler
      return ret


## class to deserialize soap envelope
#
class SoapEnvelopeDeserializer(SoapAdapter.ExpatDeserializerNSHandlers):
   """ Soap envelope serializer """

   ## Constructor
   #
   def __init__(self):
      """ Constructor """
      SoapAdapter.ExpatDeserializerNSHandlers.__init__(self)
      self.parser = None
      self.helper = None
      self.soapHeaderBodyDeserializer = None

   ## Reset this obj back to clean state
   #
   # @param version the SOAP message vmomi version
   def Reset(self, version=None):
      """ Reset this obj back to clean state """
      # We cannot use the same xml parser to parse multiple documents.
      # Have to reset
      del self.parser
      self.parser = xml.parsers.expat.ParserCreate(namespace_separator=NS_SEP)
      try: # buffer_text only in python >= 2.3
         self.parser.buffer_text = True
      except AttributeError:
         pass
      del self.helper
      self.helper = XMLExpatHelper(self.parser)
      self.helper.SubHandler(self)

      # Put version into helper
      assert(not hasattr(self.helper, "soapVersion"))
      setattr(self.helper, "soapVersion", version)

      # Reset deserializer
      self.soapHeaderBodyDeserializer = None

   ## Get result string
   #
   # @return soap envelope result str
   def GetResult(self):
      """ Get result string """
      if self.soapHeaderBodyDeserializer:
         result = self.soapHeaderBodyDeserializer.GetResult()
      else:
         result = None
      return result

   ## Parse SOAP envelope
   #
   # @param envelope the envelope to parse
   # @param version the SOAP message vmomi version
   def Parse(self, envelope, version=None):
      """ Parse SOAP envelope """
      self.Reset(version)
      # Many existing tests pass in str directly in python2 for testing purpose.
      # But in python3 the input become unicode and the handling will fall into ParseFile case.
      # Adding unicode input support to make it more test friendly.
      if isinstance(envelope, six.binary_type) or isinstance(envelope, six.text_type):
         self.parser.Parse(envelope)
      else:
         self.parser.ParseFile(envelope)

   ## Handle an opening XML tag
   #
   # @param  tag the XML tag
   # @param  attr the XML attribute
   def StartElementHandler(self, tag, attr):
      """ Start XML element """

      if tag == _STR_ENVELOPE:
         if self.soapHeaderBodyDeserializer is None:
            self.soapHeaderBodyDeserializer = \
               self.helper.SubHandler(SoapHeaderBodyDeserializer(self.helper,
                                                                 self.nsMap))
         else:
            message = "Duplicated tag " + tag
            logging.error(message)
            raise Vmodl.Fault.InvalidRequest(msg=message)
      else:
         # Disallow unknown tag?
         # raise Vmodl.Fault.InvalidRequest(msg="Unexpected tag " + tag)
         pass

   ## Handle a closing XML tag
   #
   # @param  tag the XML tag
   def EndElementHandler(self, tag):
      """ End XML element """

      if tag == _STR_ENVELOPE:
         pass
      else:
         # Disallow unknown tag?
         # raise Vmodl.Fault.InvalidRequest(msg="Unexpected tag " + tag)
         pass

   ## Get/Set expat handlers fn for this class
   #  Callback required by XMLExpatHelper
   #
   # @param  dst Expat parser to copy handlers to
   # @param  src Expat parser to copy handlers from
   # @return an object with overwritten handlers
   @staticmethod
   def ExpatHandlers(dst, src):
      """ Get/Set expat handlers fn for this class """
      ret = VmomiSupport.Object(StartElementHandler=dst.StartElementHandler,
                                EndElementHandler=dst.EndElementHandler,
                                StartNamespaceDeclHandler=dst.StartNamespaceDeclHandler,
                                EndNamespaceDeclHandler=dst.EndNamespaceDeclHandler)
      dst.StartElementHandler = src.StartElementHandler
      dst.EndElementHandler = src.EndElementHandler
      dst.StartNamespaceDeclHandler = src.StartNamespaceDeclHandler
      dst.EndNamespaceDeclHandler = src.EndNamespaceDeclHandler
      return ret


## Soap serializer class
#
class SoapSerializer:
   """ Soap serializer """

   _SOAP_BEGIN = _STR_XML_HEADER + _STR_ENVELOPE_START + _STR_BODY_START
   _SOAP_END = _STR_BODY_END + _STR_ENVELOPE_END

   _anyType = VmomiSupport.GetVmodlType("anyType")

   ## Constructor
   #
   def __init__(self, encoding=_STR_XML_ENCODING):
      """ Constructor """
      self.encoding = encoding

   ## Serialize SOAP response
   #
   # @param  version the response version (client version)
   # @param  method the managed object method
   # @param  value the response object to serialize
   # @return serialized soap response msg
   def SerializeResponse(self, version, method, value):
      """ Serialize SOAP response """
      ns = VmomiSupport.GetWsdlNamespace(version)
      responseTag = method.info.wsdlName + "Response"
      resultType = method.info.result
      returnval = ""
      if resultType is VmomiSupport.NoneType:
         # void return value
         if value:
            # TODO: throw invalid return value?
            pass
      else:
         if value != None:
            # Fetch result are serialized as any since ESX 3.5
            if method.info.name == _STR_FETCH:
               resultType = self._anyType
            info = VmomiSupport.Object(name=_STR_RETURNVAL,
                                       type=resultType,
                                       version=version,
                                       flags=method.info.resultFlags)
            nsMap = SoapAdapter.SOAP_NSMAP.copy()
            # Set default ns
            nsMap[ns] = ''
            returnval = SoapAdapter.SerializeToUnicode(value, info, version, nsMap)
         else:
            # Throw if return value is not optional
            if not method.info.resultFlags & VmomiSupport.F_OPTIONAL:
               raise Vmodl.RuntimeFault(msg="Missing return value")

      result = "".join([self._SOAP_BEGIN,
                         "<", responseTag, " xmlns='", ns, "'>",
                         returnval,
                         "</", responseTag, ">",
                        self._SOAP_END])
      return result

   ## Serialize SOAP header fault (fault not related to SOAP body processing)
   #  Should not contain detail tag
   #
   # @param  faultCode the fault code
   # @param  message the fault message
   def SerializeHeaderFault(self, faultCode, message):
      """ Serialize SOAP header fault """
      escapedMsg = message and SoapAdapter.XmlEscape(message) or ""
      result = "".join([self._SOAP_BEGIN,
                         _STR_FAULT_START,
                          "<faultcode>", faultCode, "</faultcode>",
                          "<faultstring>", escapedMsg, "</faultstring>",
                         _STR_FAULT_END,
                        self._SOAP_END])
      return result

   ## Serialize SOAP fault
   #
   # @param  faultCode the fault code
   # @param  fault the fault object
   # @param  version the response version (client version)
   # @return serialized soap fault msg
   def SerializeFault(self, faultCode, fault, version):
      """ Serialize SOAP fault """
      if not version:
         version = fault._version
      info = VmomiSupport.Object(name=fault._wsdlName + "Fault",
                                 type=VmomiSupport.Type(fault),
                                 version=version, flags=0)
      message = fault.msg
      fault.msg = None
      escapedMsg = message and SoapAdapter.XmlEscape(message) or ""
      nsMap = SoapAdapter.SOAP_NSMAP.copy()
      faultDetail = SoapAdapter.SerializeFaultDetail(fault, info, version, nsMap,
                                                     encoding=self.encoding)
      result = "".join([self._SOAP_BEGIN,
                         _STR_FAULT_START,
                          "<faultcode>", faultCode, "</faultcode>",
                          "<faultstring>", escapedMsg, "</faultstring>",
                          "<detail>", faultDetail, "</detail>",
                         _STR_FAULT_END,
                        self._SOAP_END])
      return result

   ## Serialize server SOAP fault
   #
   # @param  fault the fault object
   # @param  version the response version (client version)
   # @return serialized soap fault msg
   def SerializeServerFault(self, fault, version=None):
      """ Serialize server SOAP fault """
      return self.SerializeFault("ServerFaultCode", fault, version)


## class to deserialize soap message
#
class SoapHandler:
   """ Soap handler """

   # Create server stubs
   _moMgr = MoManager.GetMoManager()
   _moStubs = {}
   for version in VmomiSupport.nsMap:
      stub = SoapServerStubAdapter(version, _moMgr)
      _moStubs[version] = [(_moMgr, stub)]

   @classmethod
   def RegisterValidator(cls, validator):
      for stubList in list(cls._moStubs.values()):
         for _mgr, stub in stubList:
            stub.validatorList.append(validator)

   # Create soap fault
   _soapSystemError = SoapSerializer().SerializeServerFault(
                                 Vmodl.Fault.SystemError(msg="System Error",
                                                         reason="System Error"))

   ## Default (dummy) managed objects manager
   #  Overrides base to make LookupObject always return a managed object
   #
   class DefaultManagedObjectsManager(MoManager.ManagedObjectsManager):
      """ Default managed objects manager """

      def __init__(self, stub):
         """ Constructor """
         MoManager.ManagedObjectsManager.__init__(self)
         self.stub = stub

      def LookupObject(self, moId, serverGuid=None):
         """ Return a fake object """
         return VmomiSupport.ManagedObject(moId, self.stub, serverGuid)

   ## Constructor
   #
   # @param  stubs a dict of {version: (moMgr, stub)}
   #       - moMgr must implement LookupObject (to lookup a moid)
   #       - stub will be used for calling method if LookupObject succeed
   #       - If the version name is 'default', the corresponding stubs will be
   #         used for all known versions
   #       - If moMgr is None, a default mo manager will be created, and
   #         simply return a valid object for all moId
   def __init__(self, stubs=None):
      """
      Constructor
      """
      if stubs:
         # Default stub
         defaultMoMgr, defaultStub = stubs.get('default')
         if not defaultMoMgr:
            defaultMoMgr = self.DefaultManagedObjectsManager(defaultStub)
         for version in VmomiSupport.nsMap:
            moMgr, stub = stubs.get(version, (defaultMoMgr, defaultStub))
            if stub:
               if not moMgr:
                  moMgr = self.DefaultManagedObjectsManager(stub)
               self._moStubs[version].append( (moMgr, stub) )
      elif len(VmomiSupport.nsMap) != len(self._moStubs):
         # The _moStubs array is created by walking nsMap when this module is loaded.
         # If new entries have been added to nsMap (e.g. by the DTM) since then, we
         # need to rebuild _moStubs.
         for version in VmomiSupport.nsMap:
            stub = SoapServerStubAdapter(version, self._moMgr)
            self._moStubs[version] = [(self._moMgr, stub)]
      self._soapSerializer = SoapSerializer()

   ## Handle a soap request
   #
   # @param request a soap request (str or file)
   # @param nsAndVersion the request version (namespace/versionId)
   # @return tuple(isFault, soap response str)
   def HandleRequest(self, request, nsAndVersion=None):
      """
      Handle a soap request
      """

      try:
         version = None
         if nsAndVersion:
            version = self._GetHeaderVersion(nsAndVersion)
            if not version:
               # namespace not found. Not able to handle it at all
               message = "Unsupported version URI urn:" + nsAndVersion
               logging.error(message)
               isFault = True
               return isFault, self._soapSerializer.SerializeHeaderFault(
                                                   "ClientFaultCode", message)

         return self._HandleRequest(request, version)
      except Exception as err:
         message = ExceptionMsg(err)
         if message:
            logging.error(message)
         stackTrace = traceback.format_exc()
         if stackTrace:
            logging.error(stackTrace)

         # Return something if SerializeServerFault throw exception
         isFault = True
         return isFault, self._soapSystemError

   ## Get vmomi version from request version (namespace/versionId)
   #
   # @param nsAndVersion the request version (namespace/versionId)
   # @return vmomi version
   def _GetHeaderVersion(self, nsAndVersion):
      """
      Get vmomi version from request version (namespace/versionId)
      """

      # Get vmomi version from incoming namespace/versionId
      reqVersion = VmomiSupport.versionMap.get(nsAndVersion)
      if not reqVersion:
         # namespace/versionId not found. Try namespace alone
         reqNS = nsAndVersion.split('/')[0]
         reqVersion = VmomiSupport.versionMap.get(reqNS)
         if reqVersion:
            # Get the latest version within that namespace
            # TODO: This is not right to walk down the tree. Need some
            #       other way to get to the current version
            for version, ns in six.iteritems(VmomiSupport.nsMap):
               if reqNS == ns and VmomiSupport.IsChildVersion(version,
                                                              reqVersion):
                  reqVersion = version
      return reqVersion

   ## Get managed object stub
   #
   # @param version the request version
   # @param mo managed object
   # @return stub
   def _GetMoStub(self, version, mo):
      """
      Get stub for this version of managed object
      """
      moStub = None
      moMgrStubs = self._moStubs.get(version)
      for moMgr, stub in moMgrStubs:
         try:
            # Throws key error if not found
            moMgr.LookupObject(mo._moId, mo._serverGuid)
            moStub = stub
            break
         except KeyError:
            try:
               # Try looking up a MoFactory
               logging.info("Looking up mo factory for %s" % mo.__class__._wsdlName)
               factory = moMgr.LookupMoFactory(mo.__class__)
               factory.CreateInstance(mo._moId, mo._serverGuid)
               # If successful, set the stub
               moStub = stub
               break
            except KeyError:
               logging.error("Failed to find object using mofactory")
               pass

      return moStub

   ## Handle a soap request, internal
   #
   # @param request a soap request (str or file)
   # @param version the request vmomi version
   # @return tuple(isFault, soap response str)
   # @throw Vmodl.MethodFault (and its subclass)
   def _HandleRequest(self, request, version=None):
      """
      Handle a soap request, internal
      """

      try:
         msg = self._DeserializeMessage(request, version)
         return self._InvokeMethod(msg)
      except Exception as err:
         message = ExceptionMsg(err)
         if message:
            logging.error(message)
         stackTrace = traceback.format_exc()
         if stackTrace:
            logging.error(stackTrace)
         return self.ProcessException(err, message, version)

   def _DeserializeMessage(self, request, version):
      # Parse incoming soap request
      soapDeserializer = SoapEnvelopeDeserializer()
      try:
         soapDeserializer.Parse(request, version)
      except xml.parsers.expat.ExpatError as expatErr:
         # Handle expat error
         message = "Parse error at line " + str(expatErr.lineno) + \
                   ": " + xml.parsers.expat.ErrorString(expatErr.code)
         logging.error(message)
         raise Vmodl.Fault.InvalidRequest(msg=message)
      msg = soapDeserializer.GetResult()

      # Only handle request
      if not isinstance(msg, SoapMsgRequest):
         # Invalid request
         message = "Expecting soap request"
         logging.error(message)
         raise Vmodl.Fault.InvalidRequest(msg=message)

      # In the future, version could have changed after parsing soap msg
      version, mo, method = msg.version, msg.mo, msg.method

      # Make sure method is available to this version
      if not VmomiSupport.IsChildVersion(version, method.info.version):
         message = version + " not child verison of " + method.info.version
         logging.error(message)
         raise Vmodl.Fault.MethodNotFound(receiver=mo,
                                          method=method.info.name)

      return msg

   def _InvokeMethod(self, msg):
      try:
         version, mo, method = msg.version, msg.mo, msg.method

         # Get managed object stub
         stub = self._GetMoStub(version, mo)
         if not stub:
            message = "Failed to find (" + mo._moId + ")" + \
                      " serverGuid (" + str(mo._serverGuid) + ")"
            logging.error(message)
            raise Vmodl.Fault.ManagedObjectNotFound(msg=message, obj=mo)
         mo._stub = stub

         # Put the VMOMI request context in thread-local storage
         context = VmomiSupport.GetRequestContext()
         context.clear()
         context.update(msg.context)

         callerVersion = context.get("callerVersion")
         if not callerVersion:
            context['callerVersion'] = msg.version

         opID = context.get("operationID")
         if not opID:
            opID = ('%s' % uuid.uuid1())[:18]
            context['operationID'] = opID

         with ThreadName(opID):
             # Call method according to soap msg
             if isinstance(msg, SoapMsgAccessor):
                value = getattr(mo, msg.params)
             else:
                # TODO: Task method
                value = method.f(method.info, mo, **msg.params)

         # Serialize response
         isFault = False
         result = self._soapSerializer.SerializeResponse(version, method, value)
         return isFault, result
      except Exception as err:
         message = ExceptionMsg(err)
         if message:
            logging.error(message)
         stackTrace = traceback.format_exc()
         if stackTrace:
            logging.error(stackTrace)
         return self.ProcessException(err, message, version, method.info)

   def _ValidateException(self, err, mInfo):
      # All methods are allowed to throw RuntimeFault and its descendants.
      if isinstance(err, Vmodl.RuntimeFault):
         return err

      # Otherwise, the fault must be an instance of one of the faults the
      # method declares that it throws.
      for fault in mInfo.faults:
         if isinstance(err, VmomiSupport.GetVmodlType(fault)):
            return err

      # If its not, log a message and throw a SystemError instead.
      msg = 'Method %s threw undeclared fault of type %s' % (mInfo.wsdlName,
                                                             type(err).__name__)
      logging.error(msg)
      return Vmodl.Fault.SystemError(msg='Invalid Fault', faultCause=err, reason=msg)

   def ProcessException(self, err, message, version, mInfo=None):
      if issubclass(err.__class__, Vmodl.MethodFault) or \
         issubclass(err.__class__, Vmodl.RuntimeFault):
         if err.__class__ == Vmodl.MethodFault or \
            err.__class__ == Vmodl.RuntimeFault:
            # Method fault or Runtime fault
            err = Vmodl.Fault.SystemError(msg=message,
                                          reason=ExceptionMsg(err))
      else:
         err = Vmodl.Fault.SystemError(msg=message,
                                       reason="Runtime fault")

      if mInfo:
         err = self._ValidateException(err, mInfo)

      isFault = True
      return isFault, self._soapSerializer.SerializeServerFault(err, version)
