Teil 2. Anbindung von Java und SAP

https://pdfslide.net/documents/sap-fuer-java-entwickler-konzepte-schnittstellen-technologien.html

Teil 2. Anbindung von Java und SAP

5. Anbindung von Java und SAP – Übersicht
6. RFC-Schnittstellen und Java Connector
7. IDocs

5 Anbindung von Java und SAP

Übersicht Die Sprache Java hat Einzug in viele Unternehmen gehalten und sich dabei den Ruf als Integrationsplattform erworben. Zu annähernd jeder Technologie und jedem Protokoll stellt Java eine Schnittstelle bereit. Sei es LDAP, IMS oder MQSeries, Java liefert die Funktionalität in abstrahierter Form als APIs wie in diesem Fall JNDI, JCA und JMS.

Daher steht an Knotenpunkten zwischen heterogenen Systemen oft ein Java-Server. Muss man die Daten aus Datenbank X mit denen aus Altsystem Y kombinieren und schließlich in Datenbank Z schreiben, so strickt man sich ein paar passende EJBs und kann darauf aufbauend dem Benutzer eine homogene Oberfläche liefern. Es liegt nahe, dass eines der proprietären Systeme, die es zu integrieren gilt, durchaus auch ein SAP-System sein kann. In großen Unternehmen ist es sogar recht wahrscheinlich, dass ein beliebiger fachlicher Aspekt wenigstens teilweise eine Anbindung an Daten erfordert, die in SAP verwaltet werden. Entsprechend groß ist der Bedarf für eine Kopplung von Java und SAP. In diesem Teil 2 stellen wir zwei unterschiedliche Vorgehensweisen vor, um Java an SAP anzubinden.

Zum einen ist das die Kombination aus RFC und JCo, die primär für synchrone Anbindungen genutzt wird. Sie wird in Kapitel 6 behandelt. Zum anderen ist das die IDoc-Technologie, die asynchrone Anbindungen möglich macht. Sie wird in Kapitel 7 näher beleuchtet. Voraussetzung ist in beiden Fällen ein SAP-System, wie Sie es heute, im Jahr 2005 in der Praxis vorfinden. Also ein R/3-System und kein Netweaver-System, wie es in Teil 3 beschrieben wird. Im Netweaver verschwimmen zwar die Grenzen zwischen Java-Programm und SAP-System und die Koppelung unterschiedlicher Systeme gewinnt einiges an Eleganz. Doch das nützt Ihnen im Alltag wenig, da diese Technologie noch keine nennenswerte Verbreitung gefunden hat. Es lohnt sich also, einen Blick auf die etablierten Anbindungstechniken in diesem Teil des Buches zu werfen.

6 RFC-Schnittstellen und Java Connector

Möchte man ein SAP-System synchron mit einem Fremdsystem koppeln, dann ist die RFC-Technologie die bewährte Lösung. RFC steht für Remote Function Call und bezeichnet einen Mechanismus, um auf einem entfernten System eine Funktion auszuführen. Dabei handelt es sich in der Regel um ein SAP-System. Die Technik ist vergleichbar mit dem RPC-Mechanismus unter Unix.
Ursprünglich wurde RFC über eine C-Bibliothek realisiert, die das Fremdsystem einbinden musste, um mit einem Funktionsbaustein im SAP System zu kommunizieren. Da C relativ mühsam zu programmieren ist, hat sich schnell eine Reihe von Produkten etabliert, die die eigentliche RFC/C-Schnittstelle vor den Benutzern verbergen und handlichere und höherwertige Schnittstellen zur Verfügung stellen. Wenn man heute von einer RFC-Schnittstelle spricht, dann meint man also meist einen Mechanismus, der nur intern das RFC-Protokoll verwendet.
Wir werden hier den RFC-Mechanismus nur über SAPs eigene Implementierung eines Java-Wrappers einsetzen. Der sogenannte Java Connector (JCo) ist das einfachste Mittel, um von Java aus auf ein SAP-System zuzugreifen. Intern verwendet er die ursprüngliche C-basierte RFC-Bibliothek. Sie sollten im Hinterkopf behalten, dass der JCo nur eine von mehreren Möglichkeiten darstellt, die RFC-Schnittstelle zu nutzen. Die übrigen werden in der Regel von kleinen Fremdanbietern hergestellt.
Wie auch immer die Implementierung auf Seiten des Fremdsystems geartet sein mag, die R/3-Seite einer RFC-Verbindung folgt immer derselben Logik. Vereinfachend haben wir gesagt, es handle sich dabei um einen Funktionsbaustein. Um die Datenkonsistenz im SAP-System sicherzustellen, muss man jedoch ein paar Umwege in Kauf nehmen.
Zuerst sollen diese übergeordneten R/3-seitigen Aspekte des RFC Zugriffs beschrieben werden. Anschließend werden die dazu notwendigen Zugriffstechniken auf Java-Seite detailliert untersucht. Im Anschluss folgt ein Abschnitt über Debugging-Techniken und einer über weiterführende Themen.

6.1 RFC auf R/3-Seite
6.1.1 Überblick

In der Regel spielt die SAP-Seite bei einer RFC-Schnittstelle die Rolle des Servers. Dahinter steht der Sachverhalt, dass das SAP-System oft der zentrale Ablageort für unternehmensweit genutzte Daten ist. Die Datenseite ist meist die passive Seite in einem Kommunikationsvorgang, während die andere aktive Seite auf die Daten zugreift. Daher gehen wir davon aus, dass die SAP-Seite passiv ist und somit den Part des Servers übernimmt.

Sollte der Zugriff lediglich zum Auslesen von Daten aus SAP dienen, ist das Vorgehen einfach. Sie implementieren einen Funktionsbaustein, der direkt auf die Tabellen zugreift. Bei dessen Erstellung müssen Sie lediglich darauf achten, dass Sie ihn als remote-fähig markieren. Außerdem sind bei der Auswahl der Parameter einige Regeln zu beachten, die weiter unten diskutiert werden.
Wesentlich schwieriger sind schreibende Zugriffe auf Daten im SAP System zu implementieren. Wenn Sie die vom System vorgegebenen Transaktionen manuell bedienen, sorgen ja viele tausend Zeilen ABAP Code dafür, dass die betriebswirtschaftlichen Tabelleninhalte auf konsistente Art und Weise modifiziert werden. Beispielsweise wird kein Konto belastet, ohne ein anderes zu entlasten. Rein technisch gesehen haben Sie die Möglichkeit, die Tabelleninhalte beliebig zu modifizieren und Kontostände im System nach Belieben zu manipulieren. Doch dadurch verlieren Sie nicht nur die Datenkonsistenz, sondern auch den Haftungsanspruch der Firma SAP für Ihr Produkt. Außerdem müssen Sie Ihren Code nach einem Wechsel des SAP-Releases eventuell neu schreiben, da sich die systeminterne Logik geändert haben kann. Aus diesem Grund sollten Sie nie direkt schreibend auf SAP-eigene Tabellen zugreifen.
Die Abhilfe für dieses Dilemma schaffen sogenannte BAPIs. BAPIs sind vorgegebene Funktionsbausteine für wohldefinierte, häufig anzutreffende betriebswirtschaftliche Szenarien. Die Abkürzung steht für Business Application Programming Interface. Aus technischer Sicht sind BAPIs Funktionsbausteine, die bestimmten syntaktischen Regeln gehorchen.

Fachlich gesehen stellen BAPIs für Sie sicher, dass Datenbankinhalte nur auf konsistente Art und Weise modifiziert werden. Sie sollten also zuerst prüfen, ob für Ihren Zweck bereits ein BAPI existiert. Falls ja, sollten Sie es auf jeden Fall verwenden, selbst wenn Sie nur einen Teil seiner Funktionalität benötigen. Alle Alternativen sind wesentlich fehleranfälliger.
Auch wenn kein BAPI für Ihren Zweck existiert, können Sie mit einem Trick auf offiziell ausgelieferten Code zum Modifizieren von Daten zugreifen. Ein Mechanismus, der Batch Input genannt wird, gestattet es nämlich, die SAP-eigenen Oberflächen in Funktionsbausteine zu kapseln. Das Vorgehen, das andernorts auch als Screen Scraping bezeichnet wird, ist ebenso plump wie naheliegend. Sie zeichnen einen Bedienvorgang der passenden SAP-Transaktion als Marko auf. Das ist möglich, da jedes Feld einer SAP-Oberfläche einen innerhalb der Maske eindeutigen Namen trägt. So können Sie auf automatisierte Weise die Benutzereingaben in die Masken emulieren – inklusive dem Klick auf den Speichern-Button. Liegt der Vorgang einmal als Makro vor, wandeln Sie ihn in ABAP-Code, um dann die eingegebenen Werte nach Bedarf zu parametrisieren und als Funktionsbaustein aufzurufen.

Das Batch-Input-Verfahren ist allerdings anfällig gegenüber Änderungen an der Oberfläche oder Bedienvarianten. Beispielsweise kann ein Eingabefeld unter bestimmten Rahmenbedingungen ausgegraut sein. Falls Ihr Makro davon ausgeht, dass es immer veränderbar ist, wird es die Bearbeitung mit einem Fehler abbrechen müssen. Dennoch hat das Batch-Input-Verfahren seine Daseinsberechtigung. Wenn es für Ihren Zweck kein BAPI gibt, bietet Batch Input die einzige Möglichkeit, auf konsistente Weise SAP-eigene Tabellen zu ändern.

Ganz anders verhält es sich, wenn Sie lediglich die Inhalte von Tabellen ändern wollen, die Sie selbst angelegt haben. Dann haben Sie deren Konsistenz ganz in eigener Verantwortung. Sie dürfen und müssen unter diesen Umständen natürlich einen eigenen Funktionsbaustein zur Verfügung entwickeln.
Hier sind die soeben erklärten Regeln noch einmal als Stichpunkte zusammengefasst. Sie gelten für die SAP-Seite von RFC-Schnittstellen, wo-bei SAP die Rolle des Servers einnimmt.

  1. Für schreibende Zugriffe nutzen Sie soweit vorhanden BAPIs.
  2. Falls kein BAPI für Ihren schreibenden Zugriff existiert, nutzen Sie den Batch-Input-Mechanismus.
  3. Für schreibende Zugriffe auf selbstdefinierte Tabellen implementieren Sie selbst einen remote-fähigen Funktionsbaustein.
  4. Für lesende Zugriffe implementieren Sie selbst einen remote-fähigen Funktionsbaustein oder nutzen falls vorhanden einen vom System vorgegebenen.

Im Folgenden werden die einzelnen besprochenen Techniken ausführlicher vorgestellt.

6.1.2 Remote-fähige Funktionsbausteine

Damit ein selbstgeschriebener Funktionsbaustein über den RFC-Mechanismus aufrufbar wird, muss er ein paar Bedingungen erfüllen. Betrachten wir diesen minimalen Funktionsbaustein, der später auch für den ersten JCo-Zugriff verwendet wird.

Um ihn über RFC ansprechbar zu machen, muss man ihn als remote-fähig markieren. Dies geschieht im Function Builder bei den allgemeinen Attributen des Funktionsbausteins, wie in Kapitel 4 beschrieben wurde.
Sobald Sie einen remote-fähigen Funktionsbaustein aktivieren, wird eine Reihe von Hilfsfunktionen generiert und der SAP Application Server regiert ab sofort auf RFC-Aufrufe an diesen Baustein. Beim Aktivieren oder Speichern eines remote-fähigen Funktionsbausteins überprüft die ABAP-Umgebung eine Reihe von Bedingungen ab, die für unser Minimalbeispiel Z_EINFACH allesamt erfüllt sind. Bei einem komplexeren Funktionsbaustein, der über eine Reihe von Parametern verfügt, ist dies ein wenig komplizierter.

Bevor wir uns einem zweiten Funktionsbaustein Z_PARAMETER zu-wenden, der die erwähnten Schwierigkeiten auslotet, ist ein kurzer Exkurs über Speicherverwaltung im Allgemeinen notwendig. Wenn man sich überlegt, wie überhaupt Parameter von einem Prozess zum anderen oder gar von einem Rechner zum anderen gelangen können, werden die Restriktionen der RFC-Bausteine plausibel.

