Behavior Driven Development mit JBehave

Behavior Driven Development (BDD) – ist eine Kombination aus Test-Driven-Development (TDD) und Domain-Driven-Design (DDD). TDD wird in der agilen Softwareentwicklung eingesetzt und ist ein Prozess, bei dem zuerst Testfälle konzeptioniert und erst im Anschluss die Funktionalität umgesetzt wird. Nähere Informationen hierzu, finden sich auch in meinem früheren Artikel https://martinzimmermann1979.wordpress.com/2011/12/09/testgetriebene-entwicklung-mit-junit-easymock-und-spring. Auch DDD orientiert sich an der agilen Softwareentwicklung, sieht aber die Fachlichkeit einer Anwendung im Vordergrund. Anforderungen werden in einer ubiquitären Sprache beschrieben, welche von allen am Softwareentwicklungsprozess beteiligten Personen verstanden wird.

Auch bei BDD kommt eine ubiquitäre Sprache zum Einsatz, um fachliche Anforderungen in sogenannten Szenarios zu definieren. Das hier definierte Verhalten wird später mit automatisierten Tests überprüft. Bei der Beschreibung werden Schlüsselworte verwendet, damit die Sprache von den Testfällen korrekt interpretiert werden kann:

Given: Vorraussetzungen für den Testfall
When: Aktion die ausgeführt wird
Then: Überprüft ob das erwartete Verhalten eingetreten ist

Nach einer Aktion kann zunächst das Zwischenergebnis geprüft und anschließend mit weiteren Aktionen und Prüfungen fortgefahren werden. Folgen mehrere Sätze des gleichen Typs, können sie mit and verknüpft werden.

Kommen wir nun zu einem konkreten Beispiel. Es besteht die fachliche Anforderung, dass ein vorhandener Benutzer aktiviert werden kann. Diese Funktionalität wird im UserService realisiert. Das zu testende Verhalten wird mit folgendem Szenario beschrieben:
jbehave_scenario

Das Szenario wird unter src/test/resources/de/mz/jbehave/user_scenarios.story gespeichert und wird dem Testfall als Grundlage zur Ausführung dienen.

Als BDD-Framework wird JBehave in der aktuellen Version 3.7.5 eingesetzt, welches bequem als Maven Dependency eingebunden werden kann:

<properties>
<jbehave.version>3.7.5</jbehave.version>
</properties>

<dependency>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-core</artifactId>
<version>${jbehave.version}</version>
<scope>test</scope>
</dependency>

Nun wird der UserService erstellt, der folgende Methoden implementiert:

void addUser(User user);

User getUser(String name);

boolean activateUser(String name);

Der hier verwendete User besteht aus den zwei Eigenschaften:

private String name;
private boolean active;

Nun wird der Testfall src/test/java/de/mz/jbehave/UserActivationStory erstellt, der somit die gleiche  Verzeichnisstruktur besitzt, wie das zuvor erstellte Szenario. Es handelt sich um eine Unterklasse von Embedder. An den verwendeten Annotations ist bereits ersichtlich, wie die textuelle Beschreibung mit den implementierten Testfällen in Verbindung steht. Jeder Satz des Szenarios, ist in einer Annotation der UserActivationStory wiederzufinden:

public class UserActivationStory extends Embedder {

private UserService userService = new UserServiceImpl();

@Given(„a user with the name $name“)
public void addUser(String name) {
User user = new User();
user.setName(name);
userService.addUser(user);
}

@When(„the user with the name $name will be activated“)
public void activateUser(String name) {
userService.activateUser(name);
}

@Then(„the user with the name $name should be active“)
public void userShouldBeActive(String name) {
User user = userService.getUser(name);
Assert.assertEquals(name, user.getName());
Assert.assertTrue(user.isActive());
}
}

Um die Wiederverwendbarkeit der Test-Methoden zu erhöhen, können Parameter benutzt werden. Innerhalb der Annotation werden sie mit dem Dollar-Symbol gekennzeichnet und stehen in der Test-Methode als Übergabe-Parameter zur Verfügung. Bei der Szenario-Beschreibung wird einfach der entsprechende Wert angegeben, in obigen Beispiel also mustermann.

Im gleichen Package wie der Testfall wird nun ein Runner zum Ausführen des selbigen erstellt. Dieser erbt von JUnitStory und benötigt eine Configuration und eine StepFactory. JBehave verwendet das Convention over Configuration Prinzip, so dass hier nur Einstellungen konfiguriert werden müssen, die nicht der Norm entsprechen. Will man während der Testausführung Meldungen auf der Konsole erhalten, muss ein StoryReportBuilder definiert werden. Als StepFactory wird die InstanceStepsFactory verwendet, welche die Configuration und die auszuführende Story-Klasse benötigt:

public class UserScenarios extends JUnitStory {

@Override
public Configuration configuration() {
return new MostUsefulConfiguration()
.useStoryReporterBuilder(new StoryReporterBuilder().withDefaultFormats().withFormats(Format.CONSOLE, Format.TXT));
}

@Override
public InjectableStepsFactory stepsFactory() {
return new InstanceStepsFactory(configuration(), new UserActivationStory());
}
}

Ohne weitere Konfiguration, wird die Szenario-Beschreibung im gleichen Package wie der Testfall gesucht. Möchte man dies ändern, so kann ein anderer StoryLoader verwendet werden:

configuration.useStoryLoader(new LoadFromURL(„pathToResource“));

Für dieses Beispiel ist das aber nicht nötig, so dass die Klasse UserScenarios nun als JUnit-Test ausgeführt werden kann. Im Anschluss befindet sich im target unter jbehave/view die Datei reports.html, die Auskunft über den Erfolg des Testfalls gibt.

Sollen die JBehave Testfälle automatisch beim Bauen mit maven ausgeführt werden, so kann das jbehave-maven-plugin verwendet werden. Hier die Anpassungen in der pom.xml:

