
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.
Inhaltsverzeichnis
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.