This is a test message to test the length of the message box.
Login
ABAP Tipp Adobe Forms
Erstellt von Software-Heroes

ABAP Tipp - Adobe Formulare zu groß

In diesem kleinen Tipp wollen wir uns anschauen, wieso im schlechtesten Fall Adobe Formulare größer werden, als sie eigentlich sein sollten.

Werbung

In diesem Artikel wollen wir auf einen kleinen Bug eingehen, der erzeugte PDF-Dokumente im System größer macht, als sie eigentlich sein sollten. Im schlimmsten Fall erzeugt das System eine Fehlermeldung und generiert gar keine Dokumente mehr.

 

Hintergrund

Vor kurzem sind wir in unserem System über einen Fehler gestolpert, der uns auf dieses Problem aufmerksam gemacht hat. Im Speziellen geht es um zu große Adobe PDFs, obwohl kaum Inhalt in den Formularen angedruckt wird. Aufgefallen ist dieses Problem, als der Adobe Document Service (ADS) mit einer Fehlermeldung abgebrochen ist und ein Formular nicht mehr erzeugen wollte.

Im Testsystem funktionierte der gleiche Fall noch problemlos, es dauerte aber bereits sehr lang, um das Dokument zu generieren. Gleichzeitig war das Dokument über 36 MB und das obwohl nur 4 Seiten im Dokument vorhanden waren und es jeweils ein Logo im Header gab. Diese Größe schien im ersten Moment erst einmal seltsam und dem Inhalt nicht angemessen.

Der erste Fehler konnte im Systemlog (Transaktion SM21) des Systems festgestellt werden. Die Meldung verweist auf den ADS und einem Problem beim Aufruf der Schnittstelle:

 

Theorie

Nach einer kurzen Debugging-Session der Formularschnittstelle und des Aufrufs, entwickelte sich die Theorie, dass die übergebenen Daten schuld am Problem sein. Die Formularschnittstelle war generisch aufgebaut, sodass bei jedem Formular alle Daten des Vorgangs übergeben werden. Zu diesen Daten gehören auch die Anhänge des Vorgangs, die direkt RAW übergeben werden. Diese Daten, also die Anhänge, werden für die Erstellung dieses Formulars auch überhaupt nicht benötigt, die Schnittstelle scheint überdimensioniert.

 

Aufbau der Demo

Um sich die Theorie einmal genauer anzuschauen, bauen wir uns ein einfaches Demo Formular auf und stellen den Fall einmal nach. Dazu muss zuerst einmal ein Formular und eine Formularschnittstelle über die Transaktion SFP aufgebaut werden.

 

Strukturen

Für eine saubere Schnittstelle zum Formular legen wir einige Datentypen an, die wir dann in der eigentlichen Formularschnittstelle verwenden können. Zuerst einmal benötigen wir eine Struktur, mit der wir einfache Informationen ans Formular geben und direkt in einem Textfeld anzeigen.

 

Als zweites benötigen wir einen Tabellentypen und eine Struktur die weiteren Daten ans Formular geben, aber nicht wirklich relevant für die Erstellung sind. Hier versorgen wir die Schnittstelle mit einigen Anhängen aus dem Vorgang.

 

Schnittstelle

Bevor das Formular angelegt wird, definieren wir eine Schnittstelle, über die wir die Daten von außen, an das Formular übergeben können. Dafür muss in der Transaktion SFP eine Schnittstelle angelegt werden, die auf DDIC Objekten basiert. Nach Anlage können wir die Struktur und den Tabellentyp mit den Standardparametern unter IMPORTING einbinden.

 

Formular

Für Testzwecke ist das Formular sehr einfach aufgebaut. Nach Einbindung der Schnittstelle, müssen die Parameter in den Kontext des Formulars gezogen werden, damit sie während der Erstellungszeit zur Verfügung stehen und genutzt werden können:

 

