Tutorial “Enterprise Service Bus mit Mule ESB”: Exceptions und Email

1 Kommentar

Es wäre schön, wenn immer alles funktioniert. Aber wie wir alle wissen: Jeder mögliche Fehler wird irgendwann auftreten. Früher musste man dafür seinen Code mit vielen if-Abfragen verunstalten, heute nutzt man Exceptions, die man dann in catch-Blöcken abfangen kann. Das Konzept gibt es nicht nur in Programmiersprachen, sondern auch in Mule (und anderen ESBs). Um ein paar Fehlerquellen zu haben, gibt es in dem Beispiel auch noch einige Endpoints und Transformer, die bisher in der Serie noch keinen Platz gefunden haben.
Was soll unser Beispiel machen? Es geht um einen automatischen Email-Eingang, eine low-tech-Variante einer Queue, die einfach über Firmengrenzen hinweg aufgesetzt werden kann. Es werden Mails verarbeitet, die im Anhang eine XML-Datei enthalten. Die Datei wird im Mule-Flow an einen Rest-Service übergeben. Falls der XML-Anhang fehlt oder ein anderer Fehler auftritt (z.B. könnte die XML-Struktur falsch sein), soll eine Fehlermail an eine definierte Adresse verschickt werden. Der Flow sieht im grafischen Editor folgendermaßen aus:

Flow mit Exception-Handling

Flow mit Exception-Handling

Er beginnt mit einem POP3-Endpoint, den man ähnlich wie ein Email-Programm konfiguriert: Host, Port, User, Passwort, was halt einen POP3-Account so ausmacht. Wer möchte, kann natürlich auch IMAP statt POP3 verwenden. Der Mail-Inhalt steht anschließend in der Payload der Message, aber der interessiert und nicht: Wir wollen ja schließlich an die Attachments ran, insbesondere das eine XML-Attachment. Das geht am einfachsten mit einem Transformer in Java:

public class AttachmentTransformer extends AbstractMessageTransformer {
    @Override
    public Object transformMessage(MuleMessage message, String outputEncoding) 
           throws TransformerException {
        for (String name : message.getInboundAttachmentNames()) {
            if (name.endsWith(".xml")) {
                DataHandler attm = message.getInboundAttachment(name);
                try {
                    return attm.getInputStream();
                } catch (IOException e) {
                    throw new TransformerException(MessageFactory.
                              createStaticMessage("I/O problem"), e);
                }
            }
        }
        throw new TransformerException(MessageFactory.
                  createStaticMessage("No XML attachment found"));
    }
}

Wie schon in vorigen Beispielen wird von AbstractMessageTransformer abgeleitet und die Methode transformMessage() überladen. In der Methode wird das erste Attachment genommen, das einen Namen mit „.xml“ am Ende besitzt, es wird dann in Form eines InputStreams als neue Payload weitergegeben. (Mule Streaming macht’s möglich.) Falls kein passendes Attachment gefunden wird, löst die Methode eine Exception aus. Dem Leser als Übung überlassen bleibt die Aufgabe, auch bei mehr als einem XML-Attachment eine Exception auszulösen. Attachments kommen übrigens nur bei wenigen Transports vor, neben Mail auch in JMS.

Im Flow haben wir anschließend den XML-Stream in der Message-Payload stehen, der mit einem xml-to-object-transformer in ein Java-Objekt umgewandelt wird. Mule setzt dafür XStream ein. Über einen Alias muss man XStream noch mitteilen, dass die Root-Entity <person> auf die Java-Klasse de.codecentric.basics.Person abgebildet werden soll. (Wer möchte, kann über den Transformer jaxb-xml-to-object-transformer auch Jaxb statt Xstream verwenden.)

