Startseite > Techblog > Artikel mit dem Tag: java
nächste Seite
ast

Im Rahmen eines Projektes sollten XML Mockup Dateien in einer Datenbank gehalten und über eine Webservice-Schnittstelle ausgelesen werden. Für gewöhnlich habe ich aus der XSD per JAXB Java Klassen generiert und in Entity Beans gemappt, die anschließend in die Datenbank geschrieben werden. Um dieses, aus meiner Sicht recht aufwendige und fehleranfällige Mapping in die Entity Beans zu umgehen, habe ich mich nach einem alternativen Framework umgesehen und bin mit Hyperjaxb3 fündig geworden. Hyperjaxb3 geht einen Schritt weiter als JAXB, in dem es die generierten Klassen per Annotation zu vollständigen JPA Entitybeans macht. Aktuell steht Hyperjaxb3 in der Version 0.5.4 unter https://hyperjaxb3.dev.java.net zum Download bereit. Unterstützt werden Hibernate und Oracle TopLink, wobei es mit einigen Anpassungen auch mit OpenJPA und EclipseLink funktioniert. Aus meiner Sicht ist aber der Einsatz von OpenJPA (1.2.2) mit Hyperjaxb3 nicht zu empfehlen, da es zu Problemen mit den Relations kam (Es werden keine bidirektionalen Beziehungen unterstützt). Ein weiteres Problem sind die unterschiedlichen Datentypen, da JAXB und JPA nicht vollständig kompatibel sind. Beispiele kann man auf der z.Z. noch etwas lückenhaften Wiki Seite von Hyperjaxb3 finden. http://confluence.highsource.org/display/HJ3/Home

Der große Vorteil dieses Frameworks  ist, dass die Erzeugung der Klassen sehr leicht ins Maven eingebunden werden kann und das das Persistieren nur wenige Zeilen Code nötig macht.

Einbindung in Maven

Zum erzeugen der Beans ist eigentlich nur die Dependency für Hyperjaxb3 der bestehenden pom.xml hinzuzufügen:

Der Aufruf der Codegenerierung sollte sinnvollerweise in einem Profile-Zweig erfolgen, damit nicht bei jedem Compile die Klassengenerierung neu angestoßen wird.

Die Generierung kann dann per Aufruf des Profiles erfolgen: mvn install -Phyperjaxb


Objekt erzeugen und persistieren

In der Anwendung kann dann das Objekt aus dem XML mithilfe des Unmarshallers erzeugt …

und vom Entitymanager persistiert werden:

Optional kann am Unmarshaller noch die Validierung eingeschaltet werden:

Hyperjaxb3 kann bei der Erzeugung von komplexen Datenmodellen aus einer vogegebenen XSD sehr hilfreich sein. Es werden alle Relations, die persistence.xml mit allen nötigen Konfigurationen und die ObjectFactory erzeugt.

Kommentar Feed Trackback URL
sdi

Asynchrone Prozesse werden sehr häufig in Enterprise Applikationen eingesetzt um Abläufe zu beschleunigen. Um diese mit EJB 3.0 zu realisieren kam man um den Einsatz von Message Driven Beans und JMS nicht herum, da diese die einzige Möglichkeit darstellten asynchrone Aufrufe zu realisieren. Mit der Einführung von EJB 3.1 und der neuen Annotation @Asynchronous wird es nun möglich, Methoden direkt als asynchron zu deklarieren.

Was bedeutet das?

Bei einem Aufruf einer asynchronen Methode wird die Kontrolle sofort an den Client zurückgegeben, noch bevor der EJB Container den Aufruf an die Session Bean delegiert hat. Der Client ist somit nicht blockiert und kann mit der Ausführung fortfahren. (fire and forget Prinzip)

Wie werden Methoden als asynchron markiert?

Für die asynchrone Ausführung wurde die Annotation @Asynchronous eingeführt.

Die Annotation kann angewendet werden auf

  • eine Methode einer Beanklasse
  • eine Methode eines Local/Remote Interfaces
  • alle Methoden einer Klasse
  • alle Methoden eines Local/Remote Interfaces

Alternativ können Methoden auch per deployment Descriptor als asynchron definiert werden.

Welche Signatur haben asynchrone Methoden?

Der Rückgabewert asynchroner Methoden ist entweder void oder Future<V>, wobei V dem entsprechenden Rückgabetyp entspricht.

Beim Rückgabewert void darf die Methode keinerlei anwendungsspezifische Ausnahmen deklarieren.

Bei Rückgabe eines Futureobjektes hat der Client die Möglichkeit, das Ergebnis des Aufrufes abzufragen. Dabei kann es sich um ein reguläres Ergebnis oder um eine Ausnahme handeln.

Wie verhält es sich mit Transaktionen?

Bei Aufrufen von asynchronen Methoden ist zu beachten, dass der Transaktionskontext des Clients nicht übernommen wird.

  • Ist die Methode mit dem Transaktionsattribut REQUIRED markiert, wird immer eine neue Transaktion erzeugt. Sie verhält sich somit genauso als wenn sie mit REQUIRES_NEW markiert wäre.
  • Ist die Methode mit dem Transaktionsattribut MANDATORY markiert wird immer eine TransactionRequiredException Exception  erzeugt.
  • Ist die Methode mit dem Transaktionsattribut SUUPORTS markiert wird die Methode immer ohne Transaktion ausgeführt.

