# WebServiceHelper.java

    /*
    ** Module   : WebServiceHelper.java
    ** Abstract : A helper class to implement web service calls.
    **
    ** Copyright (c) 2013-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description---------------------------------
    ** 001 CA  20131220 Created initial version.
    ** 002 GES 20140116 Some constants moved to a different class so the references were fixed.
    ** 003 MAG 20140625 Fix port name on ServiceClient constructor.
    **                  Merge SOAP Envelope namespaces on response like on P4GL.
    ** 004 CA  20150209 Add wsdl-specified namespace support for input that is pre-formed XML passed
    **                  via a string.
    ** 005 CA  20150416 Added isConnected - returns always true, as the web services do not use a
    **                  permanent socket connection.
    ** 006 CA  20160404 Enhanced the appserver invocations, to allow implicit or explicit termination
    **                  of requests being executing on a certain connection.
    ** 007 OM  20160323 Moved WebService invocation on client side.
    ** 008 CA  20190118 disconnect() must be aware if the FWD session is gone.
    ** 009 CA  20190628 Added invoke() API which uses a InvokeConfig instance (unsupported for this).
    ** 010 CA  20220514 The active session is now set for leaf sessions, too - this is required for a change in
    **                  RemoteObject.obtainInstance, where first a check is done for an existing local proxy, and
    **                  after that a network instance is obtained (a requirement for the server-side OS resources
    **                  support, like memptr).
    ** 011 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 012 GBB 20240101 ConnectHelper renamed to ServerConnectHelper.
    ** 013 GBB 20240826 Moving WebService to osresource package.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import java.util.*;
    import java.util.logging.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.main.*;
    import com.goldencode.p2j.util.ControlFlowOps.ArgValidationErrors;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.p2j.util.osresource.*;
    import com.goldencode.util.*;

    /**
     * Provides a collection of APIs for establishing Progress-style connections to web-services and
     * performing Progress-style invocation of service operations.
     * <p>
     * This class allows connections only to SOAP 1.1 services, to conform with Progress runtime.
     * <p>
     * Rules describing the validation of the web-service connect options and validation of the WSDL
     * file:
     * <ol>
     * <li>the target WSDL file must be a well-formed WSDL file; local or remote URLs can be used.
     * </li>
     * <li>if <code>-Service</code> or <code>-Port</code>is specified, then <code>-Binding</code> and
     *     <code>-SOAPEndpoint</code> must not be specified (and viceversa).</li>
     * <li>if <code>-Binding</code> is specified, then <code>-SOAPEndpoint</code> must be specified.
     * </li>
     * <li>if <code>-TargetNamespace</code> is specified, it must match the one at the WSDL document.
     * </li>
     * <li>if <code>-Service</code> is specified, then:
     *  <ul>
     *   <li>it must match (case-sensitively) a service defined in the WSDL document.</li>
     *   <li>if <code>-Port</code> is specified, then the port must be matched with one specified at
     *       the service in the WSDL document and it must have an associated SOAP 1.1. binding.</li>
     *   <li>if no <code>-Port</code> is specified, then there must exist only one SOAP 1.1.
     *       <code>(port, binding)</code> pair at the service configuration.</li>
     *  </ul>
     * </li>
     * <li>if <code>-Binding</code> is specified, then:
     *  <ul>
     *   <li>it must be defined in the WSDL document.</li>
     *   <li>it must be for SOAP 1.1.</li>
     *   <li>there must exist a service configured for this binding and its associated port.</li>
     *  </ul>
     * </li>
     * <li>else, if no <code>-Binding</code> or <code>-Service</code> is specified, then:
     *  <ul>
     *   <li>there must exist exactly only on service definied in the document.</li>
     *   <li>the service must have associated only one SOAP 1.1. port.</li>
     *  </ul>
     * </li>
     * </ol>
     * <p>
     * The {@link #CONNECT_OPTION_MAX_CONNECTIONS}, {@link #CONNECT_OPTION_CONNECTION_LIFETIME},
     * {@link #CONNECT_OPTION_NO_SESSION_REUSE} and {@link #CONNECT_OPTION_NO_HOST_VERIFY} options are
     * not yet implemented by {@link #connect}.
     * <p>
     * TODO: implementation for these options will not be trivial. There probably will be needed
     * changes to AXIS2 core to allow persistent connections and to validate the SSL Host and reuse
     * the SSL Session. Host's SSL certificate validation may be done separately (by establishing a
     * connection just to make sure the target host is known), but SSL Session reuse is linked to
     * AXIS2 code.
     */
    public class WebServiceHelper
    extends ServerHelper
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(WebServiceHelper.class.getName());

       /** Constant identifying the '-WSDL' connect option. */
       static final String CONNECT_OPTION_WSDL = "-WSDL";

       /** Constant identifying the '-WSDLUserid' connect option. */
       static final String CONNECT_OPTION_WSDL_USERID = "-WSDLUserid";

       /** Constant identifying the '-WSDLPassword' connect option. */
       static final String CONNECT_OPTION_WSDL_PASSWORD = "-WSDLPassword";

       /** Constant identifying the '-Service' connect option. */
       static final String CONNECT_OPTION_SERVICE = "-Service";

       /** Constant identifying the '-ServiceNamespace' connect option. */
       static final String CONNECT_OPTION_SERVICE_NAMESPACE = "-ServiceNamespace";

       /** Constant identifying the '-Port' connect option. */
       static final String CONNECT_OPTION_PORT = "-Port";

       /** Constant identifying the '-Binding' connect option. */
       static final String CONNECT_OPTION_BINDING = "-Binding";

       /** Constant identifying the '-BindingNamespace' connect option. */
       static final String CONNECT_OPTION_BINDING_NAMESPACE = "-BindingNamespace";

       /** Constant identifying the '-SOAPEndpoint' connect option. */
       static final String CONNECT_OPTION_SOAP_ENDPOINT = "-SOAPEndpoint";

       /** Constant identifying the '-SOAPEndpointUserid' connect option. */
       static final String CONNECT_OPTION_SOAP_ENDPOINT_USER = "-SOAPEndpointUserid";

       /** Constant identifying the '-SOAPEndpointPassword' connect option. */
       static final String CONNECT_OPTION_SOAP_ENDPOINT_PASSWORD = "-SOAPEndpointPassword";

       /** Constant identifying the '-TargetNamespace' connect option. */
       static final String CONNECT_OPTION_TARGET_NAMESPACE = "-TargetNamespace";

       /** Constant identifying the '-maxConnections' connect option. */
       static final String CONNECT_OPTION_MAX_CONNECTIONS = "-maxConnections";

       /** Constant identifying the '-connectionLifetime' connect option. */
       static final String CONNECT_OPTION_CONNECTION_LIFETIME = "-connectionLifetime";

       /** Constant identifying the '-nosessionreuse' connect option. */
       static final String CONNECT_OPTION_NO_SESSION_REUSE = "-nosessionreuse";

       /** Constant identifying the '-nohostverify' connect option. */
       static final String CONNECT_OPTION_NO_HOST_VERIFY = "-nohostverify";

       /** Constant identifying the '-pf' connect option. */
       static final String CONNECT_OPTION_PF = "-pf";

       /** The set of web-service connect options used by the legacy CONNECT method. */
       private static final Set<String> knownConnectOptions;

       static
       {
          knownConnectOptions = new HashSet<>();
          knownConnectOptions.add(CONNECT_OPTION_WSDL);
          knownConnectOptions.add(CONNECT_OPTION_WSDL_USERID);
          knownConnectOptions.add(CONNECT_OPTION_WSDL_PASSWORD);
          knownConnectOptions.add(CONNECT_OPTION_SERVICE);
          knownConnectOptions.add(CONNECT_OPTION_SERVICE_NAMESPACE);
          knownConnectOptions.add(CONNECT_OPTION_PORT);
          knownConnectOptions.add(CONNECT_OPTION_BINDING);
          knownConnectOptions.add(CONNECT_OPTION_BINDING_NAMESPACE);
          knownConnectOptions.add(CONNECT_OPTION_SOAP_ENDPOINT);
          knownConnectOptions.add(CONNECT_OPTION_SOAP_ENDPOINT_USER);
          knownConnectOptions.add(CONNECT_OPTION_SOAP_ENDPOINT_PASSWORD);
          knownConnectOptions.add(CONNECT_OPTION_TARGET_NAMESPACE);
          knownConnectOptions.add(CONNECT_OPTION_CONNECTION_LIFETIME);
          knownConnectOptions.add(CONNECT_OPTION_MAX_CONNECTIONS);
          knownConnectOptions.add(CONNECT_OPTION_NO_SESSION_REUSE);
          knownConnectOptions.add(CONNECT_OPTION_NO_HOST_VERIFY);
          knownConnectOptions.add(CONNECT_OPTION_PF);
       }

       /** The target SOAP endpoint to which requests are sent. */
       private final String soapEndpoint;

       /** The context-local data of this class. */
       private static final ContextLocal<WebService> localService =
          new ContextLocal<WebService>()
          {
             protected WebService initialValue()
             {
                Class<?> cls = WebService.class;

                if (!SessionManager.get().isLeaf())
                {
                   return (WebService) RemoteObject.obtainNetworkInstance(cls);
                }
                else
                {
                   return (WebService) RemoteObject.obtainLocalInstance(cls, true);
                }
             };
          };

       /** The counter for async requests. */
       private int nextAsyncReqId = 0;

       /** The client side context id for the web service associated with this helper. */
       private int webServiceId = -1;

       /**
        * Instantiate a new helper.
        *
        * @param   webServiceId
        *          The client side id of WebService implementation.
        * @param   soapEndpoint
        *          The target SOAP endpoint to which requests are sent.
        */
       private WebServiceHelper(int webServiceId, String soapEndpoint)
       {
          this.webServiceId = webServiceId;
          this.soapEndpoint = soapEndpoint;
       }

       /**
        * Do a first level validation of parameters and send them to client to establish a new
        * web-service connection using the connect options in the given map.
        *
        * @param   opts
        *          A map with the connect options.
        *
        * @return   A new {@link WebServiceHelper} instance or {@code null} if no connection
        *           was possible.  Check the {@link ErrorManager} for details about any errors.
        */
       static WebServiceHelper connect(Map<String, String> opts)
       {
          // wsdl target is required
          String wsdlTarget = ServerConnectHelper.getString(opts, CONNECT_OPTION_WSDL, null);
          WebServiceConnectOptions options = new WebServiceConnectOptions(wsdlTarget);

          // wsdl user/passwd are optional
          String wsdlUserid = ServerConnectHelper.getString(opts, CONNECT_OPTION_WSDL_USERID, null);
          options.setWsdlUserid(wsdlUserid);

          options.setWsdlPassword((wsdlUserid == null)
             ? null
             : ServerConnectHelper.getString(opts, CONNECT_OPTION_WSDL_PASSWORD, ""));

          // -Service and -Binding groups are mutually exclusive, but are optional
          options.setServiceName(ServerConnectHelper.getString(opts, CONNECT_OPTION_SERVICE, null));
          options.setServiceNamespace(
             ServerConnectHelper.getString(opts, CONNECT_OPTION_SERVICE_NAMESPACE, null));
          options.setPortName(ServerConnectHelper.getString(opts, CONNECT_OPTION_PORT, null));

          options.setBindingName(ServerConnectHelper.getString(opts, CONNECT_OPTION_BINDING, null));
          options.setBindingNamespace(
             ServerConnectHelper.getString(opts, CONNECT_OPTION_BINDING_NAMESPACE, null));
          options.setSoapEndpoint(ServerConnectHelper.getString(opts, CONNECT_OPTION_SOAP_ENDPOINT, null));

          // soap user/passwd are optional
          String soapEndpointUserid =
             ServerConnectHelper.getString(opts, CONNECT_OPTION_SOAP_ENDPOINT_USER, null);
          options.setSoapEndpointUserid(soapEndpointUserid);
          options.setSoapEndpointPassword((soapEndpointUserid == null)
               ? null
               : ServerConnectHelper.getString(opts, CONNECT_OPTION_SOAP_ENDPOINT_PASSWORD, ""));

          // target NS is optional, used to validate the WSDL
          options.setTargetNamespace(
             ServerConnectHelper.getString(opts, CONNECT_OPTION_TARGET_NAMESPACE, null));

          options.setMaxConnections(ServerConnectHelper.getInt(opts, CONNECT_OPTION_MAX_CONNECTIONS, 0));
          options.setConnectionLifetime(
             ServerConnectHelper.getInt(opts, CONNECT_OPTION_CONNECTION_LIFETIME, 300));

          // related to ssl connections
          options.setNoSessionReuse(opts.containsKey(CONNECT_OPTION_NO_SESSION_REUSE));
          options.setNoHostVerify(opts.containsKey(CONNECT_OPTION_NO_HOST_VERIFY));

          WebService webService = localService.get();
          int webServiceId = webService.connect(options);

          if (webServiceId >= 0)
          {
             return new WebServiceHelper(webServiceId, options.getSoapEndpoint());
          }

          return null;
       }

       /**
        * Get the set of known options for web-service connections.
        *
        * @return   See above.
        */
       static Set<String> getKnownConnectOptions()
       {
          return knownConnectOptions;
       }

       /**
        * Run the port type with the given name, only if the name matches (case-insensitively) with
        * the {@link WebService#getPortTypeName(int) target port type} determined during connection.
        *
        * @param   name
        *          The port type name.
        *
        * @return  A {@link PortTypeWrapper} instance associated with the port type or {@code null}
        *          if the port type name does not match.
        */
       public PersistentProcedure runPort(character name)
       {
          // validate the port type
          String portTypeName = name.toStringMessage();

          WebService webService = localService.get();
          String webServicePortTypeName = webService.getPortTypeName(webServiceId);

          // search is done case-insensitive
          if (!webServicePortTypeName.equalsIgnoreCase(portTypeName))
          {
             final int[] errCodes = { 11754, 11755 };
             final String[] errMsgs =
             {
                String.format("Failure in initializing Port Type %s (11754)", portTypeName),
                String.format("Invalid WSDL PortType name %s (11755)", portTypeName)
             };
             ErrorManager.recordOrThrowError(errCodes, errMsgs, false, true);

             return null;
          }

          return new PortTypeWrapper(portTypeName, this.getServer());
       }

       /**
        * Get the sub-type of this web service connection.
        *
        * @return   Always the <code>WEBSERVICE</code> string.
        */
       @Override
       public String getSubType()
       {
          return "WEBSERVICE";
       }

       /**
        * Get the connection ID of this web service connection.
        *
        * @return   The {@link #soapEndpoint}.
        */
       @Override
       public String getConnectionID()
       {
          return soapEndpoint;
       }

       /**
        * Check if this is a web service.
        *
        * @return   Always <code>true</code>.
        */
       @Override
       public boolean isWebService()
       {
          return true;
       }

       /**
        * Check if this is a session-free connection.
        *
        * @return   Always <code>true</code> for web service connections.
        */
       @Override
       public boolean isSessionFree()
       {
          return true;
       }

       /**
        * Get the name of this server.
        *
        * @return   Always the connected {@link #soapEndpoint}.
        */
       @Override
       public String getServerName()
       {
          return soapEndpoint;
       }

       /**
        * Disconnect this server.
        * <p>
        * This is a no-op for web-services.
        *
        * @param    disconnecting
        *           Flag indicating if the FWD client session is being terminated.
        */
       @Override
       public void disconnect(boolean disconnecting)
       {
          if (webServiceId < 0)
          {
             return;
          }

          if (!disconnecting)
          {
             WebService webService = localService.get();
             webService.disconnect(webServiceId);
          }

          webServiceId = -1;
       }

       /**
        * This implementation always returns true, as the connection to remote side is not permanent.
        *
        * @return    Always <code>true</code>.
        */
       @Override
       public boolean isConnected()
       {
          return true;
       }

       /**
        * Send a STOP condition to the specified async request.
        *
        * @param    requestId
        *           The request where to send a STOP condition.
        */
       @Override
       public void sendStop(int requestId)
       {
          UnimplementedFeature.todo("WebServiceHelper.sendStop is not implemented.");
       }

       /**
        * Send a STOP condition to the request currently being executed on this connection.
        *
        * @throws   IllegalStateException
        *           If the appserver is not connected.
        */
       @Override
       public void sendStop()
       {
          UnimplementedFeature.todo("WebServiceHelper.sendStop is not implemented.");
       }

       /**
        * Invoke a web service operation.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    asyncReq
        *           When not-null, an {@link AsyncRequestImpl} instance for the associated async
        *           request.
        * @param    name
        *           The name for the web service operation.
        * @param    h
        *           The port-type procedure in which to invoke the web service operation. May not
        *           be {@code null}.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION DISTINCT clause is in effect.
        * @param    modes
        *           A string representation of the modes of each parameter. May not be {@code null}.
        * @param    args
        *           The procedure's arguments.
        *
        * @return   The result of this procedure/function invocation or {@code null} if the
        *           invocation could not be performed.
        *
        * @throws   IllegalArgumentException
        *           If this is a super call, the argument modes are not specified or the passed
        *           procedure handle is {@code null} or not a {@link PortTypeWrapper} procedure.
        */
       @Override
       @SuppressWarnings("unchecked")
       BaseDataType invoke(long             timeout,
                           AsyncRequestImpl asyncReq,
                           character        name,
                           handle           h,
                           boolean          function,
                           boolean          dynamicFunction,
                           boolean          superCall,
                           boolean          transactionDistinct,
                           String           modes,
                           Object...        args)
       {
          if (asyncReq != null && asyncReq._isCancelled())
          {
             // if the procedure is cancelled, nothing else to do
             asyncReq.setResult(new RemoteInvocationResult());
             return new unknown();
          }

          if (superCall)
          {
             throw new IllegalArgumentException("superCall argument must always be false!");
          }

          if (h == null || !(h.get() instanceof PortTypeWrapper))
          {
             throw new IllegalArgumentException("The passed handle is not a port type procedure!");
          }

          if (modes == null || modes.length() != args.length)
          {
             throw new IllegalArgumentException("The parameter modes must always be specified!");
          }

          WebService webService    = localService.get();
          PortTypeWrapper portProc = (PortTypeWrapper) h.get();
          String portTypeName      = portProc.name().toStringMessage();
          String opName            = name.toStringMessage();

          if (!webService.prepareInvoke(webServiceId, portTypeName, opName, modes, args))
          {
             // failure to prepare to call the web service. Eventual messages were already fed into
             // ErrorManager
             return new unknown();
          }

          String opNs = webService.getNamespace(webServiceId);
          ConditionException condition = null;
          Object[] outputParams = null;

          // if SOAP call initialization was successful then we can invoke the request-header callback
          character requestHeader = invokeRequestHeaderCallback(portProc, opName, opNs);
          if (requestHeader == null)
          {
             // problems invoking the request-header callback, abort
             webService.cleanupInvocation(webServiceId);
             return new unknown();
          }

          if (asyncReq != null)
          {
             asyncReq.setRequestId(++nextAsyncReqId); // set the request ID
          }

          try
          {
             // now we are ready to invoke the web service from client side:
             String cbHeader = requestHeader.isUnknown() ? null : requestHeader.toStringMessage();
             if (!webService.invoke(webServiceId, opName, portTypeName, cbHeader))
             {
                // invocation failed!
                return new unknown();
             }

             // invoke the response-header callback
             if (portProc.getResponseHeaderProc() != null)
             {
                handle headerRef = new handle();
                SOAPHeaderImpl header = new SOAPHeaderImpl();
                headerRef.set(header);

                String[] wsHeaderEntries = webService.getHeaderEntries(webServiceId);
                if (wsHeaderEntries != null)
                {
                   for (String wsEntry : wsHeaderEntries)
                   {
                      // first register it as a header entry
                      SOAPHeaderEntryImpl entry = new SOAPHeaderEntryImpl();
                      header.addHeaderEntry(entry);

                      entry.setSerialized(new longchar(wsEntry));
                   }
                }

                // even if there is no header, an empty SOAP Header resource is sent.
                if (!invokeResponseHeaderCallback(portProc, headerRef, opName, opNs))
                {
                   // problems invoking the response-header callback, abort
                   return new unknown();
                }
             }
             outputParams = webService.getOutputValues(webServiceId);
          }
          catch (ConditionException ce)
          {
             if (asyncReq == null)
             {
                // if not an async request, let it unwind
                throw ce;
             }

             condition = ce;
          }
          finally
          {
             webService.cleanupInvocation(webServiceId);
          }

          character res = new character();
          if (outputParams != null)
          {
             // copy outputParams back to [args] for parameters with O modes
             int j = 0;
             for (int i = 0; i < args.length; i++)
             {
                if (modes.charAt(i) != 'I')
                {
                   if (j >= outputParams.length)
                   {
                      if (LOG.isLoggable(Level.SEVERE))
                      {
                         LOG.log(Level.SEVERE,
                               "Incorrect number of output parameters received from WebService.");
                      }
                      break;
                   }
                   ((BaseDataType)args[i]).assign((BaseDataType)outputParams[j++]);
                }
             }

             if (outputParams.length == 1)
             {
                res.assign((BaseDataType) outputParams[0]);
             }
          }
          if (asyncReq != null)
          {
             RemoteInvocationResult asyncResult = new RemoteInvocationResult();
             asyncResult.setArguments(outputParams);
             asyncResult.setModes(
                   StringHelper.repeatChar('I', outputParams == null ? 0 : outputParams.length));
             asyncResult.setResult(res);
             asyncResult.setError(condition);
             asyncReq.setResult(asyncResult);
          }

          return res;
       }

       /**
        * This API is not available for web services.
        *
        * @param    timeout
        *           The maximum allowed time for this invocation to complete.  In milliseconds.
        *           Use <code>0</code> to disable the timeout.
        * @param    asyncReq
        *           When not-null, an {@link AsyncRequestImpl} instance for the associated async
        *           request.
        * @param    cfg
        *           The invoke configuration.
        *
        * @return   Always <code>unknown</code>.
        */
       @Override
       BaseDataType invoke(long timeout, AsyncRequestImpl asyncReq, InvokeConfig cfg)
       {
          UnimplementedFeature.unsupported("This API is not available for Web Services!");
          return new unknown();
       }

       /**
        * Invoke the response-header callback, if it was set for the specified procedure.
        * <p>
        * This will also validate the response-header callback and the associated context procedure.
        *
        * @param    portProc
        *           The {@link PortTypeWrapper port-type procedure}.
        * @param    soapHeader
        *           A handle pointing to the {@link SOAPHeader} resource.
        * @param    opName
        *           The name of operation to be invoked.
        * @param    opNamespace
        *           The namespace of operation to be invoked.
        *
        * @return   <code>true</code> if the callback was invoked and no error condition was raised.
        */
       private boolean invokeResponseHeaderCallback(PortTypeWrapper portProc,
                                                    handle          soapHeader,
                                                    String          opName,
                                                    String          opNamespace)
       {
          String procedure = portProc.getResponseHeaderProc();
          handle context = portProc.getResponseHeaderContext();

          if (procedure == null)
          {
             // nothing to invoke
             return false;
          }

          if (!context._isValid() || context.getResource() instanceof PortTypeWrapper)
          {
             final String msg1 = "Could not process callback: " +
                                 "invalid procedure handle for the callback.";
             final String msg2 = "Error receiving Web Service Response: " +
                                 "An error occurred processing SOAP Response Headers";
             final int[] errCodes = { 11574, 11773 };

             ErrorManager.recordOrThrowError(errCodes, new String[] {msg1, msg2}, false, true);
             return false;
          }

          String modes = "III";
          character operNS = new character(opNamespace);
          character operName = new character(opName);

          try
          {
             // validate the procedure name and parameters now. the modes are not validated.
             ArgValidationErrors argErr =
                ControlFlowOps.reachableInternalEntry(context, procedure, false, modes, false,
                                                      soapHeader, operNS, operName);
             if (argErr == null || argErr != ArgValidationErrors.NO_ERROR)
             {
                showCallbackError(false, argErr, procedure);
                return false;
             }

             ControlFlowOps.invokeInWithMode(procedure, context, null, soapHeader, operNS, operName);
             return true;
          }
          catch (ErrorConditionException e)
          {
             final String msg = "Error receiving Web Service Response: " +
                                "An error occurred processing SOAP Response Headers";
             ErrorManager.recordOrThrowError(11773, msg, false, true);

             return false;
          }
       }

       /**
        * Invoke the request-header callback, if it was set for the specified procedure.
        * <p>
        * This will also validate the request-header callback and the associated context procedure.
        *
        * @param    portProc
        *           The {@link PortTypeWrapper port-type procedure}.
        * @param    opName
        *           The name of operation to be invoked.
        * @param    opNamespace
        *           The namespace of operation to be invoked.
        *
        * @return   an unknown {@link character} instance if nothing is needed to be invoked,
        *           {@code null} in case of any raised error conditions or the request-header
        *           XML payload serialized as text, in case of success.
        */
       private character invokeRequestHeaderCallback(PortTypeWrapper portProc,
                                                     String          opName,
                                                     String          opNamespace)
       {
          String procedure = portProc.getRequestHeaderProc();
          handle context = portProc.getRequestHeaderContext();

          if (procedure == null)
          {
             // nothing to invoke
             return new character();
          }

          if (!context._isValid() || context.getResource() instanceof PortTypeWrapper)
          {
             final int[] errCodes = { 11574, 11767 };
             final String msg1 = "Could not process callback: " +
                                 "invalid procedure handle for the callback";
             final String msg2 = "Error sending Web Service Request: " +
                                 "An error occurred processing SOAP Request Headers";

             ErrorManager.recordOrThrowError(errCodes, new String[] { msg1, msg2 }, false, true);
             return null;
          }

          String modes = "OIIO";
          handle soapHeader = new handle();
          character operNS = new character(opNamespace);
          character operName = new character(opName);
          logical deleteOnDone = new logical();
          String header = null;

          try
          {
             // validate the procedure name and parameters now. The modes are not validated.
             ArgValidationErrors argErr =
                ControlFlowOps.reachableInternalEntry(context, procedure, false, modes, false,
                                                      soapHeader, operNS, operName, deleteOnDone);
             if (argErr == null || argErr != ArgValidationErrors.NO_ERROR)
             {
                showCallbackError(true, argErr, procedure);
                return null;
             }

             ControlFlowOps.invokeInWithMode(procedure, context, null,
                                             soapHeader, operNS, operName, deleteOnDone);

             if (soapHeader._isValid())
             {
                // no error is shown if the handle doesn't reference a SOAP-header...
                Object res = soapHeader.get();
                if (res instanceof SOAPHeaderImpl)
                {
                   SOAPHeaderImpl hdr = (SOAPHeaderImpl) res;

                   // get the header serialized to a string and return it.
                   if (hdr.valid())
                   {
                      header = hdr.asString();
                   }
                }

                if (deleteOnDone.booleanValue())
                {
                   // the soap header and the underlying DOM XML Nodes (referring the header entries)
                   // get deleted
                   HandleOps.delete(soapHeader);
                }
             }

             return new character(header);
          }
          catch (ErrorConditionException e)
          {
             final String msg = "Error sending Web Service Request: " +
                                "An error occurred processing SOAP Request Headers";
             ErrorManager.recordOrThrowError(11767, msg, false, true);

             return null;
          }
       }

       /**
        * Show a callback error for a failed callback validation.
        *
        * @param    request
        *           When <code>true</code>, the request-header callback is invoked.
        * @param    err
        *           The validation errors for this callback. {@code null} specifies the callback
        *           procedure was not found in the specified context.
        * @param    procedure
        *           The procedure name for this callback.
        */
       private void showCallbackError(boolean request, ArgValidationErrors err, String procedure)
       {
          final String reqMsg2 = "Error sending Web Service Request: " +
                                 "An error occurred processing SOAP Request Headers";
          final String respMsg2 = "Error receiving Web Service Response: " +
                                  "An error occurred processing SOAP Response Headers";

          final String msg2 = (request ? reqMsg2 : respMsg2);
          final int errCode2 = (request ? 11767 : 11773);

          final int errCode1;
          final String msg1;

          if (err != null)
          {
             if (err == ArgValidationErrors.INCORRECT_COUNT ||
                 err == ArgValidationErrors.UNEXPECTED_PARAM)
             {
                msg1 = String.format("Callback error: " +
                                     "mismatched number of parameters passed to procedure %s",
                                     procedure);
                errCode1 = 10511;
             }
             else
             {
                msg1 = String.format("%s-HEADER sent procedure %s mismatched parameters",
                                     (request ? "REQUEST" : "RESPONSE"),
                                     procedure);
                errCode1 = 10512;
             }
          }
          else
          {
             msg1 = null;
             errCode1 = -1;
          }

          if (msg1 != null)
          {
             ErrorManager.recordOrThrowError(new int[] { errCode1, errCode2 },
                                             new String[] { msg1, msg2 },
                                             false, true);
          }
          else
          {
             ErrorManager.recordOrThrowError(errCode2, msg2, false, true);
          }
       }

    }