RemoteWebRequest.java

/*
** Module   : RemoteWebRequest.java
** Abstract : Expose a remote web request to 4GL APIs.
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------Description--------------------------------
** 001 CA  20190924 First version.
** 002 CA  20191024 Added method support levels and updated the class support level.
** 003 CA  20200427 A more improved runtime.  cookie/header and other support is still missing.
** 004 ME  20210128 Add errors for read-only, not implemented method/properties.
** 005 CA  20210221 Fixed parameters at the LegacySignature annotation.
** 006 ME  20210423 Annotate class and finish implementation as per OE12.2.
**     CA  20210609 Fixed getPathInfo and getPathParameter, when the path info has more than one leading '/' or
**                  the path parameter doesn't exist. 
**     CA  20210816 Fixed getEntity() - parameters from 'content-type' must be removed.
**                  Fixed getHeader() when the header is not found (NULL header must be returned).
**     ME  20210916 Default cookie path is unknown.
**     VVT 20210917 Javadoc fixed. 
** 007 CA  20231113 The 'execute' method must be annotated with LegacySignature Type.Execute, and also can be
**                  dropped if is a no-op.
** 008 CA  20240219 'getEntity' must fallback to 'raw/memptr' if the body's content type can't be resolved.
** 009 CA  20240523 Cookies from the request may be null.
** 010 AS  20241206 Fixed getUri to return the full uri.
** 011 GBB 20250403 Fixes in comments.
*/

/*
** 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.oo.web;

import static com.goldencode.p2j.report.ReportConstants.*;
import static com.goldencode.p2j.util.BlockManager.*;

import java.io.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

// explicit import
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Request;

import com.goldencode.p2j.directory.Base64;
import com.goldencode.p2j.main.*;
import com.goldencode.p2j.oo.core.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.oo.net.*;
import com.goldencode.p2j.oo.net.http.*;
import com.goldencode.p2j.oo.net.http.filter.payload.MessageWriter;
import com.goldencode.p2j.oo.net.http.filter.writer.EntityWriterRegistry;
import com.goldencode.p2j.oo.web.WebHandler.WebContext;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.BlockManager.Action;
import com.goldencode.p2j.util.BlockManager.Condition;
import com.goldencode.p2j.util.InternalEntry.Type;

/**
 * A wrapper over a {@link HttpServletRequest} instance, to expose it as a legacy 
 * {@link IwebRequest}.
 */
