La gestion des pieces jointes est un sujet central dans les applications metier modernes. Qu’il s’agisse de factures, de contrats ou de dessins techniques, les utilisateurs s’attendent a pouvoir telecharger, stocker et retelecharger des documents directement dans l’application. Dans ABAP Cloud et RAP, il existe plusieurs approches pour repondre a cette exigence.
Concepts fondamentaux
Dans ABAP Cloud, differentes methodes de gestion des pieces jointes sont disponibles :
| Approche | Description | Cas d’utilisation |
|---|---|---|
| Champs LOB dans CDS | Large Objects directement dans la base de donnees | Fichiers de petite a moyenne taille |
| Attachment Service (GOS) | Service standard SAP pour les pieces jointes | Integration avec les objets standard SAP |
| External Storage | Services de stockage externes (Object Store) | Grands fichiers, conformite |
| Implementation personnalisee | Tables et logique propres | Flexibilite maximale |
Vue d’ensemble de l’architecture
+-----------------------------------------------------------------------------+| Gestion des pieces jointes dans RAP || || +------------------------------------------------------------------------+ || | Interface Fiori Elements | || | +---------------------+ +---------------------+ | || | | Controle Upload | | Action Download | | || | | (Drag & Drop) | | (Bouton/Lien) | | || | +----------+----------+ +----------+----------+ | || +-------------|-----------------------|----------------------------------+ || | | || v v || +------------------------------------------------------------------------+ || | RAP Business Object | || | +---------------------+ +---------------------+ | || | | Champ LOB | | RAP Actions | | || | | (Attachment) | | upload/download | | || | +----------+----------+ +----------+----------+ | || +-------------|-----------------------|----------------------------------+ || | | || v v || +------------------------------------------------------------------------+ || | Options de stockage | || | +-------------------+ +-------------------+ +-------------------+ | || | | HANA LOB | | Object Store | | DMS | | || | | (Base donnees) | | (Cloud) | | (Optionnel) | | || | +-------------------+ +-------------------+ +-------------------+ | || +------------------------------------------------------------------------+ |+-----------------------------------------------------------------------------+Gestion LOB dans les vues CDS
L’approche la plus simple pour les pieces jointes est l’utilisation de champs Large Object (LOB) directement dans la table de base de donnees. Les vues CDS prennent en charge les champs LOB pour les donnees binaires.
Table de base de donnees avec champ LOB
@EndUserText.label : 'Document avec piece jointe"@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE@AbapCatalog.tableCategory : #TRANSPARENT@AbapCatalog.deliveryClass : #A@AbapCatalog.dataMaintenance : #RESTRICTEDdefine table zdocument { key client : abap.clnt not null; key document_uuid : sysuuid_x16 not null; document_id : abap.numc(10); document_name : abap.char(100); description : abap.char(255);
// Champs LOB pour la piece jointe @AbapCatalog.typeUsage : #OPEN attachment : abap.rawstring(0);
mime_type : abap.char(128); file_name : abap.char(255); file_size : abap.int4;
created_by : abap.uname; created_at : timestampl; last_changed_by : abap.uname; last_changed_at : timestampl;}Remarques importantes sur les champs LOB :
abap.rawstring(0)definit un BLOB (Binary Large Object) sans limitation de longueurabap.string(0)serait un CLOB pour les donnees textuelles- Les champs LOB sont stockes efficacement dans HANA (pas dans le Row Store)
- Taille maximale : jusqu’a 2 Go par champ
Vue CDS avec champ LOB
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Document avec piece jointe"define root view entity ZI_Document as select from zdocument{ key document_uuid as DocumentUUID, document_id as DocumentID, document_name as DocumentName, description as Description,
// Exposer le champ LOB @Semantics.largeObject: { mimeType: 'MimeType', fileName: 'FileName', contentDispositionPreference: #ATTACHMENT } attachment as Attachment,
mime_type as MimeType, file_name as FileName, file_size as FileSize,
@Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt, @Semantics.user.lastChangedBy: true last_changed_by as LastChangedBy, @Semantics.systemDateTime.lastChangedAt: true last_changed_at as LastChangedAt}L’annotation @Semantics.largeObject est cruciale :
| Parametre | Description |
|---|---|
mimeType | Reference au champ avec le type MIME (par ex. application/pdf) |
fileName | Reference au champ avec le nom de fichier |
contentDispositionPreference | #ATTACHMENT pour le telechargement, #INLINE pour l’affichage |
Upload et Download dans Fiori Elements
Fiori Elements prend automatiquement en charge les champs LOB avec une fonctionnalite d’upload et de download.
Vue de projection avec annotations UI
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Projection Document"@Metadata.allowExtensions: truedefine root view entity ZC_Document provider contract transactional_query as projection on ZI_Document{ key DocumentUUID, DocumentID, DocumentName, Description,
// Champ LOB pour l'UI Attachment, MimeType, FileName, FileSize,
CreatedBy, CreatedAt, LastChangedBy, LastChangedAt}Extension de metadonnees pour l’UI d’upload
@Metadata.layer: #COREannotate view ZC_Document with{ @UI.facet: [ { id: 'GeneralInfo', type: #IDENTIFICATION_REFERENCE, label: 'Informations generales', position: 10 }, { id: 'AttachmentFacet', type: #FIELDGROUP_REFERENCE, targetQualifier: 'Attachment', label: 'Piece jointe', position: 20 } ]
@UI.lineItem: [{ position: 10, importance: #HIGH }] @UI.identification: [{ position: 10 }] DocumentID;
@UI.lineItem: [{ position: 20, importance: #HIGH }] @UI.identification: [{ position: 20 }] DocumentName;
@UI.identification: [{ position: 30 }] Description;
// Champs de piece jointe @UI.fieldGroup: [{ qualifier: 'Attachment', position: 10, label: 'Fichier' }] Attachment;
@UI.fieldGroup: [{ qualifier: 'Attachment', position: 20, label: 'Type de fichier' }] @UI.hidden: true MimeType;
@UI.fieldGroup: [{ qualifier: 'Attachment', position: 30, label: 'Nom du fichier' }] FileName;
@UI.fieldGroup: [{ qualifier: 'Attachment', position: 40, label: 'Taille (octets)' }] FileSize;}Definition de comportement pour la gestion LOB
managed implementation in class zbp_i_document unique;strict ( 2 );
define behavior for ZI_Document alias Documentpersistent table zdocumentlock masterauthorization master ( instance ){ field ( readonly ) DocumentUUID, CreatedBy, CreatedAt, LastChangedBy, LastChangedAt; field ( readonly ) FileSize; field ( mandatory ) DocumentName;
create; update; delete;
// Determination pour le calcul de FileSize determination calculateFileSize on modify { field Attachment; }
mapping for zdocument { DocumentUUID = document_uuid; DocumentID = document_id; DocumentName = document_name; Description = description; Attachment = attachment; MimeType = mime_type; FileName = file_name; FileSize = file_size; CreatedBy = created_by; CreatedAt = created_at; LastChangedBy = last_changed_by; LastChangedAt = last_changed_at; }}Implementation du comportement
CLASS lhc_document DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS calculatefilesize FOR DETERMINE ON MODIFY IMPORTING keys FOR document~calculatefilesize.ENDCLASS.
CLASS lhc_document IMPLEMENTATION. METHOD calculatefilesize. " Lire les documents avec les pieces jointes modifiees READ ENTITIES OF zi_document IN LOCAL MODE ENTITY document FIELDS ( attachment ) WITH CORRESPONDING #( keys ) RESULT DATA(documents).
" Calculer FileSize pour chaque document LOOP AT documents ASSIGNING FIELD-SYMBOL(<doc>). DATA(lv_size) = xstrlen( <doc>-attachment ).
MODIFY ENTITIES OF zi_document IN LOCAL MODE ENTITY document UPDATE FIELDS ( filesize ) WITH VALUE #( ( %tky = <doc>-%tky filesize = lv_size ) ) FAILED DATA(failed) REPORTED DATA(reported). ENDLOOP. ENDMETHOD.ENDCLASS.Actions personnalisees Upload/Download
Pour plus de controle sur le processus d’upload et de download, des actions RAP personnalisees peuvent etre implementees.
Definition de comportement avec Actions
managed implementation in class zbp_i_document unique;strict ( 2 );
define behavior for ZI_Document alias Documentpersistent table zdocumentlock masterauthorization master ( instance ){ // ... Operations standard ...
// Action Upload avec parametre action uploadAttachment parameter ZD_AttachmentUpload result [1] $self;
// Action Download action downloadAttachment result [1] ZD_AttachmentDownload;
// Supprimer la piece jointe action deleteAttachment result [1] $self;}Entite abstraite pour les parametres d’upload
@EndUserText.label: 'Parametre Upload Piece jointe"define abstract entity ZD_AttachmentUpload{ @Semantics.largeObject: { mimeType: 'MimeType', fileName: 'FileName', contentDispositionPreference: #ATTACHMENT } Content : abap.rawstring(0); MimeType : abap.char(128); FileName : abap.char(255);}Entite abstraite pour le resultat de download
@EndUserText.label: 'Resultat Download Piece jointe"define abstract entity ZD_AttachmentDownload{ @Semantics.largeObject: { mimeType: 'MimeType', fileName: 'FileName', contentDispositionPreference: #ATTACHMENT } Content : abap.rawstring(0); MimeType : abap.char(128); FileName : abap.char(255); FileSize : abap.int4;}Implementation des actions
CLASS lhc_document DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS uploadattachment FOR MODIFY IMPORTING keys FOR ACTION document~uploadattachment RESULT result.
METHODS downloadattachment FOR READ IMPORTING keys FOR ACTION document~downloadattachment RESULT result.
METHODS deleteattachment FOR MODIFY IMPORTING keys FOR ACTION document~deleteattachment RESULT result.ENDCLASS.
CLASS lhc_document IMPLEMENTATION. METHOD uploadattachment. " Validation LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>). IF <key>-%param-content IS INITIAL. APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Veuillez selectionner un fichier' ) ) TO reported-document. APPEND VALUE #( %tky = <key>-%tky ) TO failed-document. CONTINUE. ENDIF.
" Verifier la taille du fichier (max 10 Mo) DATA(lv_size) = xstrlen( <key>-%param-content ). IF lv_size > 10485760. " 10 Mo APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Fichier trop volumineux (max. 10 Mo)' ) ) TO reported-document. APPEND VALUE #( %tky = <key>-%tky ) TO failed-document. CONTINUE. ENDIF.
" Valider le type MIME DATA(lv_allowed) = abap_false. CASE <key>-%param-mimetype. WHEN 'application/pdf" OR 'image/jpeg" OR 'image/png" OR 'application/vnd.openxmlformats-officedocument.wordprocessingml.document" OR 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'. lv_allowed = abap_true. ENDCASE.
IF lv_allowed = abap_false. APPEND VALUE #( %tky = <key>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Type de fichier non autorise' ) ) TO reported-document. APPEND VALUE #( %tky = <key>-%tky ) TO failed-document. CONTINUE. ENDIF.
" Sauvegarder la piece jointe MODIFY ENTITIES OF zi_document IN LOCAL MODE ENTITY document UPDATE FIELDS ( attachment mimetype filename filesize ) WITH VALUE #( ( %tky = <key>-%tky attachment = <key>-%param-content mimetype = <key>-%param-mimetype filename = <key>-%param-filename filesize = lv_size ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported).
IF update_failed IS NOT INITIAL. APPEND LINES OF update_failed-document TO failed-document. APPEND LINES OF update_reported-document TO reported-document. ELSE. " Succes : Retourner l'entite modifiee READ ENTITIES OF zi_document IN LOCAL MODE ENTITY document ALL FIELDS WITH VALUE #( ( %tky = <key>-%tky ) ) RESULT DATA(documents).
result = VALUE #( FOR doc IN documents ( %tky = doc-%tky %param = doc ) ). ENDIF. ENDLOOP. ENDMETHOD.
METHOD downloadattachment. " Lire le document avec la piece jointe READ ENTITIES OF zi_document IN LOCAL MODE ENTITY document FIELDS ( attachment mimetype filename filesize ) WITH CORRESPONDING #( keys ) RESULT DATA(documents) FAILED failed.
" Construire le resultat de download LOOP AT documents ASSIGNING FIELD-SYMBOL(<doc>). IF <doc>-attachment IS INITIAL. APPEND VALUE #( %tky = <doc>-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-warning text = 'Aucune piece jointe disponible' ) ) TO reported-document. CONTINUE. ENDIF.
APPEND VALUE #( %tky = <doc>-%tky %param = VALUE #( content = <doc>-attachment mimetype = <doc>-mimetype filename = <doc>-filename filesize = <doc>-filesize ) ) TO result. ENDLOOP. ENDMETHOD.
METHOD deleteattachment. " Vider les champs de piece jointe MODIFY ENTITIES OF zi_document IN LOCAL MODE ENTITY document UPDATE FIELDS ( attachment mimetype filename filesize ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky attachment = VALUE #( ) mimetype = '" filename = '" filesize = 0 ) ) FAILED failed REPORTED reported.
IF failed IS INITIAL. " Retourner les entites modifiees READ ENTITIES OF zi_document IN LOCAL MODE ENTITY document ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(documents).
result = VALUE #( FOR doc IN documents ( %tky = doc-%tky %param = doc ) ). ENDIF. ENDMETHOD.ENDCLASS.Plusieurs pieces jointes par entite
Si un Business Object doit prendre en charge plusieurs pieces jointes, une entite enfant separee pour les pieces jointes est utilisee.
Entite enfant de piece jointe
@EndUserText.label : 'Pieces jointes du document"define table zdoc_attachment { key client : abap.clnt not null; key attachment_uuid : sysuuid_x16 not null; parent_uuid : sysuuid_x16 not null; attachment_name : abap.char(100);
@AbapCatalog.typeUsage : #OPEN content : abap.rawstring(0);
mime_type : abap.char(128); file_name : abap.char(255); file_size : abap.int4;
created_by : abap.uname; created_at : timestampl;}Vue CDS avec composition
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Document"define root view entity ZI_DocumentWithAtt as select from zdocument composition [0..*] of ZI_DocumentAttachment as _Attachments{ key document_uuid as DocumentUUID, document_id as DocumentID, document_name as DocumentName, description as Description,
// Association aux pieces jointes _Attachments}
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Piece jointe du document"define view entity ZI_DocumentAttachment as select from zdoc_attachment association to parent ZI_DocumentWithAtt as _Document on $projection.ParentUUID = _Document.DocumentUUID{ key attachment_uuid as AttachmentUUID, parent_uuid as ParentUUID, attachment_name as AttachmentName,
@Semantics.largeObject: { mimeType: 'MimeType', fileName: 'FileName', contentDispositionPreference: #ATTACHMENT } content as Content,
mime_type as MimeType, file_name as FileName, file_size as FileSize,
@Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt,
_Document}Definition de comportement pour Parent-Enfant
managed implementation in class zbp_i_documentwithatt unique;strict ( 2 );
define behavior for ZI_DocumentWithAtt alias Documentpersistent table zdocumentlock masterauthorization master ( instance ){ create; update; delete;
association _Attachments { create; }
mapping for zdocument { DocumentUUID = document_uuid; DocumentID = document_id; DocumentName = document_name; Description = description; }}
define behavior for ZI_DocumentAttachment alias Attachmentpersistent table zdoc_attachmentlock dependent by _Documentauthorization dependent by _Document{ field ( readonly ) AttachmentUUID, ParentUUID, CreatedBy, CreatedAt; field ( readonly ) FileSize;
update; delete;
determination calculateSize on modify { field Content; }
mapping for zdoc_attachment { AttachmentUUID = attachment_uuid; ParentUUID = parent_uuid; AttachmentName = attachment_name; Content = content; MimeType = mime_type; FileName = file_name; FileSize = file_size; CreatedBy = created_by; CreatedAt = created_at; }}Options de stockage et limites
Limites de base de donnees
| Base de donnees | Taille max. LOB | Recommandation |
|---|---|---|
| SAP HANA | 2 Go | Jusqu’a 100 Mo directement en BD |
| MaxDB | 2 Go | Jusqu’a 100 Mo directement en BD |
Alternatives pour les gros fichiers
Pour les fichiers de plus de 100 Mo ou en cas de besoin de stockage eleve, des solutions de stockage externes sont recommandees :
- SAP Object Store Service sur BTP
- Azure Blob Storage / AWS S3 via Destination
- SAP Document Management Service
Integration avec Object Store
CLASS zcl_object_store_client DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_object_metadata, object_key TYPE string, bucket TYPE string, content_type TYPE string, size TYPE i, END OF ty_object_metadata.
METHODS upload_object IMPORTING iv_bucket TYPE string iv_object_key TYPE string iv_content TYPE xstring iv_content_type TYPE string RETURNING VALUE(rs_metadata) TYPE ty_object_metadata RAISING cx_http_dest_provider_error cx_web_http_client_error.
METHODS download_object IMPORTING iv_bucket TYPE string iv_object_key TYPE string RETURNING VALUE(rv_content) TYPE xstring RAISING cx_http_dest_provider_error cx_web_http_client_error.
METHODS delete_object IMPORTING iv_bucket TYPE string iv_object_key TYPE string RAISING cx_http_dest_provider_error cx_web_http_client_error.
ENDCLASS.
CLASS zcl_object_store_client IMPLEMENTATION. METHOD upload_object. " Destination HTTP pour Object Store DATA(lo_dest) = cl_http_destination_provider=>create_by_cloud_destination( i_name = 'OBJECT_STORE" i_service_instance_name = 'object-store-instance' ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_dest ).
" Preparer la requete DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/{ iv_bucket }/{ iv_object_key }| ). lo_request->set_header_field( i_name = 'Content-Type" i_value = iv_content_type ). lo_request->set_binary_body( iv_content ).
" Executer l'upload (PUT) DATA(lo_response) = lo_client->execute( if_web_http_client=>put ).
IF lo_response->get_status( )-code <> 200 AND lo_response->get_status( )-code <> 201. RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
" Retourner les metadonnees rs_metadata = VALUE #( object_key = iv_object_key bucket = iv_bucket content_type = iv_content_type size = xstrlen( iv_content ) ).
lo_client->close( ). ENDMETHOD.
METHOD download_object. DATA(lo_dest) = cl_http_destination_provider=>create_by_cloud_destination( i_name = 'OBJECT_STORE" i_service_instance_name = 'object-store-instance' ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_dest ).
DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/{ iv_bucket }/{ iv_object_key }| ).
" Executer le download (GET) DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
IF lo_response->get_status( )-code <> 200. RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
rv_content = lo_response->get_binary( ).
lo_client->close( ). ENDMETHOD.
METHOD delete_object. DATA(lo_dest) = cl_http_destination_provider=>create_by_cloud_destination( i_name = 'OBJECT_STORE" i_service_instance_name = 'object-store-instance' ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_dest ).
DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/{ iv_bucket }/{ iv_object_key }| ).
" Executer la suppression DATA(lo_response) = lo_client->execute( if_web_http_client=>delete ).
IF lo_response->get_status( )-code <> 200 AND lo_response->get_status( )-code <> 204. RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
lo_client->close( ). ENDMETHOD.ENDCLASS.Bonnes pratiques
1. Validation a l’upload
METHOD validate_attachment. " Verifier la taille du fichier DATA(lv_max_size) = 10485760. " 10 Mo IF xstrlen( iv_content ) > lv_max_size. RAISE EXCEPTION TYPE zcx_attachment_error EXPORTING textid = zcx_attachment_error=>file_too_large max_size = lv_max_size. ENDIF.
" Verifier le type MIME DATA(lt_allowed_types) = VALUE string_table( ( `application/pdf` ) ( `image/jpeg` ) ( `image/png` ) ( `application/vnd.openxmlformats-officedocument.wordprocessingml.document` ) ( `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` ) ).
IF NOT line_exists( lt_allowed_types[ table_line = iv_mime_type ] ). RAISE EXCEPTION TYPE zcx_attachment_error EXPORTING textid = zcx_attachment_error=>mime_type_not_allowed mime_type = iv_mime_type. ENDIF.
" Analyse antivirus (si disponible) " cl_vscan_scanner=>scan( ... )ENDMETHOD.2. Optimisation des performances
" Charger le contenu de la piece jointe uniquement si necessaireREAD ENTITIES OF zi_document IN LOCAL MODE ENTITY document FIELDS ( documentname mimetype filename filesize ) " PAS : attachment WITH CORRESPONDING #( keys ) RESULT DATA(document_list).
" Charger la piece jointe separement pour le downloadREAD ENTITIES OF zi_document IN LOCAL MODE ENTITY document FIELDS ( attachment ) WITH CORRESPONDING #( download_keys ) RESULT DATA(attachments).3. Securite
- Verifier les autorisations : Download de piece jointe uniquement pour les utilisateurs autorises
- Analyse antivirus : Scanner les fichiers avant le stockage pour les applications critiques
- Validation des entrees : Verifier strictement les types et tailles de fichiers
- Chiffrement : Stocker les documents sensibles de maniere chiffree
Resume
La gestion des pieces jointes dans ABAP Cloud offre des options flexibles :
| Approche | Avantages | Inconvenients |
|---|---|---|
| LOB dans la table | Simple, UI automatique | Taille limitee, charge BD |
| Actions personnalisees | Controle total, validation | Plus d’effort d’implementation |
| Entite enfant | Plusieurs pieces jointes | Structure plus complexe |
| Object Store | Taille illimitee, performant | Dependance externe |
Le choix depend des exigences : pour les documents simples jusqu’a 10 Mo, les champs LOB suffisent, pour les scenarios complexes avec beaucoup de gros fichiers, une combinaison de metadonnees en BD et de contenu dans l’Object Store est recommandee.
Sujets connexes
- Fondamentaux RAP - Developpement de Business Objects
- Operations sur fichiers en ABAP - Traitement de fichiers classique
- Client HTTP en ABAP Cloud - Connecter des services externes
- Actions et fonctions RAP - Definir des operations personnalisees