Beliebte Suchanfragen
//

Tutorial “Enterprise Service Bus mit Mule ESB”: MuleMessage und Java-Komponenten

11.1.2013 | 9 Minuten Lesezeit

Im letzten Teil habe gezeigt, wie man im Mule Studio ein kleines Projekt anlegt. Die umgesetzte Funktionalität war jedoch einfach: Im Eingabeverzeichnis liegende Dateien wurden in das Ausgabeverzeichnis kopiert.  Zusätzlich wurde für jede Datei eine Meldung über den Logger ausgegeben.

Ich habe mich gefragt, womit ich in diesem Teil weitermachen soll: In einer Verkaufsveranstaltung würde ich vermutlich bunte Bilder zeigen, auf denen die vielen Konnektoren (File, JMS, Http, etc.) und internen Blöcke (Filter, Router, Transformatoren) von Mule zu sehen sind. Die Serie soll aber keine Verkaufsveranstaltung sein, daher spare ich mir das für spätere Artikel auf. Heute steht die MuleMessage und wie man ihren Inhalt transformiert im Mittelpunkt.

MuleMessage

Fangen wir also mit der MuleMessage an. Sucht man im Mule Studio – zur Erinnerung: ein Eclipse mit zusätzlichen Plugins – über „Open Type…“ nach MuleMessage, so öffnet sich ein Interface. Also steckt ein Java-Objekt dahinter. Und was ist seine Aufgabe? Im Wesentlichen ist die MuleMessage ein Container, der durch den ESB wandert. Schauen wir uns dazu den Flow aus dem letzten Teil nochmals an:

Der einfache Flow aus dem letzten Teil.

Links und rechts sieht man die beiden Endpoints, über die Mule mit der Außenwelt kommuniziert. In der Mitte symbolisieren die Pfeile zwischen den Komponenten den Datenfluss innerhalb von Mule, genau dort werden Instanzen von MuleMessage weitergereicht. Eine Komponente kann eine Message unverändert durchreichen und einen Seiteneffekt auslösen (Logger). Sie kann aber auch die Message verändern (transformieren). Einige Komponenten lassen sogar Messages verschwinden (Filter) oder erzeugen aus einer einzelnen Nachricht mehrere (Collection Splitter).

Wenn der „Inbound Endpoint“ auf der linken Seite eine neue Datei findet, erzeugt er eine MuleMessage und schickt sie zum Logger. Wenn der „Outbound Endpoint“ die MuleMessage erhält, schreibt er wieder eine Datei. Mit Ausnahme einiger kleiner Details wird der gesamte Zustand des Busses in den Messages gehalten. Eine MuleMessage lässt sich in vier Bereiche aufteilen:

  1. Die Properties teilen sich in mehrere Maps (String -> Object) auf, die wichtigsten davon sind die Inbound Properties und die Outbound Properties. Im Beispiel-Flow steht in der Inbound Property „originalFilename“ der Name der Eingabedatei. Setzt man keine Outbound Property „filename“, so wird dort automatisch der Eingabedateiname übernommen.
  2. Die Anwendungsdaten stehen in der Payload, deren Typ ist Object. Bei der Nutzlast kann es sich also um ein beliebiges Java-Objekt handeln. Hier unterscheidet sich Mule von anderen ESB-Implementierungen, die sich oft auf XML-Nachrichten beschränken.
  3. In einigen Fällen, zum Beispiel bei der Verarbeitung von Emails, enthält die MuleMessage zusätzlich noch Attachments.
  4. Die ExceptionPayload ist nur gefüllt, wenn bei der Verarbeitung Fehler aufgetreten sind. Sie enthält Informationen zur Exception und die Nachricht, durch die der Fehler ausgelöst wurde.

Wie mag in unserem Beispiel die Payload aussehen? Es ist eine Implementierung von InputStream. Die Datei wird also nicht sofort gelesen, sondern nur als Dateihandle über den Bus weitergereicht. Im Beispiel würde sie auch der Logger nicht anfassen, erst der Outbound Endpoint kopiert die Datei. Die Mule-Implementierung des InputStreamsorgt dafür, dass die Eingabedatei beim Aufruf von close() gelöscht oder verschoben wird. Baut man eigene Transformer, darf man daher auf keinen Fall das Schließen der Datei vergessen. Der Trick spart Speicher: Die Anwendungsdaten belasten den Speicher erst in dem Moment, in dem er auch wirklich benötigt wird. Im Idealfall liegt die Datei sogar nie komplett im Speicher. Das funktioniert, wenn nur Teile benötigt werden oder sie komplett per Streaming (wie im Beispiel) verarbeitet werden kann.

Java-Komponenten

Wie kann man jetzt in die Verarbeitung der MuleMessage eingreifen? Über die vielen „Kästen“ (Komponenten), die man rechts in der Toolbox findet. Zieht man einen der Kästen auf einen Pfeil, so wird er an genau dieser Stelle in den Flow eingebaut. Im letzten Artikel wurde auf diese Art der Logger eingebaut, der den Kopiervorgang auf der Konsole bestätigt.

