[ABAP] Einfacher REST-Service (ohne OData)

Piyush Philip auf sap.com hat eine simple Möglichkeit beschrieben, wie man in ABAP einen REST-Service mit den entsprechenden CRUD-Methoden implementieren kann. Dazu ist im wesentlichen nur eine eigene Handler-Klasse zu implementieren, die von IF_HTTP_EXTENSION erbt. Diese Handler-Klasse wird von einem Service des ICF gerufen.

  1. SE80 -> Neue Klasse anlegen
  2. In der Klasse im Reiter Interfaces: vom Interface IF_HTTP_EXTENSION erben
  3. die Interface-Methode IF_HTTP_EXTENSION~HANDLE_REQUEST implementieren (implementiert nur REST, kein OData-Sevice!)
    METHOD if_http_extension~handle_request.
    * Typen
      TYPES: BEGIN OF ty_json_request,
               param1 TYPE char64,
               param2 TYPE char64,
             END OF ty_json_request.
    
      TYPES: BEGIN OF ty_json_response,
               success   TYPE string,
               msg       TYPE string,
               operation TYPE string,
             END OF ty_json_response.
    
    * Variablen
      DATA: rv_response TYPE ty_json_response.
      DATA: it_path_info TYPE stringtab.
      DATA: it_query_strings TYPE stringtab.
    
    * Header Fields auslesen
      DATA: it_header_fields TYPE tihttpnvp.
      server->request->get_header_fields( CHANGING fields = it_header_fields ).
    
    * Request Methode (POST, GET, PUT, DELETE ...)
      DATA(lv_request_method) = server->request->get_header_field( name = '~request_method' ).
    
    * URL Pathinfo
      DATA(lv_path_info) = server->request->get_header_field( name = '~path_info' ).
    * erstes '/' entfernen
      SHIFT lv_path_info LEFT BY 1 PLACES.
      SPLIT lv_path_info AT '/' INTO TABLE it_path_info.
    
    * URL Parameter (Query Strings) für Auswertung
      DATA(lv_query_string) = server->request->get_header_field( name = '~query_string' ).
      SPLIT lv_query_string AT '&' INTO TABLE it_query_strings.
    
    * Request Methoden behandeln
      CASE lv_request_method.
    * C (Create)
        WHEN 'POST'.
    * Beispiel: per Request übermittelte JSON-Daten auslesen und behandeln
    * Content-Typ auslesen
          DATA(lv_request_content_type) = server->request->get_content_type( ).
    
    * Wenn Content-Typ JSON
          IF lv_request_content_type CP 'application/json'.
    * JSON-Daten auslesen und nach ABAP-Typ konvertieren -> Parameternamen müssen übereinstimmen, sonst HTTP 500!
            DATA(lv_request_cdata) = server->request->get_cdata( ).
    
            DATA: lv_request_abap TYPE ty_json_request.
    
            TRY.
                /ui2/cl_json=>deserialize( EXPORTING json = lv_request_cdata
                                                     pretty_name = /ui2/cl_json=>pretty_mode-camel_case
                                           CHANGING  data = lv_request_abap ).
    
              CATCH cx_root INTO DATA(e_txt).
                MESSAGE e_txt->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
            ENDTRY.
    
            rv_response = VALUE #( success   = 'true'
                                   msg       = lv_request_abap-param1 && ' | ' && lv_request_abap-param2
                                   operation = lv_request_method ).
          ELSE.
            rv_response = VALUE #( success   = 'false'
                                   msg       = 'Request content type must be application/json'
                                   operation = lv_request_method ).
          ENDIF.
    * R (Read)
        WHEN 'GET'.
    * Wert des URL-Parameters 'param' lesen
          DATA(lv_param) = server->request->get_form_field( 'param' ).
    
          rv_response = VALUE #( success   = 'true'
                                 msg       = lv_param
                                 operation = lv_request_method ).
    * U (Update)
        WHEN 'PUT'.
          rv_response = VALUE #( success   = 'true'
                                 msg       = ''
                                 operation = lv_request_method ).
    * D (Delete)
        WHEN 'DELETE'.
          rv_response = VALUE #( success   = 'true'
                                 msg       = ''
                                 operation = lv_request_method ).
    
    * unerlaubte Methoden abfangen
        WHEN OTHERS.
    * Fehlercode 405 zurückgeben
          server->response->set_status( code   = '405'
                                        reason = 'method not allowed' ).
    
          server->response->set_header_field( name  = 'allow'
                                              value = 'POST, GET, PUT, DELETE' ).
    
          rv_response = VALUE #( success   = 'false'
                                 msg       = 'error'
                                 operation = lv_request_method ).
      ENDCASE.
    
    * Service-Response als JSON zurückgeben
      DATA(o_conv) = NEW /ui2/cl_abap2json( ).
      DATA(lv_json_str) = o_conv->struc2json( iv_struc = rv_response ).
    
      server->response->set_header_field( name  = 'Content-Type'
                                          value = 'application/json; charset=iso-8859-1' ).
    
      server->response->set_cdata( data = lv_json_str ).
    
    ENDMETHOD.
    
  4. SICF -> neue ICF-Node (Sub-Element) unterhalb default_host -> sap -> bc als neuen unabhängigen Service anlegen
  5. in der Handlerliste des Service die erstellte Klasse als „Handler“ angeben
  6. auf die neue ICF-Node rechtsklicken -> Service aktivieren
  7. auf die neue ICF-Node rechtsklicken -> Service testen
  8. URL aus Browserfenster kopieren (http://domain.de/sap/bc/servicename?sap-client=900)
  9. im Browser mir einem Rest-Plugin (FireFox-Prugin: Rested) testen

Links

[ABAP] Quellcode im ABAP-Editor anzeigen und mit Pretty Print überarbeiten

* https://wiki.scn.sap.com/wiki/display/ABAP/Create+your+own+New+Editor+for+BOR+objects
DATA(it_source_code_in) = VALUE stringtab( ).
DATA(it_source_code_out) = VALUE stringtab( ).

* Quelltext des Reports in Stringtab einlesen
READ REPORT sy-repid INTO it_source_code_in.

* Pretty Print für Code durchführen
CALL FUNCTION 'PRETTY_PRINTER'
  EXPORTING
    inctoo             = space
  TABLES
    ntext              = it_source_code_out
    otext              = it_source_code_in
  EXCEPTIONS
    enqueue_table_full = 1
    include_enqueued   = 2
    include_readerror  = 3
    include_writeerror = 4
    OTHERS             = 5.

IF sy-subrc = 0.
* ABAP-Editor im cl_gui_container=>default_screen anzeigen
  DATA(o_editor) = NEW cl_gui_abapedit( parent = cl_gui_container=>default_screen ).
* ReadOnly setzen
  o_editor->set_readonly_mode( 1 ).
* Quelltext setzen
  o_editor->set_text( it_source_code_out ).

* cl_gui_container=>default_screen erzwingen
  WRITE space.
ENDIF.

[ABAP] CSS-Code als ABAP-INCLUDE speichern und laden

Über die Nutzung von Includes ist es möglich beliebigen Code im SAP abzulegen und darauf zuzugreifen. Folgende Schritte sind notwendig:

  • SE80 -> neues INCLUDE anlegen (Bsp.: ZCSSINCLUDE)
  • CSS-Code im INCLUDE eintragen
  • Aktvieren! (Ja, das funktioniert.)
  • CSS-Code (INCLUDE) im ABAP-Code lesen
* Name des INCLUDEs mit dem CSS-Code
CONSTANTS: co_cssinclude TYPE char12 VALUE 'ZCSSINCLUDE'.
* String für CSS-Daten
DATA: lv_css TYPE string.
	
* Prüfen, ob INCLUDE im System (TADIR) aktiv vorhanden
SELECT SINGLE obj_name
  INTO @DATA(lv_obj_name)
  FROM tadir
  WHERE obj_name = @co_w3css
    AND pgmid    = 'R3TR'
    AND object   = 'PROG'.

IF sy-subrc = 0.

* Stringtab für Code
  DATA: it_incl_code TYPE stringtab.

* Code des INCLUDEs lesen
  READ REPORT co_w3css INTO it_incl_code.

  IF sy-subrc = 0.
* String-Tabelle mit CSS-Code in String wandeln, Trennzeichen ist CRLF
    lv_css = REDUCE string( INIT s = ||
                            FOR <s> IN it_incl_code
                            NEXT s = COND #( WHEN s IS INITIAL THEN |{ <s> }| ELSE |{ s }{ cl_abap_char_utilities=>cr_lf }{ <s> }| ) ).
  ENDIF.
ENDIF.

* HTML mit CSS-INCLUDE
DATA(lv_html) = '<!DOCTYPE html>' &&
                '<html>' &&
                '<title>Lagermaterialkatalog</title>' &&
                '<meta name="viewport" content="width=device-width, initial-scale=1">' &&
                '<style>' && lv_css && '</style>' &&
                '<body>' &&
                '</body>' &&
                '</html>'.

[ABAP] Tabelleninhalt einer internen Tabelle löschen

Variante 1 (WHERE)

SELECT carrid, connid FROM sflight INTO TABLE @DATA(it_sflight).

DELETE it_sflight WHERE NOT carrid = 'AA'.

cl_demo_output=>display( it_sflight ).

Variante 2 (RANGE)

SELECT * FROM sflight INTO TABLE @DATA(it_sflight).

* alle Carrier, die nicht 'AA' sind aus der iTab löschen
DELETE it_sflight WHERE NOT carrid IN VALUE rseloption( ( sign   = 'I'
                                                          option = 'EQ'
                                                          low    = 'AA'
                                                          high   = '' ) ).

cl_demo_output=>display( it_sflight ).

Variante 3 (RANGE mit Pattern)

SELECT carrid, connid, passname FROM sbook INTO TABLE @DATA(it_sbook) UP TO 500 ROWS.

SORT: it_sbook by passname.

* alle Einträge beginnend mit 'A', 'B' und 'C'
DELETE it_sbook WHERE passname IN VALUE rseloption( ( sign   = 'I'
                                                      option = 'CP'
                                                      low    = 'A*'
                                                      high   = '' )
                                                    ( sign   = 'I'
                                                      option = 'CP'
                                                      low    = 'B*'
                                                      high   = '' )
                                                    ( sign   = 'I'
                                                      option = 'CP'
                                                      low    = 'C*'
                                                      high   = '' ) ).

cl_demo_output=>display( it_sbook ).

Variante 4 (Pattern CP)

SELECT carrid, connid, passname FROM sbook INTO TABLE @DATA(it_sbook) UP TO 500 ROWS.

SORT: it_sbook BY passname.

* alle Einträge, deren passname mit 'Anna' beginnen
DELETE it_sbook WHERE passname CP 'Anna*'.

cl_demo_output=>display( it_sbook ).

Variante 5 (dynamische WHERE-Condition)

SELECT carrid, luggweight, passname FROM sbook INTO TABLE @DATA(it_sbook) UP TO 500 ROWS.

SORT: it_sbook BY passname.

* Stringtab mit WHERE-Conditions -> Leerzeichen beachten!
DATA(it_where) = VALUE stringtab(
                                  ( |testfehler = 'AA'| )
                                  ( |carrid EQ 'AA' AND passname CP 'A*'| )
                                  ( |luggweight > '10.0'| )
                                ).

LOOP AT it_where ASSIGNING FIELD-SYMBOL(<w>).

  WRITE: / <w>.

  TRY.
* dynamische WHERE-Condition ausführen
* fehlerhafte WHERE-Conditions werfen eine Exception
      DELETE it_sbook WHERE (<w>).
    CATCH cx_root INTO DATA(e_txt).
      WRITE: / e_txt->get_text( ).
  ENDTRY.

ENDLOOP.

cl_demo_output=>display( it_sbook ).

[ABAP] Open SQL: Dynamische WHERE-Condition

Variante 1 (Werteliste / Filter)

* Liste mit Benutzernamen
DATA(lv_users) = |'USER1', 'USER2'|.
* WHERE-Condition zusammenbauen
DATA(lv_where_condition) = |bname IN ({ lv_users })|.

TRY.
    DATA: it_usr TYPE STANDARD TABLE OF usr02 WITH DEFAULT KEY.

    SELECT * FROM usr02 INTO TABLE @it_usr WHERE (lv_where_condition).

    IF sy-subrc = 0.
      SORT: it_usr BY trdat DESCENDING bname ASCENDING.

      WRITE: / |USER         \| DATE       \| TIME     \| CREATED|.
      WRITE: / |-------------------------------------------------|.

      LOOP AT it_usr ASSIGNING FIELD-SYMBOL(<usr>).
        WRITE: / <usr>-bname, '|', <usr>-trdat, '|', <usr>-ltime, '|', <usr>-erdat.
      ENDLOOP.
    ENDIF.
  CATCH cx_root INTO DATA(e_txt).
    WRITE: / e_txt->get_text( ).
ENDTRY.

Variante 2 (Mehrzeilige WHERE-Bedingung)

* Liste mit Benutzernamen
DATA(lv_users) = |'USER1', 'USER2'|.
* Erstelldatum
DATA(lv_erdat) = |20100101|.

* WHERE-Condition zusammenbauen
DATA(it_where_condition) = VALUE stringtab( ( |bname IN ({ lv_users })| )
                                            ( |AND erdat > '{ lv_erdat }'| ) ).

TRY.
    DATA: it_usr TYPE STANDARD TABLE OF usr02 WITH DEFAULT KEY.

    SELECT * FROM usr02 INTO TABLE @it_usr WHERE (it_where_condition).

    IF sy-subrc = 0.
      SORT: it_usr BY trdat DESCENDING bname ASCENDING.

      WRITE: / |USER         \| DATE       \| TIME     \| CREATED|.
      WRITE: / |-------------------------------------------------|.

      LOOP AT it_usr ASSIGNING FIELD-SYMBOL(<usr>).
        WRITE: / <usr>-bname, '|', <usr>-trdat, '|', <usr>-ltime, '|', <usr>-erdat.
      ENDLOOP.
    ENDIF.
  CATCH cx_root INTO DATA(e_txt).
    WRITE: / e_txt->get_text( ).
ENDTRY.

[ABAP] OLE2: VBScript-Code über MSScriptControl ausführen

* VBS-Code einfügen
DATA(it_vbs_code) = VALUE stringtab( ( |Option Explicit| ) " Alle Variablen im VBS müssen mit Dim deklariert werden
                                     ( || )
                                     ( |Function Add(val1, val2)| )
                                     ( |  Add = val1 + val2| )
                                     ( |End Function| )
                                     ( || )
                                     ( |Function ShowMsgBox(InfoText)| )
                                     ( |  Dim msg| )
                                     ( |  msg = MsgBox(InfoText, vbOkOnly, "Info")| )
                                     ( |End Function| )
                                     ( || )
                                     ( |Function Run(FileName)| )
                                     ( |  Dim wshell| )
                                     ( |  Set wshell = CreateObject("Wscript.Shell")| )
                                     ( |  wshell.Run FileName, 1, False| )
                                     ( |End Function| )
                                     ( || )
                                     ( |Function Input()| )
                                     ( |  Input = InputBox("Namen:", "Namen eingeben", "Horst")| )
                                     ( |End Function| )
                                     ( || )
                                     ( |Function GenerateXLSX(FileName)| )
                                     ( |  Dim oXL| )
                                     ( |  Set oXL = CreateObject("Excel.Application")| )
                                     ( |  oXL.Visible = True| )
                                     ( |  oXL.Workbooks.Add| )
                                     ( |  oXL.Cells(1, 1).Value = "Test"| )
                                     ( |  oXL.ActiveWorkbook.SaveAs(FileName)| )
                                     ( |  oXL.Quit| )
                                     ( |  oXL = Nothing| )
                                     ( |End Function| )
                                   ).

DATA: lv_vbscode TYPE string.

* VBA-Code in String wandeln
LOOP AT it_vbs_code ASSIGNING FIELD-SYMBOL(<fs_codeline>).
  DATA(lv_codeline) = condense( <fs_codeline> ).
  IF lv_vbscode IS INITIAL.
    lv_vbscode = lv_codeline.
  ELSE.
    lv_vbscode = |{ lv_vbscode }{ cl_abap_char_utilities=>cr_lf }{ lv_codeline }|.
  ENDIF.
ENDLOOP.

DATA: o_scr TYPE ole2_object.

* Scriptcontrol-Objekt erzeugen
CREATE OBJECT o_scr 'MSScriptControl.ScriptControl'.

* wenn Erzeugung ok
IF sy-subrc = 0 AND o_scr-handle <> 0 AND o_scr-type = 'OLE2'.
* GUI anzeigen
  SET PROPERTY OF o_scr 'AllowUI' = 1.
* Sprache ist 'VBScript'
  SET PROPERTY OF o_scr 'Language' = 'VBScript'.

* Code hinzufügen und Syntaxcheck
  CALL METHOD OF o_scr 'AddCode'
    EXPORTING
      #1 = lv_vbscode.

* wenn Syntax ok
  IF sy-subrc = 0.
* einfacher Funktionsaufruf mit Übergabeparametern
    DATA: lv_res_i TYPE i.

    CALL METHOD OF o_scr 'Eval' = lv_res_i
      EXPORTING
        #1 = 'Add(1, 2)'.

    WRITE: / '1 + 2 =', lv_res_i.

* Eingabefeld mit Rückgabestring
    DATA: lv_res_s TYPE string.

    CALL METHOD OF o_scr 'Eval' = lv_res_s
      EXPORTING
        #1 = 'Input()'.

    DATA(lv_txt) = |ShowMsgBox("Hallo { lv_res_s }!")|.
* MessageBox anzeigen
    CALL METHOD OF o_scr 'Eval'
      EXPORTING
        #1 = lv_txt.

* Exe-Datei ausführen
    CALL METHOD OF o_scr 'Eval'
      EXPORTING
        #1 = 'Run("cmd.exe")'.

* Excel-Datei generieren
    CALL METHOD OF o_scr 'Eval'
      EXPORTING
        #1 = 'GenerateXLSX("c:\temp\test.xlsx")'.
  ELSE.
    WRITE: / 'Fehler im Code: ', sy-subrc.
  ENDIF.

* Objekt zerstören
  FREE OBJECT o_scr.
ENDIF.

Weiterführende Infos: Link, Link und Link

[ABAP] Listenausgabe mit Zeilennummern (Integer-Werte) – führende Nullen auffüllen

DATA(it_text_lines) = VALUE stringtab( ( |Heinz| )
                                       ( |Udo| )
                                       ( |Horst| )
                                       ( |Gerda| )
                                       ( |Hilde| )
                                       ( |Ulf| )
                                       ( |Marianne| )
                                       ( |Bertram| )
                                       ( |Eduart| )
                                       ( |Marianne| ) ).

DATA(lv_width) = strlen( |{ lines( it_text_lines ) }| ).

LOOP AT it_text_lines ASSIGNING FIELD-SYMBOL(<fs_line>).
  DATA(lv_cnt) = |{ sy-tabix }|.

  WRITE: / |{ lv_cnt WIDTH = lv_width ALPHA = IN }: { <fs_line> }|.
ENDLOOP.

[ABAP] IDoc als XML-Datei speichern

PARAMETERS: p_idoc TYPE edi_docnum.

START-OF-SELECTION.

  TRY.
      DATA(o_idoc_xml) = NEW cl_idoc_xml1( docnum = p_idoc ).

      DATA: lv_xml TYPE string.

      o_idoc_xml->get_xmldata_as_string( IMPORTING data_string = lv_xml ).

      DATA(it_xml) = VALUE stringtab( ( |{ lv_xml }| ) ).

      DATA: lv_temp_dir TYPE string.

* Temp-Directory holen
      cl_gui_frontend_services=>get_desktop_directory( CHANGING desktop_directory = lv_temp_dir ).
* Note 1442303, sonst ist lv_temp_dir leer
      cl_gui_cfw=>flush( ).

* Dateinamen zusammenbauen
      DATA(lv_filename) = |{ lv_temp_dir }\\my_xml.xml|.

* Datei im Zielverzeichnis erzeugen
      cl_gui_frontend_services=>gui_download( EXPORTING
                                                filename = lv_filename
                                                filetype = 'ASC'
                                              CHANGING
                                                data_tab = it_xml ).

      WRITE: / lv_filename.
    CATCH cx_root INTO DATA(e_txt).
      WRITE: / e_txt->get_text( ).
  ENDTRY.

[ABAP] VBA-Code ausführen

* VBA-Code
DATA(it_vba_code) = VALUE stringtab( ( |Dim app| )
                                     ( |Set app = CreateObject("Access.Application")| )
                                     ( |Dim msg| )
                                     ( |msg = MsgBox ("My Message.", 0, "Warning")| ) ).

DATA: lv_temp_dir TYPE string.

* Temp-Directory holen
cl_gui_frontend_services=>get_desktop_directory( CHANGING desktop_directory = lv_temp_dir ).
* Note 1442303, sonst ist lv_temp_dir leer
cl_gui_cfw=>flush( ).

* Dateinamen zusammenbauen
DATA(lv_filename) = |{ lv_temp_dir }\\my_script.vbs|.

* Datei im Zielverzeichnis erzeugen
cl_gui_frontend_services=>gui_download( EXPORTING
                                          filename = lv_filename
                                          filetype = 'ASC'
                                        CHANGING
                                          data_tab = it_vba_code ).

LOOP AT it_vba_code ASSIGNING FIELD-SYMBOL(<fs_line>).
  WRITE: / <fs_line>.
ENDLOOP.

SKIP.

WRITE: / lv_filename.

* VBA über Scripting Host ausführen
* "" für lv_filename ergänzen, sonst wird der Pfad unter parameter nicht korrekt übergeben,
* da Leerzeichen im Pfad als Trennung in einzelne Parameter erkannt wird
cl_gui_frontend_services=>execute( application = 'WSCRIPT.EXE'
                                   parameter   = |"{ lv_filename }"| ).