Freitag, 24. September 2010

Grails i18n und die GAE

Momentan scheint es mit dem aktuellen app-engine Plugin (0.8.10) für Grails ein paar Probleme bezüglich der Internationalisierung zu geben.

Bindet man in die Seite das Tag g:message

<g:message code="hallo.welt" />

ein und definiert in der messages.properties (bzw. in der entsprechenden Lokalisierung davon) einen passenden Key

hallo.welt=Hallo Welt !

wird beim lokalen starten der Applikation noch alles richtig angezeigt und auf der Webseite erscheint der Text "Hallo Welt!". Wird die Anwendung jedoch auf die App Engine deployt, erscheint an derselben Stelle nur noch "hallo.welt"

Um dieses Problem zu lösen, kann man die BootStrap.groovy um folgendes erweitern:

class BootStrap {
 def messageSource

  def init = { servletContext ->
   messageSource.basenames = [ 'WEB-INF/grails-app/i18n/messages' ]
   messageSource.clearCache()
  }
  def destroy = {
  }
}


Dadurch werden die Namen der property-Dateien, in denen die Keys stehen nochmal neu gesetzt.

Sonntag, 22. August 2010

Lasttests mit JMeter für Webanwendungen

JMeter ist ein Tool der Apache Software Foundation, mit dem sich u.a. Lasttests und Performancemessungen für Webanwendungen erstellen lassen.

Nach dem Runterladen und Entpacken des Tools lässt es sich mit der /bin/jmeter.bat starten. 
Um einen Test zu erstellen, muss in den - noch leeren - Testplan eine Thread-Gruppe hinzugefügt werden.



Eine Thread-Gruppe simuliert eine Menge von Benutzern. Ist diese erstellt, können in den Thread-Eigenschaften die Anzahl der zu simulierenden Benutzer über die Eigenschaft Anzahl der Threads festgelegt werden.
Die Ramp-Up Period gibt an, innerhalb wieviel Sekunden JMeter die gewünsche Anzahl von Threads starten soll.
Bei 5 Threads und einer Ramp-Up Period von 5 wird also jede Sekunde ein neuer Thread gestartet. Wird die Period auf 10 gesetzt, wird alle 2 Sekunden ein neuer Thread gestartet. Dies ist ein wichtiger Wert zum skalieren der Tests.

Für Lasttests ist dieser Wert möglichst klein (0-2) zu wählen, da hierbei möglichst viele Benutzer gleichzeitig simuliert werden sollen.

Sollen nur Reaktionszeiten der Anwendung für einzelne Benutzer getestet werden ist dieser Wert entsprechend hoch zuwählen, so dass sich die Anfragen nicht zu sehr überlappen. Wie hoch genau der Wert dabei sein muss, hängt natürlich von der zu testenden Funktion der Anwendung ab. Soll nur die Reaktionszeit einer statischen Webseite überprüft werden, reicht es sicherlich aus, die Ramp-Up Period = 2x(Anzahl der Threads) zu setzen, da 2 Sekunden als Antwortzeit durchaus ausreichend ist. Sollen aber Funktionen der Anwendung getestet werden, die Berechnungen durchführen, auf Datenbanken zugreifen oder sonstiges, muss die Dauer dieser Funktionbei Wahl der Zeit mit berücksichtigt werden, so dass sich hierbei die Ramp-Up Period vergrößert.

Unterhalb der Thread-Gruppe können jetzt die gewünschten Anfragen an die Webanwendung plaziert werden. Diese werden als HTTP Request HTTPClient (zu finden unter Hinzufügen -> Sampler) unter die Thread-Gruppe platziert. Für das Request können dann die notwendigen Einstellungen wie Server - IP, Port und Request - Pfad gemacht werden. Zusätzlich können dem Request auch noch Parameter übergeben werden, was ganz nützlich sein kann, wenn Methoden aufgerufen werden sollen, die Parameter verwenden.


In meinem Beispiel befindet sich auf der testme.xhtml ein Binding zu einer JSF ManagedBean - Methode, die eine Anfrage an die Datenbank startet.

Zuletzt wird noch ein Graph (zu finden unter Hinzufügen -> Listener) unter dem Http Request plaziert, um die Testergebnisse visuell darzustellen. An diesem müssen keine Einstellungen mehr vorgenommen werden.


Der Test kann über den Menüpunkt Start gestartet werden, die Ergebnisse / Zeiten erscheinen in dem Graph. Je nach Bedarf können auch mehrere Thread Gruppen definiert werden, die entweder parallel (ist per default eingestellt) oder nacheinander gestartet werden. Um sie nacheinander starten zu lassen, muss ein Haken bei Thread-Gruppen nacheinander starten gesetzt werden. Dies ist direkt unter dem Testplan zu finden.

