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