Mit der neuen Annotation wird es für den Entwickler einfacher asynchrone Prozesse zu realisieren. Er muss dafür nicht mehr auf JMS zurückgreifen und kann sich auf das Wesentliche konzentrieren.

Kommentar Feed Trackback URL
tfl

Unser Teamoffsite in Eschdorf am 13.11.09 war unter Anderem dem Thema gpars (http://gpars.codehaus.org/) gewidmet, ein Groovy-Framework, welches es erlaubt, parallele Software zu entwickeln, ohne dabei die Komplexität der dafür vorgesehenen Java-Mechanismen kennen zu müssen.
Folgende Konzepte werden von gpars unterstützt:

- Aktoren (auch verteilte)
- Datenflussoperatoren
- Sicherungen für veränderliche Objekte (Safe) sowie
- nebenläufige Operationen auf Collections und
- asynchrone Ausführungen

Gpars ist gut dokumentiert und bringt zahlreiche Beispiele mit, welche zum Teil bekannte Probleme sehr elegant und anschaulich lösen. Im Laufe des Tages haben wir verschiedene Konzepte ausprobiert und die Auswirkungen von paralleler Software auf die Bearbeitungszeit und Prozessorlast auf Mehrkern-Systemen unter verschiedenen Betriebssystemen (Windows, Linux) und unterschiedlichen Kerneln (Linux RT, Standard) untersucht. Allgemein konnte eine spürbare Verbesserung der Performance durch Parallelisierung beobachtet werden, eine reine Java-Implementierung mit Threads war zwar auch deutlich schneller, konnte aber mit der Gpars-Lösung bei weitem nicht mithalten.

Kommentar Feed Trackback URL
tre

Die nunmehr Achte Veranstaltung der Java User Group Saxony stand am 10.9.2009 unter dem Motto EJB 3.1. Wir konnten gemeinsam mit unseren Sponsoren (unser Dank gilt der planConnect GmbH und der Communardo Software GmbH) Adam Bien mit seinem Vortrag „Extreme Lightweight Architectures (XLAs :-) ) mit Java EE 6 und insbesondere EJB 3.1“ für die JUG gewinnen. Knapp 100 Besucher waren gespannt auf  den Referenten des Abends, der extra für uns aus München  in die sächsische Landeshauptstadt gekommen war. Adam Bien ist Consultant, Entwickler und Architekt,  Java Champion, Expert Group Member  für  Java EE 6, EJB 3.1, Time and Date und JPA 2.0 sowie Autor zahlreicher Bücher rund um das Thema Java Architekturen.

In seinem Vortrag hat er mit einigen typischen Vorurteilen gegenüber EJBs, die noch aus den Tagen vor EJB 3.0 stammen, aufgeräumt. Sowohl mit theoretischen Mitteln als auch anhand von Codebeispielen wurden z.B. die folgenden Aussagen widerlegt:

  • EJBs sind schwergewichtig
  • EJBs sind schwer zu testen
  • EJBs sind nicht portierbar
  • EJBs sind nicht erweiterbar
  • EJBs sind langsam
  • EJBs skalieren nicht
  • EJBs sind zu komplex
  • EJBs sind nur schwer in Web Frameworks zu integrieren
  • EJBs sind schwer zu konfigurieren
  • EJBs sind schwer zu migrieren
  • EJBs lassen sich nur schwer entwickeln

Fazit des ersten Teils: Mit EJB 3.0 und 3.1 sind diese Aussagen nicht mehr zutreffend. Die Ursachen für komplizierte EJB Konstrukte sind im Overengineering der Architektur zu suchen.

Im zweiten Teil seines Vortrages hat Adam Bien seine Vorstellungen einer zweckmäßigen Architektur vorgestellt und mit zahlreichen Pattern unterlegt.  Trotz aller Vereinfachungen auf technischer Seite gilt weiterhin der Leitsatz: Keep it simple. Auch in der Softwarearchitektur sind Wahrscheinlichkeiten abzuwägen. Benötige ich tatsächlich noch einen zusätzlichen Layer um zwei Softwarekomponenten zu entkoppeln? Gibt es bereits Anforderungen die diesen Aufwand rechtfertigen oder werden nur bisher unbekannte Anwendungsfälle mit geringer Realisierungswahrscheinlichkeit betrachtet? Das Ziel sollte verständlicher und leichtgewichtiger Code sein der sich genau aus diesem Grund  ohne großen Aufwand erweitern lässt und das tägliche Arbeiten nicht behindert.

Die Folien zum Vortrag sind wie immer auf den Seiten der Java User Group Saxony zu finden. Damit kann sich jeder selbst ein Bild von den Argumenten machen und die Codebeispiele studieren.

Das 9. Treffen der JUG Saxony wird bereits vorbereitet. Wir würden uns freuen auch Sie demnächst begrüßen zu dürfen.

