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.
- Visit the GitHub repo and switch to the
starting-app
branch. - Create a New Online Repo with program ZABAPGIT_STANDALONE selecting starting-app branch
- Import the objects into a package (e.g.,
ZINVOICE
). - Publish the Service Binding.
- 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 filenameput_object
: uploads an invoice, optionally deleting a previous versiondelete_object
: removes a specific invoice fileget_file_name
: uses regex to rename uploaded files to<invoice_id>.<extension>
For AWS examples see here
Source Code
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.
determination uploadToS3 on save { create; update; }
Implement Determination:
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
@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
.
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
@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:
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.
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
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