Jetzt könnte man natürlich wieder mit Java weitermachen, schließlich haben wir als Payload kein XML oder einen String, sondern ein echtes Person-Objekt. Müssen wir hier aber nicht, als Vorbereitung für den Http-Endpoint, der unseren REST-Call macht, benötigen wir noch die ID der Person. Die holen wir uns per Mule Expression Language (MEL) aus der Nachricht und setzen sie in eine Message-Property. Da der REST-Service altmodisches nutzt, wird das Objekt im nächsten Schritt wieder in XML umgebaut. Es folgt der Http-Endpoint, der das XML-Dokument per Put-Request beim REST-Service abliefert. Der Pfad enthält dabei – im üblichen REST-Stil – die ID der anzulegenden Person.

Für den Gut-Fall wären wir damit fertig. Für den Fehlerfall ergänzen wir noch die Exception-Strategie: Sie enthält hier nur einen Endpoint, der die Mail per smtp verschickt. Auch hier sollten die Parameter niemanden überraschen, der schon einmal ein Mail-Programm konfiguriert hat.

Im XML steht die Exception-Strategie einfach am Ende des Flows, in der grafischen Darstellung ist sie durch eine Linie vom Rest des Flows getrennt und kann für eine bessere Übersichtlichkeit auch eingeklappt werden (Pfeil links an der Trennlinie).

Hier das vollständige XML:

<flow name="XML-Mail" doc:name="XML-Mail">
    <pop3s:inbound-endpoint host="pop3.web.de"
                            port="995" user="vertrieb.klammer-ag" password="sach-ich-nicht"
                            responseTimeout="10000" doc:name="Mail-Poller" />
    <custom-transformer class="de.codecentric.basics.AttachmentTransformer"
                        doc:name="AttachmentTransformer" />
    <mulexml:xml-to-object-transformer doc:name="XML to Person"
                                       returnClass="de.codecentric.basics.Person">
        <mulexml:alias name="person" class="de.codecentric.basics.Person"/>
    </mulexml:xml-to-object-transformer>
    <set-property propertyName="id" 
                  value="#[message.payload.getId()]" doc:name="Set ID Property"/>
    <mulexml:object-to-xml-transformer doc:name="Person to XML">
        <mulexml:alias name="person" class="de.codecentric.basics.Person"/>
    </mulexml:object-to-xml-transformer>
    <http:outbound-endpoint exchange-pattern="request-response"
                            host="localhost" port="8181" 
                            path="rest/#[message.outboundProperties['id']]"
                            method="PUT" contentType="application/xml" 
                            doc:name="Put Person" />
    <catch-exception-strategy doc:name="Catch Exception">
        <smtp:outbound-endpoint host="mail.gmx.net" user="12345678" 
                                password="very-secret" to="failed@codecentric.de"
                                from="muli@gmx.de" subject="Failed Mail"
                                responseTimeout="10000" doc:name="Error Mail" />
    </catch-exception-strategy>
</flow>

Varianten

Die dargestellte Version ist natürlich nicht die einzig mögliche Variante, man kann es auch anders lösen. Es ist zum Beispiel nicht sehr schön, erst das XML in ein Objekt zu wandeln, nur um nachher wieder XML daraus zu erzeugen. Das wäre natürlich nicht aufgefallen, wenn ich den REST-Service mit dem modernerem Json statt XML bestückt hätte.

Das Problem bestand darin, dass ich die ID irgendwie aus dem XML in eine Message-Property transportieren musste. Dafür fallen mir spontan noch zwei andere Möglichkeiten ein:

  1. Im set-property hätte man in der Mule-Expression-Language per xpath direkt ins XML greifen können.
  2. Man hätte es auch direkt in Java lösen können, dann hätte man aber auf Java heraus die Umwandlung von XML in ein Objekt vornehmen müssen. Wozu hat man dann noch einen ESB?

Die erste ist vermutlich für dieses synthetische Beispiel die einfachste/beste Variante. Wenn man für weitere Tests/Transformationen sowieso in die Java-Welt muss oder wenn ein anderes Zielformat (z.B. anders strukturiertes XML oder Json) gefordert ist, dann darf es auch bei der ursprünglichen Variante bleiben.

