DefaultRequestFilter.java

/*
** Module   : DefaultRequestFilter.java
** Abstract : Implementation of the builtin class.
**
** Copyright (c) 2020-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 MP  20200603 First version, stubs taken by converting the skeleton using FWD.
** 002 MP  20210108 Adapt error message and do a cast for method access
**     ME  20210204 Fix writeMessage and extractEntity methods.
** 003 CA  20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy signature.
**     CA  20220120 Do not used TypeFactory.object when the OO reference must not be tracked or registered.
**                  Do not use TypeFactory.object for internal usages, use ObjectVar if the reference must 
**                  be tracked.
**                  All TypeFactory.object variable definitions must be done outside of the top-level block.
** 004 ME  20210929 Fixed return object on extractEntity.
**     CA  20220923 Variable definitions (including associated with parameters) must be done always outside of 
**                  the BlockManager API
** 005 CA  20231113 The 'execute' method must be annotated with LegacySignature Type.Execute, and also can be
**                  dropped if is a no-op.
** 006 AL2 20240930 Delete temporary ByteBuckets; they are still in memory due to positive reference count.
*/

/*
** 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.net.http.filter.payload;

import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.BlockManager.Action;
import com.goldencode.p2j.util.BlockManager.Condition;

import static com.goldencode.p2j.report.ReportConstants.CVT_LVL_FULL;
import static com.goldencode.p2j.report.ReportConstants.RT_LVL_STUB;
import static com.goldencode.p2j.report.ReportConstants.RT_LVL_FULL;
import static com.goldencode.p2j.util.BlockManager.*;
import static com.goldencode.p2j.util.InternalEntry.Type;

import java.util.concurrent.atomic.AtomicReference;

import com.goldencode.p2j.oo.core.ByteBucket;
import com.goldencode.p2j.oo.core.HashAlgorithmEnum;
import com.goldencode.p2j.oo.core.IAdaptable;
import com.goldencode.p2j.oo.core.assertion.AssertObject;
import com.goldencode.p2j.oo.lang.AppError;
import com.goldencode.p2j.oo.lang._BaseObject_;

import static com.goldencode.p2j.util.character.*;
import com.goldencode.p2j.oo.net.ISupportEncoding;
import com.goldencode.p2j.oo.net.ISupportMultipartEntity;
import com.goldencode.p2j.oo.net.ISupportTransferEncoding;
import com.goldencode.p2j.oo.net.http.HttpHeader;
import com.goldencode.p2j.oo.net.http.ISupportProxy;
import com.goldencode.p2j.oo.net.http.IhttpRequest;
import com.goldencode.p2j.oo.net.http.TransferEncodingEnum;
import com.goldencode.p2j.oo.net.http.filter.writer.BodyWriterBuilder;
import com.goldencode.p2j.oo.net.http.filter.writer.MessageWriterBuilder;

/**
 * Business logic (converted to Java from the 4GL source code
 * in OpenEdge/Net/HTTP/Filter/Payload/DefaultRequestFilter.cls).
 */