Für die Komponenten mit einer eigenen Java-Klasse existieren zwei „Kästen“:

  1. „Java“ in der Gruppe „Components“
  2. „Java“ in der Gruppe „Transformers“

Ungeschickterweise haben sie den gleichen Namen, obwohl dahinter verschiedene Implementierungen konfiguriert werden müssen. Wählt man die falsche, antwortet Mule mit einer unverständlichen Exception, also in den nächsten Absätzen aufgepasst!

Die erste Variante ist am einfachsten, bietet aber weniger Möglichkeiten als die anderen. Die Konfiguration ist schlicht, die notwendigen Angaben beschränken sich auf den Namen der Komponente und den voll qualifizierten Klassennamen. Letzerer lässt sich nicht nur als String eingeben, sondern man kann dafür auch den Klassenauswahldialog von Eclipse nutzen. Als Name der Komponente empfehle ich den Klassennamen ohne Packagenamen, dann erkennt man im Diagramm auf einen Blick, was sich hinter dem Symbol verbirgt. In der Klasse muss man nur eine Methode (public) mit einem Argument implementieren. In dem Argument wird die Payload übergeben, der Typ muss also passen. Ist man nicht sicher ist, was Mule liefert, kann man erst mal „Object“ nehmen und mit dem Debugger nachschauen, was ankommt. Der Ergebnistyp muss so gewählt werden, dass die nächste Komponente im Fluss damit klar kommt.

Für die beiden folgenden Varianten zieht man den anderen Kasten, nämlich „Java“ aus der Gruppe „Transformers“ in den Flow. Auch hier ist eine Klasse anzugeben, sie muss aber zwingend das Interface Transformer implementieren.

Die zweite Variante implementiert das Interface nicht direkt, sondern leitet von AbstractTransformer ab. Die Basisklasse erspart eine Menge Code, implementieren muss man nur noch eine Methode:

1Object doTransform(Object src, String enc) throws TransformerException

Als erstes Argument wird wieder die Payload übergeben, Rückgabewert ist ebenso die Payload. Das zweite Argument kann genutzt werden, um auf das Encoding zuzugreifen (bei Verarbeitung von Textdateien). Es ist aber nicht das im Konfigurationsdialog angegebene Encoding, das erhält man per getEncoding().

Die dritte Variante nutzt eine andere Basisklasse – AbstractMessageTransformer – in dem eine andere Methode zu implementieren ist:

1public Object transformMessage(MuleMessage message, String enc) throws TransformerException

Statt der Payload hat man jetzt die Message in der Hand, über die man natürlich wieder auf die Payload zugreifen kann. Die Methode sollte die veränderte oder eine neue Message zurückgeben. Sinnvoll ist diese Variante immer dann, wenn man nicht nur auf die Payload, sondern auch auf andere Teile der MuleMessage (Properties, Attachments) zugreifen möchte. Für den Rest dieses Artikels werde ich nur die zweite Variante benutzen.

Unsere erste Komponente

Was ist ein sinnvolles und dazu noch überschaubares Beispiel? Da Mule für die meisten in der Praxis geforderten Umwandlungen (CSV, XML, JSON, Objektgraphen etc. in ein anderes aus der Liste) schon fertige Transformatoren mitbringt, war die Suche nach dem Beispiel schwieriger als die Implementierung. Der Transformer arbeitet ähnlich wie das Modul mod_mime_magic aus dem Apache Webserver. Er schaut sich die ersten Bytes der Payload an und bestimmt dann den Dateityp. Implementiert habe ich das nur für einige Bildformate (bmp, gif, jpg, tif). Die für das Format übliche Dateiendung wird anschließend in die Outbound-Property „fileType“ der MuleMessage geschrieben, ein Ablageort, auf den auch mit der Mule-Expression-Language leicht zugegriffen werden kann. Der spätere Beispiel-Flow wird dies ausnutzen, um Dateien entsprechend ihres Typs in verschiedene Verzeichnisse zu sortieren.

Der Kern des Transformers ist die Methode transformMessage():

1@Override
2public Object transformMessage(MuleMessage message, String outputEncoding) 
3throws TransformerException {
4    byte[] data = (byte[]) message.getPayload();
5    for (int i = 0; i < formats.length; i++) {
6        FileFormat format = formats[i];
7        if (format.matches(data)) {
8            message.setProperty("fileType", format.getSuffix(), PropertyScope.OUTBOUND);
9            if (logger.isInfoEnabled()) {
10                logger.info("fileType is " + format.getName()
11                            + " (suffix: " + format.getSuffix() + ")");
12            }
13            break;
14        }
15    }
16    return message;
17}

Was geschieht darin? Nicht viel, die Payload – der Dateiinhalt als Byte-Array – wird der Reihe nach an Instanzen von FileFormat übergeben. Sobald man einen Treffer landet, wird die Property gesetzt und gegebenenfalls noch eine Log-Message abgesetzt. Das war’s auch schon. Der zugehörige Flow sieht folgendermaßen aus:

Mule-Flow zum Verteilen von Dateien auf Basis des Dateiinhalts.

