[ABAP] SAP Gateway File-Download über DEFINE und GET_STREAM

SE80: DDIC-Typ anlegen “ZFILESTREAM”

Feldname    Name im Gateway   ABAP-Typ     Bemerkung
------------------------------------------------------------------------------------------------------------------
FILEKEY     FileKey           beliebig     eindeutige Referenz (Key) zur späteren Ermittelung der Datei im Backend
MIME_TYPE   MimeType          CHAR100      Platzhalter für MimeType der Datei
VALUE       Value             XSTRINGVAL   Platzhalter für Binärdaten

SEGW: neuer Entitätstyp zum Lesen der Datei

  • Name: “FileStream”, DDIC-Typ “ZFILESTREAM” importieren
Name      Schlüssel   EDM-Coretyp   Bezeichner        ABAP-Feldname
-------------------------------------------------------------------
FileKey   ja          Edm.String    FileId            FILEKEY
MimeType  nein        Edm.String    Mimetyp           MIME_TYPE
Value     nein        Edm.Binary    Media Ressource   VALUE
  • Haken bei Medium setzen

SE80: ZCL_…_MPC_EXT~DEFINE redefinieren

METHOD define.
  DATA: lo_entity   TYPE REF TO /iwbep/if_mgw_odata_entity_typ.
  DATA: lo_property TYPE REF TO /iwbep/if_mgw_odata_property.

* Aufruf Basisklasse
  super->define( ).

* Entität "FileStream" holen
  lo_entity = model->get_entity_type( iv_entity_name = zcl_..._mpc=>gc_filestream ).

  IF lo_entity IS BOUND.

* Feld für MimeType (==MIME_TYPE) als ContentType setzen
    lo_property = lo_entity->get_property( iv_property_name = 'MimeType' ).
    lo_property->set_as_content_type( ).

  ENDIF.
ENDMETHOD.

SE80: /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM redefinieren

METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream.
  DATA: lv_file        TYPE zcl_..._mpc=>ts_filestream.
  DATA: lv_stream      TYPE ty_s_media_resource.

* Welche Entität?
  CASE io_tech_request_context->get_entity_type_name( ).
    WHEN zcl_..._mpc=>gc_filestream.
* Schlüssel ("FILEKEY") ermitteln
      io_tech_request_context->get_converted_keys( IMPORTING es_key_values = lv_file ).

* gewünschte Binärdaten anhand des Schlüssels aus SAP lesen
      DATA: it_bin_data TYPE STANDARD TABLE OF raw255.
      
      it_bin_data = ...

* Dateinamen ermitteln        
      DATA: lv_filename TYPE skwf_filnm.
      
      lv_filename = ...

* Für die Umwandlung die Dateigröße der Binärdaten berechnen
      DATA(lv_size) = lines( it_bin_data ).
      DATA: lv_line LIKE LINE OF it_bin_data.
      DATA(lv_length) = 0.
* für Unicode-Kompatibilität IN BYTE MODE
      DESCRIBE FIELD lv_line LENGTH lv_length IN BYTE MODE.
      lv_size = lv_size * lv_length.

* Binärdaten in xstring für die Rückgabe konvertieren        
      CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
        EXPORTING
          input_length = lv_size
        IMPORTING
          buffer       = lv_stream-value
        TABLES
          binary_tab   = it_bin_data
        EXCEPTIONS
          failed       = 1
          OTHERS       = 2.

      IF sy-subrc = 0.        
* MIME-Typen der Datei ermitteln und an das Frontend übergeben
        DATA: lv_mimetype TYPE skwf_mime.
              
* aus Dateinamen den MIME-Typen ermitteln
        CALL FUNCTION 'SKWF_MIMETYPE_OF_FILE_GET'
          EXPORTING
            filename = lv_filename
          IMPORTING
            mimetype = lv_mimetype.
                
        lv_stream-mime_type = lv_mimetype.

* HTTP-Header-Infos setzen (Dateiname usw.)
        DATA(lv_lheader) = VALUE ihttpnvp( name  = 'Content-Disposition'
                                           value = |inline; filename="{ escape( val = lv_filename format = cl_abap_format=>e_url ) }";| ). " Datei im Tab inline (Plugin) öffnen
*                                           value = |outline; filename="{ escape( val = lv_filename format = cl_abap_format=>e_url ) }";| ). " Datei zum direkten Herunterladen / Öffnen anbieten

        set_header( is_header = lv_lheader ).