Die nunmehr Achte Veranstaltung der Java User Group Saxony stand am 10.9.2009 unter dem Motto EJB 3.1. Wir konnten gemeinsam mit unseren Sponsoren (unser Dank gilt der planConnect GmbH und der Communardo Software GmbH) Adam Bien mit seinem Vortrag „Extreme Lightweight Architectures (XLAs :-) ) mit Java EE 6 und insbesondere EJB 3.1“ für die JUG gewinnen. Knapp 100 Besucher waren gespannt auf den Referenten des Abends, der extra für uns aus München in die sächsische Landeshauptstadt gekommen ist. Adam Bien ist Consultant, Entwickler und Architekt, Java Champion, Expert Group Member für Java EE 6, EJB 3.1, Time and Date und JPA 2.0 sowie Autor zahlreicher Bücher rund um das Thema Java Architekturen.

In seinem Vortrag hat er mit einigen typischen Vorurteilen gegenüber EJBs, die noch aus den Tagen vor EJB 3.0 stammen, aufgeräumt. Sowohl mit theoretischen Mittel als auch anhand von Codebeispielen wurden z.B. die folgenden Aussagen widerlegt:

· EJBs sind schwergewichtig

· EJBs sind schwer zu testen

· EJBs sind nicht portierbar

· EJBs sind nicht erweiterbar

· EJBs sind langsam

· EJBs skalieren nicht

· EJBs sind zu komplex

· EJBs sind nur schwer in Web Frameworks zu integrieren

· EJBs sind schwer zu konfigurieren

· EJBs sind schwer zu migrieren

· EJBs lassen sich nur schwer entwickeln

Fazit des ersten Teils: Mit EJB 3.0 und 3.1 sind diese Aussagen nicht mehr zutreffend. Die Ursachen für komplizierte EJB Konstrukte sind im Overengineering der Architektur zu suchen.

Im zweiten Teil seines Vortrages hat Adam Bien seine Vorstellungen einer zweckmäßigen Architektur vorgestellt und mit zahlreichen Pattern unterlegt. Trotz aller Vereinfachungen auf technischer Seite gilt weiterhin der Leitsatz: Keep it simple. Auch in der Softwarearchitektur sind Wahrscheinlichkeiten abzuwägen. Benötige ich tatsächlich noch einen zusätzlichen Layer um zwei Softwarekomponenten zu entkoppeln? Gibt es bereits Anforderungen die diesen Aufwand rechtfertigen oder werden nur bisher unbekannte Anwendungsfälle mit geringer Realisierungswahrscheinlichkeit betrachtet? Das Ziel sollte verständlicher und leichtgewichtiger Code sein der sich genau aus diesem Grund ohne großen Aufwand erweitern lässt und das tägliche Arbeiten nicht behindert.

Die Folien zum Vortrag sind wie immer auf den Seiten der Java User Group Saxony zu finden. Damit kann sich jeder selbst ein Bild von den Argumenten machen und die Codebeispiele studieren.

Das 9. Treffen der JUG Saxony wird bereits vorbereitet. Wir würden uns freuen auch Sie demnächst begrüßen zu dürfen.

Kommentar Feed Trackback URL
mse

Wer kennt das nicht? Man debuggt  sich Zeile für Zeile durch den Code um Fehler in komplexen Algorithmen zu finden.

Das Auslesen von primitiven Datentypen oder Strings klappt ziemlich gut. Wenig hilfreich ist allerdings die Darstellung von Kalenderobjekten.

Variablenansicht eines Kalenderobjekts

Mit Hilfe der in Eclipse angebotenen DetailFormater lassen sich Objekte im Debug-Modus beliebig formatieren. Für ein Objekt vom Typ “java.util.GregorianCalendar” könnte die Formatierung folgendermaßen aussehen: this.getTime().toString();

Ergebnis: Fri Jul 17 22:46:07 CEST 2009

Diese Darstellung ist dann schon deutlich lesbarer.
Auch für komplexere Datenstrukturen könnte es hilfreich sein, die wichtigen Informationen auf einem Blick zu sehen.

So funktioniert es

settings

  1. Einstellungsdialog öffnen (Menü “Windows/Preferences” Pfad “Java/Debug/Detail Formaters”)
  2. Button “Add” klicken
  3. Eintragen von java.util.Calendar in das Feld “Qualified type name” field
  4. Eintragen von Enter this.getTime().toString()
  5. Button “OK” klicken

Kommentar Feed Trackback URL
ast

Durch JCA (J2EE Connection Architekture) können externe Systeme, wie z.B. Enterprise Information Syteme, an einen J2EE Application Server angebunden werden. JCA bietet ein flexibles Framework an, welches es erlaubt, unterschiedliche Kommunikationswege, wie z.B. Message Queueing oder Files anzubinden. Bei der Kommunikation mit externen Systemen wird zwischen der Inbound- und Outbound-Kommunikation unterschieden. In dem folgenden Beispiel soll eine Inbound-Kommunikation zwischen einem MQ Manager und einem Application Server erfolgen. Dabei soll ein MessageDriven Bean beim Eingang einer neuen Message angesprochen werden.

247-1