Links startet der Flow mit dem File-Endpoint, der einen InputStream als Payload erzeugt. Über den Transformer „File to Byte Array“ wird daraus ein Byte-Array, das wir an unseren FileRecognizer weitergeben können. Falls dieser die Datei erkennt, setzt er eine Property, die man im Choice-Router auswerten kann. Welche Bedingungen an den einzelnen Ausgängen stehen sieht man leider nicht in der Flow-Ansicht, aber ein Doppelklick hilft:

Konfiguration des Mule-Choice-Routers.

Die ersten beiden Routen prüfen auf die Dateiformate gif und jpg. Andere erkannte Dateiformate – aber auch alle unerkannten mit fileType == null – landen auf der dritten, der Default-Route. Die Bedingungen sind in der Mule-Expression-Language (MEL) geschrieben. Die MEL ist einen eigenen Blog-Artikel wert, daher hier nur die wichtigsten Informationen: Ein MEL-Ausdruck wird grundsätzlich in #[] eingeschlossen. Innerhalb von Ausdrücken kann auf Properties von POJOs in der üblichen Property-Syntax zugegriffen werden, aus .outboundProperties wird intern ein Aufruf von getOutboundProperties(). Strings sind in einfache Anführungszeichen einzuschließen, da der gesamte Ausdruck im XML in doppelten Anführungszeichen steht. Auf die Elemente von Maps mit Strings als Schlüssel lässt sich in Array-Syntax zugreifen. Ansonsten existieren die typischen Operatoren (+, -, etc.) analog zu Java.

Die Bedingungen am Choice-Router ergeben zusammen eine if-else-Kaskade, mit einem unbedingten else-Zweig am Ende (der Default-Route). Die Default-Route sollte man auf keinen Fall weglassen, da ansonsten Nachrichten im Nirwana verschwinden können. Selbst wenn man alle Fälle abzudecken glaubt, sollte man eine Default-Route einbauen. In ihr kann immer noch eine Log-Meldung ausgeben und die Payload mittels eines File-Endpoints in einem Fehler-Verzeichnis speichern.

Die Routen enden in File Endpoints, jeweils mit einem eigenen Verzeichnis. Auf Basis des Dateiinhalts erkannte gif- oder jpg-Bilder landen also in gleinamigen Verzeichnissen, alle anderen Dateien im Verzeichnis other. Da wir nur eine zusätzliche Property gesetzt haben, bleiben die Dateinamen erhalten (wie im Beispiel aus dem letzten Artikel).

Zusammenfassung

Was haben wir nun erreicht? Einen ESB dafür genutzt, einige Dateien in Verzeichnisse zu sortieren? Ein geübter Unix-Nutzer löst das Problem sicher mit weniger als zehn Zeilen bash-Script. Es ist halt das übliche Problem mit Beispielen: Sind sie übersichtlich, so sind sie meistens inhaltlich flach. Die zwei wesentlichen Teile aus diesem Beispiel kann man sich aber auch in der realen Welt vorstellen: Eine Java-Komponente, die den Inhalt einer Message analysiert und Properties setzt sowie einen Choice-Router, der auf Basis der Property die Message auf unterschiedlichen Wegen weiterleitet. In der IT-Landschaft nach einer Firmen-Fusion könnte sich hinter den Wegen jeweils ein IT-System aus einer der ursprünglichen Firmen verbergen. Vorne könnten jedoch Aufträge, Rechnungen oder andere Daten bereits über einen gemeinsamen Kanal eintreffen (der in der Realität meistens Fällen auch kein File Endpoint sein dürfte).

Wie geht es weiter? Im nächsten Teil soll dann tatsächlich eine Message transformiert werden, statt nur auf Basis des Inhalts Properties zu setzen. Der Schwerpunkt wird dabei nicht auf der Transformation liegen – dafür habe ich wieder nur ein weniger sinnvolles Beispiel – sondern auf der Konfiguration der Java-Komponente.

Weitere Teile dieser Artikelserie

  1. Was ist ein ESB und wofür kann man ihn nutzen?
  2. Tutorial “Enterprise Service Bus mit Mule ESB”: Hello World/Bus
  3. Tutorial “Enterprise Service Bus mit Mule ESB”: MuleMessage und Java-Komponenten
  4. Tutorial „Enterprise Service Bus mit Mule ESB“: Nachrichten mit Java transformieren
  5. Tutorial „Enterprise Service Bus mit Mule ESB“: Integration von CICS Programmen
  6. Tutorial “Enterprise Service Bus mit Mule ESB”: Transport, Connector, Endpoint: Ein paar Grundlagen…
  7. Tutorial “Enterprise Service Bus mit Mule ESB”: Performance und Threads
  8. Tutorial “Enterprise Service Bus mit Mule ESB”: Steuerung und Kontrolle per JMX
  9. Veröffentlichen von Informationen zu Mule Applikationen im Maven-Umfeld
  10. Tutorial “Enterprise Service Bus mit Mule ESB”: Exceptions und Email

Beitrag teilen

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.