Bei Funktionsaufrufen, ganz egal in welcher Programmiersprache, unterscheidet man zwischen wertbasierter Parameterübergabe und referenz-basierter Übergabe. In ersterem Fall erhält das aufgerufene Programm eine Kopie des Parameters, in letzterem lediglich eine Referenz auf einen Speicherbereich, in dem der eigentliche Parameter liegt. Dieser Speicherbereich wurde von dem aufrufenden Programm vorbereitet und mit Inhalt gefüllt. Es gibt zwei Hauptgründe, um referenzbasierte Parameterübergabe zu nutzen: man verwendet Parameter variabler Länge oder man möchte sich einen Kopiervorgang ersparen.

In ABAP ist der Typ string der typische Vertreter für einen Typ variabler Länge. Der ABAP-Compiler weiß im Voraus nicht, wie viel Platz zur Laufzeit für einen string benötigt wird. Daher realisiert er ihn intern über einen Zeiger, dessen Größe natürlich feststeht und der auf einen Speicherbereich variabler Größe verweist. Funktionsparameter vom Typ CHANGING sind ein Beispiel für das Sparen eines Kopiervorgangs. Sie geben dieselbe Struktur in den Funktionsbaustein hinein, die dieser wieder zurückgibt. Sie mag zwar modifiziert werden, aber es handelt sich um das-selbe Exemplar einer Struktur. Dasselbe gilt für Parameter, die Sie als REFERENCE(…) und nicht als VALUE(…) deklarieren. Auch hier-bei spart man sich einen Kopiervorgang und operiert innerhalb der Funktion auf demselben Speicherbereich wie außerhalb.

Der Gedankengang schließt sich nun, wenn man sich klar macht, was mit einer Referenz passiert, die nicht lokal in einem Programm übergeben, sondern über einen RFC-Aufruf auf einen anderen Rechner übertragen wird. Auf dem anderen Rechner ist der Hauptspeicher gänzlich anders angeordnet als auf dem Ursprungsrechner. Daher hat die Referenz dort keinerlei sinn-volle Bedeutung. Vielleicht weist sie auf einen verfügbaren Speicherbereich, aber keinesfalls auf einen, in dem sich der erwartete Inhalt befindet.

Daher verbietet der ABAP-Compiler in remote-fähigen Funktionsbau-steinen jede Art von referenzbasierten Parametern. Das gilt nicht nur für die offensichtlichen Referenzen, die über REFERENCE(…) deklariert wurden, sondern auch für die versteckten Referenzen, also für CHANGING-Parameter und solche, die variable Länge haben wie string oder Strukturen mit string- oder expliziten Referenzfeldern. Zur Erinnerung: im Function Builder stellen Sie über eine Checkbox in der Spalte „Wertübergabe“ ein, dass ein Parameter per Wert übergeben wird. Um alle typischen und erlaubten Parametertypen eines remote-fähigen Funktionsbausteins abzudecken, betrachten wir die Definition von Z_PARAMETER.

Dieser Funktionsbaustein enthält IMPORTING- und EXPORTING-Parameter, die allesamt wertbasiert übergeben werden. Außerdem verwendet er eine Tabelle und eine Exception.
Versteckte Referenztypen sind nicht vorhanden, denn der Typ Z_C10 ist ein Datentyp äquivalent zu c(10), also von fester Länge 10. Auch die Struktur ZPARAMSTRUCT enthält, wie in Abb. 6.1 gezeigt, nur Felder fester Länge.


Abb. 6.1. Definition der Struktur ZPARAMSTRUCT im ABAP Dictionary

Merken Sie sich am besten die Feldnamen, denn wir werden später vom JCo aus auf diese Felder zugreifen.

Ein Wort noch zu dem einen TABLES-Parameter. In nicht remote-fähigen Funktionsbausteinen sind TABLES-Parameter unüblich, da Sie seit R/3-Release 4.5 auch unter IMPORTING, EXPORTING und CHANGING beliebige Strukturen und insbesondere Tabellen übergeben können. Sie müssen die Typdefinition lediglich unter einem eigenen Namen im ABAP Dictionary bekannt machen. Aus Konsistenzgründen nutzen Sie diese Möglichkeit auch, da so alle Parameterarten gleich behandelt werden und die Verwendungsrichtung immer ablesbar ist.

Die Implementierung der RFC-Bibliotheken hinkt der Entwicklung des R/3-Systems ein wenig hinterher. Daher werden von den Client-seitigen RFC-Bibliotheken zum gegenwärtigen Zeitpunkt noch keine Tabellenparameter außerhalb des TABLES-Abschnitts unterstützt. Da der Function Builder im R/3 auch für remote-fähige Funktionsbausteine Tabellen in den IMPORTING, EXPORTING und CHANGING-Abschnitten zulässt, müssen Sie selbst darauf achten, diese vorerst zu vermeiden.

Sie haben nun das Werkzeug in der Hand, um selbst remote-fähige Funktionsbausteine zu schreiben. Wie eingangs erläutert sollten Sie dies nur dann tun, wenn Sie keine potentiell konsistenzgefährdenden Datenmanipulationen vornehmen. Andernfalls sollten Sie auf BAPIs oder notfalls auf den Batch-Input-Mechanismus zurückgreifen. Rein technisch gesehen reduzieren sich beide Vorgehensweisen wiederum auf einen RFC-Baustein, der denselben Gesetzmäßigkeiten folgt, wie in diesem Abschnitt erörtert.

6.1.3 BAPIs

BAPIs sind viel mehr als nur eine besondere Art von Funktionsbaustein. BAPIs stellen den Versuch dar, das R/3-System sauber zu modularisieren und als Schnittstelle zu standardisieren. In der Frühzeit von R/3 war das System wild gewachsen mit dem Ergebnis, dass dieselbe Funktionalität gelegentlich doppelt implementiert wurde und nicht immer als solche erkennbar war. Das erschwerte die Wartung und machte tiefgreifende Strukturänderungen zu einem schwierigen Unterfangen.

Die BAPI-Technologie schafft an dieser Stelle Abhilfe. Ihr Hauptzweck ist es, das System in autarke Teile zu gliedern, die voneinander unabhängig sind und lediglich über wohldefinierte Schnittstellen aufeinander zugreifen. Die Umstellung auf die BAPI-Architektur ist ein langwieriger Prozess, der bis heute andauert.

Doch BAPIs dienen nicht nur der internen Modularisierung, sondern stellen auch fachlich wohldefinierte Zugriffspunkte nach außen dar. Der Entwickler kann dadurch auf zentrale Funktionalitäten über Schnittstellen zugreifen, die sich auch in zukünftigen Releases nicht ändern werden. Zumindest gilt dies, sobald das BAPI von SAP offiziell freigegeben wurde.

BAPIs folgen dem objektorientierten Paradigma, auch wenn Sie gröber granular sind als das typische Java-Objekt. Ein BAPI verfügt über mehrere Methoden, die im ABAP-Code über

angesprochen werden. Ganz ähnlich wie bei Java-Methoden gibt es instanzabhängige und instanzunabhängige Methoden. Die meisten BAPIs lassen sich über eine instanzunabhängige Create-Methode instanziieren oder über eine Find-Methode suchen. Auf der dadurch gewonnenen BAPI-Instanz kann man dann instanzabhängige Methoden ausführen. Außerdem verfügen BAPIs über Attribute und einen Vererbungsmechanismus.

Ein BAPI spielt die Rolle einer vom System mitgelieferten Enterprise Java Bean. Es kapselt die Geschäftslogik zum konsistenten Modifizieren von Datenbankinhalten für einen bestimmten Use Case.

Wenn Sie ein BAPI über den RFC-Mechanismus aufrufen, merken Sie wenig von dessen objektorientierter Natur. Das liegt daran, dass der RFC-Mechanismus streng prozedural ist. Um objektorientierte Methodenaufrufe auf Funktionen abzubilden, hat SAP jeder BAPI-Methode einen remote-fähigen Funktionsbaustein zugeordnet, der ungefähr dieser Namenskonvention folgt:

So entspricht der BAPI-Aufruf Vendor.GetInternalNumber dem Funktionsbaustein BAPI_VENDOR_GETINTNUMBER. Auch die Parameterliste des Funktionsbausteins kann geringfügig von der der zugehörigen

BAPI-Methode abweichen. Wenn Sie beispielsweise eine instanzabhängige BAPI-Methode aufrufen möchten, müssen Sie dem entsprechenden Funktionsbaustein zusätzlich im ersten Parameter die Objekt-ID der Instanz mit-geben. Anders könnten Sie den Bezug zu der speziellen Instanz nicht her-stellen. Durch dieses Nebeneinander der unterschiedlichen Notationen ist es auch nachvollziehbar, dass im allgemeinen Sprachgebrauch gelegentliche einzelne BAPI-Methoden selbst als BAPI bezeichnet werden.

Im R/3 können Sie über den BAPI-Explorer durch die verfügbaren BA-PIs navigieren. Sie erreichen ihn über die Transaktion BAPI. In Abb. 6.2 sehen Sie das BAPI Vendor, wie es im BAPI-Explorer dargestellt wird. Durch Doppelklick auf eine BAPI-Methode öffnen Sie das Fenster, das rechts unten sichtbar ist. So können Sie den Namen des entsprechenden Funktionsbausteins ermitteln.

Wenn Sie ein BAPI identifiziert haben, das Ihren Zwecken dient, und den Namen des zugehörigen Funktionsbausteins ermittelt haben, können Sie diesen wie jeden anderen RFC-Baustein verwenden.

Abb. 6.2. Das Vendor-BAPI im BAPI-Explorer

Ein paar Eigenheiten der BAPI-Funktionsbausteine sind dennoch wissenswert. So werden Sie feststellen, dass BAPI-Funktionsbausteine nie eine Exception in der Schnittstelle deklarieren. Stattdessen werden Sie in der Deklaration immer einen Exporting-Parameter finden, der ungefähr so geartet ist:

Die BAPIRET2-Struktur gibt Ihnen Auskunft darüber, ob der Aufruf erfolgreich ausgeführt wurde. Sie enthält weiterhin Meldungstexte über die Ausführung des BAPIs, die entweder rein informativ sein können oder aber eine Fehlersituation erläutern. Die wichtigsten Felder von BAPIRET2sind in der folgenden Liste aufgeführt.

Tabelle 6.1. Ausgewählte Felder der BAPIRET2-Struktur

Feld Typ Bedeutung
Type Char 1 E = Error
W=Warning
I = Information
A = Abort
S = Success oder System
ID Char 20 Meldungs-ID, oft bedeutet TYPE=S und ID=0, dass ein Fehler aufgetreten ist.
MESSAGE Char 220 Meldungstext
MESSAGE_V1 – MESSAGE_V4 Char 50 Variable Anteile der Meldung

Das Feld TYPE entscheidet darüber, ob die Struktur eine gutartige Meldung enthält (W, I) oder auf einen Fehler hinweist (E, A). Tückisch ist der Typ S, der meist für eine erfolgreiche Ausführung (Success) steht, gelegentlich aber auf Systemfehler hinweist. Letzteres tritt meist in Kombination mit der ID 0 auf. Oft gibt ein BAPI nicht nur eine einzige BAPIRET2-Struktur, sondern eine Tabelle solcher Strukturen zurück. Sie müssen daher alle Einzelmeldungen durchgehen und prüfen, ob Sie auf einen Fehler hinweisen, um sicherzustellen, dass der Aufruf als ganzer erfolgreich war.