@LegacyResource(resource = "OpenEdge.Web.WebRequest")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_PARTIAL)
public class RemoteWebRequest
extends BaseObject
implements IwebRequest
{
   /** A marker for unsupported {@link #CGI_VARS}. */
   private static final Object UNSUPPORTED_CGI_VAR = new Object();
   
   /** All reported CGI variables and their expression to calculate it. */
   private static final Map<String, Function<HttpServletRequest, Object>> CGI_VARS = new HashMap<>();
   
   static
   {
      CGI_VARS.put("AUTH_TYPE"              , (r) -> r.getAuthType());
      CGI_VARS.put("BASE_PATH"              , (r) -> UNSUPPORTED_CGI_VAR); // explicitly handled
      CGI_VARS.put("CONTENT_LENGTH"         , (r) -> r.getContentLength());
      CGI_VARS.put("CONTENT_TYPE"           , (r) -> r.getContentType());
      CGI_VARS.put("CONTEXT_PATH"           , (r) -> r.getContextPath());
      CGI_VARS.put("DOCUMENT_ROOT"          , (r) -> r.getServletContext().getRealPath("/"));
      CGI_VARS.put("GATEWAY_INTERFACE"      , (r) -> UNSUPPORTED_CGI_VAR);
      CGI_VARS.put("HTTP_ACCEPT_ENCODING"   , (r) -> r.getHeader("Accept-Encoding"));
      CGI_VARS.put("HTTP_CONNECTION"        , (r) -> r.getHeader("Connection"));
      CGI_VARS.put("HTTP_HOST"              , (r) -> r.getHeader("Host"));
      CGI_VARS.put("HTTP_PORT"              , (r) -> 
                                              {
                                                 String host = r.getHeader("Host"); 
                                                 int idx = host.indexOf(':'); 
                                                 return idx < 0 ? "" : host.substring(idx + 1); 
                                              });
      CGI_VARS.put("HTTPS"                  , (r) -> r.isSecure());
      CGI_VARS.put("HTTP_USER_AGENT"        , (r) -> r.getHeader("User-Agent"));
      CGI_VARS.put("LOCAL_ADDR"             , (r) -> r.getLocalAddr());
      CGI_VARS.put("LOCAL_NAME"             , (r) -> r.getLocalName());
      CGI_VARS.put("LOCAL_PORT"             , (r) -> r.getLocalPort());
      CGI_VARS.put("PATH_INFO"              , (r) -> r.getPathInfo());
      CGI_VARS.put("PATH_TRANSLATED"        , (r) -> r.getPathTranslated());
      CGI_VARS.put("QUERY_STRING"           , (r) -> r.getQueryString());
      CGI_VARS.put("REMOTE_ADDR"            , (r) -> r.getRemoteAddr());
      CGI_VARS.put("REMOTE_HOST"            , (r) -> r.getRemoteHost());
      CGI_VARS.put("REMOTE_PORT"            , (r) -> r.getRemotePort());
      CGI_VARS.put("REQUEST_METHOD"         , (r) -> r.getMethod());
      CGI_VARS.put("REQUEST_SCHEME"         , (r) -> r.getScheme());
      CGI_VARS.put("REQUEST_URI"            , (r) -> r.getRequestURI());
      CGI_VARS.put("SCRIPT_NAME"            , (r) -> r.getServletPath());
      CGI_VARS.put("SERVER_NAME"            , (r) -> r.getServerName());
      CGI_VARS.put("SERVER_PORT"            , (r) -> r.getServerPort());
      CGI_VARS.put("SERVER_PROTOCOL"        , (r) -> r.getProtocol());
      CGI_VARS.put("SERVER_SOFTWARE"        , (r) -> r.getServletContext().getServerInfo());
      CGI_VARS.put("SERVLET_APPLICATION_URL", (r) -> UNSUPPORTED_CGI_VAR); // explicitly handled 
      CGI_VARS.put("SERVLET_PATH"           , (r) -> r.getServletPath());
      CGI_VARS.put("SERVLET_SERVER_APP_MODE", (r) -> "production"); // FWD doesn't allow debug mode
      CGI_VARS.put("SERVLET_SRVR_DEBUG"     , (r) -> "0"); // FWD doesn't allow debug mode
      CGI_VARS.put("SERVLET_WSROOT"         , (r) -> UNSUPPORTED_CGI_VAR);
      CGI_VARS.put("URI_FINAL_MATCH_GROUP"  , (r) -> UNSUPPORTED_CGI_VAR); // explicitly handled
      CGI_VARS.put("URI_TEMPLATE"           , (r) -> UNSUPPORTED_CGI_VAR); // explicitly handled
   }
   
   /** The base path for this request. */
   private String basepath;
   
   /** The target path for this request. */
   private String target;
   
   /** The servlet request. */
   private HttpServletRequest request;
   
   /** The paths for this request, as configured at the handler. */
   private String[] paths;

   /** The MD5 hash of body */
   private raw contentMD5 = TypeFactory.raw();
   
   /** The body entity. */
   private object<? extends _BaseObject_> entity = TypeFactory.object(_BaseObject_.class);
   
   /**
    * The execute method to initialize the FWD-related state for an instance.
    */
   @LegacySignature(type = Type.EXECUTE)
   public void __web_RemoteWebRequest_execute__()
   {
      onBlockLevel(Condition.ERROR, Action.THROW);
   }

   /**
    * Default constructor.
    */
   @LegacySignature(type = Type.CONSTRUCTOR)
   public void __web_RemoteWebRequest_constructor__()
   {
      internalProcedure(this, "__web_RemoteWebRequest_constructor__", new Block((Body) () -> 
      {
         __lang_BaseObject_constructor__();
      }));
   }

   /**
    * Initialize this instance with the specified details.
    * 
    * Web context is fetch from WebHandler's context for now.
    * 
    */
   private boolean _initialize()
   {
      if (this.request == null) 
      {
         WebContext context = WebHandler.getWebContext();
         
         if (context != null) 
         {
            this.basepath = context.getBasepath();
            this.target = context.getTarget();
            this.paths = context.getPaths();
            this.request = context.getRequest();
         }
         else 
         {
            ErrorManager.recordOrShowWarning(4088, 
                     "Progress does not support the GET-CGI-VALUE attribute for the PSEUDO-WIDGET in this display environment", 
                     true, false, false, true);
         }
      }
      
      return this.request != null;
   }

   /**
    * Get the HTTP method.
    * 
    * @return    the HTTP method.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Method")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getMethod()
   {
      return function(RemoteWebRequest.class, this, "Method", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(request.getMethod()));
      }));
   }

   /**
    * Set the HTTP method.  This is read-only.
    * 
    * @param   method 
    *          the HTTP method.
    */
   @LegacySignature(type = Type.SETTER, name = "Method", parameters =
   {
         @LegacyParameter(name = "Method", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setMethod(character method)
   {
      throwReadOnly("Method");
   }

   /**
    * Get the HTTP URI.
    *  
    * @return   the HTTP URI.
    */
   @LegacySignature(returns = "OBJECT", type = Type.GETTER, name = "URI", qualified = "OpenEdge.Net.URI"   )
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public object<? extends Uri> getUri()
   {
      return function(RemoteWebRequest.class, this, "URI", object.class, new Block((Body) () ->
      {
         returnNormal(Uri.parse(_initialize() ? new character(((Request) request).getHttpURI().toString()) : new character()));
      }));
   }

   /**
    * Set the HTTP URI.  This is read-only.
    * 
    * @param    uri
    *           the HTTP URI.
    */
   @LegacySignature(type = Type.SETTER, name = "URI", parameters =
   {
         @LegacyParameter(name = "uri", type = "OBJECT", 
               qualified = "OpenEdge.Net.URI", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setUri(object<? extends Uri> uri)
   {
      throwReadOnly("URI");
   }

   /**
    * Get the character encoding.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "CharacterEncoding")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getCharacterEncoding()
   {
      return function(RemoteWebRequest.class, this, "CharacterEncoding", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getCharacterEncoding())));
      }));
   }

   /**
    * Set the character encoding.  This is read-only.
    * 
    * @param   _encoding
    *           The character encoding.
    */
   @LegacySignature(type = Type.SETTER, name = "CharacterEncoding", parameters =
   {
         @LegacyParameter(name = "encoding", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setCharacterEncoding(character _encoding)
   {
      throwReadOnly("CharacterEncoding");
   }

   /**
    * Get the content length.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "ContentLength")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public integer getContentLength()
   {
      return function(RemoteWebRequest.class, this, "ContentLength", integer.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new integer());
         
         returnNormal(new integer(request.getContentLength()));
      }));
   }

   /**
    * Set the content length.  This is read-only.
    * 
    * @param   _var
    *          The content length.
    */
   @LegacySignature(type = Type.SETTER, name = "ContentLength", parameters =
   {
         @LegacyParameter(name = "var", type = "INTEGER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setContentLength(integer _var)
   {
      throwReadOnly("ContentLength");
   }

   /**
    * Get the content MD5.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "RAW", type = Type.GETTER, name = "ContentMD5")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public raw getContentMd5()
   {
      return function(RemoteWebRequest.class, this, "ContentLength", raw.class, new Block((Body) () ->
      {
         if (contentMD5.isUnknown() && _initialize())
         {
            try
            {
               // 4GL QUIRK - this will throw in 4GL because of the setter method throwing error
               contentMD5.assign(Base64.byteArrayToBase64(LegacyServiceHandler.readBody(request)));
            }
            catch (IOException e)
            {
               // no-op
            }
         }
         
         returnNormal(contentMD5);
      }));
   }

   /**
    * Set the content MD5.  This is not implemented.
    * 
    * @param    _var
    *           The content MD5.
    */
   @LegacySignature(type = Type.SETTER, name = "ContentMD5", parameters =
   {
         @LegacyParameter(name = "var", type = "RAW", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setContentMd5(raw _var)
   {
      throwNotImplementedProperty("ContentMD5");
   }

   /**
    * Get the content type.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "ContentType")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getContentType()
   {
      return function(RemoteWebRequest.class, this, "ContentType", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getContentType())));
      }));
   }

   /**
    * Set the content type.  This is read-only.
    * 
    * @param    _var
    *           The content type.
    */
   @LegacySignature(type = Type.SETTER, name = "ContentType", parameters =
   {
         @LegacyParameter(name = "var", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setContentType(character _var)
   {
      throwReadOnly("ContentType");
   }

   /**
    * Get the body entity.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "OBJECT", type = Type.GETTER, name = "Entity", qualified = "Progress.Lang.Object")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public object<? extends _BaseObject_> getEntity()
   {
      return function(RemoteWebRequest.class, this, "Entity", object.class, new Block((Body) () ->
      {
         if (!entity._isValid() && _initialize()) 
         {
            byte[] body;
            try
            {
               body = LegacyServiceHandler.readBody(request);
               
               if (body.length > 0)
               {
                  // delegate that to entity writer???
                  String ct = request.getContentType();
                  if (ct.indexOf(';') > 0)
                  {
                     ct = ct.substring(0, ct.indexOf(';')).trim();
                  }
                  object<? extends LegacyClass> writerType = EntityWriterRegistry.getRegistry().ref()._get(ct);
                  
                  if (writerType._isValid()) {
                     try 
                     {
                        object<? extends MessageWriter> writer = ObjectOps.cast(writerType.ref().new_(), MessageWriter.class);
                        writer.ref().open();
                        writer.ref().write(new memptr(body));
                        writer.ref().close();
                        
                        entity.assign(writer.ref().getEntity());
                     }
                     catch (Exception e) 
                     {
                        // fallback to raw body
                        entity.assign(ObjectOps.newInstance(Memptr.class, "I", new raw(body)));
                     }
                  }
                  else
                  {
                     // fallback to raw body
                     entity.assign(ObjectOps.newInstance(Memptr.class, "I", new raw(body)));
                  }
               }
            }
            catch (IOException e)
            {
               // no-op
            }
         }
         
         if (!entity._isValid())
         {
            entity.assign(ObjectOps.newInstance(Memptr.class, "I", new integer(0)));
         }
         
         returnNormal(entity);
      }));

   }

   /**
    * Set the body entity.  This is a read-only.
    * 
    * @param    _var
    *           The body entity.
    */
   @LegacySignature(type = Type.SETTER, name = "Entity", parameters =
   {
         @LegacyParameter(name = "Entity", type = "OBJECT", 
               qualified = "Progress.Lang.Object", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setEntity(object<? extends _BaseObject_> _var)
   {
      throwReadOnly("Entity");
   }

   /**
    * Get the transfer encoding.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "TransferEncoding")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getTransferEncoding()
   {
      return function(RemoteWebRequest.class, this, "TransferEncoding", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getHeader("Transfer-Encoding"))));
      }));
      
   }

   /**
    * Set the transfer encoding.  This is read-only.
    * 
    * @param    transferEncoding
    *           The transfer encoding.
    */
   @LegacySignature(type = Type.SETTER, name = "TransferEncoding", parameters =
   {
         @LegacyParameter(name = "TransferEncoding", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setTransferEncoding(character transferEncoding)
   {
      throwReadOnly("TransferEncoding");
   }

   /**
    * Get the HTTP version.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Version")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getVersion()
   {
      return function(RemoteWebRequest.class, this, "Version", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getProtocol())));
      }));
   }

   /**
    * Set the HTTP version.  This is read-only.
    * 
    * @param    version
    *           The HTTP version.
    */
   @LegacySignature(type = Type.SETTER, name = "Version", parameters =
   {
         @LegacyParameter(name = "version", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setVersion(character version)
   {
      throwReadOnly("Version");
   }

   /**
    * Clear the headers.  This is not implemented.
    */
   @LegacySignature(type = Type.METHOD, name = "ClearHeaders")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void clearHeaders()
   {
      throwNotImplementedMethod("ClearHeaders");
   }

   /**
    * Get the specified header's value.
    * 
    * @param    _pcName
    *           The header name.
    */
   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetHeader", qualified = "OpenEdge.Net.HTTP.HttpHeader", parameters = {
            @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT") })
   @Override
   public object<? extends HttpHeader> getHeader(character _pcName)
   {
      character pcName = TypeFactory.initInput(_pcName);
      
      return function(this, "GetHeader", object.class, new Block((Body) () ->
      {
         character val = new character();
         
         if (!pcName.isUnknown() && _initialize())
         {
            String name = pcName.toStringMessage();
            
            Enumeration<String> hnames = request.getHeaderNames();
            while (hnames.hasMoreElements())
            {
               String header = hnames.nextElement();
               
               if (name.equalsIgnoreCase(header))
                  val.assign(request.getHeader(header));
            }
         }
         
         if (!val.isUnknown())
         {
            returnNormal(HttpHeaderBuilder.build(pcName)
                  .ref().value(val).ref().getHeader());
         }
         else
         {
            returnNormal(HttpHeader.getNullHeader()); 
         }
      }));
   }

   /**
    * Populate the specified extent variable with all the headers.
    * 
    * @param    _poHeaders
    *           The header array.
    *           
    * @return   The number of read headers.
    */
   @LegacySignature(returns = "INTEGER", type = Type.METHOD, name = "GetHeaders", parameters = {
            @LegacyParameter(name = "poHeaders", type = "OBJECT", extent = -1, qualified = "OpenEdge.Net.HTTP.HttpHeader", mode = "OUTPUT") })
   @Override
   public integer getHeaders(OutputExtentParameter<object<? extends HttpHeader>> _poHeaders)
   {
      object<? extends HttpHeader>[][] poHeaders = new object[][] { TypeFactory.initOutput(_poHeaders) };
      
      return function(this, "GetHeaders", integer.class, new Block((Body) () ->
      {
         Map<String, String> headers = new LinkedHashMap<>();
         
         if (_initialize())
         {
            Enumeration<String> hnames = request.getHeaderNames();
            while (hnames.hasMoreElements())
            {
               String header = hnames.nextElement();
               headers.put(header, request.getHeader(header));
            }
         }
         
         poHeaders[0] = ArrayAssigner.resize(poHeaders[0], headers.size());
         int i = 1;
         for (String header : headers.keySet())
         {
            String value = headers.get(header);
            object<? extends HttpHeader> oheader = HttpHeaderBuilder.build(new character(header))
                     .ref().value(new character(value)).ref().getHeader();
            
            ArrayAssigner.assignSingle(poHeaders[0], i++, oheader);
         }
         
         returnNormal(poHeaders[0].length);
      }));
   }

   /**
    * Check if the specified header name exists.
    * 
    * @param    _pcName
    *           The header name.
    *           
    * @return   See above.
    */
   @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "HasCookie", parameters = {
            @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT") })
   @Override
   public logical hasHeader(character _pcName)
   {
      character pcName = TypeFactory.initInput(_pcName);
      
      return function(this, "HasHeader", logical.class, new Block((Body) () ->
      {
         if (pcName.isUnknown() || !_initialize())
               returnNormal(new logical());
            
         String name = _pcName.toStringMessage();
         
         Enumeration<String> hnames = request.getHeaderNames();
         while (hnames.hasMoreElements())
         {
            String header = hnames.nextElement();
            
            if (name.equalsIgnoreCase(header))
               returnNormal(new logical(true));
         }
         
         returnNormal(new logical(false));
      }));
   }

   /**
    * Remove the specified header.  This is not implemented.
    * 
    * @param    _pcName
    *           The header name.
    */
   @LegacySignature(type = Type.METHOD, name = "RemoveHeader", parameters = 
   {
      @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void removeHeader(character _pcName)
   {
      throwNotImplementedMethod("RemoveHeader");
   }

   /**
    * Set the specified header.  This is not implemented.
    * 
    * @param    _poHeader
    *           The header instance.
    */
   @LegacySignature(type = Type.METHOD, name = "SetHeader", parameters = 
   {
      @LegacyParameter(name = "poHeader", type = "OBJECT", 
               qualified = "OpenEdge.Net.HTTP.HttpHeader", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setHeader(object<? extends HttpHeader> _poHeader)
   {
      throwNotImplementedMethod("SetHeader");
   }


   /**
    * Set the specified header.  This is not implemented.
    * 
    * @param    _pcName
    *           The header name.
    * @param    _pcValue
    *           The header value.
    * @return see above
    */
   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "SetHeader", parameters = 
   {
      @LegacyParameter(name = "poHeader", type = "CHARACTER", mode = "INPUT"),
      @LegacyParameter(name = "pcValue", type = "CHARACTER", mode = "INPUT")
   }, qualified = "OpenEdge.Net.HTTP.HttpHeader")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   public object<? extends HttpHeader> setHeader(character _pcName, character _pcValue)
   {
      return function(this, "SetHeader", object.class, new Block((Body) () -> 
      {
         throwNotImplementedMethod("SetHeader");
      }));
   }
   
   /**
    * Resolve the web app path.
    * 
    * @return   The servlet path.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "ResolvedWebAppPath")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getResolvedWebAppPath()
   {
      return function(RemoteWebRequest.class, this, "ResolvedWebAppPath", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getServletPath())));
      }));
      
   }

   /**
    * Get the context names.  This returns all {@link #CGI_VARS} keys.
    * 
    * @return   A comma-separated list of CGI var names.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetContextNames")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getContextNames()
   {
      return function(RemoteWebRequest.class, this, "GetContextNames", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         String res = "";
         for (String key : CGI_VARS.keySet())
         {
            res = res + (res.isEmpty() ? "" : ",") + key;
         }
         
         returnNormal(new character(res));
      }));
   }

   /**
    * Get the default cookie domain.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "DefaultCookieDomain")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getDefaultCookieDomain()
   {
      UnimplementedFeature.missing("RemoteWebRequest.getDefaultCookiePath");
      
      return function(RemoteWebRequest.class, this, "DefaultCookieDomain", character.class, new Block((Body) () ->
      {
         // TODO: WEB properties in multi-session agent appservers
         if (_initialize())
            ;
         
         returnNormal(new character());
      }));
   }

   /**
    * Get the default cookie path.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "DefaultCookiePath")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getDefaultCookiePath()
   {
      UnimplementedFeature.missing("RemoteWebRequest.getDefaultCookiePath");
      
      return function(RemoteWebRequest.class, this, "DefaultCookiePath", character.class, new Block((Body) () ->
      {
         // TODO: WEB properties in multi-session agent appservers
         if (_initialize())
            ;
         
         returnNormal(new character());
      }));
   }

   /**
    * Get the local address.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "LocalAddress")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getLocalAddress()
   {
      return function(RemoteWebRequest.class, this, "LocalAddress", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getLocalAddr())));
      }));
      
   }

   /**
    * Get the local host.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "LocalHost")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getLocalHost()
   {
      return function(RemoteWebRequest.class, this, "LocalHost", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getLocalName())));
      }));
   }

   /**
    * Get the local port.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "LocalPort")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public integer getLocalPort()
   {
      return function(RemoteWebRequest.class, this, "LocalPort", integer.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new integer());
         
         returnNormal(new integer(request.getLocalPort()));
      }));
   }

   /**
    * Get the path information.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "PathInfo")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getPathInfo()
   {
      return function(RemoteWebRequest.class, this, "PathInfo", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(_getPathInfo()));
      }));
      
   }
   
   protected String _getPathInfo()
   {
      // the URL after the transport path (i.e. basePath)
      String res = target;
      if (res.indexOf("?") > 0)
      {
         res = res.substring(0, res.indexOf('?'));
      }
      if (!res.startsWith("/"))
      {
         res = "/" + res;
      }
      while (res.startsWith("//"))
      {
         res = res.substring(1);
      }
      
      return res;
   }

   /**
    * Get the path parameter names.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetPathParameterNames")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getPathParameterNames()
   {
      return function(this, "GetPathParameterNames", character.class, new Block((Body) () ->
      {
         String pnames = "";
         
         if (_initialize())
         {
             pnames = Arrays.stream(paths)
                     .filter(n -> n.startsWith("{") && n.endsWith("}"))
                     .map(n -> n.substring(1, n.length() - 1))
                     .collect(Collectors.joining(","));
         }
         
         returnNormal(new character(pnames));
      }));
      
   }

   /**
    * Get the remote address.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "RemoteAddress")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getRemoteAddress()
   {
      return function(RemoteWebRequest.class, this, "RemoteAddress", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getRemoteAddr())));
      }));
   }

   /**
    * Get the remote host.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "RemoteHost")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getRemoteHost()
   {
      return function(RemoteWebRequest.class, this, "RemoteHost", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getRemoteHost())));
      }));
   }

   /**
    * Get the remote port.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "RemotePort")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public integer getRemotePort()
   {
      return function(RemoteWebRequest.class, this, "RemotePort", integer.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new integer());
         
         returnNormal(new integer(request.getRemotePort()));
      }));
   }

   /**
    * Get the remote user.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "RemoteUser")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getRemoteUser()
   {
      return function(RemoteWebRequest.class, this, "RemoteUser", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getRemoteUser())));
      }));
      
   }

   /**
    * Get the resolved transport path.
    * 
    * @return   Always empty string.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "ResolvedTransportPath")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getResolvedTransportPath()
   {
      // always empty
      return function(RemoteWebRequest.class, this, "ResolvedTransportPath", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(""));
      }));
   }

   /**
    * Get the server software info.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "ServerSoftware")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getServerSoftware()
   {
      return function(RemoteWebRequest.class, this, "ServerSoftware", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getServletContext().getServerInfo())));
      }));
      
   }

   /**
    * Get the transport path.
    * 
    * @return   The {@link #basepath}.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "TransportPath")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getTransportPath()
   {
      return function(RemoteWebRequest.class, this, "TransportPath", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(basepath)));
      }));
   }

   /**
    * Get the URI template.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "UriTemplate")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getUriTemplate()
   {
      return function(RemoteWebRequest.class, this, "UriTemplate", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         String pnames = Arrays.stream(paths)
                  .collect(Collectors.joining("/", "/", ""));

         returnNormal(new character(pnames));
      }));
   }

   /**
    * Get the servlet path.
    * 
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "WebAppPath")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getWebAppPath()
   {
      return function(RemoteWebRequest.class, this, "WebAppPath", character.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new character());
         
         returnNormal(new character(emptyIfNull(request.getServletPath())));
      }));
   }

   /**
    * Get the context value with the specified name.
    * 
    * @param    _pcName
    *           The context name.
    *           
    * @return   The context value.
    */
   @LegacySignature(returns = "LONGCHAR", type = Type.METHOD, name = "GetContextValue", parameters = {
            @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT") })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public longchar getContextValue(character _pcName)
   {
      return function(RemoteWebRequest.class, this, "GetContextValue", longchar.class, new Block((Body) () ->
      {
         if (!_initialize())
            returnNormal(new longchar());
         
         String val = _pcName.toStringMessage();
         String key = val.toUpperCase();
         if (CGI_VARS.containsKey(key))
         {
            Function<HttpServletRequest, Object> f = CGI_VARS.get(key);
            Object res = f.apply(request);
            if (res == UNSUPPORTED_CGI_VAR)
            {
               // check if is an explicit one
               switch (key)
               {
                  case "BASE_PATH":
                     res = basepath;
                     break;
                  case "SERVLET_APPLICATION_URL":
                     res = request.getServletPath();
                     break;
                  case "URI_FINAL_MATCH_GROUP":
                     res = "/";
                     break;
                  case "URI_TEMPLATE":
                     res = "";
                     for (String p : paths)
                     {
                        res = res + "/" + p;
                     }
                     break;
                  default:
                     res = "UNSUPPORTED";
                     UnimplementedFeature.missing("RemoteWebRequest.getContextValue(" + key + ")");
                     break;
               }
            }

            returnNormal(new longchar(res == null ? "" : res.toString()));
         }

         if (val.startsWith("HTTP_"))
         {
            // this is a header
            String headerName = val.substring("HTTP_".length());
            returnNormal(new longchar(emptyIfNull(request.getHeader(headerName))));
         }

         returnNormal(new longchar("")); // empty string always
         
      }));
      
      
   }

   /**
    * Get the path parameter value, with the specified name.
    * 
    * @param    _pcName
    *           The path parameter name.
    *           
    * @return   See above.
    */
   @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetPathParameter", parameters = {
            @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT") })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public character getPathParameter(character _pcName)
   {
      character pcName = TypeFactory.initInput(_pcName);
      
      return function(this, "GetPathParameter", character.class, new Block((Body) () ->
      {
         if (!pcName.isUnknown() && _initialize())
         {
            String param = pcName.toStringMessage();
            
            for (int i = 0; i < paths.length; i++)
            {
               if (paths[i].equalsIgnoreCase("{" + param + "}"))
               {
                  // skip leading /, entry is 1-base
                  returnNormal(TextOps.entry(i + 1, _getPathInfo().substring(1), "/"));
               }
            }
            
         }
         
         returnNormal(new character(""));
         
      }));
   }
      
      

   /**
    * Get the request cookies.
    * 
    * @param    _poCookies
    *           The extent instance to populate with cookies.
    *           
    * @return   The number of found cookies.
    */
   @LegacySignature(returns = "INTEGER", type = Type.METHOD, name = "GetCookies", parameters = {
            @LegacyParameter(name = "poCookies", type = "OBJECT", extent = -1, qualified = "OpenEdge.Net.HTTP.Cookie", mode = "OUTPUT") })
   
   @Override
   public integer getCookies(OutputExtentParameter<object<? extends Cookie>> _poCookies)
   {
      object<? extends HttpHeader>[][] poCookies = new object[][] { TypeFactory.initOutput(_poCookies) };
      
      return function(this, "GetCookies", integer.class, new Block((Body) () ->
      {
         javax.servlet.http.Cookie[] cookies = request.getCookies();
         if (_initialize() &&  cookies != null) 
         {
            poCookies[0] = ArrayAssigner.resize(poCookies[0], cookies.length);
            
            int i = 1;
            
            for (javax.servlet.http.Cookie cookie : cookies)
            {
               object<? extends Cookie> oCookie = _getCookie(cookie);
               
               ArrayAssigner.assignSingle(poCookies[0], i++, oCookie);
            }
         }

         returnNormal(poCookies[0].length);
      }));
   }

   /**
    * Get the cookie with the specified name.
    * 
    * @param    _pcName
    *           The cookie name.
    *           
    * @return   The cookie instance.
    */
   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetCookie", qualified = "OpenEdge.Net.HTTP.Cookie", parameters = {
            @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT") })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   @Override
   public object<? extends Cookie> getCookie(character _pcName)
   {
      return function(this, "GetCookie", object.class, new Block((Body) () -> {

         String name = _pcName.isUnknown() ? "" : _pcName.toStringMessage().trim();

         javax.servlet.http.Cookie[] cookies = request.getCookies();
         if ( _initialize() && !name.isEmpty() && cookies != null)
         {
            for (javax.servlet.http.Cookie cookie : cookies)
            {
               if (cookie.getName().equalsIgnoreCase(name))
                  returnNormal(_getCookie(cookie));
            }
         }

         returnNormal(new object());
      }));
   }

   private object<? extends Cookie> _getCookie(javax.servlet.http.Cookie cookie)
   {
      return ObjectOps.newInstance(Cookie.class, "IIIIIIIII", 
               new character(cookie.getName()),
               new character(cookie.getDomain() != null ? cookie.getDomain() : request.getLocalName()),
               new character(cookie.getPath() != null ? cookie.getPath() : "/"),
               new character(cookie.getValue()),
               cookie.getMaxAge() > 0 ? new integer(cookie.getMaxAge()) : new integer(),
               new datetimetz(),
               new logical(cookie.getSecure()),
               new logical(cookie.isHttpOnly()),
               cookie.getVersion() > 0 ? new decimal(cookie.getVersion()) : new decimal());
   }
   
   /**
    * Set or add the specified cookie.  This is a no-op.
    * 
    * @param    _poCookie
    *           The cookie to set.
    */
   @LegacySignature(type = Type.SETTER, name = "SetCookie", parameters = 
   {
      @LegacyParameter(name = "poHeader", type = "OBJECT", 
               qualified = "OpenEdge.Net.HTTP.Cookie", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setCookie(object<? extends Cookie> _poCookie)
   {
      throwNotImplementedMethod("SetCookie");
   }

   /**
    * Check if the specified cookie exists in the request.
    * 
    * @param    _poCookie
    *           The cookie instance.
    *           
    * @return   See above.
    */
   @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "HasCookie", parameters = {
            @LegacyParameter(name = "poCookie", type = "OBJECT", qualified = "OpenEdge.Net.HTTP.Cookie", mode = "INPUT") })
   @Override
   public logical hasCookie(object<? extends Cookie> _poCookie)
   {
      object<? extends Cookie> poCookie = TypeFactory.initInput(_poCookie);
      
      return function(this, "HasCookie", logical.class, new Block((Body) () ->
      {
         Assert.notNull(poCookie, new character("Cookie"));
         
         returnNormal(hasCookie(poCookie.ref().getName()));
      }));
   }

   /**
    * Check if the specified cookie exists in the request.
    * 
    * @param    _pcName
    *           The cookie name.
    *           
    * @return   See above.
    */
   @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "HasCookie", parameters = {
            @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT") })
   @Override
   public logical hasCookie(character _pcName)
   {
      character pcName = TypeFactory.initInput(_pcName);
      
      return function(this, "HasCookie", logical.class, new Block((Body) () ->
      {
         Assert.notNullOrEmpty(pcName, new character("Cookie name"));
         
         javax.servlet.http.Cookie[] cookies = request.getCookies();
         if (_initialize() && cookies != null)
         {
            String name = _pcName.toStringMessage();
            
            for (javax.servlet.http.Cookie cookie : cookies)
            {
               if (cookie.getName().equalsIgnoreCase(name))
               {
                  returnNormal(new logical(true));
               }
            }
         }
         
         returnNormal(new logical(false));
      }));
   }

   /**
    * Remove the specified cookie.  This is not implemented.
    * 
    * @param    _pcName
    *           The cookie name.
    */
   @LegacySignature(type = Type.METHOD, name = "RemoveCookie", parameters = 
   {
      @LegacyParameter(name = "pcName", type = "CHARACTER", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void removeCookie(character _pcName)
   {
      throwNotImplementedMethod("RemoveCookie");
   }

   /**
    * Remove the specified cookie.  This is a no-op.
    * 
    * @param    _poCookie
    *           The cookie instance.
    */
   @LegacySignature(type = Type.METHOD, name = "RemoveCookie", parameters = 
   {
      @LegacyParameter(name = "poHeader", type = "OBJECT", 
               qualified = "OpenEdge.Net.HTTP.Cookie", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void removeCookie(object<? extends Cookie> _poCookie)
   {
      throwNotImplementedMethod("RemoveCookie");
   }

   /**
    * Clear all cookies.  This is a no-op.
    */
   @LegacySignature(type = Type.METHOD, name = "ClearCookies")
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void clearCookies()
   {
      throwNotImplementedMethod("ClearCookies");
   }
   
   /**
    * Return the empty value for a null string.
    * 
    * @param    val
    *           The string.
    *           
    * @return   See above.
    */
   private String emptyIfNull(String val)
   {
      return val == null ? "" : val;
   }

   @LegacySignature(type = Type.METHOD, name = "SetCookies", parameters = 
   {
      @LegacyParameter(name = "poHeader", type = "OBJECT", 
               qualified = "OpenEdge.Net.HTTP.Cookie", extent = -1, mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setCookies(object<? extends Cookie>[] _poCookies)
   {
      throwNotImplementedMethod("SetCookies");
   }

   @LegacySignature(type = Type.METHOD, name = "SetHeaders", parameters = 
   {
      @LegacyParameter(name = "poHeader", type = "OBJECT", 
               qualified = "OpenEdge.Net.HTTP.HttpHeader", extent = -1, mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   @Override
   public void setHeaders(object<? extends HttpHeader>[] _poHeader)
   {
      throwNotImplementedMethod("SetHeaders");
   }
   
   private void throwReadOnly (String name) 
   {
      undoThrow(AppError.newInstance(TextOps.substitute("OpenEdge.Web.WebRequest.&1 property is read-only", name), new integer()));
   }
   
   private void throwNotImplementedMethod (String name) 
   {
      undoThrow(AppError.newInstance(TextOps.substitute("OpenEdge.Web.WebRequest.&1 method is not implemented", name), new integer()));
   }
   
   private void throwNotImplementedProperty (String name) 
   {
      undoThrow(AppError.newInstance(TextOps.substitute("OpenEdge.Web.WebRequest.&1 property is not implemented", name), new integer()));
   }
}