How to Cut SAP HANA Storage by Using AWS S3 in ABAP RAP Apps

Hey SAP developers! Welcome to Part 3 of our hands-on blog and video series focused on building SAP ABAP RAP applications that integrate with AWS services.

In Part 2, we configured the AWS SDK for SAP ABAP to enable secure interaction with Amazon S3 for storage and Textract for OCR capabilities. In this tutorial, you’ll learn how to:

  • Upload invoice attachments to AWS S3 from your RAP app
  • Retrieve stored invoices directly from S3
  • Delete invoices from S3 when removed in SAP

๐Ÿ“Œ By the end of this tutorial, youโ€™ll have a working RAP app capable of fully managing invoice documents in the cloud.

๐Ÿ“น Watch the Video

๐Ÿ“ Step 1: Download and Demo the Base RAP Application

This tutorial builds on a pre-existing RAP application. We wonโ€™t cover the RAP fundamentals here โ€” the focus is AWS integration.

  1. Visit the GitHub repo and switch to the starting-app branch.
  2. Create a New Online Repo with program ZABAPGIT_STANDALONE selecting starting-app branch
  3. Import the objects into a package (e.g., ZINVOICE).
  4. Publish the Service Binding.
  5. Create a number range interval in transaction SNUM for your invoice object.

After setup, preview the application. You should see:

  • Header + line item information
  • File attachment upload capability
  • Create, Approve, and Reject actions

โš ๏ธ If you see a virus scan profile error, disable the scan temporarily via /n/wfnd/virus_scan for local Docker systems. For productive scenarios refer to SAP note 3027559

๐Ÿ› ๏ธ Step 2: Create and Test an AWS Wrapper Class

Instead of writing AWS S3 logic inline, weโ€™ll encapsulate it in a dedicated class: zdm_cl_aws_invoice_storage.

Why a Wrapper Class?

  • Centralizes reuse of SDK profile, logical resource, and S3 operations
  • Simplifies testing

Methods Implemented

  • constructor: for initialising the SDK profile and logical resource
  • get_object: retrieves an invoice by filename
  • put_object: uploads an invoice, optionally deleting a previous version
  • delete_object: removes a specific invoice file
  • get_file_name: uses regex to rename uploaded files to <invoice_id>.<extension>

For AWS examples see here

Source Code

zdm_cl_aws_invoice_storage
CLASS zdm_cl_aws_invoice_storage DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    METHODS constructor IMPORTING iv_invoice_id TYPE zdm_int_invnum
                        RAISING
                                  /aws1/cx_rt_technical_generic
                                  /aws1/cx_rt_no_auth_generic
                                  /aws1/cx_rt_service_generic.
    METHODS get_object
      IMPORTING
                iv_filename   TYPE zdm_file_name
      RETURNING VALUE(result) TYPE xstring
      RAISING
                /aws1/cx_rt_technical_generic
                /aws1/cx_rt_service_generic.

    METHODS delete_object
      IMPORTING
        iv_filename TYPE zdm_file_name
      RAISING
        /aws1/cx_rt_technical_generic
        /aws1/cx_rt_service_generic.

    METHODS put_object
      IMPORTING
        iv_filename     TYPE zdm_file_name
        iv_old_filename TYPE zdm_file_name
        iv_body         TYPE xstring
      RAISING
        /aws1/cx_rt_technical_generic
        /aws1/cx_rt_service_generic.

    METHODS get_filename
      IMPORTING
                iv_filename   TYPE zdm_file_name
      RETURNING VALUE(result) TYPE zdm_file_name.

  PROTECTED SECTION.
  PRIVATE SECTION.
    CONSTANTS: cv_pfl TYPE /aws1/rt_profile_id VALUE 'ZINVOICE'.
    CONSTANTS: cv_lres TYPE  /aws1/rt_resource_logical VALUE'ZINVOICE_BUCKET'.
    DATA: bucket     TYPE /aws1/s3_bucketname.
    DATA: o_s3       TYPE REF TO /aws1/if_s3.
    DATA: invoice_id TYPE zdm_int_invnum.
ENDCLASS.