<build>
<finalName>de.mz.jbehave</finalName>
<plugins>
<plugin>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-maven-plugin</artifactId>
<version>${jbehave.version}</version>
<executions>
<execution>
<id>run-stories-as-embeddables</id>
<phase>integration-test</phase>
<configuration>
<scope>test</scope>
<includes>
<include>**/*Scenarios.java</include>
</includes>
</configuration>
<goals>
<goal>run-stories-as-embeddables</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Sofern sich die Test-Klassen in src/test/java befinden, muss der Scope explizit auf test gesetzt werden. Andernfalls werden nur Klassen in src/main/java gefunden. Inkludiert werden alle Java-Klassen die auf Scenarios enden. Nun kann JBehave als maven goal ausgeführt werden:

mvn integration-test

Leider sind die Reports komplett unformatiert und werden ohne css-Styles generiert. Um grafisch ansprechende Reports zu erzeugen, wird das maven-dependency-plugin benötigt, welches zwei hierfür notwendige JBehave Artefakte lädt:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-jbehave-site-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.jbehave.site</groupId>
<artifactId>jbehave-site-resources</artifactId>
<version>3.1.1</version>
<type>zip</type>
<outputDirectory>${project.build.directory}/jbehave/view</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-core</artifactId>
<version>${jbehave.version}</version>
<outputDirectory>${project.build.directory}/jbehave/view</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>

Nach erneutem Ausführen der Integrations-Tests sieht der Report nun so aus:jbehave_statistic

Sofern die Eclipse IDE mit dem m2e Plugin verwendet wird, erscheint nach Anpassung der pom.xml folgender Fehler in der Problem View:

maven-dependency-plugin (goals „copy-dependencies“, „unpack“) is not supported by m2e.

Über die Option „Permanently mark goal unpack in pom.xml as ignored in Eclipse build“ lässt sich der Fehler ignorieren. Die Funktionalität wird hierdurch nicht beeinträchtigt. Will man JBehave in einem bestehenden Projekt einsetzten, benötigt man zumeist eine Spring Integration. Hierfür werden zwei weitere Maven Dependencies benötigt:

<properties>
<spring.version>3.1.1.RELEASE</spring.version>
</properties>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-spring</artifactId>
<version>${jbehave.version}</version>
</dependency>

Im Folgenden werden zwei Spring-Kontexte erstellt, der eine kommt während der Laufzeit zum Einsatz, während der andere für die Integrations-Tests benötigt wird. Zunächst wird unter src/main/resources/applicationContext.xml der Laufzeit-Kontext erstellt. Er beinhaltet den zu testenden UserService und aktiviert das autowiring:

<context:annotation-config />

<bean id=“userService“ class=“de.mz.jbehave.UserServiceImpl“/>

Für die Integrations-Tests wird unter src/test/resources/applicationTestContext.xml ein weiterer Kontext erstellt, der die UserActivationStory beinhaltet und den Laufzeit-Kontext importiert. So ist gewährleistet, dass Laufzeit- und Test-Kontext sich bis auf die zusätzlichen Test-Klassen gleichen:

<import resource=“classpath:applicationContext.xml“/>

<bean id=“userActivationStory“ class=“de.mz.jbehave.UserActivationStory“/>

Nun wird die Runner-Klasse UserScenarios angepasst. Statt der InstanceStepsFactory wird die SpringStepsFactory verwendet, die zusätzlich zur Configuration den Spring-Test-Kontext erhält. Die configuration-Methode kann aus obigem Beispiel übernommen werden:

@Override
public InjectableStepsFactory stepsFactory() {
ApplicationContext context = new SpringApplicationContextFactory(„classpath:applicationTestContext.xml“).createApplicationContext();
return new SpringStepsFactory(configuration(), context);
}

Die Klasse UserActivationStory erstellt nun den UserService nicht mehr selber, sondern erhält ihn per Dependency Injection:

@Autowired
private UserService userService;

So lassen sich Integration-Tests mittels JBehave auch in Spring-Projekten ausführen, so dass das BDD-Framework in vielen Projekten einfach zu integrieren ist.

Zur Unterstützung beim Erstellung von Szenarios, kann das JBehave Eclipse Plugin installieren werden http://jbehave.org/reference/eclipse/updates/. Bei mir führte dies allerdings wiederholt zu einem einfrieren von Eclipse, so dass ich mich von dem Plugin wieder getrennt habe.

JAXB und Anpassungen des JAXB Bindings

JAXB – Java Architecture for XML Binding – (http://jaxb.java.net/) wird eingesetzt um ein Mapping zwischen XML und Java-Klassen herzustellen und aus einer XSD-Schema-Datei Java-Klassen zu generieren. Hierfür kann das Programm xjc verwendet werden, dass seit Java 6 Bestandteil vom Java SE JDK ist.

Im nun folgenden Beispiel sollen die Daten eines Benutzers von XML in ein Java-Objekt konvertiert werden (marshalling). Auch der Umgekehrte Weg – also das Umwandeln eines Java-Objektes in XML (unmarshalling) – soll möglich sein. Ein Benutzer hat verschiedene Eigenschaften, wie z.B. Vor- und Nachname, die beim Konvertieren berücksichtigt werden sollen. Außerdem soll ein Benutzer beliebig viele Adress-Informationen besitzen können.

Zunächst wird ein Maven-Projekt erstellt. Die Beschreibung der Benutzer-Eigenschaften wird in der Schema-Datei src/main/resources/user.xsd hinterlegt. Die Definition sieht wie folgt aus:

<xs:schema xmlns:xs=“http://www.w3.org/2001/XMLSchema&#8220;
targetNamespace=“http://www.mz.de/user/schema&#8220;
xmlns=“http://www.mz.de/user/schema&#8220;
elementFormDefault=“qualified“
attributeFormDefault=“unqualified“>

<xs:element name=“user“ type=“UserType“ />

<xs:complexType name=“UserType“>
<xs:sequence>
<xs:element name=“forename“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“lastname“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“title“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“phonenumber“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“mail“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“age“ type=“xs:int“ minOccurs=“0″ />
<xs:element name=“addresses“ type=“AddressType“ minOccurs=“0″ maxOccurs=“unbounded“ />
</xs:sequence>
</xs:complexType>

<xs:complexType name=“AddressType“>
<xs:sequence>
<xs:element name=“street“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“housenumber“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“citycode“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“country“ type=“xs:string“ minOccurs=“0″ />
</xs:sequence>
</xs:complexType>
</xs:schema>

Eine XML-Datei die zu obigem Schema passen würde, sähe folgendermaßen aus:

<user xmlns=“http://www.mz.de/user/schema“&gt;
<forename>Max</forename>
<lastname>Mustermann</lastname>
<addresses>
<street>Musterweg</street>
<citycode>50123</citycode>
</addresses>
<addresses>
<street>Musterstrasse</street>
<citycode>50123</citycode>
</addresses>
</user>

Aus der Schema-Datei lassen sich auch ohne Maven die entsprechenden Java-Klassen generieren. Hierfür führt man im Projekt-Verzeichnis xjc mit folgenden Parametern aus:

xjc -d generated-sources/xjc src/main/resources/user.xsd

Sofern in der Projekt-Struktur die Verzeichnisse generated-sources/xjc existieren, werden hier nun folgende Dateien erzeugt:

  • AddressType
  • ObjectFactory
  • package-info
  • UserType

Die Klassen UserType und AddressType ergeben sich aus der XSD-Beschreibung. Die ObjectFactory wird mit Methoden zum Erstellen der automatisch generierten Klassen versehen und bietet zudem die Möglichkeit alle Elemente, die auf nicht anonyme komplexe Typen verweisen – das name-Attribut muss gesetzt sein – zu JAXBElementen zu transformieren. In diesem Beispiel trifft das also auf das Element UserType zu.

Statt xjc nach jeder Änderung der XSD-Datei manuell auszuführen, kann alternativ Maven zum Bauen des Projektes und zur Generierung der Java-Klassen eingesetzt werden. Hierfür kommt das Maven JAXB2 Plugin (http://java.net/projects/maven-jaxb2-plugin/) zum Einsatz, wofür der build-Eintrag der pom.xml-Datei ergänzt werden muss:

<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.8.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Wie bei Maven üblich, werden die gebauten Klassen im target-Verzeichnis abgelegt. Die von xjc generierten Klassen sind hier unter generated-sources/xjc zu finden, wobei das Verzeichnis automatisch einen ClassPathEntry erhält und die hier liegenden Klassen somit im Projekt direkt verwendet werden können. Sollte dies in der Eclipse IDE einmal nicht der Fall sein, so reicht es Maven – Update Project Configuration auszuführen.

Nun wird der oben aufgeführte XML-Inhalt in der Datei src/main/resources/user.xml gespeichert, die anschließend unmarshallt werden soll.

JAXBContext jaxbContext = JAXBContext.newInstance(UserType.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
UserType user = (UserType) unmarshaller.unmarshal(getClass().getResourceAsStream(„/user.xml“));

Das Root-Element der XML-Datei ist vom Typ UserType, daher wird zunächst eine JAXBContext-Instanz für dieses Objekt erstellt. Anschließend wird auf dieser Instanz ein Unmarshaller erstellt, der den Inhalt der XML-Datei in Form eines InputStreams unmarshallen soll. Hierbei kommt es jedoch zu einer Exception, deren Ursache im StackTrace nicht direkt ersichtlich ist:

Exception in thread „Main“ javax.xml.bind.UnmarshalException: unexpected element (uri:“http://www.mz.de/user/schema&#8220;, local:“user“). Expected elements are (none) at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:631)

Ursache für diesen Fehler ist die fehlende Annotation XmlRootElement in der Klasse UserType. Klassen die mit JAXB unmarshallt werden sollen (Root-Elemente), müssen diese Annotation besitzen. Im Fall der Klasse UserType sähe die korrekte Annotation so aus:

@XmlRootElement(name = „UserType“)

Es gibt verschiedene Möglichkeiten, um xjc dazu zu bewegen, die Klasse UserType wie gewünscht zu annotieren.
Eine Möglichkeit ist es, die XSD-Datei anzupassen:

<xs:element name=“UserType“>
<xs:complexType>
<xs:sequence>
<xs:element name=“forename“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“lastname“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“title“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“phonenumber“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“mail“ type=“xs:string“ minOccurs=“0″ />
<xs:element name=“age“ type=“xs:int“ minOccurs=“0″ />
<xs:element name=“addresses“ type=“AddressType“ minOccurs=“0″ maxOccurs=“unbounded“ />
</xs:sequence>
</xs:complexType>
</xs:element>

Hintergrund ist, dass das Element UserType nun einen anonymen complexType beinhaltet. Damit ist gewährleistet, dass der complexType nicht nur nicht in dieser XSD-Datei, sondern auch in keiner anderen referenziert werden kann. Damit ist der complexType zwangsläufig ein Root-Element und wird auch als solches von xjc erkannt und annotiert.

Bevor die obige XML-Datei unmarshallt werden kann, muss für diese Lösung noch das Root-Element von User auf UserType unbenannt werden:

<UserType xmlns=“http://www.mz.de/user/schema“&gt;
<lastname>Mustermann</lastname>

</UserType>

Nun kann die XML-Datei unmarshallt werden und deren Inhalt steht im Objekt UserType zur Verfügung.

Die Lösung hat allerdings einen Nachteil. Da der complexType des UserType-Elementes annonym ist, erhält die ObjectFactory keine Methode zum Transformieren des UserTypes in ein JAXBElement. Sofern das Unmarshallen von XML-Inhalt in Java Objekte ausreicht, kann das vernachlässigt werden. Ist der umgekehrte Weg ebenfalls gewünscht – Java Objekt in XML marshallen – muss eine andere Lösung gefunden werden.

So gibt es die Möglichkeit das Standardverhalten des JAXB Bindings anzupassen, wofür die Datei src/main/resources/jxbBindings.xjb mit folgendem Inhalt erstellt wird:

<jxb:bindings version=“1.0″
xmlns:jxb=“http://java.sun.com/xml/ns/jaxb&#8220;
xmlns:xs=“http://www.w3.org/2001/XMLSchema&#8220;
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance&#8220;
xmlns:xjc=“http://java.sun.com/xml/ns/jaxb/xjc&#8220;
xmlns:annox=“http://annox.dev.java.net&#8220;
xsi:schemaLocation=“http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd&#8220;
jxb:extensionBindingPrefixes=“xjc“>

<jxb:bindings schemaLocation=“user.xsd“ node=“/xs:schema/xs:complexType[@name = ‚UserType‘]“>
<annox:annotate>
<annox:annotate annox:class=“javax.xml.bind.annotation.XmlRootElement“ name=“user“/>
</annox:annotate>
</jxb:bindings>

</jxb:bindings>

Die schemaLocation des jxb:binding bestimmt für welches Schema Anpassungen vorgenommen werden sollen, in diesem Fall also für die XSD-Datei user.xsd. Das Attribut node definiert mittels XPath, welche XSD-Einträge anzupassen sind. Der gewählte XPath Ausdruck matched auf alle complexType-Einträge, die sich direkt unterhalb des Schemas befinden. Da dies auch den AddressType betreffen würde, muss zusätzlich mit dem name-Attribut gearbeitet werden, welches den Wert UserType besitzt.

Über annox:annotate wird letztendlich bestimmt, was an den zuvor ausgewählten XSD-Einträgen modifiziert werden soll. Die Klasse UserType erhält die Annotation XmlRootElement mit dem name-Attribut user:

@XmlRootElement(name = „user“)

Der Anschließende Maven-Build wird allerdings fehlschlagen, da weitere Anpassungen an der build-Einstellung in der pom.xml-Datei vorzunehmen sind:

<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.8.0</version>
<configuration>
<strict>false</strict>
<extension>true</extension>
<args>
<arg>-Xannotate</arg>
</args>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>0.8.0</version>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics-annotate</artifactId>
<version>0.8.0</version>
</plugin>
</plugins>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

Zunächst muss eine Konfiguration hinzugefügt werden. Damit komplexe Typen als Root-Element behandelt werden, auf die theoretisch von anderen XSD-Dateien verwiesen werden könnte, darf nicht der strict-Mode zum Einsatz kommen. Weiterhin müssen Extensions von Fremd-Anbietern zugelassen werden, da andernfalls die annotate-Anweisung nicht verwendet werden darf. Für diese Library ist außerdem der Argument-Eintrag Xannotate nötigt. Außerdem werden die Plugins jaxb2-basics und jaxb2-basics-annotate und die Dependency activation benötigt, damit der Build-Prozess wie gewünscht durchläuft.

Als Ergebnis ist die Klasse UserType korrekt annotiert und die ObjectFactory bietet nun wieder die Möglichkeit Objekte vom Typ UserType zu JAXBElementen zu transformieren. Damit ist auch die Möglichkeit gegeben, Java-Objekte zu marshallen und so in XML-Inhalt umzuwandeln. Hierzu wird das entsprechende Java-Objekt über die ObjectFactory erstellt:

ObjectFactory factory = new ObjectFactory();
UserType userType = factory.createUserType();
userType.setForename(„Max“);
userType.setLastname(„Mustermann“);

AddressType addressType = factory.createAddressType();
addressType.setCitycode(„50123“);
addressType.setStreet(„Musterweg“);
userType.getAddresses().add(addressType);

addressType = factory.createAddressType();
addressType.setCitycode(„50123“);
addressType.setStreet(„Musterstrasse“);
userType.getAddresses().add(addressType);

Nun kann der UserType in ein JAXBElement umgewandelt werden:

JAXBElement<UserType> jaxbElement = factory.createUser(userType);

Das marshallen ähnelt dem unmarshallen aus obigem Beispiel. Es wird zunächst der JAXBContext für die Klasse UserType und im Anschluss der entsprechende Marshaller erzeugt. Dieser erhält das JAXBElement zum marshallen und einen OutputStream in den der XML-Inhalt geschrieben wird:

JAXBContext jaxbContext = JAXBContext.newInstance(UserType.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(jaxbElement, outputStream);

Benötigt man den XML-Inhalt in einer Datei, so kann dem Marshaller statt dem OutputStream auch ein File übergeben werden.

Weiter Information zur Anpassung des JAXB Bindinings sind auf der Oracle Webseite zu finden:

http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/2.0/tutorial/doc/JAXBUsing4.html

Eclipse Extension Points und Extensions

Eclipse beinhaltet ein hoch modulares Konzept um Erweiterungen Plugin-übergreifend realisieren zu können. Das zu erweiternde Plugin bietet einen Extension-Point an, an dem andere Plugins mit ihren Extensions andocken können. Beide Arten an Plugins müssen hierbei zwangsläufig im Manifest als singleton definiert sein.

Um einen Extension-Point zu definieren, wird die plugin.xml Datei des zu erweiternden Bundles geöffnet und zum Reiter Extension Points gewechselt. Sofern keine plugin.xml Datei existiert, kann alternativ im Manifest (MANIFEST.MF) der genannte Reiter aktiviert werden, Eclipse erstellt nun automatisch die plugin.xml Datei. Nun kann ein Extension-Point erzeugt werden, als ID und Namen erhält er im folgenden Beispiel den Wert de.mz.myExtensionPoint und myExtensionPoint.


Nach erfolgreicher Erstellung des Extension-Points wird automatisch das dazugehörige Schema geöffnet. Auch wenn es nicht zwingend notwendig ist, sollten alle Attribute im Schema – Reiter Definition – definiert werden. Nur so können die Felder beim Anlegen einer Erweiterung bequem im Plug-In Manifest Editor über die UI angegeben werden können. Will man später Veränderungen am Schema vornehmen, so ist dieses in der Dateistruktur des Projektes unter schema/myExtensionPoint.exsd zu finden.

Hier wird nun ein neues Element mit dem Namen client erzeugt, dem zwei Attribute zugewiesen werden. Das Erste Attribut hat den Namen class und ist vom Typ Java. Hier hat man nun die Möglichkeit ein Interface zu hinterlegen, welches von der im Attribut class verwiesenen Klasse zu implementieren ist. Im folgenden Beispiel ist das zu implementierende Interface IMyExtension.

Das Interface selber enthält für Beispielzwecke die Methode doSomething:

public interface IMyExtension {
public void doSomething(String text);
}

Das zweite Attribut erhält den Namen text und ist vom Typ String. Hier sind keine weiteren Einstellungen nötig.

Per Context-Menü wird der extension zunächst ein Choice-Element zugewiesen, welches wiederum den Eintrag client erhält. Damit die Extension beliebig viele Client-Einträge erhalten kann, wird die Max Occurrences des Client-Elements auf Unbounded gesetzt. Eine Extension kann also mehrere Erweiterungen enthalten.

Die Plugin.xml hat nun folgenden Eintrag erhalten:

<extension-point id=“de.mz.myExtensionPoint“ name=“myExtensionPoint“ schema=“schema/myExtensionPoint.exsd“/>

Damit andere Bundles nun tatsächlich den Extension-Point erweitern können, müssen sie das Interface IMyExtension implementieren können. Daher muss im Manifest das Package des Interfaces exportiert werden:

Export-Package: de.mz

Damit ist die Definition des Extension-Points abgeschlossen und wir können uns der Erstellung einer Extensions zuwenden. Öffnen wir hierzu die plugin.xml Datei des Bundles, welches den Extension-Point erweitern soll. Auf dem Reiter Extensions kann zwischen den möglichen Erweiterungen gewählt werden, auch der zuvor erstelle Extension-Point de.mz.myExtensionPoint wird hier aufgeführt. Per Context-Menü können hier die Client-Erweiterungen hinzugefügt werden. Für die im Schema deklarierten Attribute stehen im Plug-In Manifest Editor Eingabehilfen zur Verfügung, so kann bequem für das class Attribut eine Implementierung und für das text Attribut ein String angegeben werden.

Diese Einstellung führt zum folgenden Eintrag in der plugin.xml Datei:

<extension point=“de.mz.myExtensionPoint“>
<client
class=“de.mz.MyExtensionImpl“
text=“Hallo Welt“>
</client>
</extension>

Extension-Point und eine dazu passende Extension wurden bereits erstellt, nur Verwendung findet die Erweiterungen bislang noch nicht. Doch hierzu bietet Eclipse die ExtensionRegistry an, auf die man über die Runtime-Platform Zugriff erhält:

IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(„de.mz.extension.example.myExtensionPoint“);
for (IConfigurationElement extension : extensions) {
try {
String text = extension.getAttribute(„text“);
IMyExtension myExtension = (IMyExtension) extension.createExecutableExtension(„class“);
myExtension.doSomething(text);
// do with myExtension what you have to do
} catch (CoreException e) {
// do some logging
}
}

In obigem Beispiel erhält man alle Erweiterungen die zum Extension-Point de.mz.extension.example.myExtensionPoint gehören. Nun können die Attribute der einzelnen Erweiterungen erfragt werden – hierbei ist es nicht relevant, ob diese im Schema definiert sind – und für das Klassen-Attribut ein entsprechendes Objekt erstellen werden (createExecutableExtension). Bei einem optionalen Klassen-Feld macht es Sinn, sich zunächst zu vergewissern, dass das Attribut einen Wert zugewiesen bekommen hat. Andernfalls wird nämlich eine CoreException geworfen, die über diese Abfrage verhindert werden kann.

Auch wenn das Beispiel auf die Eclipse 3.x Plattform ausgelegt ist, funktioniert der Mechanismus grundsätzlich auch bei Eclipse 4. Hier spart man sich den Umweg über die Runtime-Platform, da sich die ExtensionRegistry bequem per Dependency Injection injizieren lässt.

Will man das Beispiel testen, kann das Laden der Extensions in einem Activator vorgenommen werden, der hierfür lediglich im Manifest eingetragen werden muss:

Bundle-Activator: de.mz.Activator

Nun kann das Extension-Point Bundle als OSGI Framework ausgeführt werden. Treten hierbei Fehler auf, sollten in der Run-Configuration zunächst alle Bundles deselektiert und anschließend nur die Plugins ausgewählt werden, die den Extension-Point und die Extensions beinhalten. Natürlich werden auch Eclipse spezifische Bundles benötigt, die sich über einen Klick auf Add Required Bundles hinzufügen lassen.

In 10 Minuten ein Spring Web MVC Projekt erstellen

Bereits einen vorangegangenen Artikel widmete ich dem Thema REST Services mit Spring 3 (https://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3) und auch der folgende Beitrag beschäftigt sich mit Spring Web MVC. Diesmal wird allerdings eine Web-Seite realisiert und als Views JSPs (Java Server Pages) verwendet.

Innerhalb von maximal 10 Minuten soll ein vollständiges Spring Web MVC Projekt erstellt werden, mit dem sich eine Benutzerliste darstellen und über ein Formular weitere Benutzer hinzufügen lassen. Zum Einsatz kommt die aktuelle Spring Version 3.1.0. Da ich als Entwicklungsumgebung Eclipse verwende, werden zusätzlich ein paar Eclipse spezifische Details erläutert, grundsätzlich kann das Beispiel aber aus jeder IDE heraus nachvollzogen werden.

Zunächst wird in Eclipse ein Maven Projekt erstellt. Sofern das Eclipse WTP-Plugin (Web Tool Platform – http://www.eclipse.org/webtools/) installiert ist, kann man anschließend über die Projekt Einstellungen, die Project Facets konfigurieren. Hier wird nun das Häkchen bei Dynamic Web Module gesetzt.

Natürlich kann man auch andersherum vorgehen und zuerst ein Web Projekt erstellen und es anschließend zu einem Maven Projekt konvertieren.

Nun werden die drei Spring Libraries Core, Spring-Web und Spring-WebMVC als Maven Dependencys in die pom.xml eingetragen:

<properties>
<spring.version>3.1.0.RELEASE</spring.version>
</properties>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

Um mit Maven ein Web-Archive (WAR-Datei) erstellen zu können, muss das maven-war-plugin in die Maven-Konfiguration eingetragen und das Packaging von jar auf war umgestellt werden:

<packaging>war</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>

Sobald das Maven-Packaging geändert wird, erscheint in der Problem View von Eclipse folgender Fehler:

Project configuration is not up-to-date with pom.xml. Run Maven->Update Project Configuration or use Quick Fix.

Wie in der Meldung beschrieben, muss lediglich die Projekt-Konfiguration aktualisiert werden, um das Problem zu beheben.

Wer bereits mit WTP gearbeitet hat, ist gewohnt, dass das Web-Verzeichnis unter WebContent zu finden ist. Beim Aktivieren des Facets Dynamic Web Module wurde dieses Verzeichnis irreführender weise angelegt. Da unser Beispiel-Projekt allerdings mit Maven gebaut wird, ist dessen Standardverhalten entscheidend. Daher können die Verzeichnisse WebContent/WEB-INF getrost gelöscht und stattdessen src/main/webapp/WEB-INF angelegt werden. Hier wird vom maven-war-plugin die web.xml Datei erwartet – ist diese nicht vorhanden bricht der Build-Prozess mit einer Fehlermeldung ab.

In der web.xml Datei wird nun das DispatcherServlet definiert, welches alle eingehenden Requests annimmt und an die verantwortlichen Controller delegiert. Das DispatcherServlet ist Bestandteil von Springs IoC-Container und ermöglicht die Nutzung aller von Spring zur Verfügung gestellten Features:

<web-app>
<servlet>
<servlet-name>myWebApp</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>myWebApp</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Der Spring-Dispatcher scannt die unter Spring-Context stehenden Klassen nach Controller Annotationen und erkennt für enthaltene Methoden, ob ein RequestMapping angegeben wurde. Unter dem hier angegebenen Pfad ist die Ressource dann später erreichbar, da das DispatcherServlet entsprechende Requests an die Controller-Methoden delegiert.

Wenden wir uns nun dem UserController zu, der im Package de.mz.server erstellt und mit @Controller annotiert wird. Für die Darstellung der Benutzerliste ist die Methode getUsers verantwortlich, die ein RequestMapping unter dem Pfad users erhält. Da in der web.xml als url-pattern der Root-Pfad angegeben wurde, ist die Ressource unter http://<ip-addresse&gt;:<port>/de.mz.server/users erreichbar. Als Rückgabewert dient der Methode getUsers ein ModelAndView-Objekt. Wie der Name schon sagt, beinhaltet das Objekt die anzuzeigende View und als Model die Benutzerliste:

@Controller
public class UserController {

@RequestMapping(„/users“)
public ModelAndView getUsers() {
List<User> users = loadUsers();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(„users“);
modelAndView.addObject(„users“, users);
return modelAndView;
}

// add loadUsers-method
}

Widmen wir uns nun der Spring-Konfiguration, die im WEB-INF-Verzeichnis platziert wird. Der Name der Datei beginnt mit dem in der web.xml definierten Servlet-Namen ergänzt um „-servlet.xml“ – also myWebApp-servlet.xml. Damit der UserController von Spring generiert wird, wird das Component-Scanning für das Package de.mz.server aktiviert:

<context:component-scan base-package=“de.mz.server“/>

Im Controller wurde dem ModelAndView-Objekt der View Name users gesetzt. Um den Namen auf eine View zu mappen, muss ein ViewResolver definiert werden. Abhängig von der View stellt Spring hier verschiedene Strategien zur Verfügung. In diesem Beispiel wird der UrlBasedViewResolver verwendet, der ohne explizites Mapping auskommt, da die Namen der Views automatisch auf die View-Ressourcen gemappt werden:

<bean id=“viewResolver“ class=“org.springframework.web.servlet.view.UrlBasedViewResolver“>
<property name=“viewClass“ value=“org.springframework.web.servlet.view.JstlView“/>
<property name=“prefix“ value=“/WEB-INF/jsp/“/>
<property name=“suffix“ value=“.jsp“/>
</bean>

Als viewClass wird die JstlView gewählt, es werden also JSPs unterstützt, welche die JSP Standard Tag Library verwenden können. Per Präfix wird definiert, dass sich die JSP-Seiten in dem Unterverzeichnis jsp befinden und das Suffix gibt an, dass Views die gleichnamige Datei-Endung besitzen. Die im UserController referenzierte View users befindet sich also unter WEB-INF/jsp/users.jsp und hat folgenden Inhalt:

<%@taglib uri=“http://java.sun.com/jsp/jstl/core&#8220; prefix=“c“%>
<html>
<head><title>Users</title></head>
<body>

<h3>Users</h3>
<table>
<tr>
<th>Vorname</th>
<th>Nachname</th>
</tr>

<c:forEach var=“user“ items=“${users}“>
<tr>
<td>${user.forename}</td>
<td>${user.lastname}</td>
</c:forEach>
</table>

</body>
</html>

Ohne die explizite Angabe der Tag-Library in der ersten Zeile, können die c-Tags nicht verwendet werden. Mit Hilfe des forEach-Befehls wird über die Liste der Benutzer iterieren. Dem items-Attribut wird die Liste der Benutzer zugewiesen, indem auf das im ModelAndView-Objekt hinterlegte Model verwiesen wird. Im var-Feld wird eine temporäre Variable definiert, über die während der Schleifendurchläufe auf die Benutzer zugegriffen werden kann. Vor- und Nachnamen der Benutzer können so in separate Spalten der Tabelle geschrieben werden. Natürlich müssen hierfür die Eigenschaften forename und lastname im Model vorhanden und mittels getter-Methoden zugreifbar sein:

public class User {

private String forename;
private String lastname;

// getter and setter

}

Sofern die Web-Applikation in einem Tomcat-Server laufen soll, muss die JSTL-Library als Maven Dependency eingebunden werden, da diese nicht im Tomcat integriert ist:

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

Im Anschluss kann die Web-Applikation mit Maven gebaut werden. Dies geschieht über den Kommandozeilen-Aufruf mvn package, oder aus der Eclipse IDE heraus. Egal welchen Weg man wählt, nach erfolgreichem Build-Prozess befindet sich die frisch erzeugte WAR-Datei im Target-Verzeichnis und kann deployed werden.

Im zweiten Schritt sollen weitere Benutzer über ein Formular hinzufügbar sein. Hierzu wird der UserController um die Methode addUser erweitert. Über das RequestMapping wird der Pfad auf user und POST als Request-Art gesetzt. Als Übergabe-Parameter kann direkt das Model – also die User-Klasse – verwendet werden, da sich Request-Parameter bequem mit Objekt-Eigenschaften verknüpfen lassen. Hierzu reicht es aus, den Übergabe-Parameter mit der Annotation ModelAttribute zu versehen. Der hier vergebene Name userForm muss später auch im HTML-Formular verwendet werden. Nach erfolgreichem Speichern des Benutzers, soll anschließend ein redirect auf die users-Ressource stattfinden, damit die vervollständigte Benutzerliste betrachtet werden kann. Daher ist der Rückgabe-Wert diesmal ein String und kein ModelAndView-Objekt:

@RequestMapping(value=“/user“, method = RequestMethod.POST)
public String addUser(@ModelAttribute(„userForm“) User user) {
// save user
return „redirect:/users“;
}

Widmen wir uns dem HTML-Formular. Die Input-Tags im HTML-Formular müssen die gleichen Namen verwenden, wie die entsprechenden Eigenschaften in der User-Klasse und es müssen die dazugehörigen setter-Methoden existieren. Das Form-Attribut modelAttribute erhält denselben Namen, wie die gleichnamige Annotation im Controller, um das Mapping zwischen Input-Feldern und Eigenschaften der User-Klasse zu ermöglichen. Als action wird der Pfad zur addUser-Methode und als method POST angegeben, was den RequestMapping-Einstellungen des Controllers entspricht:

<form action=“user“ modelAttribute=“userForm“ method=“POST“>
<p>Vorname:<br><input name=“forename“ type=“text“></p>
<p>Nachname:<br><input name=“lastname“ type=“text“></p>
<p><input type=“submit“ value=“Absenden“></p>
</form>

Nach einem Redeployment können wie in der Zielsetzung beschrieben, nun weitere Benutzer hinzugefügt werden.

Innerhalb kürzester Zeit wurde mit Spring 3.x eine Web-Applikation erstellt, die aus Controller und View zur Administrierung von Benutzern besteht. Hierfür wurde das DispatcherServlet in die web.xml-Datei eingetragen und für die Auswahl der richtigen View ein ViewResolver verwendet, der den View-Namen auf eine JSP-Datei mappt. Alle weiteren Einstellungen ließen sich bequem per Annotation direkt im Controller vornehmen.

Eclipse Libra

Bei Eclipse Libra handelt es sich um ein Unter-Projekt vom Web-Tools-Platform-Projekt (WTP), welches den Funktionsumfang von WTP und dem Plug-in Development Environment (PDE) vereint. So können in einem WTP-Projekt auch die Werkzeuge eines PDE-Projektes genutzt werden.

Für die Installation von Libra unter der aktuellen Eclipse Version Indigo reicht es, auf der Indigo Update-Site die Bundles OSGI Bundle Facet und WAR Products zu installieren.

Erstellt man ein Dynamic Web Project können auf der ersten Seite des Wizards unter Configuration die Project Facets konfiguriert werden. Nach der Installation der oben genannten Pakete ist hier der Menüpunkt OSGI Bundle hinzugekommen, den man auswählt. Dies führt dazu, dass im Wizard eine weitere Seite verfügbar ist, auf der Details für das Manifest angegeben werden können. Das Manifest selbst befindet sich im META-INF Verzeichnis des Web-Ordners, also eine Hierarchiestufe tiefer als in herkömmlichen OSGI Bundles.

Um das Web-Projekt testen zu können, ist zumindest ein Servlet nötig, welches in der web.xml definiert wird:

  <servlet>
<servlet-name>hello</servlet-name>
<servlet-class>test.TestServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

Das Servlet selber erbt von der Klasse javax.servlet.http.HttpServlet und überschreibt die doGet-Methode.

Sofern nicht bereits eine passende Target-Platform definiert wurde, wird es spätestens bei der Implementierung des Servlet zu einem Compile-Fehler kommen, da javax.servlet nicht aufgelöst werden kann. Zur Ausführung des Web-Projektes mit dem OSGI-Framework kann die Target-Platform von http://wiki.eclipse.org/Gemini/Web verwendet werden.

Bei Gemini selbst, handelt es sich um modulare Enterprise-Technologien, welche speziell für das OSGI-Framework konzipiert wurden.

Für die Installation werden folgende Archive benötigt: http://www.eclipse.org/downloads/download.php?file=/equinox/drops/R-3.7.1-201109091335/org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar und http://www.eclipse.org/downloads/download.php?file=/gemini.web/release/GW/2.0.1.RELEASE/gemini-web-2.0.1.RELEASE.zip.

Anschließend wird gemini-web in ein beliebiges Verzeichnis entpackt und org.eclipse.osgi.jar in eben dieses kopiert. Nun wird hier noch das Verzeichnis configuration erstellt und die folgenden beiden Konfigurationsdateien hinein kopiert:

http://wiki.eclipse.org/images/0/0b/Config.ini.2.0.1.zip

http://wiki.eclipse.org/images/3/32/Java6-server.profile.zip

Jetzt wird in Eclipse eine neue Target-Platform (Window / Preferences / Target Platform) erstellt, deren Location auf das gemini-web Verzeichnis verweist.

Als nächstes wird eine neue Run-Configuration unter dem Punkt OSGI Framework erstellt. Unter den VM-Arguments wird folgender Parameter hinzugefügt:

-Dosgi.java.profile=file:configuration/java6-server.profile

Außerdem muss als Working directory das gemini-web Verzeichnis angegeben werden.

Weitere Schritte sind nicht nötig, nun kann die Run-Configuration ausgeführt werden und die Web Applikation ist unter http:127.0.0.1:8080/<application-name>/hello verfügbar. Der Applikations-Name wird hier standardmäßig genommen, will man dies ändern, so ist der Parameter Web-ContextPath im Manifest anzupassen.

Die Facets von Eclipse Libra lassen sich nicht nur in WTP-Projekten nutzen. Es ist durchaus möglich, dieses Facet z.B. einem Maven-Projekt zuzuweisen, um hier die PDE Funktionalitäten nutzen zu können. Der Nachteil zu Apache-Felix ist hierbei allerdings, dass die pom.xml und das Manifest separat gepflegt werden müssen. Werden also Maven-Dependendies hinzugefügt, müssen diese auch im Manifest eingetragen werden, damit die Abhängigkeiten zur Laufzeit verwendbar sind.

Erstellung einer Android Anwendung

Um ein Android-Projekt zu Erstellen benötigt man zunächst das Android SDK, welches zur Zeit in der aktuellen Version 3.2 zum Download unter http://developer.android.com/sdk/index.html bereit liegt.
Weiterhin wird das ADT-Plugin für Eclipse benötigt, dass die Erstellung eines Android Projektes extrem vereinfacht und das Debuggen ermöglicht. Zusätzlich kann die Anwendung zertifiziert und als apk-Datei exportiert werden, so dass diese auf einem Android Gerät installiert werden kann. Die aktuelle Version des ADT-Plugins ist 12.0.0 und kann unter folgender Update Site https://dl-ssl.google.com/android/eclipse/ installiert werden.
Anschließend stellt man nun in Eclipse unter Window, Preference, Android die SDK Location ein.

Im Anschluss kann das erste Android-Projekt erstellt werden. Hierzu reich ein Projekt- und Applikations-Name, ein Package und das gewünschte Android SDK. Bei letzterem Punkt sollte man beachten, dass für das eigene Android-Gerät meist noch nicht die neuste Firmware-Version zum Download bereit liegt. Für das Samsung Galaxy ist die Version 2.3.3 verfügbar, während man bereits das Android SDK 3.2 verwenden kann.
Sofern man sich eine Activity generieren lässt, kann nun das Projekt direkt im Emulator ausgeführt werden und man bekommt einen HelloWorld Text zu Gesicht.

Das Design der Anwendung, sofern man bei der HelloWorld Anwendung von Design reden kann, befindet sich unter /res/layout/main.xml. Es wird ein einfaches LinearLayout verwendet, welches vertikal ausgerichtet ist und die komplette Größe einnimmt. Das Layout wird einer TextView zugewiesen, die für die HelloWorld Ausgabe zuständig ist.

<?xml version=“1.0″ encoding=“utf-8″?>
<LinearLayout    xmlns:android=“http://schemas.android…“
android:orientation=“vertical“
android:layout_width=“fill_parent“
android:layout_height=“fill_parent“   >
<TextView
android:layout_width=“fill_parent“
android:layout_height=“wrap_content“
android:text=“@string/hello“
/>
</LinearLayout>

Der Text selbst ist externalisiert und befindet sich unter /res/values/strings.xml.

Schaut man sich den Erzeugten Code an, so befindet sich in der onCreate-Methode der erstellten Activity folgende Zeile:

setContentView(R.layout.main);

Die hier verwendete Klasse R wird vom ADT-Plugin angelegt und verwaltet – werden weitere grafische Komponenten im XML angelegt, werden diese hier gepflegt. Das Design kann zwar auch im Code verändert werden, aufgrund einer klaren Trennung sind die XML-Dateien aber vorzuziehen.

Will man die Anwendung nicht nur im Emulator testen, sondern auf seinem Android-Gerät, kann man diese bequem als apk-Datei exportieren, auf sein Handy einspielen und installieren.

Auch wenn es sich hierbei nur um eine HelloWorld Anwendung handelt, ist es doch erstaunlich, wie schnell man diese erstellt, auf sein Handy aufgespielt und getestet hat. Lediglich die Langsamkeit des Emulators treibt einen ab und an in die Verzweiflung. Aber der erste Start ist schnell gemacht und man kann sich um die erste richtige Anwendungen kümmern.

Veröffentlichung einer Fremdbibliothek als OSGI Plugin

Wer mit OSGI arbeitet kennt das. Man findet eine Library die genau das tut, was man benötigt, es liegt nur keine OSGI-Plugin-Variante zum Download bereit.

Eine Möglichkeit ist nun das Jar-Archive zu downloaden und in Eclipse ein neues Projekt Plug-in from Existing JAR Archive zu erstellen. Im nächsten Schritt können nun External JAR Archives hinzugefügt werden. Benötigt man verschiedene Libraries, bzw. hat die gewünschte Library weitere Dependencies, können diese hier direkt hinzugefügt werden. Selbst wenn die verschiedenen Archive eine gleiche package-Struktur aufweisen, ergibt dies keine Probleme, die class-Dateien werden in die entsprechenden Packages gemergt.

Theoretisch ist man nun fertig und man kann die gewünschte Library verwenden. Allerdings werden sich dann die Kollegen bedanken, da sie nun ein weiteres Projekt auschecken müssen. Also exportiert man das soeben erstellte Projekt noch als Deployable plug-ins and fragments, legt das erstellte OSGI-Plugin in die Target-Plattform und kann nun das Projekt wieder schließen.

Sofern die Library nicht in einem Maven-Kontext benötigt wird, ist man nun tatsächlich fertig, andernfalls muss man sie noch manuell in ein Maven-Repository legen.

Eine zweite Möglichkeit, um aus einer Fremdbibliothek ein OSGI-Plugin zu erstellen, ist die Verwendung des Maven Plugins Felix von Apache. Hier wird eine pom.xml der folgenden Art benötigt:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>de.example</groupId>
<artifactId>de.example.project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>de.example.project</name>
<packaging>bundle</packaging>

<dependencies>
<dependency>
<groupId>de.example</groupId>
<artifactId>de.example.dependency</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/xxx.jar</systemPath>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>*</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

Wichtig ist hierbei, dass packaging den Wert bundle erhält, wofür allerdings das Plugin org.apache.felix korrekt eingebunden seien muss. Zu beachten ist hier der Wert true für Extensions.

Als dependency wird im obigen Beispiel die Library de.example.dependency benötigt, die sich in dem aktuellen Maven-Projekt in dem Jar-Archive xxx.jar befindet. Der Ort des Jar-Archives wird im systemPath definiert.

Die class-Dateien der auf diese Weise angegebenen Dependencies werden beim Bauen des Maven-Projektes mittels package mit ins target gelegt. Weiterhin erstellt bei obiger Definition das Plugin Felix ein Manifest in dem alle packages der eingebundenen Libriries exportiert werden. Möchte man diese Einschränken, so können die zu exportierenden packages explizit in der pom-Datei unter Export-Package definiert werden.

Als Fazit lässt sich sagen, dass beide Varianten Arbeit bedeuten und man stehts hoffen sollte, dass die benötigten Libraries bereits als OSGI-Plugin zur Verfügung stehen. So lohnt sich immer zuerst ein Blick auf das SpringSource Enterprise Bundle Repository, da hier bereits viele Libraries als OSGI-Bundle vorliegen: http://ebr.springsource.com/repository/app/

Migration eines Eclipse RCP Projektes auf 4.x

Zur Zeit liegt das Eclipse SDK in Version 4.1 zum Download unter http://www.eclipse.org/eclipse4/ bereit. Im Gegensatz zum Early Adopter Release (Version 4.0) verwendet die Version 4.1 die neuere Indigo Update Seite, die seit dem 22 Juni 2011 zur Verfügung steht. Der Vorgänger verwendete hier noch die Update Seite von Helios.

Für die Migration eines bestehenden RCP Projektes auf das Eclipse SDK 4.1 habe ich mich dazu entschieden, dass Produkt Plugin komplett zu ersetzten, um die Vorteile von Eclipse 4.x direkt verwenden zu können. Insgesamt besteht das Projekt aus 13 reinen Plugin Projekten und weiteren 3 Projekten, die sowohl dem Server als auch dem Client zur Verfügung stehen. Hierbei handelt es sich um Maven Projekte, das Manifest wird mit dem Maven Plugin Felix erstellt. Weiterhin wird in den Client Projekten Spring DM eingesetzt, die OSGI Services werden hierüber veröffentlicht.

Nachdem das Eclipse SDK installiert ist fällt einem neben dem veränderten Aussehen vor allem die Geschwindigkeit auf, mit der sich nun die Plugins installieren lassen. Selbst die Installation von größeren Plugins wie WTP gehen extrem schnell vonstatten. Nach der Installation der benötigten Plugins (Eclipse e4 Tools, Subclipse, WTP, Maven, Maven Extras) werden zunächst alle Projekte in den aktuellen Workspace ausgecheckt, mit Ausnahme von der Target Plattform und dem Produkt Plugin.

Schnell ist ein neues Projekt angelegt, welches als neues Target dienen soll. Hier werden nun die benötigten neuen Eclipse SDK 4.1 Plugins rein kopiert und natürlich alle anderen verwendeten OSGI Libraries. Nun fügt man dem Projekt noch die Target Definition hinzu und konfiguriert sie entsprechend. Nachdem nun die von Eclipse verwendete Target Plattform auf das neu angelegte Target gewechselt wurde, dürften die Compile-Fehler in den Projekten verschwunden sein.

Anschließend kann das neue Produkt Plugin erstellt werden, hierbei handelt es sich zunächst um ein ganz normales „Plug-in Project“, welches als OSGI Framework Equinox verwendet. Öffnet man nun das Manifest kann man mit einem Klick auf Extensions den entsprechenden Reiter sichtbar machen. Nun fügt man als Abhängigkeit org.eclipse.equinox.app hinzu und setzt das Häkchen bei „This plug-in is a singleton“. Anschließend fügt man die Extension org.eclipse.core.runtime.products
hinzu und trägt bei ID product ein. Dem Feld wird nun ein Product hinzugefügt mit der Application org.eclipse.e4.ui.workbench.swt.E4Application und einem beliebigen Namen. Dem Product wiederum wird die Property appName mit einem entsprechenden Namen hinzugefügt. Es wird noch eine zweite Property mit dem Namen applicationXMI benötigt, mit dem Wert <Projektname>/Application.e4xmi. Die entsprechende Datei existiert zum jetzigen Zeitpunkt noch nicht, wird aber im folgenden Schritt angelegt.

Dem Projekt wird nun ein neues Application Model hinzugefügt. Dies ist nur möglich, wenn vorher die Eclipse e4 Tools erfolgreich installiert worden sind. Als Container wird das Produkt Projekt angegeben, der Dateiname Application.e4xmi sollte beibehalten werden.

In dem sich nun öffnenden Editor fügt man unter Windows ein Trimmed Window hinzu, hier kann der Titel und die gewünschte Fenster-Größe definiert werden. Nun benötigt das Projekt noch eine Product-Configuration. Sobald sich der entsprechende Editor geöffnet hat, fügt man neben den eigenen Projekten noch folgende Abhängigkeiten hinzu:

  • org.eclipse.equinox.ds
  • org.eclipse.equinox.event
  • org.eclipse.e4.ui.workbench.renderers.swt

Nun kann das neu erstellte Produkt zum ersten Mal gestartet werden, es wird sich ein leeres Fenster in der gewünschten Größe mit dem angegebenen Titel öffnen.

Will man nun noch Spring DM verwenden, muss anschließend noch das Bundle org.springframework.osgi.extender gestartet werden. Werden jetzt die eigenen Bundle gestartet, erkennt Spring DM – sofern vorhanden – die XML-Konfigurationsdateien im META-INF Ordner und fährt für die jeweiligen Bundle den Spring Context hoch.

Jetzt erst wird die erste Java Klasse im Product Projekt erstellt, die bisher benötigten Standard Implementierungen von

  • ActionBarAdvisor
  • WorkbenchAdvisor
  • WorkbenchWindowAdvisor
  • IApplication
  • IPerspectiveFactory

sind endlich nicht mehr nötig.

Stattdessen kann direkt mit der Implementierung von Funktionalitäten begonnen werden, für ein simples Beispiel reicht zunächst einmal ein ExitHandler zum Beenden der Workbench aus.

public class ExitHandler {

@Execute
public void execute(IWorkbench workbench) {
workbench.close();
}

}

Die Workbench selber, wird dem ExitHandler hierbei von OSGI per Dependency Injection übergeben. Es können beliebige weitere Parameter verwendet werden, wie z.B. in Spring DM exportierte OSGI Services oder andere OSGI Komponenten. Kann OSGI die entsprechenden Klassen nicht injizieren kommt es zu einer RuntimeException.

Um den ExitHandler zu verwenden müssen nun noch ein paar Einstellungen in der Application.e4xmi vorgenommen werden.  Zunächst wird ein Command erstellt mit der Id app.exit und dem Namen Exit. Jetzt wird ein Handler erstellt, der das gerade erstellte Command verwendet und der auf den ExitHandler als Implementierung verweist.

In dem Trimmed Window kann anschließend ein Main Menu erzeugt werden, dieses wiederum erhält ein Menu und dieses schlussendlich ein HandledMenuItem, welches das Exit Command Aufruft.

Natürlich soll das Applikations-Fenster entsprechenden Inhalt bekommen. Hierzu wird, wie in der Vorgänger Version, eine View benötigt. Mittlerweile muss diese aber nicht von ViewPart Erben, ein ganz normales POJO reicht als View aus. Lediglich über dem Konstruktor wird eine @Inject Annotation benötigt, so dass das Parent Composite vom OSGI Framework übergeben werden kann.

  @Inject
public MyView(Composite parent) {
// do what you have to do – something with ui for example
}

Um die View zu verwenden muss auch diesmal eine Anpassung in der Application.e4xmi vorgenommen werden. Unterhalb des Trimmed Window befindet sich der Punkt Controls, hier kann nun ein Part Stack erstellt werden. Dieser bekommt wiederum einen Part der die soeben erstellte MyView Klasse zur Ausführung erhält.

Dieser Artikel soll einen kurzen Einblick in Eclipse SDK 4.x vermitteln und erläutern wie man einfach ein bestehendes RCP Projekt migrieren kann. Grade die endlich in OSGI integrierte Dependency Injection und das neue Application Model mit dem hilfreichen und einfach zu bedienenden e4xmi-Editor machen Freude.