* alle Daten zum Frontend schicken
        me->copy_data_to_ref( EXPORTING is_data = lv_stream
                              CHANGING cr_data  = er_stream ).
      ENDIF.

    WHEN OTHERS.
* andere Entitäten standardmäßig behandeln
      super->/iwbep/if_mgw_appl_srv_runtime~get_stream(
        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
          io_tech_request_context = io_tech_request_context
          IMPORTING
            er_stream             = er_stream
            es_response_context   = es_response_context ).
  ENDCASE.
ENDMETHOD.

Weiterführende Infos: SAP Fiori tricks: Get rid of $value in PDF display/downloads

[ABAP] OData: $top und $skip – Pagingfunktionen implementieren

* https://blogs.sap.com/2017/12/06/display-countfilterorderbyinlinecounttop-and-skip-operations-using-odata-services/
* für SAPUI5-Elemente muss zugehöriges EntitySet in der SEGW auf "Paginierbar" gestellt sein

METHOD xyz_get_entityset.

  ...

* $top und $skip auslesen
    DATA(lv_top) = io_tech_request_context->get_top( ).
    DATA(lv_skip) = io_tech_request_context->get_skip( ).
    DATA(lv_maxrows) = 0.

* lv_maxrows setzen
    IF lv_top > 0.
      lv_maxrows = lv_top + lv_skip.
    ENDIF.

* Daten holen
    SELECT * FROM abc INTO TABLE @et_entityset UP TO @lv_maxrows ROWS.

* überflüssige Daten löschen
    IF NOT lv_skip IS INITIAL.
      DELETE et_entityset TO lv_skip.
    ENDIF.

  ...

ENDMETHOD.

[ABAP] OData: $orderby implementieren

Variante 1

* https://blogs.sap.com/2013/09/03/sap-gw-implement-a-better-orderby-for-cust-ext-class/
* http://www.techippo.com/2016/08/sorting-query-options-orderby-sap-odata-service.html
* http://www.saplearners.com/orderby-query-option-in-sap-netweaver-gateway/
* https://blogs.sap.com/2017/12/06/display-countfilterorderbyinlinecounttop-and-skip-operations-using-odata-services/

METHOD xyz_get_entityset.

  ...

  DATA(it_orderby) = io_tech_request_context->get_orderby( ).

  IF line_exists( it_orderby[ property = 'MATNR' ] ).
    DATA(lv_orderby) = it_orderby[ property = 'MATNR' ].

    CASE lv_orderby-order.
      WHEN 'asc'.
        SORT: et_entityset BY name ASCENDING.
      WHEN 'desc'.
        SORT: et_entityset BY name DESCENDING.
    ENDCASE.
  ENDIF.

  ...

ENDMETHOD.

Variante 2

METHOD xyz_get_entityset.

  ...

  DATA(it_orderby) = io_tech_request_context->get_orderby( ).

  READ TABLE it_orderby INTO DATA(ls_orderby) INDEX 1.
  IF sy-subrc = 0.
* generische Order-Property verwenden, wenn z.B. nur ein Sortierkriterium vorhanden
* (z.B. beim Anklicken von Tabellenfeldern)
    CASE ls_orderby-order.
      WHEN 'asc'.
        SORT: et_entityset BY (ls_orderby-property) ASCENDING.
      WHEN 'desc'.
        SORT: et_entityset BY (ls_orderby-property) DESCENDING.
    ENDCASE.
  ENDIF.

  ...

ENDMETHOD.

[ABAP] OData: $inlinecount implementieren

* https://blogs.sap.com/2017/12/06/display-countfilterorderbyinlinecounttop-and-skip-operations-using-odata-services/
* $inlinecount=allpages
* $inlinecount=none

METHOD xyz_get_entityset.
  
  ...

  IF abap_true = io_tech_request_context->has_inlinecount( ).
    es_response_context-inlinecount = lines( et_entityset ).
  ELSE.
    CLEAR: es_response_context-inlinecount.
  ENDIF.

ENDMETHOD.

[ABAP] OData: Navigation/Assoziation zwischen Entitätsmengen

Transaktion SEGW

  • Neues Projekt: ZFLIGHT_ASSOC
  • Entitätstyp:
    • Carrier aus DDIC Struktur “SCARR”
      • Carrid (Key)
      • Carrname
      • Url
  • Flights aus DDIC Struktur “SPFLI”
    • Carrid (Key)
    • Connid (Key)
    • Countryfr
    • Cityfrom
    • Airpfrom
  • Entitätsmenge:
    • CarrierSet
    • FlightsSet
  • Serviceklassen generieren
  • Service registrieren