Im Anschluss übernehmen wir unsere Informationen aus der Struktur als einfaches Textfeld ins Formular, um auch Content aus dem Kontext zu verwenden:

 

Test

Nun geht es darum die Theorie zu beweisen. Dazu schreiben wir uns eine kleine Testklasse, die das Formular mit verschiedenen Daten aufruft und die entsprechenden Größen der Datei misst. Dabei werden wir unterschiedliche viele Datenmengen an das Formular schicken, um eine mögliche Korrelation festzustellen. Um ein Formular zu erzeugen, gibt es einige Standardbausteine und -logiken:

DATA:
  ld_functionname TYPE funcname,
  ls_outputparams TYPE sfpoutputparams,
  ls_docparams    TYPE sfpdocparams,
  ls_formoutput   TYPE fpformoutput.

CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
  EXPORTING
    i_name     = 'ZBS_DEMO_FORM'
  IMPORTING
    e_funcname = ld_functionname.

ls_outputparams-getpdf = abap_true.
ls_outputparams-dest = 'LOCL'.

CALL FUNCTION 'FP_JOB_OPEN'
  CHANGING
    ie_outputparams = ls_outputparams
  EXCEPTIONS
    cancel          = 1
    usage_error     = 2
    system_error    = 3
    internal_error  = 4
    OTHERS          = 5.
IF sy-subrc <> 0.
ENDIF.

ls_docparams-langu = 'D'.
ls_docparams-country = 'DE'.
ls_docparams-dynamic = abap_true.

CALL FUNCTION ld_functionname
  EXPORTING
    /1bcdwb/docparams  = ls_docparams
    is_data            = is_data
    it_documents       = it_documents
  IMPORTING
    /1bcdwb/formoutput = ls_formoutput
  EXCEPTIONS
    usage_error        = 1
    system_error       = 2
    internal_error     = 3
    OTHERS             = 4.
IF sy-subrc <> 0.
ENDIF.

rd_result = xstrlen( ls_formoutput-pdf ).

CALL FUNCTION 'FP_JOB_CLOSE'
  EXCEPTIONS
    usage_error    = 1
    system_error   = 2
    internal_error = 3
    OTHERS         = 4.
IF sy-subrc <> 0.
ENDIF.

 

Dabei erwarten wir als Ergebnis erst einmal nur die Größe des erzeugten PDFs, um dieses zu vergleichen. Nun benötigen wir eine Methode, die dynamisch Content in Form von Dateien zur Verfügung stellt. Dazu lesen wir eine Datei vom Dateisystem ein und hängen diese immer wieder an die Tabelle an.

DATA:
  ls_document TYPE zbs_s_demo_document.

TRY.
    OPEN DATASET c_dummy_file FOR INPUT IN BINARY MODE.
    READ DATASET c_dummy_file INTO ls_document-content.
    CLOSE DATASET c_dummy_file.
  CATCH cx_root.
    CLEAR ls_document-content.
ENDTRY.

ls_document-file_name = c_dummy_file.
ls_document-length = xstrlen( ls_document-content ).

DO id_count TIMES.
  INSERT ls_document INTO TABLE rt_result.
ENDDO.

 

Zum Abschluss noch einmal das vollständige Beispiel der ausführbaren Klasse. In der Main Methode implementieren wir unsere Testfälle, die für unterschiedliche Größen die Formulare erzeugen. In der Ausgabe Methode wird dann das Ergebnis und der übertragene Content verglichen:

CLASS zcl_bs_demo_formtest DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

  PROTECTED SECTION.
  PRIVATE SECTION.
    TYPES:
      td_num TYPE p LENGTH 16 DECIMALS 4.

    CONSTANTS:
      c_conversion  TYPE i VALUE 1048576,
      c_dummy_file  TYPE string VALUE `PUT A FILE IN HERE`,
      c_header_text TYPE string VALUE 'Test'.

    METHODS:
      create_pdf_form
        IMPORTING
                  is_data          TYPE zbs_s_demo_data
                  it_documents     TYPE zbs_t_demo_document OPTIONAL
        RETURNING VALUE(rd_result) TYPE i,

      generate_content
        IMPORTING
                  id_count         TYPE i
        RETURNING VALUE(rt_result) TYPE zbs_t_demo_document,

      output_data
        IMPORTING
          io_out         TYPE REF TO if_oo_adt_intrnl_classrun
          id_file_length TYPE i
          it_documents   TYPE zbs_t_demo_document.