Buchtip:
Handbuch zum Testen von Web-Applikationen: Testverfahren, Werkzeuge, Praxistipps (Xpert.press)

Mittwoch, 11. August 2010

GAE und JasperReports

Nachdem ich nun einige Applikationen auf der Google App Engine entwickelt habe, wollte ich JasperReports in eine Anwendung einbinden, um PDF Ausgaben zu erzeugen.

Leider benötigt JasperReports die Klasse java.awt.GraphicsEnvironment und diese ist nicht mit auf der Whitelist der verfügbaren JRE Klassen. Aus diesem Grund kommt es zu folgender Fehlermeldung:

java.lang.NoClassDefFoundError: java.awt.GraphicsEnvironment is a restricted class.
...

Das Issue 1423 fordert, diese Klasse in der Whitelist aufzunehmen.
Bis es soweit ist, ist die Nutzung von JasperReport auf der GAE nicht möglich.

Mittwoch, 28. Juli 2010

JAXB und das @XmlRootElement

JAXB wird verwendet um aus einem XML Schema Java Klassen zu erstellen. Diese können dann mit Daten gefüllt und als XML - Datei abgespeichert werden. Andersrum können auch bestehende XML - Dateien in Java Klassen umgewandelt werden.
Je nach Schemadefinition ergibt sich dabei allerdings ein Problem.

Der folgende Ausschnitt beschreibt ein einfaches Schema:

<xs:schema targetnamespace="http://test.kirchner.de/jaxb"            xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="start">
        <xs:complextype>
       </xs:complextype>
    </xs:element>
</xs:schema>


JAXB erstellt daraus diese Klasse, die im Package test.kirchner.de.jaxb untergebracht wird:

@XmlRootElement(name = "start")
public class Start {
}

Das folgende Schema beschreibt dieselbe XML Struktur, nur das dem start Element jetzt ein Typ über das type Attribut zugeordnet wird und dieser nicht innerhalb des Elements deklariert wurde:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"            xmlns:mk="http://test.kirchner.de/jaxb"            targetNamespace="http://test.kirchner.de/jaxb">
   <xs:element name="start" type="mk:startType"/>
   <xs:complexType name="startType"/>
</xs:schema>

Hieraus wird von JABX jedoch folgendes erstellt:

@XmlType(name = "startType")
public class StartType {
}

In der zugehörigen ObjectFactory wird zum Erzeugen eines start Elements die folgende Methode zur Verfügung gestellt:

@XmlRegistry
public class ObjectFactory {
  private final static QName _Start_QNAME =
      new QName("http://test.kirchner.de/jaxb", "start");
  ...
  @XmlElementDecl(namespace = "http://test.kirchner.de/jaxb",
      name = "start")
  public JAXBElement createStart(StartType value) {
    return new JAXBElement(_Start_QNAME, StartType.class, null,         value);
  }
}

Daraus ergibt sich nun aber das Problem, dass kein XmlRootElement definiert ist und der Versuch die Java Klassen mit dem Marshaller als XML Datei zu speichern schlägt fehl. Die hervorgehobene Zeile führt zu einer javax.xml.bind.MarshalException

JAXBElement list = (new ObjectFactory()).createStart(new StartType());
StartType root = list.getValue();
JAXBContext jaxbContext = JAXBContext.newInstance("test.kirchner.de.jaxb");
Marshaller m = jaxbContext.createMarshaller();
m.marshal(root, new File("d:/start.xml"));

Ausschnitt aus dem Stacktrace:

#javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: unable to marshal type "test.kirchner.de.jaxb.StartType" as an element because it is missing an @XmlRootElement annotation]

Abhilfe schaffen lässt sich, indem man im Schema folgendes hinzufügt:

<jaxb:globalBindings>
   <xjc:simple/>
</jaxb:globalBindings>


Das komplette XSD sieht somit wie folgt aus:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mk="http://test.kirchner.de/jaxb" targetNamespace="http://test.kirchner.de/jaxb" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="1.0" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc">
    <xs:annotation>
        <xs:appinfo>
            <jaxb:globalBindings>
                <xjc:simple/>
            </jaxb:globalBindings>
        </xs:appinfo>
    </xs:annotation>
    <xs:element name="start" type="mk:startType"/>
    <xs:complexType name="startType"/>
</xs:schema>


Durch die xjc:simple wird von JAXB wieder die Start Klasse mit der @XmlRootElement Notation erzeugt.

Siehe auch: den Blog von Kohsuke Kawaguchi

Donnerstag, 22. Juli 2010

Webanwendung mit JSF (MyFaces), Facelets und Richfaces

Im folgenden wird beschrieben welche Bibliotheken und Einstellungen notwendig sind, um eine JSF - fähig Webanwendung für einen JBoss Server 5.1.0 zu erstellen. Als JSF Implementierung wird dabei MyFaces gewählt. Zusätzlich sollen noch Facelets und Richfaces unterstützt werden.