Anstelle der BAPIRET2-Struktur geben manche BAPIs Strukturen vom Typ BAPIRET1 oder BAPIRETURN zurück. Dies sind Vorgängerversionen mit ähnlicher Semantik. Sie entsprechen der generellen Praxis von SAP, neuere Versionen eines ABAP-Objekts mit einer abschließenden Versionsnummer zu kennzeichnen. Diese Richtlinie kann auch auf ganze BAPIs oder BAPI-Methoden angewandt werden. Bevor Sie also BAPI_VENDOR_GETDETAIL aufrufen, sollten Sie überprüfen, ob nicht eventuell schon ein BAPI_VENDOR_GETDETAIL2 existiert. Letzterem sollten Sie den Vorrang geben, denn die Existenz einer neueren Version weist darauf hin, dass die ältere Version in zukünftigen Releases nicht mehr unterstützt wird. Schließlich ist der Leitgedanke beim BAPI-Einsatz die Langlebigkeit der Schnittstellen.

Das Anhängen von Versionsnummern an BAPI-Methodennamen entspricht dem Deprecation-Mechanismus in Java. Methoden mit niedrigeren Versionen im Namen werden in zukünftigen Releases nicht mehr unterstützt.

Ebenso wichtig für die Langlebigkeit der Schnittstellen ist ihr Freigabestatus. Im BAPI-Explorer können Sie unter den zentralen Eigenschaften eines BAPIs nachsehen, ob es von SAP freigegeben wurde. Nur dann ist sichergestellt, dass es in dieser Form lange erhalten bleibt. Nicht freigegebene BAPIs sollten Sie nur dann nutzen, wenn Sie keine anderen BAPIs finden, die Ihrem Zweck genügen. Zusammenfassend lässt sich sagen, dass BAPIs tatsächlich nur wie ein spezieller Typ von RFC-Baustein zu verwenden sind. Ihre Nutzung hat jedoch ihre Tücken und erfordert in der Praxis viel Sorgfalt und Geduld.

6.1.4 Batch Input / Call Transaction

Die Bezeichnung Batch Input steht ursprünglich für ein Verfahren, das die Eingabe von Massendaten in R/3-Oberflächen ermöglicht. Lang laufende oberflächenlose Prozesse werden ja oft als Batch-Prozesse bezeichnet. Wenn man vermeiden möchte, bei der Datenübernahme viele hundert Mal dieselbe Maske manuell auszufüllen, bietet das Batch-Input-Verfahren einen Ausweg. Es ermöglicht, den Eingabevorgang einmal in einem Makro-Editor aufzuzeichnen und anschließend parametrisiert mit den ge-wünschten Daten immer wieder abzuspielen.

Man kann den Batch-Input-Mechanismus aber auch zweckentfremden, um eine Oberfläche von einem Funktionsbaustein aus zu befüllen, wenn man kein BAPI findet, das die Funktion dieser Oberfläche kapselt. Wir wollen exemplarisch die Bedienung der Transaktion IE02 auf-zeichnen, um die Bezeichnung eines Equipments zu ändern. Den Transaktionsrecorder erreichen Sie über die Transaktion SHDB. Wenn Sie eine neue Aufzeichnung anlegen, müssen Sie dieser einen Namen geben und festlegen, welche Zieltransaktion aufgezeichnet werden soll. In unserem Fall ist das IE02.


Abb. 6.3. Beginn der Aufzeichnung im Transaktionsrecorder

Die Transaktion IE02 ist relativ simpel aufgebaut. Wir geben die Nummer eines Equipments ein, bekommen dessen Eigenschaften angezeigt, ändern den Beschreibungstext und speichern. Anschließend speichern wir ein zweites Mal, um die Transaktionsaufzeichnung abzuschließen. Das Ergebnis ist das Makro, das in Abb. 6.4 zu sehen ist. Es beginnt mit dem Namen der aufgezeichneten Transaktion und enthält unter anderem Einträge für jede Cursorbewegung (BDC_CURSOR). Außerdem sind zwei feste Werte erkennbar: die Equipment-Nummer, die in Zeile fünf in das Feld RM63E-EQUNR eingetragen wird und die geänderte Equipment-Bezeichnung. Sie wird in Zeile 24 in das Feld ITOB-SHTXT eingefügt.

Damit im Makro die festen Werte durch Variablen ersetzt werden können, müssen Sie den Makro-Code in ABAP-Code umwandeln. An dieser Stelle weichen wir von dem reinen Batch-Input-Verfahren ab, denn wir wollen ja keine Massendateneingabe mit Hilfe statischer Eingabedateien durchführen, sondern Eingabeparameter zur Laufzeit festlegen. Genau genommen heißt dieses abgewandelte Vorgehen „Call Transaction“, we-gen der großen Nähe ist der verbreitetere Begriff „Batch Input“ dennoch ebenso häufig auch in diesem Zusammenhang zu hören. Um den ABAP-Code zu erzeugen, navigieren Sie eine Ebene zurück, wählen aus der Liste der gespeicherten Makros das soeben aufgezeichnete Z_EQU aus und gehen in der Kopfleiste auf „Programm anlegen“. Dort vergeben Sie einen Programmnamen, z. B.Z_EQUNAME und wählen „aus Aufzeichnung übernehmen“.


Abb. 6.4. Makro-Code, der bei der Bedienung von IE02 aufgezeichnet wurde.

Nun können Sie sich den Quelltext des generierten ABAP-Programms ansehen. Mit ein paar Auslassungen sieht er so aus:

Jetzt ist es ein Leichtes, die festen Werte durch variable Parameter zu ersetzen und den Code in einen remote-fähigen Funktionsbaustein umzubetten.

6.2 RFC auf Java-Seite

Der Java Connector ist ein Produkt, das SAP ihren eingetragenen Kunden kostenlos zur Verfügung stellt. Wie erwähnt handelt sich dabei um eine Kapselung der RFC-Schnittstelle, die diese von Java-Code aus aufrufbar macht. Auch wenn es andere vergleichbare Produkte gibt, ist JCo die am weitesten verbreitete Lösung und soll hier stellvertretend für die Java-Seite des RFC-Aufrufs beschrieben werden.

Die folgenden Punkt charakterisieren die JCo-Bibliothek:

  • JCo erlaubt es, Client-seitigen Java-Code für den RFC-Zugriff zu schreiben.
  • Dazu ermöglicht JCo einen einfachen Verbindungsaufbau zum R/3-System. Nach Verbindungsaufbau kann man ein dynamisches API zum Auffinden und Ausführen der verfügbaren Funktionsbausteine nutzen, das ähnlich wie das Java Reflection API funktioniert.
  • Optional bietet JCo Connection Pooling an, um die Verbindungsressourcen zu schonen.
  • JCo wird in erster Linie für synchrone Aufrufe genutzt.
  • Mit dem JCo lässt sich auch Server-seitiger Java-Code schreiben. Der Java JCo-Server emuliert dadurch ein SAP-System und lässt sich von ABAP-Code aus aufrufen.
  • Da der JCo eine C-Bibliothek über das Java Native Interface (JNI) auf-ruft, ist er nicht geeignet, von einem J2EE-Server aus aufgerufen zu werden. JNI-Aufrufe sind in J2EE-Servern verboten, da sie die Thread-Synchronisation stören und die Systemstabilität gefährden.

Der letzte Punkt stellt eine schwerwiegende Einschränkung an die Verwendung des JCo dar. Dennoch war JCo lange Zeit die einzige Möglichkeit, von Java aus auf SAP zuzugreifen und hat daher durchaus seine Daseinsberechtigung. Die Netweaver-Umgebung, die im dritten Teil des Buches beschrieben wird, verfügt über eine J2EE-konforme Implementierung des JCo auf Basis der Java Connector Architecture (JCA). Die Lektüre dieses Abschnitts ist also auch dann nicht vergebens, wenn Sie den JCo nicht alleinstehend, sondern nur als Teil des Netweaver einsetzen wollen.

Der Java Connector eignet sich generell für den Aufruf von remote-fähigen Funktionsbausteinen. Die Unterscheidung in selbstgeschriebene Funktionsbausteine, BAPIs und Batch-Input-basierte Funktionsbausteine ist daher auf Java-Seite nicht notwendig.

6.2.1 Installation und Verbindungsaufbau

Wenn Sie SAP-Kunde sind, können Sie den JCo von http://services.sap.com/connectorsunentgeltlich herunterladen. Zur Installation müssen Sie die mitgelieferten DLLs bzw. Shared Libraries auf Ihren Rechner kopieren und über den Bibliothekspfad Ihres Betriebssystems erreichbar machen. Unter Unix ist dies die Umgebungsvariable LD_LIBRARY_PATH, unter Windows die PATH-Variable. Anschließend nehmen Sie die Java-Bibliothek sapjco.jar in den Classpath Ihrer Java-Entwicklungsumgebung auf. Sobald dies geschehen ist, können Sie den ersten Verbindungsaufbau zum SAP-System durchführen. Dazu genügt das folgende Codefragment, das einen Import von com.sap.mw.jco.* voraussetzt. Sie müssen im Code die Parameter desjenigen R/3-Systems eintragen, das Sie für den RFC-Aufruf nutzen wollen. Ihr SAP Basis-Administrator kann Ihnen die notwendigen Werte nennen. Wenn Sie den Code ausführen können ohne dass eine Exception auftritt, ist die Verbindung zum SAP-System geglückt.

Für den produktiven Betrieb werden Sie in der Regel nicht Ihren persönlichen User verwenden, sondern einen technischen User. Dieser wird Ihnen ebenfalls vom SAP Basis-Administrator zur Verfügung gestellt. Für erste Tests spricht aber nichts dagegen, den User zu verwenden, unter dem Sie sich auch im SAP GUI anmelden.

Die Fehlerbehandlung in dem kurzen Codeabschnitt bedarf noch einer Erläuterung. Die JCo-Klassen deklarieren generell keine geprüften Exceptions. Sie werden daher vom Compiler nicht zur Fehlerbehandlung gezwungen, was eigentlich unsauber ist. Stattdessen werfen die JCo-Klassen Exceptions, die von RuntimeException abgeleitet sind. Sie müssen also selbst darauf achten, diese zu behandeln. Sämtliche von JCo geworfenen Exceptions sind von JCO.Exception abgeleitet. Daher empfiehlt es sich, zumindest diese generell nach JCo-Aufrufen zu behandeln.

6.2.2 Auffinden und Aufrufen eines einfachen Funktionsbausteins

Als nächstes soll der fehlende Code für den eigentlichen RFC-Aufruf ein-gefügt werden. Der Übersichtlichkeit halber geschieht dies zunächst am Beispiel des minimalen Funktionsbausteins Z_EINFACH aus dem Ab-schnitt über remote-fähige Funktionsbausteine. Bevor Sie einen RFC an ein SAP-System starten, benötigen Sie Informationen über die dort vorhandenen Funktionsbausteine. Der JCo bietet als zentrale Auskunftsstelle für Metainformationen über den aufrufbaren Code ein Repository-Objekt an. Dieses lässt sich anhand der Session er-zeugen, denn die Session definiert das R/3-System eindeutig. Als zweiten Parameter geben Sie dem Konstruktor von JCO.Repository einen Namen mit, unter dem Sie ihn ansprechen wollen. Der folgende Code zeigt, wie man ein Repository-Objekt instanziiert und wie man anschließend die darin befindlichen Metainformationen verwendet, um den gewünschten Funktionsbaustein aufzurufen.

Die Art und Weise, wie hier ein JCO.Function-Objekt stellvertretend für die aufzurufende Funktion erzeugt wird, erinnert stark an das Reflection API von Java. Über repository.getFunctionTemplateermitteln Sie zu dem Funktionsnamen ein Funktionstemplate, das dessen Struktur repräsentiert. Bei einer produktiv genutzten Implementierung müssen Sie den Fall behandeln, dass diese Methode null zurückgibt, weil die gewünschte Methode nicht gefunden wurde. Aus dem Funktionstemplate können Sie ein JCO.Function-Objekt generieren, das wiederum über connection.execute ausgeführt wird.

6.2.3 Aufruf eines komplexen Funktionsbausteins