Der Inbound Resource Adapter besteht aus den Komponenten:

  • MQResourceAdapter
  • MQEndpointConsumer
  • MQWorkConsumer
  • MQActivationSpec
  • ra.xml

Im JCA Deploymentdiscriptor ra.xml wird der ResourceAdapter, der MessageListener und die ActivationSpec Klasse angegeben. Zusätzlich können Parameter wie, z.B. der Name des Queue-Managers oder der Host angegeben werden. Als Beispiel hier ein Auszug aus der ra.xml:

ramxl_image

Die Klasse MQActivationSpec wird zur Übertragung von Konfigurationen an den ResourcenAdapter benötigt (Auszug):

mqactivationspec

In der Klasse MQWorkConsumer findet die eigentliche Arbeit statt. Es wird die Verbindung durch den EndpointConsumer zum MQServer aufgebaut und in einem Thread wird periodisch (polling) nach neuen Messages in der Queue gesucht. Wird eine Nachricht gefunden, wird diese an die MethodeonMessage des MessageDrivenBean übergeben. Beispiel das Thread der Work-Klasse:

run_thread

Die Methode zur Übergabe der empfangenen Nachricht an onMessage des MessageDrivenBean sieht wie folgt aus:

onmessage_uebergabe

Die MessageDrivenBean Klasse, die die Verarbeitung der eingegangenen Nachricht übernimmt, muss das Interface MessageListener implementieren und im EJB Deploymentdescriptor eingetragen werden.

MQResourceAdapter ist die Implementierung des ResourceAdapters. Diese Klasse reagiert auf Lifecycle Ereignisse des Application Servers und sorgt für die Instanziierung, Start und Stop des Listeners.

mqresourceadapter

Der eigene ResourceAdapter muss in den DeploymentDescriptor des jeweiligen Applicationserver eingetragen werden. In diesem Beispiel sieht der Eintrag so aus:

ejb_jar

Der Ablauf einer Instanziierung eines Resource Adapters sieht in einem Sequenzdiagram, welches in der ConnectorArchitecture Specification zu finden ist, wie folgt aus:

240-1

 

Links zum Thema:

Kommentar Feed Trackback URL
tis

Da im neuen Jahr 2009 bereits ein Monat vergangen ist, wurde es Zeit für eine kleine Recherche zu Konferenzen und Veranstaltungen, die in diesem Jahr zu Technologien wie Java und Dot.NET bzw. zu Themen im Bereich web 2.0 und Wissensmanagement angeboten werden.

Januar:

Microblogging. Conference. Hamburg
23. und 24.01.2009  Hamburg
http://www.mbc09.de/

OOP München
Soft(ware) Skills: The key to successful projects
26. – 30. Januar 2009
http://www.sigs-datacom.de/sd/kongresse/oop_2009/program.php?cat=list

Februar

SharePoint konferenz 2009
11.-12.02.2009 München
http://www.sharepointkonferenz.de/index.aspx

BASTA! Spring .Net, Visual Studio & More
23.- 27.02.2009  Darmstadt
http://it-republik.de/dotnet/basta/

März

iX Cebit Forum 2009
3.-7.03.2009
Thema 1 von 3: Softwareentwicklung – vom Design bis zum Deployment
http://www.ix-konferenz.de/einstieg.php?konferenzid=51

Spring Expert Day
10.03.2008 München
http://springmuenchen.eventbrite.com/

SABRE 09 – Internationale IT-Konferenz an der Uni Leipzig
Business Process and Services Computing, Textmining
23.-25.03. Leipzig
https://sabreconference.wifa.uni-leipzig.de/frontend/index.php

Professionelles Wissensmanagement WM2009
25. – 27. März, Solothurn
u.a. ein Tutorial zu Enterprise 2.0 – Der Einsatz von Social Software in Unternehmen
http://www.wm-konferenz2009.org/

Web 2.0 Kongress
30.03.-31.03, München
http://www.web2.0-kongress.de/programm/

April

re:publica 2009 – Shift happens
01.-03. April 2009 Berlin-Mitte
http://www.re-publica.de/09/information/

Java User Group Dresden
SpringSource DM Server
2.4.

Jax Konferenz
20.-24.04.2009 Mainz
OSGi, Groovy, Grails, Semantic web Day u.v.m.
http://it-republik.de/jaxenter/jax/

Mai

IA Konferenz 2009
„IA in Business und Praxis“
16.-17.05.2009 Hamburg
http://www.iakonferenz.org/de/2009/conference.html

Juni

Jazoon09 – The International Conference on Java Technology
22.-25.06. Zürich
http://jazoon.com/

Juli

12. Java Forum Stuttgart
2. Juli 2009
http://www.java-forum-stuttgart.de/

September

I-KNOW 2009
9th International Conference on Knowledge Management and
Knowledge Technologies
02.09.2009 bis 04.09.2009
http://i-know.tugraz.at/

berlin.jar – Java-Konferenz in Berlin
13. und 14. September
Java-Konferenz in Berlin

Oktober
KnowTech, Frankfurt
06. – 07. Oktober

Kommentar Feed Trackback URL
twi