ENDCLASS.


CLASS zcl_bs_demo_formtest IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA:
      ld_file_length TYPE i,
      lt_documents   TYPE zbs_t_demo_document.

    ld_file_length = create_pdf_form(
      is_data      = VALUE #( header = c_header_text )
      it_documents = VALUE #( )
    ).
    output_data( io_out = out id_file_length = ld_file_length it_documents = lt_documents ).

    lt_documents = generate_content( 1 ).
    ld_file_length = create_pdf_form(
      is_data      = VALUE #( header = c_header_text )
      it_documents = lt_documents
    ).
    output_data( io_out = out id_file_length = ld_file_length it_documents = lt_documents ).

    lt_documents = generate_content( 2 ).
    ld_file_length = create_pdf_form(
      is_data      = VALUE #( header = c_header_text )
      it_documents = lt_documents
    ).
    output_data( io_out = out id_file_length = ld_file_length it_documents = lt_documents ).

    lt_documents = generate_content( 16 ).
    ld_file_length = create_pdf_form(
      is_data      = VALUE #( header = c_header_text )
      it_documents = lt_documents
    ).
    output_data( io_out = out id_file_length = ld_file_length it_documents = lt_documents ).

    lt_documents = generate_content( 64 ).
    ld_file_length = create_pdf_form(
      is_data      = VALUE #( header = c_header_text )
      it_documents = lt_documents
    ).
    output_data( io_out = out id_file_length = ld_file_length it_documents = lt_documents ).
  ENDMETHOD.


  METHOD create_pdf_form.
    DATA:
      ld_functionname TYPE funcname,
      ls_outputparams TYPE sfpoutputparams,
      ls_docparams    TYPE sfpdocparams,
      ls_formoutput   TYPE fpformoutput.

    CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
      EXPORTING
        i_name     = 'ZBS_DEMO_FORM'
      IMPORTING
        e_funcname = ld_functionname.

    ls_outputparams-getpdf = abap_true.
    ls_outputparams-dest = 'LOCL'.

    CALL FUNCTION 'FP_JOB_OPEN'
      CHANGING
        ie_outputparams = ls_outputparams
      EXCEPTIONS
        cancel          = 1
        usage_error     = 2
        system_error    = 3
        internal_error  = 4
        OTHERS          = 5.
    IF sy-subrc <> 0.
    ENDIF.

    ls_docparams-langu = 'D'.
    ls_docparams-country = 'DE'.
    ls_docparams-dynamic = abap_true.

    CALL FUNCTION ld_functionname
      EXPORTING
        /1bcdwb/docparams  = ls_docparams
        is_data            = is_data
        it_documents       = it_documents
      IMPORTING
        /1bcdwb/formoutput = ls_formoutput
      EXCEPTIONS
        usage_error        = 1
        system_error       = 2
        internal_error     = 3
        OTHERS             = 4.
    IF sy-subrc <> 0.
    ENDIF.

    rd_result = xstrlen( ls_formoutput-pdf ).

    CALL FUNCTION 'FP_JOB_CLOSE'
      EXCEPTIONS
        usage_error    = 1
        system_error   = 2
        internal_error = 3
        OTHERS         = 4.
    IF sy-subrc <> 0.
    ENDIF.
  ENDMETHOD.


  METHOD generate_content.
    DATA:
      ls_document TYPE zbs_s_demo_document.

    TRY.
        OPEN DATASET c_dummy_file FOR INPUT IN BINARY MODE.
        READ DATASET c_dummy_file INTO ls_document-content.
        CLOSE DATASET c_dummy_file.
      CATCH cx_root.
        CLEAR ls_document-content.
    ENDTRY.

    ls_document-file_name = c_dummy_file.
    ls_document-length = xstrlen( ls_document-content ).

    DO id_count TIMES.
      INSERT ls_document INTO TABLE rt_result.
    ENDDO.
  ENDMETHOD.


  METHOD output_data.
    DATA:
      ld_add_content TYPE i.

    LOOP AT it_documents INTO DATA(ls_document).
      ld_add_content = ld_add_content + ls_document-length.
    ENDLOOP.

    io_out->write( |--- New document with { lines( it_documents ) } lines| ).
    io_out->write( |Generated PDF file: { CONV td_num( id_file_length / c_conversion ) } MB| ).
    io_out->write( |Additional content: { CONV td_num( ld_add_content / c_conversion ) } MB| ).
  ENDMETHOD.