Die große Fleißarbeit bei der JCo-Programmierung besteht darin, die zu übergebenden Funktionsparameter korrekt zu befüllen und die zurückgelieferten Ergebnisstrukturen wieder auszulesen. Das Repository hilft Ihnen zwar, zur Laufzeit zu prüfen, ob der RFC-Baustein verfügbar ist. Dennoch müssen Sie im Voraus das Wissen darüber in Code gießen, wie er heißt und wie seine Parameter aussehen. Da dies nicht automatisch geschieht, ist es eine langwierige und fehlerträchtige Angelegenheit. Insofern ist das vorhergehende Beispiel alles andere als repräsentativ gewählt. Wir wollen die Mechanismen zum Setzen von Parametern anhand des zweiten Funktionsbausteins untersuchen, den wir vorbereitet haben. Der RFC-Baustein Z_PARAMETER enthält exemplarisch für jeden möglichen Typ einen Parameter. Dadurch kommen wir automatisch mit sämtlichen JCo-Aufrufen in Kontakt, die man in der Praxis beim Füllen von Parameterlisten benötigt. Zu Testzwecken erweitern wir den Rumpf von Z_PARAMETER so, dass er die Importparameter direkt an die Exportparameter weiterreicht.

Es folgt der Java-Code, mit dem man den so erweiterten Funktionsbau-stein aufruft. Er ist wiederum in das erste Listing an der Stelle zu ergänzen, an der die Auslassung gekennzeichnet ist.
Das JCO.Function-Objekt verfügt für jeden Parameterabschnitt wie IMPORTING über eine eigene Zugriffsmethode. Sie heißt beispielsweise getImportParameterList. Auf diesem Objekt wiederum kann man einfache Parameter direkt über setValue-Methoden setzen. Die set-Value-Methode ist anhand des zu setzenden Parametertyps überladen. Der zweite Parameter ist in jedem Fall der Feldname, der groß geschrieben werden muss. Strukturen wie den zweiten Import-Parameter setzt man über das von getStructure von der Importliste zurückgegebene Strukturobjekt. Die einzelnen Felder der Struktur sind wiederum über setValue zu erreichen. Der Codeabschnitt zum Befüllen der Tabellenparameter sollte verständlich sein. Dann folgt der Aufruf des Funktionsbausteins. Auf dem SAP-Server wird nun der oben aufgeführte ABAP-Code ausgeführt. Anschließend geht es weiter in diesem Listing. Die Export- und Tabellenparameter werden wieder ausgelesen. Da Methoden nicht anhand ihrer Rückgabeparameter überladen werden können, gibt es anstelle einer allgemein-gültigen getValue-Methode typspezifische Rückgabemethoden wie getString, getInt, etc.

Wenn nun alles wie gedacht funktioniert hat, sind die Ausgaben von export1, export2 und table1 identisch mit den Werten, die zuvor über import1, import2 und table1 gesetzt wurden.

Zur Fehlerbehandlung müssen Sie darauf gefasst sein, dass sämtliche Methoden, die über einen Feldnamen auf einen Parameter zugreifen, eine JCO.Exception werfen, falls dieser nicht vorhanden ist. Dieses Fehler-verhalten steht im Kontrast zu der null-Semantik von getFunction-Template. Außerdem werfen typspezifische get-Methoden wie get-Date eine Exception, wenn der vorhandene Feldwert nicht in diesen Typ gewandelt werden kann. Interessant ist noch das Verhalten bei Exceptions auf R/3-Seite. Dazu wird der Funktionsbaustein so abgeändert, dass er lediglich eine Exception wirft.

Tatsächlich lässt sich die Exception auf Java-Seite nicht durch irgendeine get-Methode des Funktionsobjekts abfragen. Stattdessen liefert die Ausgabe des catch-Blocks aus dem Rumpfprogramm diese

Java-Exception:com.sap.mw.jco.JCO$AbapException:
(126) EXCEPTION1: Fehler zum Testen

6.2.4 Problemstellungen aus der Praxis

In der Praxis werden Sie mit einer Reihe von Problemen konfrontiert, die über den reinen Gebrauch des JCo-APIs hinausgehen.

Wertebereiche von Parametern

Wo irgend möglich rufen Sie vom System mitgelieferte Funktionsbausteine auf. Diese nutzen oft Feldinhalte, die auf den R/3-Kontext abgestimmt sind. So verfügen viele Felder über einen eingeschränkten Wertebereich, der über die Domäne oder über separate Prüftabellen festgelegt wird. Be-sonders beim schreibenden Zugriff müssen Sie darauf achten, nur zugelassene Werte zu verwenden. Andere Felder besitzen eine kurze interne Darstellung und eine lange oder sprachabhängige Darstellung, die im SAP GUI angezeigt wird. An der BAPI-Schnittstelle bekommen Sie nur die interne Darstellung übergeben. Wenn Sie auf Java-Seite eine Benutzeroberfläche entwickeln, müssen Sie diese wiederum in verständliche Langdarstellungen umwandeln. Das Datumsformat aus R/3, das Sie über die getDate-Methoden des JCos abfragen, weist einige Inkonsistenzen auf, da es sowohl vom ABAP-Typ T als auch vom Typ D befüllt wird. Währungsfelder werden in SAP platzsparend so formatiert, dass die irrelevanten Stellen abgeschnitten werden. Wenn in einem Land also die kleinste Münze den Wert 1000 hat, entfallen drei Stellen in der Darstellung. Connection Pooling Sehr wahrscheinlich ist Ihr Java-Programm keine alleinstehende Applikation, sondern selbst ein Server. In diesem Fall müssen Sie sparsam mit den JCo-Verbindungen umgehen, da diese sich relevant auf den Ressourcen-haushalt auswirken. Der JCo bietet für diesen Zweck einen Connection Pool an. Die folgende Aufrufsequenz aus unserem Codebeispiel können Sie durch eine Pool-basierte Variante ersetzen.

Der äquivalente Code unter Verwendung des Connection Pools sieht so aus: Sie beschaffen sich ein Pool-Objekt, das mehrere Verbindungen zum R/3-System verwalten wird. Falls es noch nicht existiert, müssen Sie es anhand einer Properties-Datei initialisieren. Von diesem Pool erhalten Sie auf Anfrage über getClient eine einzelne Verbindung. Da alle Verbindungen des Pools auf dasselbe R/3-System zugreifen, wird das Repository für die Metadaten nicht über die Connection initialisiert, sondern über den Namen des Pools. Am Ende geben Sie die Connection an den Pool zurück, statt sie zu beenden.

Auf diese Weise reduzieren Sie die Anzahl der Verbindungsauf- und abbauten. Diese sind in der Regel recht aufwändig. Die Properties-Datei, die beim Initialisieren des Pools benötigt wird, hat den folgenden Aufbau. Die genauen Werte erfahren Sie wiederum von Ihrem SAP Basis-Administrator.

Da Sie das Connection Pooling nur dann einsetzen, wenn Sie mit mehreren Threads arbeiten, müssen Sie streng darauf achten, eine Connection nur in genau einem Thread zu verwenden. Auf keinen Fall dürfen Sie Verbindungen zwischen Threads vertauschen oder Verbindungen von mehreren Threads aus gemeinsam verwenden. Dann würden sich die unter-schiedlichen Threads die zu sendenden Inhalte inkonsistent überschreiben und unberechenbare Fehler wären die Folge.

In der Vergangenheit hat es allerdings auch JCo-Versionen gegeben, die von sich aus nicht Thread-sicher waren. Falls Sie eine solche in Händen hal-ten, werden Sie nicht umhin kommen, den Pool selbst zu implementieren.

Synchrone RFC

Wenn Sie sich für den Einsatz der RFC-Technik entscheiden, entscheiden Sie sich für eine synchrone Koppelung zweier unterschiedlicher Systeme. Das tut man in erster Linie dann, wenn man direkte Benutzerinteraktion hat. Wir unterstellen einfach, dass Ihr Java-Server dazu dient, für einen Anwender eine Oberfläche darzustellen. Wenn der Anwender auf einen bestimmten Button drückt, stößt der Java-Server einen RFC an. Der Anwender wartet so lange vor dem Bildschirm, bis das Ergebnis des RFC vom Java-Server empfangen und an ihn weitergeleitet wurde.

Synchrone Aufrufe haben einen Nachteil. Sie setzen voraus, dass beide Systeme gleichzeitig verfügbar sind und dass außerdem das Netzwerk zwischen beiden funktionsfähig ist. Mit solchen Konstellationen kann man die Ausfallsicherheit des Gesamtsystems schnell drücken. Drei synchron gekoppelte Systeme mit einer Ausfallsicherheit von passablen 99 Prozent ergeben ein Gesamtsystem mit einer Ausfallsicherheit von sehr mittelmäßigen 97 Prozent.

Ein konkreteres Problem ist das Lastverhalten. Wenn Sie eine synchrone Kopplung über RFC vornehmen, greifen auf der Strecke vom Browser des Endbenutzers bis zum Backend R/3-System eine Reihe von Timeouts. Der Browser des Anwenders wartet vielleicht eine oder ein paar Minuten auf eine Antwort vom Java-Server, die TCP/IP-Verbindung vom Java-Server zum R/3-Server hat ihren eigenen Timeout.
Bei Funktionsbausteinen mit rein lesenden Datenbankzugriffen stellt dies kein Problem dar. Bei zu übertragenden Datenmengen, die auf einen Bildschirm passen, braucht der RFC im Normalfall weniger als eine Sekunde. Anders sieht es aus, wenn Sie schreibend auf das R/3 zugreifen. Ein betriebswirtschaftliches Objekt anzulegen oder zu ändern erfordert eine Vielzahl von Änderungen in unterschiedlichen Tabellen. Wenn Sie gar eine ganze Liste von Objekten in einem RFC ändern wollen, vielleicht noch über das Batch-Input-Verfahren, kann die Ausführung viele Sekun-den dauern. In Situationen, in denen das R/3-System unter Last steht, be-deutet das, dass der Anwender lange auf eine Reaktion warten muss, was allein schon unerfreulich ist. Falls die Reaktionszeit des RFC aber einen der Timeouts überschreitet, erhält er eine Fehlermeldung vom Java-Server. Der Funktionsbaustein arbeitet im Hintergrund wahrscheinlich erfolgreich weiter, wovon der Benutzer nichts erfährt. Eventuell stößt er die Aktion erneut an, die er für fehlgeschlagen hält und erzeugt so Inkonsistenzen und noch höhere Last.

Die synchrone Kopplung eines Java-Servers mit einem R/3-System birgt also eine Reihe von Risiken, die man sich bewusst machen muss, bevor man diesen Weg einschlägt.

Es gibt aber eine Alternative, die fast so gut ist wie eine synchrone Kopplung. Sie können einen RFC aufspalten in einen synchronen kurzen Teil und einen asynchronen länger dauernden Teil. Der kurze Teil sollte die Semantik haben: „Der Aufruf wurde fehlerfrei entgegengenommen. Die Verarbeitung wurde angestoßen.“ Diese Meldung wird an das aufrufende System in den Rückgabeparametern des RFC zurückgegeben. Der asynchrone Anteil läuft dann noch im Hintergrund weiter und erledigt aufwändige Verbuchungsaktionen. Im Hinblick auf die Benutzerführung bietet es sich an, dem Anwender eine Funktion in die Hand zu geben, mit der er nachsehen kann, ob seine Verbuchung schon erfolgt ist. Technisch können Sie die Zweiteilung des Funktionsbausteins wie folgt realisieren.

Der RFC ruft den Funktionsbaustein Z_SYNCHRON auf. Dieser prüft, ob die eingegebenen Parameter vollständig und sinnvoll sind. Dies geht schnell und hilft, überflüssige Verbuchungsversuche zu vermeiden. Dann bereitet er die Rückgabemeldung für die vorläufige Erfolgsmeldung vor. Als drittes ruft er den Funktionsbaustein Z_ASYNCHRON mit dem Zusatz IN BACKGROUND TASK auf. Dadurch wird ein separater R/3-Task gestartet, der die lang laufende Verbuchungsaktion durchführt. Unter gewissen Umständen ist ein anschließendes COMMIT vonnöten, damit der gerufene Funktionsbaustein tatsächlich zu arbeiten beginnt. Der synchrone Funktionsbaustein ist nun schon beendet und gibt Rückmeldung an die aufrufende Seite. Der asynchrone Anteil arbeitet in Ruhe vor sich hin, bis er seine Aufgabe erledigt hat. Idealerweise protokolliert er den Erfolg oder Misserfolg der Aktion in einer Datenbanktabelle, die man später wieder auslesen kann. Eine weitere Alternative zum synchronen RFC ist die asynchrone IDoc-Technologie, die in Kapitel 7 behandelt wird.