Die Confluence API bietet über das Bandana-Framework eine einfache Möglichkeit zum Speichern beliebiger Java Objekte an. Im Prinzip muss man zu dem Objekt nur einen einen Kontext (bezieht sich auf einen bestimmten Confluence Space oder ist global) und einen bezüglich dieses Kontextes eindeutigen Key angeben und der BandanaManager kümmert sich um den Rest. Der Vorteil liegt auf der Hand: Man muss sich nicht mit dem Persistenzmodell von Confluence (basiert auf Hibernate) auseinander setzen. Davon rät Atlassian ohnehin ab, Zitat:

Unless you really understand our code, something weird will happen.

Das Speichern von Daten über Bandana geschieht im Wesentlichen in zwei Schritten

  1. Serialisierung der zu persistierenden Objekte mittels XStream
  2. Ablegen des Resultates in der Datenbank

Diese Form der Persistenz hat allerdings auch ihre Tücken. Als ich vor Kurzem bei der Entwicklung eines Plugins mal wieder Bandana verwendete bin ich über das folgende Verhalten gestolpert:

Ich hatte zwei Container-Datenstrukturen gespeichert, eine Map und eine List. Nachdem einige kurze Tests positiv verliefen, wendete ich mich einer anderen Komponente des Plugins zu. Um diese zu testen baute ich mein Plugin neu und installierte es über die Weboberfläche (ohne etwas an meiner “Persistenzschicht” zu ändern). Allerdings musste ich nun feststellen, dass eine der Datenstrukturen (die Map) nicht mehr geladen werden konnte.  Ein weiterer Test offenbahrte noch wunderlicheres Verhalten: Nachdem ich die Map erneut gespeichert hatte, konnte ich sie wieder auslesen, allerdings nur bis zum nächsten Deploy des Plugins.

Was war passiert? An einem falschen Kontext oder Key konnte es nicht liegen, da diese bereits zuvor das Laden der Map verhindert hätten. Der Verdacht viel schnell auf irgendeinen Cache. Und tatsächlich, nach kurzer Recherche bin ich auf diese Seite bei Atlassian gestoßen. Dort wird beschrieben, dass es eigens für Bandana einen Cache gibt. Meine erste Vermutung war nun, dass die Map aus irgend einem Grund nie den Weg durch den Cache in die Datenbank geschafft hatte, der dann bei der Neuinstallation des Plugins verfiehl. Ein kurzer Blick in die Datenbank und ein wenig Debugging überzeugten mich dann aber vom Gegenteil: Die Daten waren in der Datenbank, schafften es von da aber nicht mehr in den Cache (kurzer Hinweis: die Tabelle für die Bandana-Daten heißt treffender Weise BANDANA). Nach weiterem Debugging stand fest, dass die Daten zwar noch aus der Datenbank geholt wurden, im Anschluss aber die Deserialisierung fehl schlug. Nach einem erneuten Blick in die Datenbank war schnell klar warum. Ich hatte Enums als Schlüssel für die Map verwendet. Diese waren fehlerhaft serialisiert wurden, so dass sie (und mit ihnen die Map) nicht wiederhergestellt werden konnten. Das liegt wohl daran, dass Confluence die XStream Version 1.1.1 verwendet aber Enums erst mit der Version 1.1.2 unterstützt werden. Da die Daten im Cache immer unserialisiert gespeichert werden, fiel dieses Problem erst auf, als der Cache verfallen war. Die Lösung war nun recht einfach: Nach dem Ersetzen der Enums durch Strings verhielt sich mein Plugin wie erwartet.

Enums sind nicht die einzigen Datenstrukturen, mit denen es Probleme gibt. So sollte man es auch vermeiden in einem Plugin definierte Typen mittels Bandana zu speichern, wenn diese keinen Default-Konstruktor haben. Der Grund: Diese Typen würden, wie in diesem Issue beschrieben, beim Deserialisieren mit dem ClassLoader der Web-Applikation gesucht werden. Der kann sie aber nur finden, wenn sie im Classpath der der Web-Applikation liegen, was bei über die Weboberfläche installierten Plugins nicht der Fall ist (dafür hat jedes Plugin seinen eigenen ClassLoader). Also dürften in diesem Fall ebenfalls die oben beschriebenen Probleme auftreten.

Kommentar Feed Trackback URL
jdi

In einem aktuellen Kundenprojekt hatten wir die Anforderung eine Message-Queue zu implementieren, die neben dem reinen Queuing eine garantierte Zustellung zum Zielsystem gewährleisten sollte. Es musste sichergestellt werden, dass auch bei Nichtverfügbarkeit des Zielsystems keine Nachrichten verloren gehen.

Da keine zusätzlichen Kosten für Softwarelizenzen anfallen sollten und ich das Rad nicht neu erfinden wollte, habe ich nach einer Lösung im OpenSource-Bereich gesucht. Mit Apache ActiveMQ bin ich fündig geworden. Innerhalb der verteilten Applikation wurde Axis2 verwendet. Das Zielsystem ist per SOAP/HTTP zu erreichen, während ActiveMQ eine Message-Queue auf JMS-Basis darstellt.

