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);
      }
   }

}