6.3 Debugging

Es ist immer hilfreich, beim Entwickeln die notwendigen Werkzeuge zur Hand zu haben, um den auftretenden Fehlern wirklich auf den Grund zu gehen. Wir wollen den verfügbaren Werkzeugkasten kurz vorstellen.

Der Debugger der ABAP Workbench ist eines der Debugging-Hilfsmittel. Wenn man in seinem Funktionsbaustein einen Breakpoint setzt und dann darauf hofft, dass der JCo-Aufruf darin stoppt, wird man enttäuscht. Das Programm läuft ohne anzuhalten durch. Das liegt daran, dass Breakpoints immer nur für eine Benutzersitzung Gültigkeit haben. Andernfalls könnten sich ja unterschiedliche Benutzer oder unterschiedliche Personen, die unter demselben R/3-User arbeiten, gegenseitig die Programme anhalten.

Wenn Sie dennoch den Debugger aus der Workbench verwenden wollen, müssen Sie einen Trick einsetzen. Sie haben nämlich die Möglichkeit, über die Transaktion SM50 ein laufendes Programm in den Debugger zu holen, auch wenn es von einer anderen Benutzersitzung aus gestartet wurde. Das Problem ist nur, Sie sind nicht schnell genug, um Ihren Prozess zu treffen. Er ist ja meist in Sekundenbruchteilen abgearbeitet. Daher behelfen Sie sich mit einer Endlosschleife am Anfang Ihres Funktionsbausteins. Die kann so aussehen und sollte auf jeden Fall eine Abbruchbedingung enthalten:

Der Programmablauf stockt also in der Schleife, auch wenn Sie den Funktionsbaustein über den RFC-Mechanismus von Ihrem Java-Programm aus aufrufen. Dadurch gewinnen Sie die Zeit, die Sie benötigen, die Transaktion SM50 aufzurufen. In Abb. 6.5 sehen Sie einen Ausschnitt aus dieser Oberfläche. Sie können den vom JCo gestarteten R/3-Prozess anhand des Benutzernamens und der Funktionsgruppe identifizieren.


Abb. 6.5. Laufende ABAP-Programme aus Sicht der Transaktion SM50

Nun wählen Sie Ihren Prozess aus und wählen in der Kopfleiste „Programm/Modus / Programm / Debugging“. Dann öffnet sich der Debugger. Sie sehen die gerade ausgeführte Codezeile und können sich unten auch die Inhalte lokaler Variablen anzeigen lassen. Abb. 6.6 zeigt diese Situation.


Abb. 6.6. Debugger mit geänderter Ausgangsbedingung für die Endlosschleife

Um den Funktionsbaustein nun aus der Endlosschleife zu befreien, lassen Sie sich die Variable flag anzeigen. Diese dient ja als Abbruchbedingung für die Endlosschleife. Nun ändern Sie den Wert von flag und können dann im Einzelschrittmodus die eigentliche Debug-Sitzung beginnen. Auf keinen Fall sollten Sie vergessen die Endlosschleife zu entfernen, bevor Sie Ihren Code zum Transport freigeben. Ein viel einfacheres, aber dennoch sehr nützliches Hilfsmittel bei der Fehlersuche ist die writeHTML-Methode des JCo-Funktionsobjekts. Sie können sich damit den vollständigen Inhalt des JCo-Aufrufs in eine HTML-Datei schreiben lassen. Dazu genügt der folgende Aufruf direkt nach dem Ausführen des RFC-Bausteins.

connection.execute(function);
function.writeHTML(“C:\\temp\\rfc-parameter.html”);

Als letztes Werkzeug sollten Sie den Tracing-Mechanismus über die Transaktion TS05 kennen. Dort können Sie das Tracing für ein bestimmtes Protokoll – in unserem Fall RFC – aktivieren. Anschließend führen Sie einen zu tracenden RFC-Aufruf durch und deaktivieren das Tracing wie-der. Die Ausgaben sind allerdings nur von begrenztem Nutzen. Sie dienen in erster Linie zu Profiling-Zwecken, da jeweils die Ausführungsdauer in Millisekunden angegeben wird. Außerdem können Sie dort die Verbindungsparameter ablesen, und den Code des aufgerufenen Funktionsbausteins ansehen. Aber dies sind eigentlich Informationen, die Sie ohnehin schon besitzen.

6.4 Weiterführende Themen

Bisher sind wir davon ausgegangen, dass das R/3-System immer die Rolle des Servers bei einem RFC-Aufruf spielt. Das war lediglich eine Reduzierung auf den typischen Fall. RFC-Aufrufe können sehr wohl auch von einem SAP-System in ein anderes SAP-System erfolgen. Außerdem können Sie über RFC auch einen Nicht-SAP-Server ansprechen. Unabhängig davon, wie der RFC-Server nun geartet ist, erfolgt der RFC-Aufruf von einem ABAP-Client aus über CALL FUNCTION ‘Funktionsname’ DESTINATION ‘RFC-Destination’. Der Unterschied zu einem lokalen RFC-Aufruf liegt also nur in der DESTINATION-Klausel. Diese verweist auf die so genannte RFC-Destination, ein Systemeintrag für mögliche Ziele von RFC-Aufrufen. RFC-Destinations lassen sich über die Transaktion SM59 verwalten. Dort werden in erster Linie technische Verbindungsparameter auf den logischen Namen abgebildet, den die DESTINATION-Klausel referenziert. Wir wollen kurz skizzieren, wie die Kommunikation zwischen einem ABAP-Client und einem Java-Server über RFC aussehen kann. Dazu gehen wir davon aus, dass die RFC-Destination JCOSERVER bereits über die Transaktion SM59 konfiguriert wurde. Der ABAP-Client ruft über diese Destination die Funktion JCOSERVER_FUNCTION auf. Sie verfügt nur über einen Import- und einen Exportparameter. Nach dem Aufruf wird der erhaltene Exportparameter ausgegeben.

Der Java-Server ist von JCO.Server abgeleitet. Er nutzt dessen Konstruktur, um ihn mit den notwendigen Verbindungsparametern zu füllen. Diese sind in Konstanten abgelegt und werden der Einfachheit halber hart kodiert übergeben. Das Hauptprogramm übernimmt die mühselige Aufgabe, das Repository mit Metadaten zu füllen. Dies geschieht natürlich nicht automatisch, da unser Java-Server ja nur ein SAP-System emuliert. Am Ende des Hauptprogramms wird ein Server-Objekt instanziert und über die start-Methode zum Laufen gebracht. Wann immer nun ein RFC-Aufruf an diesen Server gerichtet wird, nimmt die Basisklasse JCO.Server ihn entgegen. Sie bereitet ihn so weit auf, dass sie ihn an die handleRequest-Methode in Form eines JCO.Function-Objekts weitergeben kann. In unserer eigenen Implementierung von handleRequest geben wir lediglich den Importparameter aus und füllen den Exportparameter mit dem Text „Antwort“.

Auf Basis dieser etwas knappen Codeskizze sollten Sie in der Lage sein, einen vollständigen RFC-Server zu implementieren. Ihnen kommt dabei zugute, dass Sie sich nicht an vorhandene umständliche Schnittstellen anpassen müssen. Den Server schreiben schließlich Sie, dann können Sie auch bestimmen, wie die Schnittstelle aussieht. Als Wermutstropfen bleibt aber die Mühsal beim manuellen Füllen des Repositorys.

7. IDocs

Schon früh in der Entwicklungshistorie von SAP entstand der Bedarf nach einem Format zum Datenaustausch zwischen SAP- und Fremdsystemen oder auch zwischen unterschiedlichen SAP-Systemen. Es sollte in erster Linie den Schriftverkehr zwischen verschiedenen Unternehmen in Papier-form ersetzen. Das Ergebnis war das Intermediate Document, kurz IDoc. Das IDoc-Format erfüllt eine Reihe von Anforderungen, die es zum genügsamen Lastesel im Datenaustausch machen und ihm eine große Verbreitung bescheren.

Dies sind die wichtigsten Eigenschaften von IDocs:

  • IDocs eignen sich zum asynchronen Datentransfer, da sie als Byteformat definiert sind und nicht an ein Transportprotokoll gekoppelt sind. Sie können somit als Files zwischengelagert, per Mail versandt oder über Message Queues verschickt werden.
  • IDocs enthalten Informationen über den Absender und das Zielsystem. Dadurch kann trotz zeitverzögerter Bearbeitung sichergestellt werden, dass sie den gewünschten Adressaten erreichen.
  • IDocs verfügen über eine mehrfach geschachtelte Struktur, die zum Übertragen von Listen oder optionalen Feldern geeignet ist.
  • IDocs enthalten eine eindeutige Typbezeichnung, die als Verweis auf ihre Formatdefinition dient. So wird die Deutbarkeit der Daten jederzeit sichergestellt. Es gibt eine große Menge von standardisierten IDocs, mit deren Hilfe sich die meisten Kommunikationsvorgänge durchführen las-sen. Bei Bedarf können Sie aber auch neue IDocs selbst definieren oder bestehende erweitern.
  • Jedes IDoc enthält eine eindeutige Nummer, die genau dieses eine Exemplar identifiziert. Dadurch lässt sich Mehrfachverarbeitung vermeiden und nachvollziehen, welche Bearbeitungsstationen es erreicht hat.
  • IDocs führen eine Bearbeitungshistorie mit, die protokolliert, welche Prozessschritte sie im SAP-System durchlaufen haben.

IDocs ähneln XML-Dokumenten in zweierlei Hinsicht: Sie haben eine geschachtelte Struktur und sie enthalten einen Verweis auf ihre eigene Strukturdefinition.

Zu der grundsätzlichen Entscheidung, ob Sie einen synchronen oder einen asynchronen Kommunikationsmechanismus einsetzen wollen, sei noch ein-mal auf das vorhergehende Kapitel verwiesen. Unter der Überschrift „Synchrone RFC“ werden dort ein paar Risiken synchroner Aufrufe zwischen zwei unterschiedlichen Systemen diskutiert. Diese Risiken drohen bei der IDoc-Technologie nicht. Wenn Sie nicht darauf angewiesen sind, dass Ihre Daten sofort von einem System zum anderen gelangen, sollten Sie in Erwägung ziehen IDocs einzusetzen. Die dadurch entstehende Systemlast lässt sich leicht auf betriebsarme Zeiten verschieben. IDocs setzen außerdem nicht voraus, dass beide Systeme zum gleichen Zeitpunkt verfügbar sind.

7.1 Aufbau eines IDocs

7.1.1 Physikalische Struktur

Ein IDoc besteht aus drei Teilen: dem Kontrollsatz, den Datensätzen und dem Statussatz. Abb. 7.1 verschafft Ihnen eine erste Anschauung dieser Teile. Sie zeigt ein Exemplar eines IDocs aus Sicht der Transaktion WE09.


Abb. 7.1. Physikalische Struktur eines IDoc-Exemplars

Zweck des Kontrollsatzes ist einerseits, das IDoc anhand von IDoc-Nummer und -Typ zu identifizieren. Andererseits enthält der Kontrollsatz auch die Informationen über den Absender und Empfänger des IDocs. Der Kontrollsatz hat eine feste Länge.