Serviceimplementierung: GetEntity(Read)

* URL/SERVICENAME/CarrierSet('AA')
METHOD carrierset_get_entity.

  io_tech_request_context->get_converted_keys( IMPORTING es_key_values = er_entity ).
  
  IF NOT er_entity-carrid IS INITIAL.
    SELECT SINGLE * FROM scarr INTO CORRESPONDING FIELDS OF @er_entity WHERE carrid = @er_entity-carrid.
  ENDIF.

ENDMETHOD.
    
* URL/SERVICENAME/FlightsSet(Carrid='AA',Connid='64')
METHOD flightsset_get_entity.

  io_tech_request_context->get_converted_keys( IMPORTING es_key_values = er_entity ).

  IF NOT er_entity-carrid IS INITIAL AND NOT er_entity-connid IS INITIAL..
    SELECT SINGLE * FROM spfli INTO CORRESPONDING FIELDS OF @er_entity
      WHERE carrid = @er_entity-carrid
        AND connid = @er_entity-connid.
  
    IF sy-subrc <> 0.
      CLEAR: er_entity.
    ENDIF.
  ENDIF.
  
ENDMETHOD.

Serviceimplementierung: GetEntitySet(Query)

* URL/SERVICENAME/CarrierSet
METHOD carrierset_get_entityset.

  SELECT * FROM scarr INTO CORRESPONDING FIELDS OF TABLE @et_entityset.

ENDMETHOD.
    
* URL/SERVICENAME/FlightsSet
METHOD flightsset_get_entityset.

  SELECT * FROM spfli INTO CORRESPONDING FIELDS OF TABLE @et_entityset.

ENDMETHOD.

Assoziation anlegen

  • Rechtsklick auf “Assoziationen” -> Anlegen
  • Name: CarrierToFlights
  • Principal-Entität: Name “Carrier”
  • Untergeordnete Entität: Name “Flights”
  • Kardinalität: Carrier 1 : Flights n
  • Navigationsproperty anlegen: ToFlights
  • Bezug: Carrid – Carrid

Code anpassen

* URL/SERVICENAME/CarrierSet('AA')/ToFlights?$format=json
METHOD flightsset_get_entityset.

  * Typen und Konstanten sind in der MPC definiert
  DATA: lv_flights TYPE zcl_zflight_assoc2_mpc=>ts_flights.

  DATA(lv_source_entity_type_name) = io_tech_request_context->get_source_entity_type_name( ).

  CASE lv_source_entity_type_name.
  * Typen und Konstanten sind in der MPC definiert
    WHEN zcl_zflight_assoc2_mpc=>gc_carrier.
      io_tech_request_context->get_converted_source_keys( IMPORTING es_key_values = lv_flights ).
  ENDCASE.

  IF NOT lv_flights IS INITIAL.
    SELECT * FROM spfli INTO CORRESPONDING FIELDS OF TABLE @et_entityset WHERE carrid = @lv_flights-carrid.
  ELSE.
    SELECT * FROM spfli INTO CORRESPONDING FIELDS OF TABLE @et_entityset.
  ENDIF.

ENDMETHOD.

Weiterführende Infos: Link

[ABAP] OData: $filter implementieren

* für SAPUI5-Elemente muss zugehöriges EntitySet in der SEGW auf "Filterbar" gestellt sein

* Groß-/Kleinschreibung beachten
* Verknüpfungen: "or" und "and" klein geschrieben
* Datumswerte mit VarDate=datetime'yyyy-mm-ddThh:mm:ss' konvertieren
* Umlaute konvertieren

* Filter-Options sind: eq, ne, le, lt, ge, gt, substringof, startswith, endswith

* Abfrage-URL
URL/SERVICENAME/ItemCollection?$filter=Col1 eq 'Horst'
URL/SERVICENAME/ItemCollection?$filter=Col1 eq 'Horst' and Col2 eq 'Schmidt'
URL/SERVICENAME/ItemCollection?$filter=Col1 eq 'Horst'&$format=json

* Methode get_entityset implementieren
METHOD xyz_get_entityset.

* Variante 1 (Tabelle mit Filter-Properties + Options + RANGE)