Hätte man auf den Java-Transformer auch verzichten können, so dass wir bei reiner „klicki-bunti-Programmierung“ geblieben wären? Ein Attachment lässt sich auch per Mule Expression Language extrahieren, mann muss dann aber wissen, welches. Eine Logik wie „nimm das erste, dessen Name auf .xml endet“ belässt man dann doch besser in einer Programmiersprache wie Java.

Der Flow ist übrigens „one-way“, etwas anderes unterstützt der POP3-Endpoint auch nicht. Das Ergebnis des Http-Endpoints für den REST-Aufruf wird also ignoriert. Die ganze Sache wird also nur dann sicher, wenn der ursprüngliche Absender irgendwann Feedback bekommt und bei einem Timeout seine Mail nochmals abschickt. Der REST-Server muss natürlich auch erkennen, dass er unter Umständen schon ein Objekt mit der ID angelegt hat und darf den zweiten Aufruf nur noch quittieren, ohne ein zweites Objekt anzulegen.

REST-Server

Zum Testen war auch noch ein REST-Server notwendig. Den hätte ich natürlich mit irgendeinem REST-Framework aufbauen können, aber wenn man einen Mule auf dem Rechner hat, dann sieht die Welt wie ein ESB aus. Also den REST-Server auch schnell in Mule zusammengeklickt:

Einfacher Rest-Flow

Einfacher Rest-Flow

Ein http-Endpoint, dahinter eine REST-Component, fertig ist die REST-Konfiguration. Als XML:

<flow name="REST-Server" doc:name="REST-Server">
    <http:inbound-endpoint exchange-pattern="request-response"
                           connector-ref="HTTP"
                           host="localhost" port="8181" doc:name="Rest@8181">
    </http:inbound-endpoint>
    <jersey:resources doc:name="REST">
        <component class="de.codecentric.basics.RestService" />
    </jersey:resources>
</flow>

Aha, doch nicht fertig, da wird eine Java-Klasse referenziert, die noch implementiert werden will:

@Path(„/rest/{id}“)

public class RestService {
    @PUT
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces(MediaType.TEXT_PLAIN)
    public String consume(@PathParam(value = "id") String id, Person person) {
        System.out.println("id: " + id);
        System.out.println("message: " + person);
        return "OK";
    }
}

Wie man schon am XML sieht, setzt Mule intern auf Jersey. Warum auch das Rad neu erfinden, wenn es schon ein passendes REST-Framework gibt. In der Klasse selbst sieht man dann auch die üblichen Annotationen von Jersey. In der Bean Person muss dann gemäß JAXB annotiert werden. Da in der @Consumes-Annotation zwei Media-Types angegeben sind, hätte der Service übrigens auch funktioniert, wenn der Client Json geschickt hätte. Eine „echte“ Implementierung, die eine Person in der Datenbank anlegt, fehlt hier aus Platzgründen. Aber das wäre doch mal was für einen Artikel mit dem JDBC- oder JPA-Transport.

Zusammenfassung

Die heutige Folge war mal etwas kürzer, obwohl mehrere Dinge vorkamen: XML-Endpoints (In- und Outbound), Exception-Handling und ein REST-Server. Werde ich jetzt schreibfaul? Vermutlich etwas, da ich bei den Lesern schon etwas Mule-Kenntnisse voraussetze. Auf der anderen Seite: Wenn man etwas mit Mule arbeitet, geht der Einsatz des nächsten Transports oder Transformers meistens sehr einfach. Und Fehler müssen nicht immer (nur) im Log-File landen, per Catch Exception Strategy lässt sich auch im Flow einfach darauf reagieren.

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

Tags

Roger Butenuth

Dr. Roger Butenuth hat in Karlsruhe Informatik studiert und anschließend in Paderborn promoviert (Kommunikation in Parallelrechnern). Er hat langjährige Erfahrung in der Projekt- und Produktentwicklung.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.