Die eigentlichen Nutzdaten sind in einem oder mehreren Datensätzen enthalten. Ein Datensatz setzt sich aus einem kurzen Anteil fester Länge mit Verwaltungsinformationen und 1000 Bytes an Nutzdaten zusammen. Die Verwaltungsinformation besteht aus einem Rückverweis auf das IDoc über seine Nummer und einer Typisierung der Nutzdaten. Ein Datensatz enthält nämlich genau eine logische Struktur, Segment genannt. Damit das Segment richtig gedeutet wird, muss sein Typ bekannt sein. Daher wird dieser wie bereits angedeutet in den Verwaltungsinformationen des Datensatzes festgehalten. Zur Unterscheidung der Segmente gleichen Typs im selben IDoc fließt außerdem eine Segmentnummer in die Verwaltungsinformationen ein. Der logischen Struktur der Nutzdaten auf Ebene der Segmente werden wir uns gleich noch ausgiebig zuwenden. IDocs sind immer als Teil eines Workflows zu verstehen. Sie enthalten nicht nur Daten, sondern sind gleichzeitig zur Verarbeitung in unterschiedlichen Teilschritten in einer vorgegebenen Reihenfolge vorgesehen. Aus diesem Grund wird der gegenwärtige Verarbeitungsstatus eines IDocs im Statussatz vermerkt. Auch der Statussatz hat eine feste Länge.


Abb. 7.2. Schemadarstellung der physikalischen Struktur eines IDocs

7.1.2 Logische Struktur

Für die Verarbeitung eines IDocs ist die logische Struktur wichtiger als die darunter liegende physikalische Struktur. Die Dreiteilung des IDocs und die 1000-Byte-Restriktion sind unter dem Blickwinkel der logischen Struktur nur ein technisches Realisierungsdetail. Das logische IDoc setzt sich schlicht aus einer Reihe von aufeinanderfolgenden Segmenten zusammen. Ein Segment besteht aus einzelnen Feldern. Es ist also mit einer ABAP-Struktur vergleichbar und lässt sich sogar über das ABAP Dictionary ansehen. Jedes Segment hat einen wohl definierten systemweit bekannten Typ. Über den IDoc-Typ ist festgelegt, von welchen Typen dessen Segmente sind, in welcher Reihenfolge sie auftreten und wie oft Segmente desselben Typs auftreten dürfen.

Der IDoc-Typ legt weiterhin eine mögliche Schachtelung von Segmenten in Form von Vater-Kind-Beziehungen fest. Ein IDoc-Typ für eine Bestellung besteht beispielsweise aus einem Segment für den Auftragskopf. Dieses darf nur genau einmal vorkommen. Darunter liegen beliebig viele Segmente für je eine Auftragsposition. Die Schachtelungsvorschrift ist keine Eigenschaft des Segmenttyps Auftragskopf, sondern des IDoc-Typs für die Bestellung. Dadurch ist gewährleistet, dass dieselben Segmenttypen in ganz unterschiedlichen IDoc-Typen verwendet werden können.

In Abb. 7.3 ist zur Veranschaulichung die Segmentstruktur des IDocs ORDERS05 dargestellt. Sie erkennen gut die Segmente der ersten Ebene. Über das Pluszeichen kann deren Struktur weiter expandiert werden. Diese Sicht auf die Definition beliebiger IDocs erreichen Sie über die Transaktion WE60.


Abb. 7.3. Logische Segmentstruktur des IDocs ORDERS05

7.1.3 Benennung von IDocs und Segmenten

Ein zentraler Gedanke der IDoc-Technologie ist der, dass das System eine große Sammlung standardisierter IDocs für eine Vielzahl von Verwendungszwecken zur Verfügung stellt. Dadurch wird sichergestellt, dass der Code zur Verarbeitung desselben Inhalts nicht mehrfach geschrieben wer-den muss. Außerdem wird durch standardisierte IDocs eine gemeinsame Basis für den Datenaustausch zwischen ansonsten disjunkten IT-Systemen geschaffen.

Das Dilemma einer eingefrorenen weil standardisierten Typisierung ist, dass das Format nicht an neue Anforderungen angepasst werden kann. SAP hat eine recht elegante Lösung dieses Problems gefunden. Die Evolution standardisierter IDoc-Segmente funktioniert durch eine Trennung von Typ und aktuellem Format.

Der Name eines IDocs besteht aus dem eigentlichen Namen und einer angehängten Versionsnummer. So ist das IDoc ORDERS03 die dritte Version vom Typ ORDERS. Eine neue Version entsteht jeweils nur durch Erweiterung aus der Vorgängerversion. Dadurch bleibt der ABAP-Code zum Verarbeiten des IDocs auch dann funktionsfähig, wenn er auf eine neuere Version desselben IDoc-Typs angewendet wird. Im Gegensatz zur Evolution von BAPI-Definitionen bedeutet die Existenz einer neueren IDoc-Version nicht, dass die alte nicht mehr unterstützt wird.

Bei der Bezeichnung von Segmenten wird noch klarer zwischen generellem Typ und versionierter Segmentdefinition unterschieden. Der Segmenttyp von SAP-eigenen Segmenten beginnt mit E1. Zu einem Segment-typ E1XXX existiert zunächst eine konkrete Definition des Segmentformats namens E2XXX. Die Segmentdefinition ist versionsabhängig, der Segmenttyp nicht. Sollten also neue Versionen der Segmentdefinition benötigt werden, so erhalten sie die Namen E2XXX001, E2XXX002, etc. Doch auch die neuen Versionen werden als zugehörig zum Segmenttyp E1XXX betrachtet. Eine neue Segmentdefinition kann ebenfalls nur durch Zufügen von neuen Feldern aus der Vorgängerversion hervorgehen, nicht aber durch Entfernen oder Ersetzen.

Noch einmal in Kürze: abstrakte Segmenttypen fangen mit E1 an, konkrete Segmentdefinitionen mit E2. Letztere enden mit einer dreistelligen Versionsnummer, sofern es sich nicht um die nullte Version handelt. Wie auch bei vielen anderen ABAP-Objekten ist der Anfangsbuchstabe Z für benutzerdefinierte IDocs und Segmente reserviert. Bei Segmenten wird zusätzlich das Typisierungsschema beibehalten. Benutzerdefinierte Segmenttypen beginnen also mit Z1, benutzerdefinierte Segmentdefinitionen mit Z2.Sämtliche Werkzeuge zum Ansehen, Versenden oder Definieren von IDocs finden Sie unter dem Pfad „Werkzeuge / Business Communication / IDoc-Basis“. Die meisten für IDocs relevanten Transaktionen beginnen mit WE.

7.2 Austausch von IDocs

Wir werden nun in mehreren Teilschritten die Kommunikation mit einem Fremdsystems mittels IDocs erläutern. Bei dem Fremdsystem handelt es sich in unserem Fall natürlich um ein Java-Programm. Um alle Aspekte des Kommunikationsvorgangs zu behandeln, soll ein IDoc von SAP aus an das Java-Programm gesendet werden und von dort wieder zurück. Dazu wird zunächst auf SAP-Seite das IDoc zusammengestellt und in der Datenbank abgespeichert. Ein Extraktionsmechanismus kopiert es dann in das Filesystem. Da es für die Weiterverarbeitung auf Java-Seite irrelevant ist, wie das IDoc dorthin gelangt, gehen wir davon aus, dass es dort wiederum in einer lokalen Datei vorliegt. In der Praxis schaltet man einen Kommunikationsmechanismus wie Mailversand oder eine Message Queue dazwischen. Das Java-Programm parst das IDoc und greift exemplarisch auf einige Felder zu.

Für die Rückrichtung erzeugt das Java-Programm ein IDoc und schreibt es ins Filesystem. SAP liest die Datei und wertet sie mittels ABAP-Code aus.

In Abb. 7.4 ist der gesamte Vorgang graphisch dargestellt. Wir werden ihn entgegen dem Uhrzeigersinn durchlaufen. Beim Export aus SAP und beim Import nach SAP ist in der Graphik jeweils ein kleiner Pfeil mit der Bezeichnung Rückmeldung eingetragen. An diesen Stellen wird der Me-chanismus zur Fehlerbehandlung angedeutet, aber nicht in voller Konse-quenz bis zum Absender ausgeführt.


Abb. 7.4. Skizze des durchzuführenden IDoc-Austauschs

Wie bereits erwähnt existieren für die meisten Verwendungszwecke, die eine Kommunikation mit einem Fremdsystem notwendig machen, standardisierte IDocs. Auch die Export- und Importmechanismen sind für solche Fälle vorgegeben. Sie ersparen sich viel Arbeit, wenn Sie auf diese etablierten Mechanismen zurückgreifen. Selbst wenn Sie nicht alle Felder eines Standard-IDocs benötigen, sollten Sie es einer Eigenimplementierung vorziehen.

In den nächsten Absätzen wird dennoch skizziert, wie Sie den Aus-tausch von IDocs auch für Eigenentwicklungen implementieren können. SAP nutzt dieselben Mechanismen für standardisierte IDocs. Daher tragen die Ausführungen auch dann zum Verständnis des Vorgangs bei, wenn Sie ihn nicht selbst implementieren wollen. Der Java-seitige Anteil der Aus-führungen ist in jedem Fall für Sie relevant. Für unser Kommunikationsbeispiel verwenden wir einen handlichen fiktiven IDoc-Typ, der zur Pizzabestellung genutzt wird. Es trägt den Namen ZPIZZA und besitzt nur die Ausprägung ZPIZZA. ZPIZZA besteht aus zwei Segmenttypen: Z1BESTELLER und Z1GERICHT. Z1BESTELLER

muss genau einmal vorkommen, während die Anzahl der Z1GERICHT-Segmente beliebig ist. Zwischen Z1BESTELLER und Z1GERICHT besteht eine Vater-Kind-Beziehung. Der Einfachheit halber soll Z1BESTELLERlediglich über die Felder NAME und TELEFON verfügen. Z1GERICHT wiederum besteht aus NAME und ANZAHL. Ein IDoc vom Typ ZPIZZA könnte also folgendermaßen dargestellt werden:

Bevor wir gleich den Code für die Pizzabestellung schreiben, soll noch eine Warnung ausgesprochen werden. Die Kommunikation über IDocs ist ein Vorgang, der tief mit der Administration des SAP-Systems verwoben ist. Wir gehen davon aus, dass Sie in einem Projekt die Rolle des Entwick-lers haben, nicht aber die des SAP Basis-Administrators. Daher beschrei-ben wir in erster Linie den Java- und ABAP-Code, der für die Kommuni-kation notwendig ist. Die administrativen Aktionen, die außerdem notwendig sind, um den Prozess zum Laufen zu kriegen, werden nur skiz-ziert. Sie werden ohnehin intensiv mit einem Administrator zusammenar-beiten müssen, wenn Sie eine IDoc-Schnittstelle in Betrieb nehmen.

7.2.1 Erzeugen eines IDocs in SAP

Anstoßen der IDoc-Erzeugung

Auf R/3-Seite wird ein IDoc in einem Funktionsbaustein erzeugt, dessen Name mit IDOC_OUTPUT beginnt. Er bekommt als Eingabe den Verweis auf ein beliebig geartetes Dokument im System und erzeugt daraus eine IDoc-Struktur, die er in einer Datenbanktabelle für das Abschicken bereit-stellt.Es kann unterschiedliche Anlässe geben, die ein IDoc erzeugen und deshalb diesen Funktionsbaustein anstoßen. Mögliche Auslöser dafür sind:

  • ein Aufruf aus einem User Exit,
  • ein Aufruf über die Nachrichtensteuerung oder
  • ein manueller Aufruf.

Ein typisches Beispiel für das Auslösen eines IDocs über einen User Exit ist die Replizierung bestimmter Stammdaten. Wenn Sie etwa in einem Fremdsystem die Lieferantenadressen zum SAP-System synchron halten wollen, gehen Sie diesen Weg. Sie nutzen einen User Exit, der vom System ausgeführt wird, sobald sich eine Adresse geändert hat. In diesem fügen Sie einen Aufruf von IDOC_OUTPUT… entweder direkt ein oder stoßen ihn über IDOC_MASTER_DISTRIBUTE indirekt an. Sie können aber auch aus einem beliebigen anderen Arbeitsablauf ein IDoc auslösen, indem Sie sich der sogenannten Nachrichtensteuerung bedienen. Deren Aufgabe ist es, die Erzeugung eines Dokuments wie etwa einer Rechnung von seiner Verschickung zu entkoppeln. Über die Nachrichtensteuerung können Sie festlegen, unter welchen Umständen die Rechnung auf Deutsch oder auf Englisch verschickt werden soll und welcher Kunde seine Rechnungen per Mail, per Post oder per IDoc entgegen-nimmt. Die Nachrichtensteuerung ist ein recht mächtiges, aber aufwändig zu bedienendes Instrument. Daher sei hier nur auf die Transaktionen NACE und NACO hingewiesen, die zu seiner Nutzung dienen. Es ist aber durchaus üblich, die IDoc-Erzeugung auf diesem Weg zu initiieren. Schließlich können Sie zu Testzwecken die IDoc-Erzeugung auch manuell anstoßen. Dies geschieht über die Transaktion WE19. Auch dadurch wird derselbe Funktionsbaustein ausgeführt.