@LegacyResource(resource = "OpenEdge.Net.HTTP.Filter.Payload.DefaultRequestFilter")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
public class DefaultRequestFilter
extends com.goldencode.p2j.oo.net.http.filter.payload.MessageWriter
{
   @LegacySignature(type = Type.EXECUTE)
   public void __net_http_filter_payload_DefaultRequestFilter_execute__()
   {
      onBlockLevel(Condition.ERROR, Action.THROW);
   }

   @LegacySignature(type = Type.CONSTRUCTOR)
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   public void __net_http_filter_payload_DefaultRequestFilter_constructor__()
   {
      internalProcedure(DefaultRequestFilter.class, this, "__net_http_filter_payload_DefaultRequestFilter_constructor__", new Block((Body) () -> 
      {
         __net_http_filter_payload_MessageWriter_constructor__(ObjectOps.getLegacyClass(ByteBucket.class));
      }));
   }

   @LegacySignature(returns = "INT64", type = Type.METHOD, name = "Write", parameters = 
   {
      @LegacyParameter(name = "poData", type = "OBJECT", qualified = "progress.lang.object", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
   @Override
   public int64 write(final object<? extends _BaseObject_> _poData)
   {
      object<? extends _BaseObject_> poData = TypeFactory.initInput(_poData);
      
      return function(DefaultRequestFilter.class, this, "Write", int64.class, new Block((Body) () -> 
      {         
         AssertObject.isType(poData, ObjectOps.getLegacyClass(IhttpRequest.class));
         
         int64 prevSize = bucket().getSize();
         
         this.writeMessage(ObjectOps.cast(poData, IhttpRequest.class));
         
         int64 actualSize = bucket().getSize();
         
         returnNormal(actualSize.intValue() - prevSize.intValue());

      }));
   }

   @LegacySignature(type = Type.METHOD, name = "WriteMessage", parameters = 
   {
      @LegacyParameter(name = "poRequest", type = "OBJECT", qualified = "openedge.net.http.ihttprequest", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
   protected void writeMessage(final object<? extends IhttpRequest> _poRequest)
   {
      object<? extends IhttpRequest> poRequest = TypeFactory.initInput(_poRequest);
      object<? extends ByteBucket> oByteBucket = TypeFactory.object(ByteBucket.class);
      
      internalProcedure(DefaultRequestFilter.class, this, "WriteMessage", new Block((Body) () -> 
      {
         if (poRequest.ref().getEntity()._isValid())
         {
            oByteBucket.assign(this.extractEntity(poRequest));
         }
         else
         {
            poRequest.ref().removeHeader(new character("Content-Type"));
            poRequest.ref().removeHeader(new character("Content-Length"));
         }
         
         this.writeRequestLine(poRequest);
         this.writeHeaders(poRequest);
         
         bucket().putString(new longchar(TextOps.concat(chr(13), chr(10))));
         
         if (poRequest.ref().getContentLength().intValue() > 0)
         {            
            bucket().putBytes(oByteBucket);
         }
      }, 
      (Fini) () -> 
      {
         if (oByteBucket._isValid())
         {
            ObjectOps.delete(oByteBucket);
         }
      }));
   }

   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "ExtractEntity", qualified = "openedge.core.bytebucket", parameters = 
   {
      @LegacyParameter(name = "poRequest", type = "OBJECT", qualified = "openedge.net.http.ihttprequest", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
   protected object<? extends ByteBucket> extractEntity(final object<? extends IhttpRequest> _poRequest)
   {
      object<? extends IhttpRequest> poRequest = TypeFactory.initInput(_poRequest);
      object<? extends ByteBucket> oMessageBody = TypeFactory.object(ByteBucket.class);
      
      return function(DefaultRequestFilter.class, this, "ExtractEntity", object.class, new Block((Body) () -> 
      {
         oMessageBody.assign(ByteBucket.instance());
         
         object<? extends MessageWriterBuilder> oMWB = BodyWriterBuilder.build_1(poRequest);
         object<? extends MessageWriter> oBodyWriter = oMWB.ref().writeTo(oMessageBody).ref().getWriter();

         if (!oBodyWriter._isValid())
         {
            character errMsg = TextOps.substitute("Unable to build request for transmission: no builder available for Content-Type: &1",
                     poRequest.ref().getContentType());
            logger.ref().error(errMsg);
            undoThrow(AppError.newInstance(errMsg, new integer(0)));
         }
         
         oBodyWriter.ref().open();
         int64 iBytesWritten = oBodyWriter.ref().write(poRequest.ref().getEntity());
         oBodyWriter.ref().close();
         
         if (ObjectOps.typeOf(oBodyWriter, ISupportEncoding.class).booleanValue())
         {
            object<? extends ISupportEncoding> oSuppEnc = ObjectOps.cast(oBodyWriter, ISupportEncoding.class);
            poRequest.ref().setCharacterEncoding(oSuppEnc.ref().getEncoding());
         }
         if (ObjectOps.typeOf(oBodyWriter, ISupportMultipartEntity.class).booleanValue())
         {
            object<? extends ISupportMultipartEntity> oSuppMultiEnt = ObjectOps.cast(oBodyWriter, ISupportMultipartEntity.class);
            poRequest.ref().getHeader(new character("Content-Type")).ref().setParameterValue(new character("boundary"), oSuppMultiEnt.ref().getBoundary());
         }
         if (ObjectOps.typeOf(oBodyWriter, ISupportTransferEncoding.class).booleanValue())
         {
            object<? extends ISupportTransferEncoding> oSuppTransfEnc = ObjectOps.cast(oBodyWriter, ISupportTransferEncoding.class);
            poRequest.ref().setTransferEncoding(oSuppTransfEnc.ref().getTransferEncoding());
         }
                  
         poRequest.ref().setContentLength(new integer(iBytesWritten));
         oMessageBody.assign(ObjectOps.cast(oBodyWriter.ref().getEntity(), ByteBucket.class));
         poRequest.ref().setContentMd5(oMessageBody.ref().getHash(HashAlgorithmEnum.md5));
         
         returnNormal(oMessageBody);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "WriteRequestLine", parameters = 
   {
      @LegacyParameter(name = "poRequest", type = "OBJECT", qualified = "openedge.net.http.ihttprequest", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
   protected void writeRequestLine(final object<? extends IhttpRequest> _poRequest)
   {
      object<? extends IhttpRequest> poRequest = TypeFactory.initInput(_poRequest);
      object<? extends ISupportProxy> oSuppProxy = TypeFactory.object(ISupportProxy.class);
      character cReqUri = TypeFactory.character();
      
      internalProcedure(DefaultRequestFilter.class, this, "WriteRequestLine", new Block((Body) () -> 
      {
         if (ObjectOps.typeOf(poRequest, IAdaptable.class).booleanValue())
         {
            object<? extends IAdaptable> oIAdapt = ObjectOps.cast(poRequest, IAdaptable.class);
            object<? extends _BaseObject_> oLegClass = oIAdapt.ref().getAdapter(ObjectOps.getLegacyClass(ISupportProxy.class));
            oSuppProxy.assign(ObjectOps.cast(oLegClass, ISupportProxy.class));
         }
         else if (ObjectOps.typeOf(poRequest, ISupportProxy.class).booleanValue())
         {
            oSuppProxy.assign(ObjectOps.cast(poRequest, ISupportProxy.class));
         }
                  
         if (oSuppProxy._isValid() && oSuppProxy.ref().getProxyURI()._isValid())
         {
            cReqUri.assign(ObjectOps.cast(oSuppProxy, IhttpRequest.class).ref().getUri().ref().encode());            
         }
         else
         {
            cReqUri.assign(TextOps.concat(poRequest.ref().getUri().ref().getPath(), 
                     poRequest.ref().getUri().ref().encodeQuery()));
         }
         
         character cMsg = TextOps.concat(
                  TextOps.toUpperCase(poRequest.ref().getMethod()), " ",
                  cReqUri, " ",
                  poRequest.ref().getVersion(),
                  chr(13), chr(10));
         
         bucket().putString(new longchar(cMsg));

      }));
   }

   @LegacySignature(type = Type.METHOD, name = "WriteHeaders", parameters = 
   {
      @LegacyParameter(name = "poRequest", type = "OBJECT", qualified = "openedge.net.http.ihttprequest", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
   protected void writeHeaders(final object<? extends IhttpRequest> _poRequest)
   {
      object<? extends IhttpRequest> poRequest = TypeFactory.initInput(_poRequest);
      object[][] headers = { TypeFactory.objectExtent(HttpHeader.class, false) };
      
      internalProcedure(DefaultRequestFilter.class, this, "WriteHeaders", new Block((Body) () -> 
      {
         int nh = _poRequest.ref().getHeaders(
                  new OutputExtentParameter<object<? extends HttpHeader>>()
                  {
                     @Override
                     public object<? extends HttpHeader>[] getVariable()
                     {
                        return headers[0];
                     }

                     @Override
                     public void setVariable(object<? extends HttpHeader>[] reference)
                     {
                        headers[0] = reference;
                     }
                  }).intValue();
         
         for (int i = nh - 1; i >= 0; i--)
         {
            object<? extends HttpHeader> hdr = headers[0][i];
            character cMsg = TextOps.concat(hdr.ref().toLegacyString(), chr(13), chr(10));
            bucket().putString(new longchar(cMsg));
         }
         
         if (!poRequest.ref().hasHeader(new character("Accept")).booleanValue())
         {
            character cMsg = TextOps.concat("Accept: */*", chr(13), chr(10));
            bucket().putString(new longchar(cMsg));
         }
         
         if (!CompareOps._isEqual(poRequest.ref().getTransferEncoding(), TransferEncodingEnum.none.ref().toLegacyString()))
         {
            character cMsg = TextOps.concat("Accept-Encoding: ", TextOps.toLowerCase(poRequest.ref().getTransferEncoding()), 
                     chr(13), chr(10));
            bucket().putString(new longchar(cMsg));
         }
         
      }));
   }
}