* Achtung: Filterbedingungen, die mit 'or' verknüpft sind, werden aufgrund eines Fehlers vom MPC
*          nicht korrekt geparst -> get_filter_select_options liefert leere Tabelle
*
* Lösung 1: die or-Teile müssen korrekt in Klammern gesetzt werden
*           https://.../SERVICENAME/DataSet?$filter=(Col1 gt '1000' or Col1 lt '9999') and Col2 eq '10'
* Lösung 2: Filterbedingungen mit 'and' verknüpfen

* Filterkriterien holen
  DATA(it_filter_so) = io_tech_request_context->get_filter( )->get_filter_select_options( ).

  DATA(so_matnr) = VALUE /iwbep/t_cod_select_options( ).

  IF line_exists( it_filter_so[ property = 'MATNR' ] ).
    so_matnr = it_filter_so[ property = 'MATNR' ]-select_options.
  ENDIF.

  SELECT * FROM mara WHERE matnr IN so_matnr.

  ...

* Variante 2 (Tabelle mit Filter-Properties + Options auslesen)
  DATA: lv_matnr TYPE matnr
  DATA(it_filter_so) = io_tech_request_context->get_filter( )->get_filter_select_options( ).
  
  IF NOT it_filter_so IS INITIAL.
    TRY.
        lv_matnr = it_filter_so[ property = 'MATNR' ]-select_options[ 1 ]-low.

        ...
      CATCH cx_root.
    ENDTRY.
  ENDIF.
  
* Variante 3 (Filter-String)
  DATA(lv_filter_string) = io_tech_request_context->get_filter( )->get_filter_string( ).
  
  ...

* Variante 4 (String für WHERE-Clause)
  DATA(lv_osql_where_clause) = io_tech_request_context->get_osql_where_clause( ).

  ...

  SELECT * FROM xy INTO TABLE @it_itab
    WHERE (lv_osql_where_clause).

  ...
  
ENDMETHOD.

Links

[ABAP] OData: Keys einer Entität auswerten

* Groß-/Kleinschreibung beachten
* Variablennamen durch Komma getrennt, keine Leerzeichen
* Datumswerte mit VarDate=datetime'yyyy-mm-ddThh:mm:ss' konvertieren
* Sonderzeichen/Umlaute konvertieren

* Abfrage-URL
URL/SERVICENAME/ItemCollection(Matnr='000000000001',Date1=datetime'2013-09-04T00%3A00%3A00')?$format=xml

* Variante 1 (get_converted_keys)
METHOD xyz_get_entity.

  DATA: lv_carr TYPE /iwbep/s_mgw_name_value_pair-value.
  DATA: lv_conn TYPE /iwbep/s_mgw_name_value_pair-value.

  io_tech_request_context->get_converted_keys( IMPORTING es_key_values = er_entity ).

  TRY.
      lv_carr = er_entity-carrid.
      lv_conn = er_entity-connid.

      ...
    CATCH cx_root.
  ENDTRY.

  er_entity = ...
ENDMETHOD.

* Variante 2 (Navigation z.B.: Header -> Position, get_converted_source_keys)
METHOD xyz_get_entityset.

* Typen in MPC definert
  DATA: lv_pos TYPE zcl_abc_mpc=>ts_pos.
  DATA: lv_matnr TYPE /iwbep/s_mgw_name_value_pair-value.
  
  DATA(lv_source_entity_type_name) = io_tech_request_context->get_source_entity_type_name( ).
  
  CASE lv_source_entity_type_name.
* Konstanten in der MPC definiert
     WHEN zcl_abc_mpc=>gc_head.
* Key-Werte in Struktur des Typs zcl_abc_mpc=>ts_pos wandeln
       io_tech_request_context->get_converted_source_keys( IMPORTING es_key_values = lv_pos ).

       lv_matnr = |{ lv_pos-matnr WIDTH = 18 ALPHA = IN }|.
  ENDCASE.
  
  er_entity = ...
ENDMETHOD.

* Variante 3 (get_keys)
METHOD xyz_get_entity.

  DATA(it_keys) = io_tech_request_context->get_keys( ).

  DATA: lv_key_matnr TYPE /iwbep/s_mgw_tech_pair.
  DATA: lv_key_date1 TYPE /iwbep/s_mgw_tech_pair.

  TRY.
      lv_key_matnr = it_keys[ name = 'MATNR' ].
      lv_key_date1 = it_keys[ name = 'DATE1' ].

      DATA(lv_matnr) = lv_key_matnr-value.
      DATA(lv_date1) = lv_key_date1-value.

      ...
    CATCH cx_root.
  ENDTRY.

  er_entity = ...
ENDMETHOD.