Funktionsbaustein zum Erzeugen des IDocs

Genau genommen lautet der Name des Funktionsbausteins zum Erzeugen eines IDocs IDOC_OUTPUT_XXX, wobei der Anteil XXX stellvertretend für den Namen des IDocs steht. Falls es sich dabei um ein Standard-IDoc handelt, existiert der Funktionsbaustein bereits. Wenn Sie dagegen das IDoc selbst definiert haben, müssen Sie auch den Baustein selbst implementieren. Dabei empfiehlt es sich, den Namen mit Z_IDOC_OUTPUT beginnen zu lassen. In beiden Varianten ist das Grundprinzip dasselbe. Die Signatur sieht folgendermaßen aus:

Um die Bedeutung des ersten Parameters OBJECT zu verstehen, muss man sich in Erinnerung rufen, dass jedes zu erzeugende IDoc letztlich aus Daten zusammengesetzt wird, die sich bereits im SAP-System befinden.
Daher wird in OBJECT-OBJKY ein Schlüssel für den Datensatz mitgegeben, anhand dessen das IDoc gefüllt werden soll. In CONTROL_RECORD_IN wird der eingangs beschriebene Kontrollsatz des IDocs übergeben. Im Normalfall wird der Funktionsbaustein ihn soweit möglich auffüllen und dann in CONTROL_RECORD_OUT übergeben. In der Tabelle INT_EDIDD wird schließlich das vom Funktionsbaustein erzeugte IDoc als eine Liste von Datensätzen zurückgegeben, die die Segmente enthalten. Das System speichert diese Inhalte in einer nicht über das ABAP Dictionary einsehbaren Datenbanktabelle zwischen, um sie später als Quelle für den Export des IDocs zu verwenden. Wir wollen nun eine rudimentäre Implementierung dieses Funktionsbausteins nahtlos fortsetzen und sie dabei gleich abschnittsweise erläutern. In realen Szenarien wird die Implementierung wesentlich umfangreicher ausfallen. Es folgen zunächst einige Konstantendefinitionen. Sie dienen später dazu, den Namen des IDocs und der Segmente sauber typisiert zu setzen. Außerdem werden exemplarisch zwei Hilfsstrukturen definiert, die jeweils vom selben Typ sind wie die Segmente des IDocs, und eine (S_INT_EDIDD), die einem allgemeinen untypisierten 1000-Byte-Daten-satz entspricht.

Die eigentliche IDoc-Erstellung beginnt mit dem Kontrollsatz. Dazu wird CONTROL_RECORD_OUT initialisiert und der übergebene CONTROL_RECORD_IN hineinkopiert. Anschließend lassen sich die noch ungesetzten Parameter mit Hilfe der zuvor definierten Konstanten setzen. Sender- und Empfängerinformationen wie Partnerart und Port bleiben jedoch unberührt. Die IDOC_OUTPUT–Funktion hat nur die Aufgabe, den Inhalt des IDocs entsprechend der Strukturdefinition aufzubauen und mit Daten zu füllen. Für den Versand sind spätere Prozessschritte zuständig.

Als nächstes nutzt man den Schlüssel, der im OBJECT-Parameter über-geben wurde, um die Nutzdaten vorzubereiten, die man verschicken möchte. Dieser Schlüssel ist in der Regel Primärschlüssel einer Tabelle, so dass Sie den betreffenden Datensatz eindeutig ermitteln können. Er wird Ihnen vom auslösenden User Exit oder über die Nachrichtensteuerung mitgeliefert. Auf beiden Seiten muss also Übereinstimmung darüber bestehen, auf welche Tabelle er sich bezieht. Hier im Funktionsbaustein legen Sie sich durch eine solche Abfrage auf die konkrete Tabelle fest.

Natürlich sind meist komplexere Verarbeitungsvorgänge und womöglich auch mehrere Abfragen notwendig, um die Nutzdaten richtig aufzubereiten.
Nun werden die Segmente des IDocs manuell zusammengesetzt. Dazu müssen Sie natürlich dessen Struktur genau kennen. Man fügt sie einfach eines nach dem anderen in eine Liste ein. Da die IDoc-Struktur auch auf Empfängerseite bekannt sein wird, reicht die Reihenfolge der Segmente aus, um die gesamte Struktur wiederherzustellen. Für eine Vater-Kind-Beziehung wird erst das Vatersegment eingefügt und danach die Kindsegmente.
Um ein einzelnes Segment einzufügen, füllt man zuerst die segmentspezifische Hilfsstruktur. Anschließend weist man diese zusammen mit der Konstante für den Segmenttyp an die allgemeine Datensatzstruktur S_INT_EDIDD für ein Segment zu. Das Feld S_INT_EDIDD-SDATA ist der am Anfang des Kapitels beschriebene Block von 1000 Byte Nutzdaten. Dann wiederum kann S_INT_EDIDD an die Rückgabetabelle INT_EDIDD angehängt werden. Dies ist die erwähnte Liste der Segmente.
So verfährt man sukzessive für alle Segmente. In der Realität werden die Feldinhalte nicht hart codiert wie hier, sondern dynamisch aus dem Ergebnis des vorhergehenden Selects ermittelt. Dieses hatte ja zum Zweck, die Nutzdaten zu beschaffen, die im IDoc verpackt werden sollen.

7.2.2 Exportieren des IDocs aus SAP

Sobald der Funktionsbaustein IDOC_OUTPUT… ausgeführt wurde, erscheint das neue IDoc in der Datenbank. Sie können es über Transaktion WE02 ansehen.

Es ist die Aufgabe Ihres SAP-Administrators sicherzustellen, dass es darüber hinaus physikalisch exportiert wird. Dies geschieht über eine ALE (Application Link and Enabling) genannte Schicht des SAP-Systems. Die ALE-Schicht fungiert für den Entwickler als Black Box. Für Sie sind zur Orientierung lediglich zwei Parameter relevant, die das Ziel des Exports definieren: der Port und der Partner, an den das IDoc übermittelt werden soll.

Der Port legt den physikalischen Ausgang fest. Ein Port kann beispiels-weise ein Verzeichnis auf Betriebssystemebene sein, in das IDocs exportiert werden. Sie können aber auch Mail- oder RFC-Ports definieren, also Ausgänge an einen bestimmten Mailempfänger oder einen Funktionsbau-stein in einem anderen SAP-System. Der Partner wiederum wird zum Bestimmen des Ports herangezogen. Aus dem Glossar in Kapitel 2 wissen Sie vielleicht noch, dass ein Partner eine natürliche Person oder Unternehmenseinheit ist, mit der Geschäftsbeziehungen unterhalten werden. Typische Partner sind Kunden oder Lieferanten. Die Information, welche IDocs an welche Partner über welche Ports übertragen werden, lässt sich über die Transaktion SALE warten.

Für unseren Fall gehen wir davon aus, dass das IDoc in ein bestimmtes Verzeichnis exportiert wird, also in einen Datei-Port. Typischerweise werden dabei mehrere IDocs gleichen Typs in eine Datei geschrieben. Auch wenn ein IDoc exportiert wurde, verschwindet es nicht aus dem SAP-System. Es bleibt weiterhin in der Datenbank erhalten und ist über die eindeutige IDoc-Nummer identifizierbar. Das ist allein deswegen wichtig, weil in der weiteren Verarbeitung Fehler auftreten können, die ein erneutes Versenden notwendig machen.

Das Programm, das die exportierten IDocs weiterverarbeiten soll, sieht in periodischen Abständen im Exportverzeichnis nach, ob neue Dateien angelegt wurden, und verarbeitet diese. Die fertig verarbeiteten Dateien löscht es. Im Idealfall gibt es auch eine Rückmeldung über den Verarbeitungsstatus an das SAP-System. Dazu kann es den Funktionsbaustein EDI_STATUS_INCOMING aufrufen und ihm den Namen der verarbeiteten Datei zusammen mit einem Statuscode melden.

Da sich Funktionsbausteine nicht ohne weiteres von außen aufrufen lassen, bietet sich als Hilfsmittel das Programm startrfc an. Das Programm startrfc wird mit dem SAP GUI mitgeliefert. Seine Aufgabe ist es, Funktionsbausteine von Betriebssystemebene aus aufzurufen.

7.2.3 Einlesen des IDocs in Java

Wir gehen nun davon aus, dass das IDoc durch einen nicht näher definierten Übertragungsmechanismus auf der Java-Seite angekommen ist und als Datei vorliegt. Dort müssen wir nun das IDoc aus der Datei in den Speicher lesen und anschließend seine Felder parsen.

Einlesen der IDoc-Datei

Das Einlesen kann mit Code von dieser Gestalt geschehen. Er verwendet hauptsächlich die Klassen File und FileInputStream aus dem Java-Standardpaket java.io. Damit der Code kompakt bleibt, verzichten wir hier und im Folgenden darauf Exceptions zu behandeln. Für den produktiven Einsatz dagegen ist eine angemessene Fehlerbehandlung zwingend notwendig.

Bevor wir den Inhalt der bytes auswerten können, müssen noch einige Vorarbeiten geleistet werden. Sobald dies geschehen ist, werden wir an dieser Stelle fortfahren.

Vorbereitungen zum Parsen Zum Parsen des IDocs nutzen wir eine Reihe von Hilfsklassen, die nichts anderes tun als die Bytes konform zum IDoc-Format abzuzählen. Ihr voll-ständiger Code ist im Anhang aufgeführt. Er ist recht leicht verständlich und so weit erprobt, dass Sie ihn wahrscheinlich nicht anfassen müssen. Für Sie ist interessanter, wie Sie die Hilfsklassen verwenden. Dies ist eine kurze Übersicht über die Klassen und deren Bedeutung. Sie liegen alle im Package de.springer.javasap.idoc.

Klasse Funktion
BaseRecord Basisklasse für Strukturen mit einer Menge benann-ter Einzelfelder von fester Länge
Attribute Einzelfeld mit Namen und Länge
IDocObject Allgemeine Struktur eines IDocs
IDocHeaderRecord Struktur des IDoc-Headers
IDocSegmentRecord Allgemeine Struktur eines IDoc-Segments

Tabelle 7.1. Übersicht über die verwendeten Hilfsklassen und ihre Funktion

Klasse Funktion BaseRecordBasisklasse für Strukturen mit einer Menge benannter Einzelfelder von fester Länge AttributeEinzelfeld mit Namen und Länge IDocObjectAllgemeine Struktur eines IDocs IDocHeaderRecordStruktur des IDoc-Headers IDocSegmentRecordAllgemeine Struktur eines IDoc-Segments Die Klassen sind so geschrieben, dass sie die IDoc-Satzstruktur in der Version 4.0 unterstützten. Diese ist seit dem SAP-Release 4.0 gültig. Um ein IDoc mit dem hier vorgeschlagenen Mechanismus parsen zu können, müssen Sie zunächst dessen Struktur in maßgeschneiderten Klas-sen festhalten. Beginnen wir mit einem Segment. Es wird durch eine eige-ne Klasse abgebildet, die von BaseRecord abgeleitet ist.