Folgende Versionen werden dabei verwendet: (In späteren Auflistungen von Bibliotheken werden alle Versionsnummern weggelassen)
  • MyFaces 1.2.8
  • Facelets 1.1.15.B1
  • Richfaces 3.3.2
Um eine leere Webanwendung zu Erstellen wird Eclipse verwendet. Dort kann über
File --> New --> Dynamic Web Project (oder File --> New --> Other und dann unter Web/Dynamic Web Project) eine entsprechende Anwendung erstellt werden. (Als Servlet Version 2.5 angeben)
Die JSF Unterstützung kann ebenfalls über Eclipse hinzugefügt werden, da ich jedoch lieber selber eine Übersicht habe, welche Dateien wo erstellt werden, wird hier beschrieben, wie sie per Hand hinzugefügt wird.

JSF Unterstützung hinzufügen

Die wichtigste Konfigurationsdatei für JSF ist die faces-config-xml. Diese Datei wird im Ordner WEB-INF erstellt.In der einfachsten Form sieht diese wie folgt aus:

<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee" xi="http://www.w3.org/2001/XInclude" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
</faces-config>

Nachdem dies geschehen ist, muss die web.xml noch angepasst werden:

Um JSF zu verwenden wird das Faces Servlet eingebunden. Zusätzlich werden noch die passenden Mappings für das Servlet hinzugefügt.

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>

Soll in der Webanwendung z.B. mit xhtml Seiten gearbeitet werden, wird als Mapping *.xhtml angegeben.

Das Mapping von /faces/* ist wichtig, da einige Bibliotheken, die ich einbinde (z.B. Richfaces), bestimmte Resourcen über den Kontextpfad .../faces/... schickt.

Diese Angaben reichen aus, um der Webanwendung JSF beizubringen. Allerdings wird dann die Default - Implementierung des Servers für JSF verwendet, was im Falle von JBoss die SUN-RI Implemetierung ist.

MyFaces Implementierung verwenden

Um die MyFaces Implementierung von JSF zu verwenden, müssen folgende Dateien in den WEB-INF/lib Ordner kopiert werden (Download auf MyFaces).
  • myfaces-impl
  • myfaces-api
  • commons-beanutils
  • commons-codec
  • commons-collections
  • commons-digester
  • commons-discovery
  • commons-logging
Nachdem diese Dateien in den Ordner kopiert wurden, müssen noch folgende Angaben der web.xml hinzugefügt werden:

<context-param>
<description>MyFaces</description>
<param-name>org.jboss.jbossfaces.WAR_BUNDLES_JSF_IMPL</param-name>
<param-value>true</param-value>
</context-param>

Der Context Parameter sorgt dafür, dass der JBos Server für diese Web Anwendung die JSF Implementierung nimmt, die im WEB-INF/lib Ordner liegt.
Zusätzlich muss noch der folgende Listener eingebunden werden:

<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>

Mit diesen Einstellungen verwendet die Webanwendung ab sofoert die Myfaces Implementierung.

Facelets hinzufügen

Die jar Datei zu Facelets kann hier runtergeladen werden. Um Facelets einzubinden, muss nur die folgende Datei in den WEB-INF/lib ordner kopiert werden:
  • jsf-facelets.jar
In der web.xml muss der folgende Context-Parameter hinzugefügt werden:

<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>

Als param-value wird dabei dieselbe Endung angegeben, die zuvor bereits für das Mapping des Faces Servlets verwendet wurde.

Des Weiteres muss in der faces-config.xml ein ViewHandler für facelets eingetragen werden. dazu wird unter dem faces-config Tag folgendes eingefügt:

<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>

Achtung: Wird vergessen der ViewHandler hinzuzufügen, kommt es beim Aufrufen einer *.xhtml Seite aus der Webanwendung zu einem Stackoverflow:

Servlet.service() for servlet Faces Servlet threw exception
java.lang.StackOverflowError
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:216)
...
--> View Handler in faces-config.xml angeben!

Richfaces hinzufügen

Die Richfaces Bibliotheken können hier runtergeladen werden. Folgende Dateien müssen in den WEB-INF/lib Ordner kopiert werden:
  • richfaces-api
  • richfaces-impl
  • richfaces-ui
Um Richfaces in die Anwendung einzubinden, müssen noch folgende Einträge in der web.xml gemacht werden:

<filter>
<display-name>RichFaces Filter</display-name>
<filter-name>richfaces</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>

<filter-mapping>
<filter-name>richfaces</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>

Fertig ist die Webapplikation !

Buchtip für Anwendungen mit JSF 2.0:
JavaServer Faces 2.0: Grundlagen und erweiterte Konzepte