ENDCLASS.

 

Nun zum spannendsten Teil des Tests, der eigentlichen Ausführung der Testklasse und dem eigentlichen Ergebnis:

 

Was können wir nun an den Messungen ableiten:

  • Eine Datei im Content ist ca. 0,5 MB groß
  • Je mehr Dokumente wir ans PDF übergeben, desto größer wird die erzeugte Datei
  • Die Dateien werden nicht auf dem Formular verwendet
  • Sehr wahrscheinlich werden die Informationen als Meta-Informationen im PDF gespeichert
  • Übergebener Content und Dateigröße korrelieren
  • Der Content scheint komprimiert zu werden

 

Learning

Unser Tipp an dich ist, dass du dir bei der Erstellung der Formularschnittstelle darauf achten solltest, sie nicht zu generisch zu fassen und wenn du es doch machst, nur die nötigsten Informationen ans Formular übergibst, da diese Informationen auch im erzeugten PDF gespeichert werden.

In unserem internen Fall konnte auch irgendwann das PDF nicht mehr erzeugt werden, da die SOAP Schnittstelle zum ADS überlastet war. Ebenso ist das Versenden der Dokumente kaum noch möglich, denn die eigentlichen 180 KB Dateien, waren nun 36 MB groß.

 

Fazit

Wir hoffen der Tipp war für dich hilfreich und du achtest in Zukunft etwas mehr auf die Schnittstellen, die du baust. Sowieso ist es immer besser, nur die nötigsten Informationen zu übergeben, wer weiß wer auf der anderen Seite zuhört.


Enthaltene Themen:
TippAdobe FormsALDSFP
Kommentare (0)

ABAP - ALV in 2022 noch relevant?

Kategorie - ABAP

Heute mal die scherzhafte Frage, benötigen wir im Jahr 2022 noch Reports die ALV Ausgaben erzeugen? Der Frage wollen wir in diesem Artikel einmal nachgehen.

01.07.2022

ABAP im Wandel

Kategorie - ABAP

Die Programmiersprache ABAP ist bereits seit Jahren im Wandel und modernisiert sich in verschiedenen Konzepten. In diesem Artikel schauen wir uns das einmal im Detail an.

24.06.2022

ABAP Tipp - Clean Core

Kategorie - ABAP

In diesem Artikel mal etwas zum Thema Clean Core, was bedeutet es für den Entwickler, was sind Vorraussetzungen und auf was muss man achten.

17.06.2022

ABAP Tipp - Verarbeitung in neuem Task

Kategorie - ABAP

In diesem Tipp geht es um die asynchrone Verarbeitung in einem neuen Prozess und auf was du dabei achten solltest.

07.01.2022

ABAP Tipp - Konvertierung JSON nach Intern

Kategorie - ABAP

In diesem kleinen Tipp gehen wir darauf ein, wie du einen JSON Stream in ein internes Format konvertierst und dann ordentlich verwenden kannst.

10.12.2021