Sie erkennen die Konstantendefinitionen für den Segmentnamen und die Länge des Segments. Letztere ist wegen der technischen Vorgabe bei Segmenten immer 1000. Die eigentliche Strukturdefinition wird in dem Array attrs festgehalten. Dieses ist aus mehreren Einträgen vom Typ Attribute zusammengesetzt. Jeder davon entspricht einem Feld des Segments. Die ersten beiden Konstruktorparameter von Attribute le-gen den Feldnamen und die Feldlänge fest. Damit ist die Struktur des Segments ausreichend spezifiziert. Die übrigen Methoden dienen zum Initialisieren des Objekts.
Da das Segment Z1GERICHT ganz ähnlich aufgebaut ist, gehen wir da-von aus, dass es in der Klasse SegmentZ1GERICHT analog abgebildet wird.

Als nächstes benötigen wir eine Strukturdefinition für das ganze IDoc.

Diese Klasse ist nicht von einer der Hilfsklassen abgeleitet. Stattdessen aggregiert sie alle benötigten Strukturen. Das sind einerseits die beiden Segmentklassen, die wir oben selbst definiert haben. Zum anderen verfügt sie über ein Attribut idocHeader, das die Kopfstruktur des IDocs abbildet. Die entsprechende Klasse IDocHeaderRecord ist wiederum eine der Hilfsklassen.
Gefüllt werden Header und Segmente über den Konstruktor, der einen Parameter vom Typ IDocObject auswertet. Der Typ IDocObject ist eine allgemeine IDoc-Struktur, die keine typspezifischen Eigenheiten enthält. Sie kann nur Segmentgrenzen erkennen, nicht aber deren Feinstruktur. Die genaue Verwendung dieses Konstruktors wird gleich im Anschluss erläutert. Sie können aber auch so erkennen, dass er aus dem übergebenen Roh-IDoc die Segmente extrahiert und ihren Typ prüft. Handelt es sich um einen der beiden erwarteten Typen, so wird die Nutzlast des Segments in die Attribute segmentZ1BESTELLER bzw. segmen-teZ1GERICHT übertragen. Letzteres ist ein Vektor von Segment-Z1GERICHT-Einträgen, denn Segmente dieses Typs können ja mehrfach vorkommen.
Da die Segmente nun mit Inhalt gefüllt sind, können die getMethoden im unteren Teil der Klasse auf einzelne Attribute zugreifen und sie zurück-liefern.

Parsen der eingelesenen IDoc-Datei

Jetzt sind die Bausteine beisammen, um eine ganze IDoc-Datei zu parsen, die zuvor aus SAP exportiert wurde. Zu Anfang des Kapitels wurde ihr Inhalt bereits in das Byte-Array bytes eingelesen. Mit diesen Zeilen können wir sie nun auch auswerten.

Eine Hälfte der Arbeit erledigt die Methode getIDocs aus der Hilfs-klasse IDocObject. Sie extrahiert alle IDocs ohne Ansehen des Typs aus dem Byte-Array und überträgt sie in einen Vektor. In einer Schleife kann man nun mit jedem dieser noch untypisierten IDocs den Konstruktor von IDocZPIZZA befüllen. Das ist natürlich nur möglich, wenn man sicher sein kann, dass die Datei nur IDocs von diesem Typ enthält. Der Konstruktor parst wie wir wissen die Segmente aus dem rohen IDoc und füllt sie in Attribute um. Nun kann man die get-Methoden nutzen, um den Wert einzelner Felder auszulesen. Da mehrere Gerichte in einer Pizzabestellung enthalten sind, benötigt man eine weitere Schleife, um die Rückgabe von getGerichte auszuwerten. Man kann auch die geerbten Methoden der IDoc-Klassen verwenden und beispielsweise über getDocNumdie Nummer des IDocs auslesen. In realen Einsatzszenarien werden Sie sicher mit komplexeren IDoc-Strukturen konfrontiert, als es hier der Fall war. Sie müssen mehrfache Schachtelungen behandeln und optionale Segmente berücksichtigen. Auch die Auswertung des Kontrollsatzes haben wir noch vernachlässigt. Dies alles bedarf natürlich noch einiger Programmierarbeit. Mit den vorgestellten Werkzeugen sollten die Probleme aber mit überschaubarem Aufwand zu meistern sein.7.2.4 Erzeugen eines IDocs in Java Setzen der IDoc-Inhalte Für die Rückrichtung verwenden wir dieselben Strukturdefinitionen wie-der. Wir möchten ein leeres IDoc instanzieren können, das wir dann feld-weise füllen. Dazu benötigen wir einen weiteren Konstruktor in IDocZ-PIZZA und passende set-Methoden. Im folgenden Codeauszug wird der schon vorhandene Code der Klasse nur angedeutet, lediglich die neuen Anteile sehen Sie vollständig.

Der neue parameterlose Konstruktor instanziert eine leere Header-Struktur und eine leere Segmentstruktur für den Besteller. Dadurch wird ein neues IDoc benutzbar gemacht, ohne dass man den Inhalt über ein Byte-Array mitgeben müsste, wie es der andere Konstruktor verlangt. Allerdings muss dieser zweite Konstruktor noch ein paar Parameter im Header setzen. Der IDoc-Typ und der Nachrichtentyp sind unabdingbar, damit das empfangende SAP-System das IDoc identifizieren kann. Sie werden über die Attribute MESTYP und IDOCTYP gesetzt. Als Richtung muss im Feld DIRECT der Wert 2 für „Eingang“ eingetragen werden, auch wenn das IDoc aus Java-Sicht hinausgeht. Für die Richtung ist nur die Sicht des SAP-Systems wichtig. Unter Umständen muss außerdem das Feld für die Nachrichtenvariante namens MESCOD gesetzt werden. Das ist für unsere Eigenentwicklung ZPIZZA nicht relevant. Wenn Sie aber standardisierte IDocs verschicken, kann es nötig sein. Die Feldinhalte der Segmente lassen sich über die neuen set-Methoden bzw. die Methode addGericht füllen. Um sicherzustellen, dass die Felder von SAP richtig gedeutet werden können, werden sie intern mit der Methode setAttrAsTrimmedString gesetzt. Diese füllt den Feldinhalt entweder rechts mit Leerzeichen oder links mit Nullen auf. Je nach-dem, ob der dritte Parameter anzeigt, dass der Inhalt numerisch (true) oder alphanumerisch (false) ist.

Setzen der technischen Parameter Zusätzlich werden beim Versenden auch einige technische Parameter benötigt, die das SAP-System braucht, um die Nachricht korrekt einzuordnen. Da diese Parameter im IDoc-Header abgelegt werden, müssen die zugehörigen set-Methoden auf die Header-Struktur zugreifen, statt auf eines der Segmente. Somit wird die Klasse IDocZPIZZA noch um die folgenden Methoden erweitert:

Diese Setter erlauben Ihnen, für Sender und Empfänger die Parameter Port, Partner und Partnerrolle einzustellen. Falls Sie weitere Parameter im Header an Ihre Bedürfnisse anpassen wollen, verfahren Sie einfach analog zu dieser Implementierung. Die Namen der vorhandenen Parameter können Sie der Klasse IDocHeaderRecord entnehmen. Erzeugen des IDocs im Byteformat Jetzt fehlt nur noch eine Methode, die aus den gesammelten IDoc-Daten das IDoc im Byteformat erzeugt. Dazu fügen wir zu IDocZPIZZA noch eine export-Methode hinzu.

Die Methode export erzeugt sich ein IDocObject-Objekt, wie es auch schon beim Auslesen eines IDocs unbekannten Typs genutzt wurde. Außerdem erzeugt sie ein temporäres IDocSegmentRecord-Objekt, das für die unterschiedlichen Segmente immer wiederverwendet wird. Dessen Segmentnummer wird über das SEGNUM-Attribut des Headers jeweils neu vergeben, beginnend mit 1. Das SEGNAM-Attribut wird auf den Namen des Segmenttyps gesetzt, über HLEVEL-Attribut legt man die Schachtelungstiefe fest. Die Schachtelungstiefe ist beim Besteller-Segment, das ja auf höchster Ebene liegt, gleich eins. Als nächstes füllt die export-Methode den Dateninhalt des Segments in Form der bekannten SDATA-Struktur von 1000 Byte Länge in das IDocSegmentRecord-Objekt.

Dessen gesamter Inhalt wiederum wird dann mit addSegment zu dem temporären IDoc-Objekt hinzugefügt. Für die Gerichtsegmente geschieht dasselbe in einer Schleife, allerdings ist die Schachtelungstiefe hier immer zwei. Abschließend wird der IDoc-Inhalt mit der Methode getByteArray zurückgegeben, die alle gesetzten Informationen in das Byteformat eines IDocs wandelt.

Aufruf der vorbereiteten Klasse Mit einer so erweiterten IDocZPIZZA-Klasse lässt sich ganz einfach ein IDoc generieren. Sie instanzieren das Objekt mit dem parameterlosen Konstruktor, setzten technische und fachliche Parameter und können dann über die export-Methoden die entsprechende Bytestruktur gewinnen.

Das fertige IDoc schreiben Sie ähnlich wie beim Einlesen über die Standard File-Klasse von Java in eine Datei.

In realen Szenarien werden Sie allerdings nie dieselben Daten in beide Richtungen schicken. Dieses Beispiel dient also nur zum Verdeutlichen der technischen Möglichkeiten.

7.2.5 Importieren des IDocs nach SAP

Wie schon auf dem Hinweg gehen wir davon aus, dass die IDoc-Datei auf nicht näher benannte Weise in das Eingangsverzeichnis des SAP-Servers gelangt.

Dann können Sie entweder über startrfc oder auf andere Weise den Funktionsbaustein EDI_DATA_INCOMING ausführen und den Namen der IDoc-Datei als Parameter mitgeben. Dieser liest die Datei ein, parst sie und ruft entsprechend dem Typ der darin enthaltenen IDocs den passenden verarbeitenden Funktionsbaustein auf. Sie sollten auch die Rückgabeparameter von EDI_DATA_INCOMING auswerten, denn diese geben Aufschluss über eventuell aufgetretene Fehler bei der Verarbeitung.

7.2.6 Auswerten des IDocs in SAP

Wie auch beim Erzeugen eines IDocs in SAP erfolgt das Auswerten des eingelesenen IDocs über einen Funktionsbaustein. Dieser trägt analog zum

Ausgabebaustein den Namen IDOC_INPUT_… oder Z_IDOC_INPUT… je nachdem, ob er sich auf ein benutzerdefiniertes IDoc bezieht. Auch die Signatur des Bausteins ist vorgegeben.

Zum Verständnis der Funktionsweise sind die TABLES-Parameter am wichtigsten. In der Tabelle IDOC_CONTRL werden die IDoc-Kontrollsätze von allen eingelesenen, aber noch nicht verarbeiteten IDocs übergeben. In der Tabelle IDOC_DATA sind die dazugehörigen IDoc-Datensätze enthalten. Als Ergebnis der Verarbeitung liefert der Funktions-baustein in IDOC_STATUS die aktualisierten IDoc-Statussätze zurück. Auch die Tabelle RETURN_VARIABLE und der Exportparameter WORKFLOW_RESULT liefern weitere Informationen über den Erfolg der Verarbeitung zurück.Die Aufgabe des Funktionsbausteins ist es also, die eingegangenen I-Docs in IDOC_CONTRL und IDOC_DATA zu parsen und deren Inhalte in der Datenbank zu verwahren. Dies geschieht im Wesentlichen in einer langen doppelten Schleife über alle IDocs und deren Segmente.

Für jedes eingegangene IDoc wird erst einmal der Typ geprüft. Falls es sich nicht um den erwarteten handelt, wird eine Exception ausgelöst. Dann werden seine Segmente durchlaufen und in einer CASE-Verzweigung anhand des Typs unterschieden. Sobald der Segmenttyp bekannt ist, kann man dieses den entsprechend typisierten Hilfsvariable S_BESTELLER bzw. S_GERICHT zuweisen. Über die Hilfsvariable ist ein Zugriff auf die einzelnen Segmentfelder möglich. Stellvertretend für einen komplexeren realen Verarbeitungsvorgang schreiben wir im Beispiel die ausgelesenen Felder einfach in eine nicht näher benannte Datenbanktabelle.

Till Jeske “SAP für Java-Entwickler”