Um das Queuing zu implementieren, war folgende Reihenfolge der Nachrichtenübermittlung erforderlich:

  • Webapplikation erzeugt SOAP-Nachrichten (mit Attachments) und verpackt diese in JMS-Nachrichten
  • JMS-Nachrichten werden von der Webapplikation in eine ActiveMQ-Queue geschrieben
  • Eine selbstentwickelte Komponente holt die SOAP-Nachrichten aus der Queue
  • die selbe Komponente baut HTTP-Verbindungen zum Zielsystem auf und schickt die SOAP-Nachrichten über die HTTP-Verbindung

Initial sah die Implementierung ziemlich trivial aus, allerdings ergaben sich im Projektverlauf einige Schwierigkeiten.

Axis2 Stubs mit Maven 1 erzeugen

Da es für Axis2 kein Maven 1 Plugin gibt, bin ich für die Generierung der Stubklassen auf Maven 2 ausgewichen und habe dies in unseren Maven 1 Buildprozess integriert.

WSDL-Beschreibung

Der Service wird durch folgende WSDL beschrieben:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
  xmlns:types="http://jdi.communardo.de/swatesttypes/2008-01"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:ref="http://ws-i.org/profiles/basic/1.1/xsd" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://jdi.communardo.de/swatest/2008-1"
  xmlns:tns="http://jdi.communardo.de/swatest/2008-1">
  <wsdl:types>
    <xsd:schema targetNamespace="http://ws-i.org/profiles/basic/1.1/xsd"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:simpleType name="swaRef">
        <xsd:restriction base="xsd:anyURI" />
      </xsd:simpleType>
    </xsd:schema>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:ref="http://ws-i.org/profiles/basic/1.1/xsd" targetNamespace="http://jdi.communardo.de/swatesttypes/2008-01">
      <xsd:import namespace="http://ws-i.org/profiles/basic/1.1/xsd" />
      <xsd:complexType name="swatestMessageType">
        <xsd:sequence>
          <xsd:element name="requestId" type="xsd:string" />
          <xsd:element name="clientNr" type="xsd:string" />
          <xsd:element name="formFields"
            type="types:formField" minOccurs="0" maxOccurs="unbounded" />
          <xsd:element name="attachmentForm" type="ref:swaRef"
            minOccurs="0" maxOccurs="1" />
          <xsd:element name="fileName" type="xsd:string" />
        </xsd:sequence>
      </xsd:complexType>
      <xsd:complexType name="formField">
        <xsd:all>
          <xsd:element name="name" type="xsd:string" />
          <xsd:element name="value" type="xsd:string" />
        </xsd:all>
      </xsd:complexType>
      <xsd:element name="newMessage" type="types:swatestMessageType" />
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="createNewMessageRequest">
    <wsdl:part name="newMessageRequest" element="types:newMessage" />
    <wsdl:part name="attachment" type="xsd:base64Binary" />
  </wsdl:message>
  <wsdl:portType name="swatestPortType">
    <wsdl:operation name="createNewMessage">
      <wsdl:input message="tns:createNewMessageRequest"></wsdl:input>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="swatestBinding" type="tns:swatestPortType">
    <soap:binding style="document"
      transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="createNewMessage">
      <soap:operation
        soapAction="http://jdi.communardo.de/swatest/2008-1/createNewMessage" />
      <wsdl:input>
        <mime:multipartRelated>
          <mime:part>
            <soap:body parts="newMessageRequest" use="literal" />
          </mime:part>
          <mime:part>
            <mime:content part="attachment" type="*/*" />
          </mime:part>
        </mime:multipartRelated>
      </wsdl:input>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="swatestService">
    <wsdl:port name="swatestPort" binding="tns:swatestBinding">
      <soap:address location="http://jdi.communardo.de/" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Der Service stellt eine Operation createNewMessage zur Verfügung, die ein Attachment nach dem SwA-Standard sowie ein paar andere Parameter erwartet.

Erzeugen der Typklassen und Stubs mit Maven 2