CLASS zdm_cl_aws_invoice_storage IMPLEMENTATION.
  METHOD constructor.
    DATA(go_session) = /aws1/cl_rt_session_aws=>create( cv_pfl   ).
    bucket   = go_session->resolve_lresource( cv_lres ).
    o_s3       = /aws1/cl_s3_factory=>create( go_session ).
    invoice_id = iv_invoice_id.
  ENDMETHOD.

  METHOD get_object.
    DATA(oo_result) = o_s3->getobject(           " oo_result is returned for testing purposes. "
                  iv_bucket = bucket
                  iv_key = CONV /aws1/s3_objectkey( iv_filename ) ).
    result = oo_result->get_body( ).
  ENDMETHOD.


  METHOD delete_object.
    o_s3->deleteobject(  iv_bucket = bucket
                  iv_key = CONV /aws1/s3_objectkey( iv_filename ) ).
  ENDMETHOD.

  METHOD put_object.
    DATA(new_filename) = get_filename( iv_filename = iv_filename ).

    IF iv_old_filename IS NOT INITIAL.
      delete_object( iv_filename = iv_old_filename ).
    ENDIF.

    o_s3->putobject(   iv_bucket = bucket
                  iv_key = CONV /aws1/s3_objectkey( new_filename )
                  iv_body = iv_body ).

  ENDMETHOD.

  METHOD get_filename.
    DATA: lv_ext TYPE string.
    FIND PCRE '\.([^\.]+)$' IN iv_filename SUBMATCHES lv_ext.
    result = |{ invoice_id }.{ lv_ext }|.
  ENDMETHOD.

ENDCLASS.

๐Ÿ’พ Step 3: Implement RAP Determination on Save

Next, we create a determination in the behavior definition of the RAP model to automatically upload attachments to S3 when an invoice is saved.

In your behavior definition ZDM_BP_INVHDRTP:

