Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

First off, disclaimer: this blog post contains my views only and are not reflective of my employer or anyone else. Now that's out of the way, we can begin!

I was asked to build a prototype UI5 application where the user can view and download existing attachments for an ISU business partner from ArchiveLink, as well as upload them. In the process of researching the prototype, I came across many helpful blogs, but I thought I’d also share my experience since I haven’t seen my particular scenario documented end to end.

The requirement to get and create attachments in ArchiveLink is not new, so I won’t really dwell on that. Instead I will be focusing on how it all hangs together, and the tweaking I had to do to deal with the ever so temperamental UploadCollection.

First things first. I had to build RFC function modules (I know, gross) because the system I had to work with is pre NW 7.31. Normally it would be a clean Gateway hub scenario (where the data retrieval would be written in the ECC Gateway service and consumed by the hub), but in my case I’m calling the function modules in ECC from the hub Gateway service. These function modules are pretty much just wrappers that are RFC enabled and call the standard ArchiveLink function modules, such as  ARCHIV_GET_CONNECTIONS,  ARCHIVOBJECT_GET_URI,  ARCHIVOBJECT_GET_TABLE, etc.

The next step was to build the Gateway service. This was pretty straightforward too, with just some tweaks to handle media types and streams. I basically had a Customer entity (unfortunately everything is in German but you’ll get the gist) which has an association with a CustomerFile entity. It’s via the CustomerFile entity that we can retrieve the value stream of the file, as well as perform the upload. In particular, note that for this to work, you need to set the entity that is handling the file content as a media type (see the checkbox below).

Other than that, the rest is as per any Gateway Service Builder project. After generating the classes and runtime objects, the next step was to redefine the methods required to handle the upload and download of content streams. I referred a lot this amazing blog by peter.marcely, which also includes handy code snippets. You can find my code snippets below for the methods that I needed to redefine:

<Your model extension class>-DEFINE


METHOD define.


  super->define( ).



  DATA:


    lo_entity  TYPE REF TO /iwbep/if_mgw_odata_entity_typ,


    lo_property TYPE REF TO /iwbep/if_mgw_odata_property.



  lo_entity = model->get_entity_type( iv_entity_name = 'KUNDEN_AKTE' ).



  IF lo_entity IS BOUND.


    lo_property = lo_entity->get_property( iv_property_name = 'Mimetype' ).


    lo_property->set_as_content_type( ).


  ENDIF.


ENDMETHOD.





/IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM (note: I didn't really need this for the UploadCollection as the URL is used to retrieve the document, but this was useful for testing. If you need to embed the content of the file, say in an image control then this would be useful).


METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream.



  DATA: ls_key        TYPE /iwbep/s_mgw_name_value_pair,


        lv_gp_id      TYPE saeobjid,


        lv_doc_id    TYPE SAEARDOID,


        ls_lheader    TYPE ihttpnvp,


        ls_doc        TYPE zrcm_gw_file_info,


        lt_docs      TYPE TABLE OF zrcm_gw_file_info,


        ls_stream    TYPE ty_s_media_resource,


        lv_filename  TYPE string.



  CASE iv_entity_name.


    WHEN 'KUNDEN_AKTE'.



      READ TABLE it_key_tab WITH KEY name = 'GP_ID' INTO ls_key.


      lv_gp_id = ls_key-value.




      clear: ls_key.


      READ TABLE it_key_tab WITH KEY name = 'Arc_Doc_Id' INTO ls_key.


      lv_doc_id = ls_key-value.




      CALL FUNCTION 'ZRCM_GW_READ_DOCUMENTS' DESTINATION <ECC_Destination>


        EXPORTING


          obj_id    = lv_gp_id


          sap_obj  = 'BUS1006'


        IMPORTING


          dokumente = lt_docs.




      READ TABLE lt_docs INTO ls_doc WITH KEY object_id = lv_gp_id arc_doc_id = lv_doc_id.



      ls_stream-value = ls_doc-content.


      ls_stream-mime_type = ls_doc-mimetype.



      lv_filename = ls_doc-arc_doc_id.


      lv_filename = escape( val = lv_filename


                            format = cl_abap_format=>e_url ).




      ls_lheader-name = 'Content-Disposition'.


      ls_lheader-value = '|inline; Filename="{ lv_filename }"|'.


      set_header( is_header = ls_lheader ).



      copy_data_to_ref( EXPORTING is_data = ls_stream


                        CHANGING  cr_data = er_stream ).


  ENDCASE.




ENDMETHOD.







/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_STREAM


METHOD /iwbep/if_mgw_appl_srv_runtime~create_stream.


  DATA: ls_key_tab    TYPE /iwbep/s_mgw_name_value_pair,


        lt_key_tab    TYPE /iwbep/t_mgw_name_value_pair,


        ls_doc        TYPE toadt,


        ls_return    TYPE bapireturn1,


        ls_kundenakte TYPE zcl_zrcm_verbindung_mpc=>ts_kunden_akte,


        lv_error      TYPE string,


        lv_filename  TYPE toaat-filename,


        lv_mimetype  TYPE w3conttype,


        lv_gp_id      TYPE char10.



  CASE iv_entity_name.


    WHEN 'KUNDEN_AKTE'.


      READ TABLE it_key_tab WITH KEY name = 'GP_ID' INTO ls_key_tab.


      lv_gp_id = ls_key_tab-value.



      lv_mimetype = is_media_resource-mime_type.


      REPLACE ALL OCCURRENCES OF '#' IN lv_mimetype WITH space.



      lv_filename = iv_slug.


      REPLACE ALL OCCURRENCES OF '#' IN lv_filename WITH space.



      CALL FUNCTION 'ZRCM_GW_STORE_DOCUMENT' DESTINATION <ECC_Destination>


        EXPORTING


          ar_object  = '/BME/V0043'


          object_id  = lv_gp_id


          sap_object = 'BUS1006'


          doc_type  = lv_mimetype


          document  = is_media_resource-value


          filename  = lv_filename


        IMPORTING


          outdoc    = ls_doc


          return    = ls_return.



      IF ls_return-type = 'E'.


        lv_error = ls_return-message.


        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception


              EXPORTING


                textid            = /iwbep/cx_mgw_busi_exception=>business_error_unlimited


                message_unlimited = lv_error.


      ELSE.



        ls_kundenakte-arc_doc_id = ls_doc-arc_doc_id.


        ls_kundenakte-filename = iv_slug.


        ls_kundenakte-mimetype = is_media_resource-mime_type.


        ls_kundenakte-gp_id = lv_gp_id.



        kunden_akteset_get_entity(


          EXPORTING


            iv_entity_name    = iv_entity_name


            iv_entity_set_name = iv_entity_set_name


            iv_source_name    = iv_source_name


            it_key_tab        = it_key_tab


            it_navigation_path = it_navigation_path


          IMPORTING


            er_entity          = ls_kundenakte ).



        copy_data_to_ref( EXPORTING is_data = ls_kundenakte


                          CHANGING  cr_data = er_entity ).



      ENDIF.


  ENDCASE.


ENDMETHOD.






KUNDEN_AKTESET_GET_ENTITY


METHOD kunden_akteset_get_entity.


  DATA: ls_key        TYPE /iwbep/s_mgw_name_value_pair,


        lv_arc_doc_id  TYPE string,


        lv_gp_id      TYPE saeobjid,


        lv_content    TYPE xstring,


        lv_bin_length TYPE sapb-length,


        ls_doc        TYPE zrcm_gw_file_info,


        lt_docs      TYPE TABLE OF zrcm_gw_file_info.



  READ TABLE it_key_tab WITH KEY name = 'GP_ID' INTO ls_key.


  lv_gp_id = ls_key-value.



  CLEAR: ls_key.


  READ TABLE it_key_tab WITH KEY name = 'Arc_Doc_Id' INTO ls_key.


  lv_arc_doc_id = ls_key-value.



  CHECK lv_gp_id IS NOT INITIAL AND lv_arc_doc_id IS NOT INITIAL.



  CALL FUNCTION 'ZRCM_GW_READ_DOCUMENTS' DESTINATION <ECC_Destination>


    EXPORTING


      obj_id    = lv_gp_id


      sap_obj  = 'BUS1006'


    IMPORTING


      dokumente = lt_docs.



  READ TABLE lt_docs INTO ls_doc WITH KEY arc_doc_id = lv_arc_doc_id.



  MOVE-CORRESPONDING ls_doc TO er_entity.



  er_entity-gp_id = lv_gp_id.



ENDMETHOD.






This is the POST URL I used to upload some content for testing:


/sap/opu/odata/sap/ZRCM_VERBINDUNG_SRV/KUNDEN_AKTESet('0000208999')/$value


So far, so good.


Next I created a simple UI5 application that allows the user to view the attachments that exist, while also having the ability to upload new ones. The control that made the most sense was UploadCollection, which seems to be relatively new (read: buggy). In principle this was the perfect control for the use case, however when I started using it I realized that it’s still in its developmental stages. I referred to the UploadCollection example in SAPUI5 Explored, but please note that uses hard coded JSON data.

First thing I realized is when I disabled instantUpload, I lost the ability to display the existing attachments in a list. The list becomes the waiting area only for the attachments you will be uploading. That was rather annoying, because sometimes you want to check the files before you decide to upload them, as well as being able to see what files already exist. Perhaps this will be improved in a later release, but for now I just had to leave instantUpload on.

Another thing I noticed: after the UploadCollection is instantiated, you can no longer set the upload URL dynamically. This means you either hardcode it in the view definition (yuck), or you set it once at initialization, which is what I ended up doing. I find it strange that we are given the setter method, but when you use it after instantiation, you get this error:

Then there’s the CSRF Token. I get why we need to use these, but to have to set it manually in the code is just mind boggling. Essentially I had to retrieve the CSRF Token in an AJAX call, and store the token to be sent with the upload request. This seems to be the case also with the FileUpload control as there are loads of other posts describing the same workaround. It would be nice if this was all handled automatically in the standard control.

The final thing I had to tweak was the completed upload event handler. I expected to have access to the response from the upload request (similar to reading the model) in this event, since it is triggered after a successful upload. Think again. If you leave this method empty, the status of the upload stays at 99% forever. I didn’t really want to rebind the whole UploadCollection aggregation each time an upload is completed, since the list could load quite slowly and appear strange to the user (while a new file is uploading, it appears at the top of the list, but after rebinding it goes somewhere else). In the end I had to resort to rebinding because it was the cleanest way. I added a busy dialog with a delay so the user wouldn’t see the file hop from the top to somewhere else.


Below are some screenshots of the application in action:

Here are the key events for UploadCollection:


onBeforeUploadStarts: function(oEvent) {
  // Stellen die Kopf Parameter slug
  var oCustomerHeaderSlug = new sap.m.UploadCollectionParameter({
  name : "slug",
  value : oEvent.getParameter("fileName")
  });
  oEvent.getParameters().addHeaderParameter(oCustomerHeaderSlug);
  _busyDialog.open();
  },






onUploadComplete: function(oEvent) {
  var sUploadedFile = oEvent.getParameter("files")[0].fileName;
  var location = oEvent.getParameter("files")[0].headers.location;
  var index = location.indexOf("/KUNDEN_AKTESet");
  var path = location.substring(index);
  var oCollection = oEvent.getSource();
  var collectionPath = "/KUNDENSet('" + _gp_id + "')/AKTEN";
  var oTemplate = this.byId(_collectionItemId).clone();
  _collectionItemId = oTemplate.getId();
  oCollection.bindAggregation("items", {
  path: collectionPath,
  template: oTemplate
  });
  setTimeout(function(){
  _busyDialog.close();
  MessageToast.show(_oBundle.getText("dokumentHochgeladen"));
  }, 3000);
  },






onChange: function(oEvent) {
  // Stellen das CSRF Token wenn ein File hinzugefügt ist
  var oUploadCollection = oEvent.getSource();
  var oCustomerHeaderToken = new UploadCollectionParameter({
  name : "x-csrf-token",
  value :  _csrfToken
  });
  oUploadCollection.addHeaderParameter(oCustomerHeaderToken);
},





So there you have it. A few tweaks here and there but now it functions quite well. I hope to see future improvements in this control, as it is quite a handy one, and I'd personally like the methods that are not allowed to be removed (why tease me with something I can't actually use).

I hope this post has helped shed some insight if you are looking into the same scenario.

31 Comments
Labels in this area