Für die Erzeugung der Axis2-Typklassen und -Stubklassen wird wie oben schon erwähnt Maven 2 eingesetzt. Für Maven 2 gibt es das axis2-wsdl2code-maven-plugin. Mit dem folgenden Maven 2 pom und mvn package können die Klassen aus der obigen WSDL generiert werden:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.communardo.jdi</groupId>
  <artifactId>techblog-wsgen</artifactId>
  <name>Techblog WSDL Axis2 Testprojekt</name>
  <version>1.0-SNAPSHOT</version>
  <description>Techblog WSDL Axis2 Testprojekt</description>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.axis2</groupId>
        <artifactId>axis2-wsdl2code-maven-plugin</artifactId>
        <version>1.4</version>
        <executions>
          <execution>
            <id>wsdl2codeSwA</id>
            <goals>
              <goal>wsdl2code</goal>
            </goals>
            <configuration>
              <packageName>de.communardo.jdi.swatest</packageName>
              <wsdlFile>${basedir}/src/main/wsdl/MySwA.wsdl</wsdlFile>
              <databindingName>adb</databindingName>
              <generateAllClasses>true</generateAllClasses>
              <generateServerSide>true</generateServerSide>
              <generateServerSideInterface>true</generateServerSideInterface>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.apache.axis2</groupId>
      <artifactId>axis2</artifactId>
      <version>1.4</version>
      <scope>provided</scope>
    </dependency>
    <!--AXIOM Dependencies-->
    <dependency>
      <groupId>org.apache.ws.commons.axiom</groupId>
      <artifactId>axiom-impl</artifactId>
      <version>1.2.7</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.ws.commons.axiom</groupId>
      <artifactId>axiom-api</artifactId>
      <version>1.2.7</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.ws.commons.axiom</groupId>
      <artifactId>axiom-dom</artifactId>
      <version>1.2.7</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.neethi</groupId>
      <artifactId>neethi</artifactId>
      <version>2.0.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.13</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-httpclient</groupId>
      <artifactId>commons-httpclient</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>wsdl4j</groupId>
      <artifactId>wsdl4j</artifactId>
      <version>1.6.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.ws.commons.schema</groupId>
      <artifactId>XmlSchema</artifactId>
      <version>1.3.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>stax</groupId>
      <artifactId>stax-api</artifactId>
      <version>1.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.codehaus.woodstox</groupId>
      <artifactId>wstx-asl</artifactId>
      <version>3.2.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>backport-util-concurrent</groupId>
      <artifactId>backport-util-concurrent</artifactId>
      <version>2.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.3</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.1.1</version>
      <scope>provided</scope>
    </dependency>
    <!-- JMS Dependencies -->
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-core</artifactId>
      <version>5.1.0</version>
    </dependency>
  </dependencies>
</project>

Aufruf von Maven 2 aus dem Maven 1 Buildprozess

Da der Buildprozess in dem Projekt noch auf Maven 1 basiert und ich trotzdem nicht auf die Codegenerierung verzichten wollte, habe ich aus dem Maven 1 Buildprozess heraus Maven 2 aufgerufen. Dazu waren folgende Einträge in der Maven 1 project.xml und maven.xml erforderlich:

project.xml:

  ...
  <build>
    <defaultGoal>jdi:build</defaultGoal>
    <sourceDirectory>${basedir}/target/generated-sources/axis2/wsdl2code</sourceDirectory>
    <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
  </build>
  ...

maven.xml:

  ...
  <goal name="jdi:build"
      description="Installiert zip in lokale Repos"
      prereqs="jdiInternal:generate,jar:install">
  </goal>

  <!-- Ruft Maven 2 auf, um den Sourcecode zu generieren. -->
  <goal name="jdiInternal:generate">
      <ant:echo message="${os.name}" />
      <ant:exec executable="mvn.bat" os="Windows 2000, Windows XP" dir="${basedir}">
        <arg line="package" />
      </ant:exec>
      <ant:exec executable="mvn" os="Linux, SunOS, Unix" dir="${basedir}">
        <arg line="package" />
      </ant:exec>
  </goal>
  ...

Die Unterscheidung nach Betriebssystem ist leider nötig, da ant:exec unter Windows nicht die Dateiendung ergänzt, wie man dies von der Kommandozeile gewohnt ist.

Axis2 1.4 und JMS

Für Axis2 gibt es einen mitgelieferten JMS-Connector, allerdings stellte sich heraus, dass dieser für unsere Zwecke unbrauchbar war. Der Connector konnte weder mit unidirektionalen WebServices umgehen noch konnte er unsere SwA-Nachricht transportieren. Um trotzdem JMS nutzen zu können, habe ich die Stub-Klassen angepasst, so dass sie die Attachments korrekt im MessageContext integrieren und das eine eigene AXIOM-basierte JMS-Client-Implementierung die JMS-Nachrichten in der überschriebenen invoke(MessageContext)-Methode zusammenbaut. Im Gegensatz zum bei Axis2 mitgelieferten JMS-Connector wird dabei ein JMSByteMessage verwendet und die SwA-Serialisierung von Axis2 genutzt. Als Proof-of-Concept habe ich das Gleiche auch mit einer MTOM-Nachricht hinbekommen, dies wurde allerdings von dem WebService-Endpoint auf der Gegenseite nicht unterstützt.

Wichtig ist, dass der Stub folgendermaßen initialisiert wird:

JMSSwaTestStub stub = new JMSSwaTestStub(config, jmsEndpointUrl.toString());
stub._getServiceClient().getOptions().setUseSeparateListener(false);
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.ENABLE_SWA, Constants.VALUE_TRUE);
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.ENABLE_MTOM, Constants.VALUE_FALSE);

Das Attachment wird folgendermaßen in den MessageContext geschrieben:

    try {
      String contentId = _messageContext.addAttachment(attachmentData);
      SwaRef swaref = new SwaRef();
      swaref.setSwaRef(new URI("cid:" + contentId));
      newMessage5.getNewMessage().setAttachmentForm(swaref);
    } catch (MalformedURIException mue) {
      throw new AxisFault(mue.getLocalizedMessage(), mue);
    }

Die Verwendung einer ByteMessage wird folgendermaßen erzwungen:

    _messageContext.setProperty(JMSConstants.JMS_MESSAGE_TYPE, JMSConstants.JMS_BYTE_MESSAGE);