Create a determination on save for create + update.

    ZDM_BP_INVHDRTP
      determination uploadToS3 on save { create; update; }

    Implement Determination:

    uploadToS3
    METHOD uploadToS3.
        READ ENTITIES OF zdm_r_invhdrtp IN LOCAL MODE
            ENTITY Invoice ALL FIELDS WITH CORRESPONDING #( keys )
            RESULT FINAL(lt_invoices_entity).
    
        LOOP AT lt_invoices_entity INTO DATA(invoice_entity)
            WHERE TmpAttachment IS NOT INITIAL.
    
          DATA(lv_success) = abap_true.
    
    
          TRY.
              DATA(storage_helper) = NEW zdm_cl_aws_invoice_storage( invoice_entity-InvoiceID ).
              storage_helper->put_object( iv_filename = invoice_entity-tmpfilename
                                          iv_old_filename = invoice_entity-filename
                                          iv_body = invoice_entity-tmpattachment ).
    
            CATCH /aws1/cx_rt_technical_generic /aws1/cx_rt_service_generic  /aws1/cx_rt_no_auth_generic.
              "handle exception
              lv_success = abap_false.
          ENDTRY.
    
          IF lv_success EQ abap_true.
            invoice_entity-Filename = storage_helper->get_filename( invoice_entity-TmpFilename ).
            invoice_entity-Mimetype = invoice_entity-TmpMimetype.
            CLEAR invoice_entity-TmpAttachment.
            CLEAR invoice_entity-TmpMimetype.
            CLEAR invoice_entity-TmpFilename.
    
            MODIFY ENTITIES OF zdm_r_invhdrtp IN LOCAL MODE
             ENTITY Invoice
             UPDATE FIELDS ( TmpAttachment TmpFilename TmpMimetype Filename Mimetype )
             WITH VALUE #( ( %key = invoice_entity-%key
                             %is_draft = invoice_entity-%is_draft
                             TmpAttachment = invoice_entity-TmpAttachment
                             TmpFilename = invoice_entity-TmpFilename
                             TmpMimetype = invoice_entity-TmpMimetype
                             Filename = invoice_entity-Filename
                             Mimetype = invoice_entity-Mimetype
                             ) ) FAILED DATA(failed_update)
                             REPORTED DATA(reported_update).
          ELSE.
            INSERT VALUE #( %tky                   = invoice_entity-%tky
                             %element-TmpAttachment = if_abap_behv=>mk-on
                             %msg                   = me->new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                                                 text     = 'Unable to Store Invoice' ) ) INTO TABLE reported-invoice.
          ENDIF.
        ENDLOOP.
      ENDMETHOD.

    ๐Ÿ“ฆ This ensures uploaded files are always stored in S3 and not the database.

    ๐Ÿ”— Step 4: Retrieve Invoice from S3 in Fiori UI

    To make the invoice retrievable:

    In your consumption CDS view, add a virtual element

    ZDM_C_INVHDRTP
     @Semantics.largeObject:
                          { mimeType: 'Mimetype',
                          fileName: 'Filename',
                          contentDispositionPreference: #INLINE }
      @ObjectModel.virtualElementCalculatedBy: 'ABAP:ZDM_CL_AWS_INVOICE_RETRIEVE'
      virtual Attachment: zdm_file_attachment,

    Implement the calculation class ZDM_CL_AWS_INVOICE_RETRIEVE with interface IF_SADL_EXIT_CALC_ELEMENT.

    zdm_cl_aws_invoice_retrieve
    CLASS zdm_cl_aws_invoice_retrieve DEFINITION
      PUBLIC
      FINAL
      CREATE PUBLIC .
    
      PUBLIC SECTION.
        INTERFACES if_sadl_exit_calc_element_read.
      PROTECTED SECTION.
      PRIVATE SECTION.
    ENDCLASS.
    
    
    
    CLASS zdm_cl_aws_invoice_retrieve IMPLEMENTATION.
      METHOD if_sadl_exit_calc_element_read~calculate.
        DATA: lt_orginal_data type standard Table of zdm_c_invhdrtp.
        lt_orginal_data = CORRESPONDING #( it_original_data ).
    
        LOOP at lt_orginal_data ASSIGNING FIELD-SYMBOL(<original_data>).
            IF <original_data>-Filename is not INITIAL.
    
                TRY.
                    DATA(storage_helper) = new zdm_cl_aws_invoice_storage( <original_data>-InvoiceID ).
                    <original_data>-attachment = storage_helper->get_object( iv_filename = <original_data>-filename ).
                  CATCH /aws1/cx_rt_technical_generic /aws1/cx_rt_service_generic /AWS1/CX_RT_NO_AUTH_GENERIC.
                    "handle exception
                ENDTRY.
    
            endif.
            ct_calculated_data = CORRESPONDING #( lt_orginal_data ).
        ENDLOOP.
    
      ENDMETHOD.
    
      METHOD if_sadl_exit_calc_element_read~get_calculation_info.
    
      ENDMETHOD.
    
    ENDCLASS.

    Add Metadata extension so virual field is visable in Fiori

    ZDM_C_INVHDRTP
        @UI.fieldGroup: [ {
       position: 20 ,
       label: 'Stored Attachment',
        qualifier: 'Attachments'
      } ]
      Attachment;

    โœ… This makes the link in Fiori clickable, letting users download directly from S3.


    ๐Ÿงน Bonus Step: Delete Invoice from S3 on Entity Deletion

    When an invoice is deleted in RAP, we should also delete it from S3.

    Enable WITH ADDITIONAL SAVE in behavior definition.

    The top section of ZDM_BP_INVHDRTP should look like:

    ZDM_BP_INVHDRTP
    managed implementation in class ZDM_BP_INVHDRTP unique;
    strict ( 2 );
    with draft;
    
    define behavior for ZDM_R_INVHDRTP alias Invoice
    persistent table zdm_ainvhdr
    draft table zdm_dinvhdr
    etag master LocalLastChangedAt
    lock master total etag LastChangedAt
    authorization master ( global )
    with additional save
    early numbering
    {
    

    Redefine SAVE_MODIFIED in the LHC class.

    lsc_zdm_r_invhdrtp
    CLASS lsc_zdm_r_invhdrtp DEFINITION INHERITING FROM cl_abap_behavior_saver.
    
      PROTECTED SECTION.
        METHODS adjust_numbers REDEFINITION.
        METHODS save_modified REDEFINITION.
    
    ENDCLASS.

    Implement save_modified

    ABAP
     METHOD save_modified.
        IF delete-invoice IS NOT INITIAL.
          SELECT InvoiceID, Filename FROM zdm_r_invhdrtp
          FOR ALL ENTRIES IN @delete-invoice
          WHERE InvoiceID = @delete-invoice-InvoiceID
          INTO TABLE @DATA(lt_filenames).
    
          LOOP AT lt_filenames INTO DATA(ls_filenames)
           WHERE Filename IS NOT INITIAL.
            TRY.
                DATA(storage_helper) = NEW zdm_cl_aws_invoice_storage( ls_filenames-InvoiceID ).
                storage_helper->delete_object( iv_filename = ls_filenames-filename ).
              CATCH /aws1/cx_rt_technical_generic /aws1/cx_rt_service_generic /aws1/cx_rt_no_auth_generic.
                "handle exception
            ENDTRY.
          ENDLOOP.
        ENDIF.
    
      ENDMETHOD.

    ๐Ÿ“Œ This ensures no orphaned files remain in your S3 bucket.


    โœ… Summary

    Your RAP application now supports:

    • โœ… Secure file uploads to AWS S3
    • โœ… Retrieving invoice files via Fiori
    • โœ… Cleaning up S3 when invoices are deleted

    All the source code for this post is available on Github on the S3Integration branch


    ๐Ÿ”œ Coming Next: OCR with AWS Textract

    In the next part of this series, weโ€™ll explore how to extract key invoice fields automatically using AWS Textract. Youโ€™ll learn how to parse PDFs, extract text, and auto-populate invoice screens.

    ๐Ÿ‘‰ Stay tuned, and subscribe for more ABAP + AWS tutorials!

    Leave a Reply

    Your email address will not be published. Required fields are marked *