gt:           if (mSOAPHeader == null) {
context: package net.foxopen.fox.webservices;
  
  import net.foxopen.fox.FoxRequest;
  import net.foxopen.fox.FoxResponse;
  import net.foxopen.fox.FoxResponseCHAR;
  import net.foxopen.fox.XFUtil;
  import net.foxopen.fox.dom.DOM;
  import net.foxopen.fox.dom.DOMList;
  import net.foxopen.fox.ex.ExActionFailed;
  import net.foxopen.fox.ex.ExCardinality;
  import net.foxopen.fox.ex.ExInternal;
  import net.foxopen.fox.ex.ExUserRequest;
  import net.foxopen.fox.module.entrytheme.EntryTheme;
  import net.foxopen.fox.module.Mod;
  import net.foxopen.fox.module.entrytheme.ThemeParam;
  
  import javax.servlet.http.HttpServletResponse;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  
  /**
   * SOAP-specific implementation for wrapping web service requests. Provides
   * error handling, data extraction and response methods in a generic way.
   */
  public class WebServiceRequestWrapperSOAP implements WebServiceRequestWrapper
  {
    public static final String SOAP_NAMESPACE_URI = "http://schemas.xmlsoap.org/soap/envelope/";
    public static final String SOAP_BODY_ELEM_NAME = "Body";
    public static final String SOAP_HEADER_ELEM_NAME = "Header";
    public static final String SOAP_MUST_UNDERSTAND_ATTR_NAME = "mustUnderstand";
    public static final String SOAP_MUST_UNDERSTAND_TRUE = "1";
    public static final String NS_DEFAULT_PREFIX = "ns";
  
    // Web service errors (special case error codes)
    public static final String SERVICE_ERROR_SERVER = "WEBSERVICE_SERVER";
    public static final String SERVICE_ERROR_CLIENT = "WEBSERVICE_CLIENT";
    public static final String SERVICE_ERROR_HEADER = "WEBSERVICE_HEADER";
  
    // Fault constants
    public static final String SOAP_FAULT_SERVER = "soap:Server";
    public static final String SOAP_FAULT_CLIENT = "soap:Client";
    public static final String SOAP_FAULT_VERSION = "soap:VersionMismatch";
    public static final String SOAP_FAULT_HEADER = "soap:MustUnderstand";
  
    // Namespace and element name for FOX thread ref SOAP header
    public static final String FOX_NAMESPACE_URI = "http://www.foxopen.net/soap/";
    public static final String FOX_NAMESPACE_PREFIX = "fox";
    public static final String FOX_THREAD_REF_HEADER_NAME = "threadref";
    public static final String FOX_XFSESSION_HEADER_NAME = "xfsessionid";
  
    public static final HashMap gSupportedWebServiceErrorCodeMap = new HashMap();
    static {
      gSupportedWebServiceErrorCodeMap.put(SERVICE_ERROR_SERVER, SOAP_FAULT_SERVER);
      gSupportedWebServiceErrorCodeMap.put(SERVICE_ERROR_CLIENT, SOAP_FAULT_CLIENT);
      gSupportedWebServiceErrorCodeMap.put(SERVICE_ERROR_HEADER, SOAP_FAULT_HEADER);
    }
  
    private DOM mRequestDataDOM;            // Request DOM
    private FoxRequest mFoxRequest;         // Reference to original FoxRequest
    private DOM mSOAPBody;                  // Reference to SOAP Body element
    private DOM mSOAPHeader;                // Reference to SOAP Header element
    private String mOperationName;          // Operation name
    private String mOperationNamespaceURI;  // Operation name including namespace
    private DOMList mOperationParams;       // Forest within operation
    private DOMList mOperationHeaderParams; // Forest within operation
    private boolean mInitialised = false;   // Flag that indicates we have JIT initialised
    private boolean mRPCStyle = false;      // Flag that indicates RPC or document style request
    private Mod mMod;                       // Target endpoint module
    private String mFoxThreadRef;           // FOX thread ref (for persistent XThread usage)
    private String mXfsessionId;            // FOX Xfsessionid (for persistent XThread usage)
  
    /**
     * Protected constructor that requires a request and the parsed XML body of
     * that request. Request used to interrogate headers.
     * @param pFoxRequest the request
     * @param pRequestDataDOM the request body as a DOM
     * @throws ExUserRequest invalid SOAP syntax
     */
    protected WebServiceRequestWrapperSOAP (FoxRequest pFoxRequest, DOM pRequestDataDOM, Mod pMod)
    throws ExUserRequest {
      // Sanity check, should never be a problem
      if (pRequestDataDOM == null) {
        throw new ExInternal("A null request data DOM was passed to WebServiceRequestWrapperSOAP");
      }
      else {
        mRequestDataDOM = pRequestDataDOM;
      }
  
      // Sanity check, should never be a problem
      if (pMod == null) {
        throw new ExInternal("A null module was passed to WebServiceRequestWrapperSOAP");
      }
      else {
        mMod = pMod;
      }
  
      // Get element prefix
      if (!SOAP_NAMESPACE_URI.equals(mRequestDataDOM.getNamespaceURI())) {
        throw new ExInternal("Non-SOAP request passed to SOAP Web Service Wrapper, expected root namespace '" + SOAP_NAMESPACE_URI + "'");
      }
  
      mFoxRequest = pFoxRequest;
    } // WebServiceRequestWrapperSOAP
  
    /**
     * Extract the operation that the request is attempting to call.
     * @return name of operation (should map to a service-based entry theme).
     * @throws ExUserRequest soap request was invalid
     * @throws ExActionFailed soap request was invalid or we don't support a required header
     */
    public String getOperationName()
    throws ExUserRequest, ExActionFailed {
      init();
      return mOperationName;
    } // getOperationName
  
    /**
     * Extract the request payload (the parameters to the operation).
     * @return xml request headerbody
     * @throws ExUserRequest soap request was invalid
     * @throws ExActionFailed soap request was invalid or we don't support a required header
     */
    public DOMList getRequestData()
    throws ExUserRequest, ExActionFailed {
      init();
      if (mOperationParams != null) {
        return mOperationParams;
      }
      else {
        return mOperationParams = new DOMList();
      }
    } // getRequestData
  
    public DOMList getRequestHeader()
    throws ExUserRequest, ExActionFailed {
      init();
      if (mOperationHeaderParams != null) {
        return mOperationHeaderParams;
      }
      else {
        return mOperationHeaderParams = new DOMList();
      }
    } // getRequestHeader
  
    /**
     * Extract the FOX thread ref (if present on the request).
     * @return thread id as a String
     */
    public String getFoxThreadRef()
    throws ExUserRequest, ExActionFailed {
      init();
      return mFoxThreadRef;
    } //getFoxThreadRef
  
    /**
     * Extract the FOX XfsessionId (if present on the request).
     * @return thread id as a String
     */
    public String getXfsessionId()
    throws ExUserRequest, ExActionFailed {
      init();
      return mXfsessionId;
    } // getXfsessionId
  
    /**
     * Creates a response based on a response data DOM.
     * @param pResponseDataDOM data to wrap in a web service response
     * @return the prepared response
     */
    public FoxResponse createResponse (DOM pResponseDataDOM) {
      // Wrap DOM in DOMList and pass to real method
      DOMList lTempDOMList = new DOMList();
      lTempDOMList.add(pResponseDataDOM);
      return createResponse(lTempDOMList);
    } // createResponse
  
    /**
     * Creates a response based on a response data DOMList.
     * @param pResponseDataDOMList data to wrap in a web service response
     * @return the prepared response
     */
    public FoxResponse createResponse (DOMList pResponseDataDOMList) {
      return createResponse(pResponseDataDOMList, null, null, null);
    }
  
    /**
     * Creates a response based on a response data DOMList.
     * @param pResponseDataDOMList data to wrap in a web service response
     * @param pResponseHeaderDOMList headers to wrap in a web service response
     * @return the prepared response
     */
    public FoxResponse createResponse (DOMList pResponseDataDOMList, DOMList pResponseHeaderDOMList) {
      return createResponse(pResponseDataDOMList, pResponseHeaderDOMList, null, null);
    }
  
    /**
     * Creates a response based on a response data DOMList.
     * @param pResponseDataDOMList data to wrap in a web service response
     * @param pResponseHeaderDOMList headers to wrap in a web service response
     * @param pOptionalFoxThreadRef optional thread ref to append to outgoing headers
     * @param pOptionalXfsessionId optional xfsessionid to append to outgoing headers
     * @return the prepared response
     */
    public FoxResponse createResponse (DOMList pResponseDataDOMList, DOMList pResponseHeaderDOMList, String pOptionalFoxThreadRef, String pOptionalXfsessionId) {
      // Create an envelope and append the node into the body
      DOM lEnvelope = createEnvelope(pOptionalFoxThreadRef, pOptionalXfsessionId);
  
      // Add operation namespace declaration to envelope
      lEnvelope.addNamespaceDeclaration(NS_DEFAULT_PREFIX, mOperationNamespaceURI);
      DOM lEnvelopeBody = lEnvelope.get1EOrNull("/*/soap:Body");
      DOM lEnvelopeHeader = lEnvelope.get1EOrNull("/*/soap:Header");
  
      // If we have SOAP headers to append, do so here regardless of type
      if (pResponseHeaderDOMList != null) {
        pResponseHeaderDOMList.copyContentsTo(lEnvelopeHeader);
      }
  
      // By convention, response element should be request element name with
      // "Response" appended (in the case of RPC), otherwise we're just copying
      // the return values
      if (mRPCStyle) {
        DOM lResponseDOM = DOM.createDocument(NS_DEFAULT_PREFIX + ":" + mOperationName+"Response", mOperationNamespaceURI, false);
        pResponseDataDOMList.copyContentsTo(lResponseDOM);
  
        // Copy response into SOAP body
        lResponseDOM.copyToParent(lEnvelopeBody);
      }
      else {
        // Document style responses are supposed to be in context of a namespace
        // so here we just-in-time assign the namespace prefix for this service
        for (int i = 0; i < pResponseDataDOMList.getLength(); i++) {
          DOM lResponseItem = pResponseDataDOMList.item(i);
          // Add namespace prefix to elements on the way out
          DOM lNewDOM = lEnvelopeBody.addElem(NS_DEFAULT_PREFIX + ":" + lResponseItem.getLocalName());
          lResponseItem.copyContentsTo(lNewDOM);
        }
      }
  
      // Remove all foxid attrs
      lEnvelope.removeRefsRecursive();
  
      // Wrap response and return
      return createFoxResponseCHAR(lEnvelope);
    } // createResponse
  
    /**
     * Creates a response based on an thrown exception.
     * @param pThrowable the exception to wrap
     * @return the prepared response
     */
    public FoxResponse createResponse (Throwable pThrowable) {
      return createResponse(pThrowable, null, null);
    }
  
    /**
     * Creates a response based on an thrown exception.
     * @param pThrowable the exception to wrap
     * @param pOptionalFoxThreadRef optional thread ref to append to outgoing headers
     * @param pOptionalXfsessionId optional xfsessionid to append to outgoing headers
     * @return the prepared response
     */
    public FoxResponse createResponse (Throwable pThrowable, String pOptionalFoxThreadRef, String pOptionalXfsessionId) {
      DOM lEnvelope = createEnvelope(pOptionalFoxThreadRef, pOptionalXfsessionId);
      try {
        DOM lFault = lEnvelope.create1E("/soap:Envelope/soap:Body/soap:Fault");
        FoxResponse lFoxResponse;
        Throwable lRootCauseThrowable;
        final String lFaultCode;
  
        // Chain through exceptions until we get to the root cause
        lRootCauseThrowable = pThrowable;
        while (lRootCauseThrowable.getCause() != null) {
          lRootCauseThrowable = lRootCauseThrowable.getCause();
        }
  
        // ExActionFailed can be thrown by fm:throw
        if (lRootCauseThrowable instanceof ExActionFailed) {
          ExActionFailed lEx = (ExActionFailed) lRootCauseThrowable;
          String lExceptionCode = lEx.getCode();
          lFaultCode = XFUtil.nvl((String) gSupportedWebServiceErrorCodeMap.get(lExceptionCode), SOAP_FAULT_SERVER);
        }
        // ExUserRequest thrown if validation fails on the way in
        else if (lRootCauseThrowable instanceof ExUserRequest) {
          lFaultCode = SOAP_FAULT_CLIENT;
        }
        // FOX has hit an unexpected error, so we have to assume it was server-side (module or engine itself)
        else {
          lFaultCode = SOAP_FAULT_SERVER;
        }
  
        lFault.addElem("faultcode", lFaultCode);
        // Belt and braces, if for some reason the root cause doesn't have a message, just take the higher level message
        lFault.addElem("faultstring", XFUtil.nvl(lRootCauseThrowable.getMessage(), pThrowable.getMessage()));
        lFoxResponse = createFoxResponseCHAR(lEnvelope);
  
        // Set HTTP status 500 on all soap:Fault responses to be WS-I compliant
        ((FoxResponseCHAR) lFoxResponse).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  
        return lFoxResponse;
      }
      catch (ExCardinality ex) {
        throw new ExInternal("Internal xpath error processing SOAP Fault", ex);
      }
    } // createResponse
  
    private FoxResponseCHAR createFoxResponseCHAR (DOM pFinalResponseDOM) {
      return new FoxResponseCHAR(
        "text/xml;charset=UTF-8"
      , new StringBuffer(pFinalResponseDOM.getRootElement().outputNodeToString()) // outputNodeToString suppresses FOX comment
      , 0
      );
    } // createFoxResponseCHAR
  
    /**
     * Convenience method that creates a basic soap Envelope element.
     * @param pOptionalFoxThreadRef optional thread ref to append to outgoing headers
     * @param pOptionalXfsessionId optional xfsessionid to append to outgoing headers
     * @return SOAP envelope as DOM
     */
    private DOM createEnvelope (String pOptionalFoxThreadRef, String pOptionalXfsessionId) {
      DOM lEnvelope = DOM.createDocument("soap:Envelope", SOAP_NAMESPACE_URI, true);
      DOM lEnvelopeHeader = lEnvelope.addElem("soap:Header");
      lEnvelope.addElem("soap:Body");
  
      // If we have a fox thread ref, write it into the outgoing SOAP headers
      // so that other calls in can resume the same thread
      if (!XFUtil.isNull(pOptionalFoxThreadRef)) {
        lEnvelope.setAttr("xmlns:" + FOX_NAMESPACE_PREFIX, FOX_NAMESPACE_URI);
        lEnvelopeHeader.addElem(FOX_NAMESPACE_PREFIX + ":" + FOX_THREAD_REF_HEADER_NAME, pOptionalFoxThreadRef);
      }
  
      // If we have a fox xfsessionid, write it into the outgoing SOAP headers
      // so that other calls in can resume the same thread
      if (!XFUtil.isNull(pOptionalXfsessionId)) {
        lEnvelope.setAttr("xmlns:" + FOX_NAMESPACE_PREFIX, FOX_NAMESPACE_URI);
        lEnvelopeHeader.addElem(FOX_NAMESPACE_PREFIX + ":" + FOX_XFSESSION_HEADER_NAME, pOptionalXfsessionId);
      }
  
      return lEnvelope;
    } // createEnvelope
  
    private void init ()
    throws ExUserRequest, ExActionFailed {
      // Bail out if we've done this already, wrappers are immutable
      if (mInitialised) {
        return;
      }
  
      // Loop through all SOAP envelope child nodes and hunt for the Body and Header elements
      DOMList lSOAPEnvelopChildElements = mRequestDataDOM.getChildElements();
      ENVELOPE_CHILD_LOOP: for (int i = 0; i < lSOAPEnvelopChildElements.getLength(); i++) {
        DOM lCurrentNode = lSOAPEnvelopChildElements.item(i);
        if (SOAP_NAMESPACE_URI.equals(lCurrentNode.getNamespaceURI())) {
          // Check to see which SOAP elements exist that we support
          String lLocalName = lCurrentNode.getLocalName();
          if (SOAP_BODY_ELEM_NAME.equals(lLocalName)) {
            if (mSOAPBody == null) {
              mSOAPBody = lCurrentNode;
            }
            else {
              throw new ExActionFailed(SERVICE_ERROR_CLIENT, "Expected exactly one SOAP Body element");
            }
          }
          else if (SOAP_HEADER_ELEM_NAME.equals(lLocalName)) {
  