ActiveMQ-Überwachung

ActiveMQ bietet mehrere Möglichkeiten zur Überwachung der Queues dazu gehören

  • WebInterface
  • RSS- und Atom-Feeds
  • JMX

Per JMX lassen sich sehr leicht die Namen der Queues und ihr Füllstand überwachen. Bei Fehlern werden Nachrichten in eine spezielle Dead Letter Queue geschrieben. Zusätzlich haben wir für Applikationsfehler noch eine Error-Queue definiert, die der Konsument der JMS-Nachrichten genutzt hat, um auf Fehler von der HTTP/SOAP-Gegenstelle zu reagieren.

Fazit

Der Einsatz von ActiveMQ als Message-Queuing Middleware hat sich für das Projekt gelohnt, auch wenn uns der schlechte JMS-Support in Axis2 einige Stolpersteine in den Weg gelegt hat.

ActiveMQ bietet auch eine ganze Reihe von Möglichkeiten, die wir im Rahmen des Projektes nicht ausschöpfen konnten. Interessant sind dabei z.B. die Konnektoren zu anderen Transportprotokollen als JMS, z.B. XMPP (Jabber), OpenWire und Stomp. Die Dokumentation auf der ActiveMQ Webseite (http://activemq.apache.org/) ist sehr umfangreich, aber insbesondere im Hinblick auf die Konfiguration nicht immer ganz aktuell und vollständig. Hier mussten wir gelegentlich einen Blick in den zum Glück verfügbaren Sourcecode werfen, um alles richtig zu konfigurieren.

Bei Axis2 sieht das Bild etwas getrübter aus. Für “normale” Einsatzgebiete wie reine HTTP/SOAP-WebServices ist die Unterstützung sehr gut, allerdings stößt man bei Attachments und insbesondere der Nutzung anderer Transportprotokolle, in unserem Fall JMS, schnell an Grenzen und Fehler und muss sich selbst helfen. Auch die Dokumentation auf der Projektwebseite (http://ws.apache.org/axis2/) ist recht lückenhaft. Axis2 hat allerdings eine große und aktive Entwicklercommunity, so dass zu hoffen ist, das sich diese Situation im Laufe der Zeit verbessert.

Kommentar Feed Trackback URL
abu

Die Validierung von Attributen direkt in Domain-Klassen durchzuführen ist nicht nur praktisch, sondern spart auch an mancher Stelle aufwändiges Exception-Handling. Mit einfachen Mitteln ist man in der Lage die Korrektheit der übergebenen Variablen zu überprüfen, bei Bedarf auch gleich mit einer lokalisierten Fehlermeldung.

Die klassische Variante

Zur Einführung wird zunächst eine simple Klasse mit entsprechenden Constraints erzeugt:

class User {
  String login
  String surname
  String lastname
  static constraints = {
    login(size:5..8,blank:false)
    surname(minSize:3, blank:false)
    lastname(minsize:3, blank:false)
  }
}

Beim Aufruf der Methoden save() bzw. validate() werden die gesetzten Werte über die Constraints überprüft und ein eventueller Fehler samt dem zugehörigen Klassen-Attribut im Objekt selbst hinterlegt. Beim standardmäßigen Aufruf einer .gsp können diese Fehler dann ausgegeben werden. Im folgenden Listing ist diese Funktion am Beispiel einer Service-Klasse dargestellt:

class UserService {
  ...
    User user = new User(login:"myLogin", surname:"hans", lastname:"m")
    if(!user.validate()) {
      user.errors.hasErrors.each {
        println "-> Error: " + it.getField()
      }
    }
    user.save()
  ...
}

Das Speichern des User-Objektes würde im oberen Fall fehlschlagen und auf der Konsole der Name des invaliden Feldes auftauchen. Da Domain-Klassen in der Regel aber etwas komplexer sind, zeigt das nächste Listing die Auslagerung des Nutzernamens in einer separaten Klasse:

class UserName {
  String surname
  String lastname
  static constraints = {
    surname(minSize:3, blank:false)
    lastname(minsize:3, blank:false)
  }
}
class User {
  String login
  UserName name
  static constraints = {
    login(size:5..8,blank:false)
    name(nullable:false)
  }
}

Artikel vollständig lesen »

Kommentar Feed Trackback URL
nächste Seite

Tag Cloud

Unsere Themen

Kommentare

  • SharePoint_Team: Rückblick zum Treffen der .NET Usergroup Dresden am 24.02.2010: im #Communardo #Techblog...
  • TorstenHu: Rückblick zum Treffen der .NET Usergroup Dresden am 24.02.2010: im #Communardo #Techblog...
  • SharePoint_Team: Neuer Blogpost zur #BastaCon im #Communardo #TechBlog: http://tinyurl.com/yjqyqpb This comment was...
  • SharePoint_Team: Nur noch etwa 1 Stunde, dann beginnt die .NET Usergroup… http://bit.ly/dxDoKg This comment was...
  • SharePoint_Team: RT @TorstenHu: ViS is waiting for an operation oder Warum Copy & Paste schlecht ist: #Communardo...

Twitter