jMeter + JSF Trinidad

jMeter eignet sich sehr gut für Stresstests. Dieses HOWTO zeigt wie es möglich ist ein Stresstest für eine JSF-Trinidad-Anwendung mit jMeter aufzunehmen und zu starten. jMeter kann von hier runter geladen werden: http://jakarta.apache.org/jmeter/

Nach dem entpacken muss die “<jMeter>/bin/jmeter.sh” ausgeführt werden. Unter Windows dementsprechend “<jMeter>/bin/jmeter.bat”. Nach dem erfolgreichen Start der Anwendung sollte zu erst eine neue “Thread Group” im Testplan erstellt werden:

In der Thread Group kann eingestellt werden mit wie vielen Usern der Test simuliert werden soll und wie oft.

Als nächstes legen wir ein Config Element “HTTP Requests Defaults” an.

Hier können Angaben zum Server, Port, Protokoll und Ziel-Path gemacht werden.

Da JSF-Trinidad die JSESSIONID in einem Cockie ablegt, fügen wir noch einen “HTTP Cockie Manager” hinzu. Dieser braucht nicht konfiguriert zu werden. Er muss einfach da sein damit es funktioniert.

Beim testen einer JSF Applikation muessen einige Dinge beachtet werden. JSF arbeitet nach dem PostBack Pattern, bei dem alle Formularinhalte per POST-Request an das aktuelle Formular zurueckgeschickt werden. Jede gerenderte HTML-Seite die im Browser zu sehen ist, stellt eine View da und hat einen eindeutigen Identyfier, den ViewState. Der ViewState Identyfier muss bei jedem POST-Request mitgeschickt werden, da ansonsten der Komponentenbaum nicht reproduziert werden kann. In so einem Fall wuerde nach der ersten Phase des JSF-Lifecycles gleich die sechste Phase ausgefuehrt und eine leere Seite gerendert werden.
Mit einem RegularExpressionExtractor in jMeter ist es moeglich die ViewState auszulesen und beim naechsten Request als Post-Parameter mitzuschicken. Dazu benoetigt es ein rechtsklick auf die Thread Group in einem jMeter-Testfall, wo schliesslich im Contextmenue folgendes ausgewaehlt werden muss “Add” – “Post Processors” – “Regular Expresseion Extractor”.

Das Formular zum Extractor muss dann wie folgt ausgefuellt werden:

Reference Name: jsfSTATE
Regular Expression: <input type="hidden" Name="org\.apache\.myfaces\.trinidad\.faces\.STATE" value="(.+?)">
Template: $1$
Match No. (0 for Random): 0
Default Value:

Dannach muss bei jedem Post-Request der Parameter “org.apache.myfaces.trinidad.faces.STATE” auf den Wert “${jsfSTATE}” gesetzt werden. Die Parameter zu einem Request koennte demach so aussehen:

loginName: name
loginPassword: password
org.apache.myfaces.trinidad.faces.FORM: loginForm
org.apache.myfaces.trinidad.faces.STATE: ${jsfSTATE}
source: loginButton

Um Testergebnisse ansehen zu können muss noch ein “Summary Report” hinzugefügt werden.

Der Testplan ist jetzt soweit fertig gestellt. Jetzt muss nur noch ein Test aufgezeichnet werden. Dafür fügen wir einen “HTTP Porxy Server” der Work Bench hinzu.

Der Proxy Server muss jetzt auf einen bestimmten Port und auf eine bestimmte URL konfiguriert werden, auf die er lauschen soll. Ganz wichtig ist auch der unterste Bereich der Maske, “URL Patterns to Exclude”. Hier sollten unbedingt mit regulären Ausdrücken Bilder, CSS und JavaScript ausgeschlossen werden, da diese für den Test unwichtig sind.

Nach dem starten des Proxy Servers muss dieser noch im Browser eingestellt werden. Im Firefox ruft man dazu den Menüpunkt “Preferences -> Advanced -> Network” auf und klickt dann auf “Settings”.

Hier muss jetzt der entsprechende Proxy mit dem richtigen Port eingetragen werden. Ganz wichtig ist hierbei das, dass Feld “No Proxy for” leer ist.

Wenn der Proxy eingetragen ist, kann damit begonnen werden die WebAnwendung aufzurufen. Jedes Klick und jedes Seitenaufruf auf den Anwendung wird nun von jMeter aufgezeichnet. Ein einfaches Test könnte z.B. darin bestehen sich an einer WebAnwendung anzumelden und alle Links im Hauptmenü durchzuklicken. Anschliessend könnte man sich abmelden.

Nach dem der Test aufgezeichnet ist, kann der Proxy Server in jMeter gestoppt werden.

Wenn alles geklappt hat, dann sieht der linke Bereich in jMeter wie folgt aus:

Jetzt ist es wichtig das bei jedem Request die Request-Variable “org.apache.myfaces.trinidad.faces.STATE” den Wert “${jsfSTATE}” hat. Die Variable kommt aus dem “Regular Expresseion Extractor”.

Dieser Wert muss leider bei jedem Request per Hand gesetzt werden. Einen anderen Weg habe ich bisher noch nicht gefunden. Wenn das erledigt ist, kann ein einfacher Test gestartet werden, über den Menüpunkt “Run -> Start”.

Nach dem Ausführen des aufgezeichneten Tests ist im Summary Report die Ergebnissliste zu sehen:

Jetzt kann man in der Thread Group die “Number of Threads (users)” auch hoch schrauben auf 50 und sich anschauen wie die Anwendung reagiert.

Ich hoffe die Anleitung war hilfreich und hat weiter geholfen.

Eine Beschreibung zu jMeter + MyFaces Core ist hier im Wiki zu finde:

http://wiki.apache.org/myfaces/PerformanceTestingWithJMeter?highlight=%28jmeter%29

SQLException: Can’t call commit when autocommit=true

In einem Projekt bei einem grossen Energieversorger habe ich Hibernate 3.X zusammen mit Spring 2.5 konfiguriert, nach der Beschreibung von Eberhard Wolf. Dier Persistenzschicht hat wunderbar funktioniert, bis ich einen Stresstest mit jMeter gemacht habe. Als ich einen aufgezeichneten Testfall in zehn Threads gestartet habe, trat bei einigen Threads folgender Fehler auf:

hibernate java.sql.SQLException: Can't call commit when autocommit=true

Daraufhin habe ich in der Hibernatekonfiguration den Wert explizit auf false gesetzt:

hibernate.connection.autocommit=false

leider blieb der erhoffte Erfolg aus. Die Loesung habe ich in einem JIRA-Eintrag gefunden. An die connection.url muss der Wert “relaxAutoCommit=true” angehaengt werden. Also z.B.

hibernate.connection.url=jdbc:mysql://localhost:3306/project?relaxAutoCommit=true

Seit dem ich meine Connection.url abgeaendert habe ist der Fehler nicht wieder augetreten.

Factory already available for this class loader

In einem JSF-Projekt habe ich einen SessionExpired-Filter eingebaut. Der Filter implementiert das javax.servlet.Filter Interface und leitet den Benutzer auf die Login-Seite um, wenn die Session abgelaufen ist. Der Filter hat an sich super funktioniert, bis ich wieder einen Stresstest mit jMeter gemacht habe. Dabei ist im Filter folgende Exception geworfen worden:

java.lang.IllegalStateException: Factory already available for this class loader.

Ich habe den Fehler zuerst in meinem Filter gesucht, der zweite Uebeltaeter den ich in Verdacht zog war Tomcat. Doch tatsachlich war es wieder einmal die JSF Komponenten Libary ADF/Trinidad.

http://www.mail-archive.com/adffaces-issues@incubator.apache.org/msg02333.html

Wenn nach einem Serverstart zwei Threads gleichzeitig auf die Applikation zugreifen kommt der Dispatcher durcheinander.
Ganz grosses Kino!!!

Tomcat 6 Cluster

Die folgende Beschreibung bezieht sich auf Tomcat 6.0.14 mit Java 5.
Eeine Anleitung zum Aufbauen eines Tomcat-Clusters mit Session-Replikation ist in der offiziellen Tomca-Dokumentation zu finden: http://tomcat.apache.org/tomcat-6.0-doc/cluster-howto.html
Ich beschreibe hier die Probleme auf die gestoßen bin beim Aufbau eines solchen Clusters.

Bei einem Cluster werden die HttpSessions auf alle Nodes (Teilnehmer/Server) im Cluster repliziert (kopiert). Somit ist es egal auf welche Node der Loadbalancer vor dem Cluster weiterleitet, da alle Sessions auf allen Nodes zur Verfuegung stehen. Das hat den Vorteil der hohen Verfuegbarkeit aber den Nachteil eines hohen Speicherverbrauchs.

Wenn sich ein Kunde auf eine Webapplikation einloggt die auf so einem Cluster laeuft, dann kriegt er nichts davon wenn der Server auf dem er gerade arbeitet abstuerzt. Der Loadbalancer leitet den naechsten Request vom Kunden auf einen anderen Server im Cluster um, wo er mit seiner alten Session weiter arbeiten kann. Bei einem einfachen Loadbalancing ohne Cluster und ohne Session-Replikation muesste sich der Kunde wieder neue anmelden an der WebApplikation und seine HttpSession waere fuer immer verloren.

Nach jedem Request wird geprueft ob sich eine Aenderung in der HttpSession ergeben hat. Wenn dies der Fall ist, wird das Delta auf alle anderen Nodes im Cluster repliziert. In der offiziellen Doku ist das folgende Stueck Konfigurationscode zu finden, das in die <TOMCAT_HOME>/conf/server.xml eingefuegt werden muss, um Tomcat im Cluster laufen zu lassen.

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8"  >

 <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"  />

 <Channel className="org.apache.catalina.tribes.group.GroupChannel"  >
   <Membership className="org.apache.catalina.tribes.membership.McastService"
               address="228.0.0.4"
               port="45564"
               frequency="500"
               dropTime="3000"  />
   <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
             address="auto"
             port="4000"
             autoBind="100"
             selectorTimeout="5000"
             maxThreads="6"  />

   <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"  >
       <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"  />
   </Sender>
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"  />
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"  />
 </Channel>

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""  />
 <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"  />

 <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
           tempDir="/tmp/war-temp/"
           deployDir="/tmp/war-deploy/"
           watchDir="/tmp/war-listen/"
           watchEnabled="false"  />

 <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"  />
 <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"  />
</Cluster>

Nach dem Starten des Tomcats habe ich dann folgende Fehlermeldung bekommen:

SEVERE: Unable to process request in NioReceiverjava.net.SocketException: Invalid argument

Das Problem kann behoben werden indem man dem Transport einen timeout und einen KeepAliveCount zuweist.

<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000" keepAliveCount="120000"  />

Mein Eintrag in der server.xml sieht so aus.

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"  >

 <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"  />

 <Channel className="org.apache.catalina.tribes.group.GroupChannel"  >
   <Membership className="org.apache.catalina.tribes.membership.McastService"
               address="228.0.0.4"
               port="45564"
               frequency="500"
               dropTime="3000"  />
   <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
             address="auto"
             port="4002"
             autoBind="100"
             selectorTimeout="5000"
             maxThreads="12"  />

   <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"  >
     <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000" keepAliveCount="120000"  />
   </Sender>
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"  />
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"  />
 </Channel>

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""  />
 <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"  />

 <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/"
           deployDir="/tmp/war-deploy/"
           watchDir="/tmp/war-listen/"
           watchEnabled="false"  />

 <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"  />
 <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"  />
</Cluster>

Wenn die Nodes alle auf der gleichen Hardware laufen dann muessen die Ports vom Receiver angepasst werden. Die server.xml des zweiten Tomcats auf der gleichen Hardware sieht bei mir so aus:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"  >

 <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"  />

 <Channel className="org.apache.catalina.tribes.group.GroupChannel"  >
   <Membership className="org.apache.catalina.tribes.membership.McastService"
               address="228.0.0.4"
               port="45564"
               frequency="500"
               dropTime="3000"  />
   <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
             address="auto"
             port="4001"
             autoBind="100"
             selectorTimeout="5000"
             maxThreads="12"  />

 <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"  >
   <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"
              timeout="60000"
              keepAliveCount="120000"  />
 </Sender>
 <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"  />
 <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"  />
</Channel>

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""  />
 <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"  />

 <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
           tempDir="/tmp/war-temp/"
           deployDir="/tmp/war-deploy/"
           watchDir="/tmp/war-listen/"
           watchEnabled="true"  />

 <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"  />
 <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"  />
</Cluster>

Der naechste Fehler auf dem ich gestossen bin ist:

java.net.SocketException: Invalid argument

Wenn der Tomcat mit der Option -Djava.net.preferIPv4Stack=true aufgerufen wird, dann tritt der Fehler nicht auf. Unter Linux kann mit folgendem Befehl die Umgebungsvariable “JAVA_OPTS” erweitert werden.

export JAVA_OPTS=$JAVA_OPTS;-Djava.net.preferIPv4Stack=true

bei starten des Tomcats werden alle Optionen aus der JAVA_OPTS gelesen.
Nun lief das Cluster endlich erfolgreich, also habe ich angefangen einige Applikation drauf zu deployen. Damit eine Applikation Clusterfaehig ist muessen alle Objekte die in der HttpSession abgelegt werden Serialisierbar sein. Ausserdem muss der Deploymentdescriptor (web.xml) folgenden Eintrag enthalten.

<distributable/>

Das deployen selber wollte ich ueber den Farmdeployer erledigen. Dabei soll die WAR-File nach “/tmp/war-listen” kopiert werden und der Farmdeployer verteil die Applikation auf alle Nodes im Cluster. Das hat leider nicht funktioniert! Nach einigem forschen im Internet bin ich in der tomcat-mailinglist auf einen Eintrag gestossen, aus dem ich erfahren habe das der farmdeployer noch nicht wirklich funktioniert und die Entwickler noch dran sind.

Also habe ich meine Applikationen per Hand deployt. Bei einer IceFaces Applikation konnte die Session mit folgender Fehlermeldung nicht repliziert werden:

java.io.NotSerializableException: com.icesoft.faces.webapp.http.servlet.ServletExternalContext

Bei einer Struts 2 + Spring 2.5 Anwendung ist die Replikation an folgender Fehlermeldung gescheitert.

java.io.NotSerializableException: org.springframework.jdbc.datasource.DriverManagerDataSource

Alles nicht so wirklich zufriedendstellend. Da gibt man sich muehe alle Seine Objekte Serialisierbar zu halten und kriegt man vom Framework nicht Serailisierbare Objekte in den Weg gelegt. Das bedarf einer intensiven Nachforschung.

Loadbalancing mit Apache2 + Tomcat 6 + mod_proxy

In dem Blogeintrag Loadbalancing mit Apache2 + Tomcat 6 + mod_jk 1.2.X habe ich beschrieben wie ein Loadbalancing mit dem “mod_jk” Module eingerichtet werden kann. Dieser Blogeintrag verfolgt das gleich Ziel, jedoch nicht mit dem “mod_jk” sondern mit dem “mod_proxy” Modul.

Im gegensatz zum “mod_jk” Modul muss das “mod_proxy” Modul nicht von einer externen Quelle heruntergeladen werden, es ist beim apache2 Webserver dabei. Unter Debian muss gegebenenfalls noch folgendes Kommando angesetzt werden.

apt-get install apache-modules

Ein Loadbalancing macht nur Sinn, wenn mindestens zwei Tomcats die gleiche Applikation ausliefern. Der Blogeintrag N Tomcats auf einer Maschine beschreibt wie zwei Tomcats zu konfigurieren sind damit sie parallel auf der selben Maschine laufen.

Gehen wir davon aus das wir eine Webapplikation namens “geo” haben die auf den zwei Tomcats “tomcat1″ und “tomcat2″ laufen. die “server.xml” von tomcat1 hat folgenden Eintrag:

<Engine name="Catalina"    defaultHost="localhost"    jvmRoute="tomcat1"   >

und die von tomcat2 dementsprechend:

 <Engine name="Catalina"    defaultHost="localhost"    jvmRoute="tomcat2"   >
 

Ganz wichtig hierbei ist das Attribut “jvmRoute”, in dem der Name des jeweiligen Tomcats gesetzt ist. Auf diesen Namen wird aus dem apache2 Webserver heraus referenziert.

In der Konfigurationsdatei des apache2, meisstens “/etc/apache2/httpd.conf” oder “/etc/apache2/apache.conf”, muss das “mod_proxy” Modul geladen werden. Das kann so aussehen:

LoadModule proxy_module libexec/apache2/mod_proxy.so
LoadModule proxy_connect_module libexec/apache2/mod_proxy_connect.so
LoadModule proxy_ftp_module libexec/apache2/mod_proxy_ftp.so
LoadModule proxy_http_module libexec/apache2/mod_proxy_http.so
LoadModule proxy_ajp_module libexec/apache2/mod_proxy_ajp.so
LoadModule proxy_balancer_module libexec/apache2/mod_proxy_balancer.so

Hier wird ein wenig mehr geladen als nur das reine “mod_proxy” Modul. Die Pfade muessen natuerlich an das entsprechende Betriebssystem angepasst sein.

Nach dem erfolgreichen laden des Moduls kann der Loadbalancer konfiguriert werden.

ProxyPass /geo balancer://apacheBalance stickysession=JSESSIONID

<Proxy balancer://apacheBalance>
 BalancerMember ajp://localhost:8009/geo route=tomcat1 loadfactor=50
 BalancerMember ajp://localhost:8010/geo route=tomcat2 loadfactor=50
 </Proxy>

In der ersten Zeile wird der Webserver angewiesen alle Anfragen mit der URI “geo” an den balancer mit dem Namen “apacheBalance” zu delegieren, der im Abschnitt weiter unten definiert ist. Der Balancer kann beliebig viele “BalancerMember” haben und unterstuetzt nicht nur das ajp Protokoll sondern auch http. Das Attribut “route” im BalancerMember referenziert auf den “jvmRoute” des Tomcats, die wir weiter oben eingestellt haben.

Der “loadfactor” muss ein Wert zwischen 0 und 100 sein und legt die Lastverteilung fest. Wenn beide balancer einen Wert von 50 haben, dann wird die Last gleich verteilt. Wuerde der “tomcat1″ einen loadfactor von 10 haben und der andere einen loadfactor von 90, dann wuerde “tomcat2″ neunmal mehr Last bekommen als “tomcat1″.
Die Applikation ist nun ueber die URL “http://localhost/geo&#8221; erreichbar.

Bei der ersten Anfrage leitet der apache2 auf eines der BalancerMember weiter, z.B. tomcat2. Alle weiteren Anfragen von dem gleichen Client werden ebenfalls an den tomcat2 weitergeleitet, sofern eine HttpSession benutzt wird. Um sich zu merken welche Clients zu welchen Tomcat weitegeleitet werden sollen, legt apache2 eine Cookie beim Cleint an. Dieser Cookie besteht aus der SessionId und dem Namen des jeweiligen Tomcats.

Bei allen weiteren Anfragen wird dieser Cookie mitgeschickt und so weis der apache2 immer genau an welchen Tomcat er die Anfrage weiterleiten soll. Dieses Verhalten macht natuerlich nur Sinn wenn die Webapplikation Kundendaten in einer HttpSession vorhaelt. Wenn das nicht der Fall ist, ist es vollkommen egal an welches der beiden Tomcats die nachfolgenden Anfragen weitergeleitet werden.
Und genau hier liegt der Vorteil vom “mod_proxy” Modul gegenueber dem “mod_jk” Modul. Mod_proxy haengt im Cookie nur dann den Namen des Tomcats an die SessionId wenn die HttpSession nicht leer ist.

Ein weiterer Vorteil des Moduls ist der balancer-manager, eine kleine Webanwendung die den aktuellen Status des Balancers zeigt. Um die Applikation einzubinden muss folgender Eintrag in die apache2 Konfigurationsdatei hinzugefuegt werden.

<Location /balancer-manager>
          SetHandler balancer-manager
</Location>

Nach der Aenderung muss der apache2 natuerlich neu gestartet werden und dannach ist die Anwendung uber die URL “http://localhost/balancer-manager&#8221; erreichbar.

Ueber den Balancer-Manager kann auch zur Laufzeit der loadfactor veraendert werden. Soll einer der beiden Tomcats (tomcat2) aus bestimmten Gruenden vom Netz genommen werden, sollte dieser einen loadfactor von 1 zugewiesen bekommen und der andere einen loadfactor von 99. Damit ist sichergestellt das alle weietern Requests an den Tomcat mit dem loadfactor 99 weitergeleitet werden. Nach einer gewissen Zeit sind auf dem tomcat2 keine HttpSessions mehr offen und er kann problemlos vom Netz genommen werden.

Um den unbefugten Zugriff auf den balancer-manager zu verhindern sollte er durch ein Passwort geschuetzt werden.

<Location /balancer-manager>
          SetHandler balancer-manager

          AuthType Basic
          AuthName "balancer manager "
          AuthUserFile /etc/apache2/htpassbalancer
          Require valid-user
</Location>

Die Passwortdatei kann mit folgendem Kommando angelegt werdne.

htpasswd -c /etc/apache2/htpassbalancer username

Multiple MessageBundles in JSF ManagedBeans

In einer bestehenden Web-Applikation, mit MyFaces 1.1.5 wollte ich aus einer MessageBean auf verschiedene MessageBundles zugreifen. Die betroffene Klasse erbt von der BaseBean, welche folgende Methode zur Verfügung stellt:

public String getStringFromResourceBundle(final String key) {
    return ResourceBundle.getBundle(getApplication().getMessageBundle()).getString(key);
}

Die faces-config habe ich um zwei weitere ResourceBundles erweitert.

<application>
 <default-render-kit-id>org.apache.myfaces.trinidad.core</default-render-kit-id>
 <variable-resolver>
     org.springframework.web.jsf.DelegatingVariableResolver
 </variable-resolver>
 <locale-config>
     <default-locale>de</default-locale>
 </locale-config>
 <message-bundle>
     de.itservices.onlineservices.resources.gui.status
 </message-bundle>
 <message-bundle>
     de.itservices.onlineservices.resources.gui.buildno
 </message-bundle>
 <message-bundle>
     de.itservices.onlineservices.resources.gui.messages
 </message-bundle>
</application>

In der betroffenen Klasse habe ich über die Methode aus der BaseBean eine Property aufgerufen dien in der status.properties hinterlegt ist. Der Aufruf endete in einer MissingResourceException.

java.util.MissingResourceException: Can't find resource for bundle java.util.PropertyResourceBundle, key Revision

Wenn mehrere message-bundles in der faces-config eingetragen sind, wird in der Implementierung von MyFaces 1.1.5 der letzte Eintrage genommen. Properties aus den ersten beiden message-bundles stehen nicht zur Verfügung.
Soll in einer ManagedBean auf mehr als auf eine message-bundle/Properties zugegriffen werden, muss der Zugriff erst implementiert werden. Hier eine mögliche Lösung.

URL url = this.getClass().getClassLoader().getResource("de/firma/projekt/resources/buildno.properties");
String path = url.getPath().replaceAll("%20", " ");
Properties prop = new Properties();
prop.load(new FileInputStream(path));
String zeit = prop.getProperty("Time");

Die zweite Zeile ist ein Workaround für Pfade mit Leerzeichen auf Windows-Systemen, aber das ist ein anderes Thema.
Ich habe mir die JSF 1.2 Spezifikation heruntergeladen und dannach durchsucht. Die Spez. ist zu diesem Thema nicht transparent, sie sagt nichts darüber aus, was geschehen soll wenn mehrere message-bundles in der faces-config.xml eingetragen sind und aus einer ManagedBean versucht wird darauf zuzugreifen. Es waere natuerlich schoen wenn die Methode getApplication().getMessageBundle() einen MessageBundle zurueckgeben wuerde der die Properties aus allen MessageBundles enthaelt die in der faces-config.xml eingetragen sind.

Einrichten eines Postfix Mailserver

Auf Debian-Servern ist standardmaessig Exim als Mailserver vorinstalliert. Der ist nicht schlecht aber mein Lieblingsmailserver ist nun mal Postfix. Exim kann wie folgt vom System entfernt werden.

apt-get remove exim*

Nun ist der Weg frei fuer Postfix.

apt-get install postfix

Und zu einem guten MTA gehoert auch ein guter MUA.

apt-get install mutt

Der Mailserver ist nun installiert und muss nur noch konfiguriert werden. Die Hauptkonfigurationsdatei fuer Postfix ist die “/etc/postfix/main.cf” Die Standardeinstellungen in der Datei sind meisstens sehr sinnvoll, so das nur wenige Variablen angepasst werden muessen.
Als erstes sollte der Hostname und die Domain angepasst werden. In meinem Fall sieht das so aus:

myhostname = ploin.de
mydomain = ploin.de

Die Variable “mydestination” legt fest fuer welche Domain der Server E-Mails entgegen nehmen soll. Es mach Sinn hier den eigenen Server einzutragen.

mydestination = ploin.de, localhost, 192.344.559.2

Die Variable “mynetworks” legt fest welche Adressen vertrauenswuerdig sind. Hier sollte auch mindestens der eigene Server drin stehen.

mynetworks = 127.0.0.0/8

die Variable “alias_maps” enthaelt den Pfad zu einer Datei, in der alle E-Mailadressen enthalten sind.

alias_maps = hash:/etc/aliases

Eine “/etc/aliases” koennte so aussehen:

reiz: pumukel
mueller: mueller@gmx.de

In der ersten Zeile wird Postfix angewiesen alle E-Mails mit der Empfaengeradresse reiz@ploin.de an den Benutzer pumukel des Systemes zuzuweisen. Die E-Mails landen also im Postfach vom pumukel Account.
In der zweiten Zeile wird der Mailserver angewiesen alle E-Mail mit der Empfaengeradresse mueller@ploin.de an die E-Mailadresse mueller@gmx.de weiterzuleiten.
Wenn die Datei geaendert wird, muss nicht gleich der Mailserver durchgestartet werden um die anederungen zu uebernehmen. Ein einfaches “postalias” mit der Datei als Parameter reicht aus.

postalias /etc/aliases

Nach dem Befehl sind die Aenderungen gueltig.
Der E-Mailserver sollte jetzt eingerichtet und betriebsbereit sein. Er nimmt E-Mails entgegen und speichert sie in den ensprechenden Postfaechern der Benutzer die in der /etc/aliases eingetragen sind.

Kompilieren mit Ant

Beim kompilieren mit dem Build-Tool Ant werden oft drei Tasks definiert, preCompile, executeCompiler und postCompile. In dem Task preCompile werden alle Arbeiten erledigt die vor dem Kompiliervorgang statt finden muessen, wie z.B. das Erstellen eines Zielverzeichnisses.

<property name="projectBuild"   value="${projectHome}/build/classes"   />

<fileset id="classesFiles"   dir="${projectBuild}"   />

<target name="preCompile"  >
   <mkdir dir="${projectBuild}"   />
</target>

Im Task executeCompiler findet das eigentliche Kompilieren statt.

<property name="compileTarget"   value="1.5"   />

<path id="compilerSourceFiles"  >
    <pathelement path="${projectSources}"   />
</path>

<path id="compilerLibraryFiles"  >
    <fileset dir="${libraries}"  >
       <include name="**/*.jar"   />
    </fileset>
    <fileset dir="${j2eeLibraries}"  >
       <include name="**/*.jar"   />
    </fileset>
</path>

<target name="executeCompiler"   depends="preCompile"  >
   <javac destdir="${projectBuild}"   target="${compileTarget}"  >
      <src refid="compilerSourceFiles"   />
      <classpath refid="compilerLibraryFiles"   />
   </javac>
</target>

Im Task postCompile sind alle Arbeiten beschrieben die nach dem kompilieren ausgefuehrt werden sollen, wie z.B. das kopieren aller nicht Quellcode-Dateien vom Source-Verzeichnis in das Build-Verzeichnis.

<target name="postCompile"   depends="executeCompiler"  >
    <copy todir="${projectBuild}"  >
        <fileset dir="${projectSources}"  >
            <exclude name="**/*.java"   />
        </fileset>
    </copy>
</target>

Der Tag “exclude” teilt Ant mit das er rekursiv durch alle Verzeichnisse gehen soll und dabei alle Dateien die mit “.java” enden auslassen soll.

Ant + Tomcat (hotdeploy)

Unter einem hotdeployment versteht man das deployen einer Applikation ohne dabei den Applikationserver/Servletcontainer herunterzufahren. Um mit Ant ein hotdeployment aus einem Tomcat durchfuehren zu koennen, muss vorher Der TCD (Tomcat Client Deployer) heruntergeladen werden, der hier zur Verfuegung steht: http://tomcat.apache.org/download-60.cgi. Die JAR-Files aus dem TCD sollten im Classpath zu finden sein.
Die Nachfolgenden Properties sollten fuer ein hotdeployment deklariert werden:

<property name="path"   value="/myapp"  />
<property name="url"   value="http://localhost:8080/manager"  />
<property name="username"   value="tomcat"  />
<property name="password"   value="tompassword"  />

Mit dem folgenden Eintrag wird der Ant Task fuer den Manager konfiguriert.

 <path id="deployer.classpath"  >
<fileset dir="${basedir}/lib"  >
<include name="*.jar"  />
</fileset>
</path>

<taskdef resource="org/apache/catalina/ant/catalina.tasks"
classpathref="deployer.classpath"  />
 

Mit den folgend Targets kann eine Applikation neu geladen, undeployt und deployt werden.

 <target name="reload"   >
<reload url="${url}"  username="${username}"  password="${password}"  path="${path}"  />
</target>

<target name="undeploy"   >
<undeploy url="${url}"  username="${username}"  password="${password}"  path="${path}"  />
</target>

<target name="hotdeploy"   >
<deploy url="${url}"  username="${username}"  password="${password}"  path="${path}"  war="${projectWARFile}"   />
</target>

Eine ausführliche Beschreibung aller Task ist auf der Homepage von Tomcat zu finden.

http://tomcat.apache.org/tomcat-6.0-doc/deployer-howto.html

Nicht selten endet das hotdeployment in einem OutOfMemmoryException. Mit dem Mechanismus des HotDeployments hatte Tomcat schon immer Probleme. Ich empfehle fuer das deployen auf Tomcats nur harddeployments, die ich im vorherigen Blogeintrag beschrieben habe.

Ant + Tomcat (harddeploy)

Der folgende Eintrag zeigt wie Ant aus einer WebApplikation eine *.war Datei backt und diese auf dem Tomcat deployt. Gehen wir davon aus das vorhergehende Task eine WebApplikation aus einem Svn-Server ausgecheckt und erofolgreich kompiliert haben. Der folgende Task baut eine *.war Datei.

<target name="buildWARFile"  >
 <war destfile="${projectWARFile}"  >
   <fileset refid="webContentFiles"   />
   <lib refid="libFiles"   />
   <classes refid="classesFiles"   />
   <webinf refid="webinfFiles"   />
 </war>
</target>

Die Attribute “refid” verweisen alle auf “filesets” (siehe Ant-docu). Die eben generiert Datei wird mit dem nachfolgenden Task deployt.

<target name="deploy"   >
 <exec executable="net"   >
   <arg line='stop "  Apache Tomcat 5.5.20 Server 3"  ' />
 </exec>
 <delete dir="${appHome}"   />
 <mkdir dir="${appHome}"   />
 <unwar src="${projectWARFile}"   dest="${appHome}"   />
 <exec executable="net"   >
   <arg line='start "  Apache Tomcat 5.5.20 Server 3"  ' />
 </exec>
</target>

Die erste Anweisung ruft das Kommandozeileninterface “net” auf mit den Uebergabeparametern ‘stop “Apache Tomcat 5.5.20 Server 3″‘. Das Kommandozeileninterface “net” steuert auf Windows Servern die Systemdienste. Damit das ganze funktioniert muss Tomcat als Windows Systemdienst installiert sein.
Sobald der Apache Tomcat heruntergefahren ist, werden mit den nachfolgenden Zeilen das Verzeichnis der Zielapplikation geloescht, ein neues Verzeichnis angelegt, die *.war Datei wird in das neu erstellte Verzeichnis entpackt und zum Schluss wird der Apache Tomcat als Systemdienst gestartet.
Der Vorgang kann ein bis zwei Minuten dauern, dannach sollte die Applikation zur Verfuegung stehen.

Auf einem Debian Linux System sieht der Task ähnlich aus.

<target name="deploy"   >
 <exec executable="/opt/tomcat/bin/shutdown.sh"   />
 <delete dir="${appHome}"   />
 <mkdir dir="${appHome}"   />
 <unwar src="${projectWARFile}"   dest="${appHome}"   />
 <exec executable="/opt/tomcat/bin/startup.sh"   />
</target>

BuildNumber ohne SvnAnt

Im vorherigen BlogEintrag “BuildNumber mit SvnAnt” habe ich beschrieben wie mit Hilfe von SvnAnt die Revisionsnummer geholt und eine Proterties Datei geschrieben werden kann. Die build.xml aus der Beschreibung habe ich auf meinem MacBook Pro (Mac OS X Tiger) mit Erfolg laufen lassen. Ein Kollege von mir konnte die Tasks ebenfalls erfolgreich auf seinem Windows XP PC laufen lassen. Auf einem alten Windows 2000 Server konnte die selbe build.xml jedoch nicht erfolgreich durchlaufen werden. Folgende Exception wurde geworfen:

Ant Exception

Ant Exception

Nach dem ich die neuste libjavahl.dll von der Subversion Seite auf den Server kopiert habe, ist beim ausfuehren der build.xml die JVM agestuerzt mit einem Hinweis das eine native Bibliothek den Fehler verursacht hat. Daraufhin habe ich in der build.xml “javahl” auf “false” gesetzt und den neusten Subversion-Client auf dem Server installiert, was leider auch nicht zum Ziel gefuehrt hat.
Da eine schnelle Loesung her musste, habe ich mir folgenden Workaround einfallen lassen um die aktuelle Revisionsnummer auszulesen:

<exec executable="svn"   >
 <arg line="info ${projectName} > ${statusFile}" />
</exec>

Das bewirkt das folgender Befehl auf der Kommandozeile ausgefuehrt wird:

svn info os > status.properties

Der Befehl aus der Kommandozeile funtkioniert wunderbar und leitet die Ausgabe, welche auch die Revisionsnummer enthaelt, in die Datei “status.properties” um. Der Ant-Task fuehrt leider zu einem Fehler:

svn: Fehler beim Ermitteln der Groß-/Kleinschreibung von '>'

Ich habe das “>” Zeichen auf versucht zu escapen mit dem Dollarzeichen, wie bei Ant ueblich, aber leider ohne Erfolg. Auch die Ersetzung durch Unicode-Zeichen hat keinerlei Wirkung gehabt.
Aus dem Grund habe ich eine Batch Datei erstellt mit folgendem Inhalt:

svn info %1 > %2

Diese Batch Datei wird nun mit folgenden Zeilen aus Ant aufgerufen:

 <exec executable="status.bat"   >
 <arg line="${projectName} ${statusFile}"   />
</exec>
 

Der Aufruf funtkioniert und tut genau das was er soll. Anschließend werden in der Ausgabedatei alle “:” durch das “=” Zeichen ersetzt.

 <replaceregexp file="${statusFile}"  match=":"  replace="=" flags="g"   />
 

Somit haben wir wieder eine Propertie-Datei auf die wir mit JSF zugreifen koennen. Das Attribut “flags” hat den Wert “g”, was soviel bedeutet das die Ersetzung in der Datei global geschehen soll.

BuildNumber mit SvnAnt

In dem vorherigen Blogeintrag habe ich beschrieben wie SvnAnt dazu benutzt werden kann die aktuellen Sourcen aus einem Subversion-Server auszuchecken. Vor dem Bauen der Applikation macht es Sinn die aktuelle Revisionsnummer und einen Timestamp in eine Properties Datei zu schreiben. Diese Informationen koennen in einer Web-Applikation im Footer eingeblendet werden. Wenn ein Fehler auftritt, kann der Kunde, im Idealfall, zu der Fehlerbeschreibung die Revisionsnummer und den Timestamp mitschicken. Diese Informationen sind fuer Entwickler bei der Fehlersuche Gold wert.
Der folgende Task in eine build.xml eingefuegt, holt sich die aktuelle Revisionsnummer vom Subversion-Server und schreibt den Wert in die Property “projectStatus.revision”.

<target name="getSVNRevison"  >
 <svn username="${svnUser}"   password="${svnPassword}"  >
 <status path="${projectHome}"   revisionProperty="projectStatus.revision"   />
 </svn>
</target>

Die Variable “projectHome” zeigt auf den Pfad der lokalen Arbeitskopie, der im Idealfall mit einem Ant-Task vorher ausgecheckt wurde.
Der Nachfolgende Task schreibt die Revisionsnummer in eine Properties Datei, erzeugt einen Timestamp und schreibt diesen ebenfalls in eine Prop. Datei.

 <target name="updateBuildNo"   depends="getSVNRevison"  >
 <replaceregexp file="${buildnoFile}"
 match="revision=(.*)"
 replace="revision=${projectStatus.revision}"   />

 <tstamp>
 <format property="buildTimestamp"   pattern="yyyyMMdd-HHmm"   locale="de,DE"   />
 </tstamp>

 <replaceregexp file="${buildnoFile}"
 match="timestamp=(.*)"
 replace="timestamp=${buildTimestamp}"   />

 <echo>Revision: ${projectStatus.revision}</echo>
 <echo>Timestamp: ${buildTimestamp}</echo>
</target>

Auf die Properties Datei kann in einer JSF-Applikation mit der JSF EL zugegriffen werden als Resource-Bundle.

Ant optional Tasks

Die Ant-Task sind in zwei Gruppen aufgeteilt, in die Core-Tasks und in die Optional-Tasks. Eine vollstaendige Auflistung alles Core- und Optional-Task ist in der offiziellen Ant-Docu http://ant.apache.org/manual/ zu finden. Die Optional-Tasks benoetigen alle zusatzliche Bibliotheken wie z.B. ant-apache-oro.jar. Nach der Installtion von Ant auf einem Debian Linux System mit dem Befehl:

apt-get install ant

stehen auf dem System nur die Core-Tasks zur Verfuegung. Wird in einer build.xml ein Optional-Task verwendet wird eine Exception geworfen. Das Problem kann dadurch geloest werden das die optionalen Tasks nachinstalliert werden mit dem Befehl:

apt-get install ant-optional

In dem Archiv das von der offiziellen Homepage http://ant.apache.org/bindownload.cgi heruntergeladen werden kann, sind alle JARs enthalten, auch die fuer die optionalen Tasks.

Ant + Subversion + SvnAnt

SvnAnt ist eine Java-Bibliothek die Ant-Tasks fuer die Vesionsverwaltungs-Software Subversion anbietet. Die Bibliothek kapselt die Moeglichkeiten des Kommandozeilenprogramms “svn” in Ant-Tasks, womit die Kommunikation zwischen Ant und Subversion sehr komfortabel und elegant moeglich ist.
SvnAnt benutzt intern die javahl Bibliothek, ein Java Interface fuer die Subversion API, welche direkt zum Subversion Projekt gehoert. Wenn die Bib. die korrespondierende native Bib. (libsvnant.dll auf Windows) finden kann, dann geschieht die Kommunikation ueber javahl. Wenn die native Implementierung auf dem Betriebssystem nicht gefunden werden kann, gibt es ein FallBack zum Kommandozeilen Interface “svn”. Wenn kein Subversion-Client installiert ist und dementsprechend kein “svn” im PATH eingetragen ist, kann die Verbindung zu Subversion nicht hergestellt werden und es treten Fehler auf.
Es muss also entweder eine native javahl Bibliothek installiert sein oder ein Subversion-Client. Die javahl Bibs werden uebrigens auch im Subclipse Projekt benutzt. Eclipse Benutzer die Subclipse installiert haben, sollten sich hierum keine Sorgen machen. Alle nicht Eclipse Benutzer koennen die Bibliothek von der offiziellen Subversion Homepage runterladen.
SvnAnt kann hier heruntergeladen werden: http://subclipse.tigris.org/svnant.html

In dem entpackten Archiv befindet sich eine vollstaendige Dokumentation im HTML-Format, in dem alle Ant-Tasks mit ihren Attributen beschrieben sind. Die *.jar Dateien aus dem Archiv muessen entweder in die <ANT_HOME>/lib kopiert werden oder die entsprechende build.xml verweist auf die Dateien.
Bei der zweiten Moeglichkeit muss folgender Path in der build.xml eingetragen sein.

<path id="project.classpath"  >
 <fileset dir="../svnant-1.0.0-rc1/lib"  >
 <include name="*.jar"  />
 </fileset>
</path>

Der Pfad zum “svnant-1.0.0-rc1/lib” Verzeichnis muss natuerlich an das jeweilige System angepasst werden.
Der Task wird wie folgt definiert:

 <taskdef name="svn"
 classname="org.tigris.subversion.svnant.SvnTask"
 classpathref="project.classpath"   />

Wenn die *.jar Dateien von SvnAnt in der <ANT_HOME>/lib liegen dann kann das Attribut “classpathref” weggelassen werden. Das auschecken von Sourcecode aus dem Subversion-Server sieht dann so aus:

 <target name="checkout"  >
 <svn username="${svnUser}"   password="${svnPassword}"   javahl="true"  >
 <checkout url="${svnURL}"   destPath="${projectHome}"   />
 </svn>
</target>
 

Mit dem Attribut “javahl” kann hier explizit angegeben werden ob javahl oder das Kommandozeileninterface “svn” zum Gebrauch kommen soll. Standardmaessig hat das Attribut “javahl” den Wert “true”.
Weitere Tasks wie commit, add, delete, copy, createRepository und noch mehr sind in der Dokumentation zu SvnAnt beschrieben.

Ant + Subversion

Subversion ist ein Programm zur Versionskontrolle und der Nachfolger von CVS. Ich habe frueher mit CVS gearbeitet aber seit dem ich Subversion kenne moechte ich nichts mehr von CVS wissen.

Das build-tool ant hat fuer Java Entwickler ungefaehr die gleiche Bedeutung wie “make” fuer die C und C++ Entwickler. Mit ant werden Dateien kopiert, JARs, WARs und EARs gebacken.

Was liegt naeher als die beiden Werkeuzge miteinander zu verbinden. Es gibt verschiedene Moeglichkeiten um mit ant den aktuellen Sourcecode aus einem Subversion-Server auszuchecken. In jedem Fall muss dafuer auf der Maschine auf dem ant laufen soll ein Subversion Client installiert sein.

Auf einem Debian-Linux-System ist das mit folgendem Befehl aus der Kommandozeile erledigt:

apt-get install subversion

Auf einem Mac OS X System empfehle ich die Darwin Ports zu benutzen. Dazu eine Konsole oeffnen und folgenden Befehl absetzen:

port install subversion

Die Windowsbenutzer muessen sich von der Subversion-Homepage ein Programm herunterladen und per Hand installieren. Hier der Link: http://subversion.tigris.org/

Nach der Installation sollte geprueft werden ob der svn-client auch wirklich funktioniert. Der folgende Befehl checkt ein Projekt aus dem Subversion-Server aus:

svn co –-username tom –-password tomtom http://ebtj09.firma.de/svn/fop/trunk zielverzeichnis

Natuerlich muessen die Werte an den eigenen Subversion-Server angepasst werden.
Der selber Aufruf kann auch aus ant heraus abgesetzt werden.

<target name="checkoutBuild"   description="auschecken von Code aus dem Subversion-Server"  >
 <exec executable="svn"  >
 <arg line="co –-username ${svn.username} –-password$ {svn.password} ${svn.projecturl} ${destUrl}"   />
 </exec>
</target>

JasperReports, strech with overflow

In JasperReports hat man oft das Problem das Texte einfach abgeschnitten werden. Wenn ein Textfeld nicht hoch oder breit genug ist, wird abgeschnitten. Das ist das standardverhalten vom Framework.
Wenn sich das Feld dem Textinhalt dynamisch anpassen soll muss das Attribut “stretch with overflow” den Wert “true” haben. In dem grafischen Editor iReport muss man dazu ein Rechtsklick auf das entsprechende Textfeld machen und im Kontextmenue den Eintrag “Properties” auswaehlen. In dem Popup-Fenster der dannach erscheint kann im Karteireiter ganz rechts (“All”) das Attribut gesetzt werden.

Sehr wichtig und weniger intuitiv dabei ist es, das Attribut “stretch type” auf dem Wert “no stretch” zu lassen. Wenn ein anderer stretch type eingestellt ist dann funktioniert das ganze bereits nicht mehr und auf diesen Fehler muss man erstmal kommen :-)

Das entsprechende Stueck Sourcecode in der *.jrxml Datei dazu sieht wie folgt aus:

<textField isStretchWithOverflow="true"  isBlankWhenNull="false"  evaluationTime="Now"  hyperlinkType="None"   hyperlinkTarget="Self"  >
     <reportElement
            x="138"
            y="112"
            width="386"
            height="14"
            key="textField-12"
            positionType="Float" />
     <box topBorder="None"  topBorderColor="#000000"  leftBorder="None"  leftBorderColor="#000000"  rightBorder="None"  rightBorderColor="#000000"  bottomBorder="None"  bottomBorderColor="#000000" />
     <textElement>
            <font/>
     </textElement>
     <textFieldExpression class="java.lang.String" ><![CDATA[$F{descriptionValue}]]></textFieldExpression>
</textField>

Wenn alles richtig eingestellt ist, kann beliebig viel Text in ein Textfeld eingefuegt werden und dieser wird sogar vollstaendig angezeigt. Alle Element die unter dem Textfeld stehen werden natuerlich dementsprechend nach unten verschoben.

JavaScript Aufruf aus einer ManagedBean heraus mit IceFaces

Mit der Klasse “com.icesoft.faces.context.effects.JavascriptContext” aus der IceFaces 1.6.2 Bibliothek ist es moeglich aus einer ManagedBean heraus JavaScript Aufrufe zu taetigen. Hier folgt ein Beispielaufruf.

JavascriptContext.addJavascriptCall(FacesContext.getCurrentInstance(), "test()");

Der statischen Methode “addJavascriptCall” muss als erster Parameter der aktuelle FacesContext uebergeben werden und als zweiter Parameter die Javascript Methode die aufgerufen werden soll.
Die Methode “test()” befindet sich in einer *.js Datei die im Header des Facelets-Templates eingebunden wird.

N Tomcats auf einer Maschine

Um zwei oder mehr Tomcats auf der selben Maschine laufen zu lassen muessen die Ports in der

<tomcat>/conf/server.xml

angepasst werden, das es sonst zu Portkonflikten kommt. Standardmaessig sind folgende Ports in der server.xml eingetragen.

- server port: 8005
- http port: 8080 redirectPort: 8443
- ajp port: 8009 redirectPort: 8443

Wenn ein zweiter Tomcat im Parallelbetrieb auf der selben Maschine laufen soll, muessen die Ports angepasst werden. Hier ist ein Vorschlag von mir.

- server port: 8006
- http port: 8180 redirectPort: 8444
- ajp port: 8010 redirectPort: 8444

Die server.xml zum ersten Tomcat sieht wie folgt aus.

<Server port="8005"   shutdown="SHUTDOWN"  >

  <Listener className="org.apache.catalina.core.AprLifecycleListener"   SSLEngine="on"   />
  <Listener className="org.apache.catalina.core.JasperListener"   />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"   />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"   />

  <GlobalNamingResources>
    <Resource name="UserDatabase"   auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml"   />
  </GlobalNamingResources>

  <Service name="Catalina"  >

    <Connector port="8080"   protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"   />

    <Connector port="8009"   protocol="AJP/1.3"   redirectPort="8443"   />

    <Engine name="Catalina"   defaultHost="localhost"   jvmRoute="mainworker"  >

       <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"  />

      <Host name="localhost"    appBase="webapps"
            unpackWARs="true"   autoDeploy="true"
            xmlValidation="false"   xmlNamespaceAware="false"  >

      </Host>
    </Engine>
  </Service>
</Server>

Die server.xml fuer den zweiten Tomcat:

<Server port="8006"   shutdown="SHUTDOWN"  >

  <Listener className="org.apache.catalina.core.AprLifecycleListener"   SSLEngine="on"   />
  <Listener className="org.apache.catalina.core.JasperListener"   />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"   />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"   />

  <GlobalNamingResources>
    <Resource name="UserDatabase"   auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml"   />
  </GlobalNamingResources>

  <Service name="Catalina"  >

    <Connector port="8180"   protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8444"   />

    <Connector port="8010"   protocol="AJP/1.3"   redirectPort="8444"   />

    <Engine name="Catalina"   defaultHost="localhost"   jvmRoute="tomcat2"  >

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"  />

      <Host name="localhost"    appBase="webapps"
            unpackWARs="true"   autoDeploy="true"
            xmlValidation="false"   xmlNamespaceAware="false"  >

      </Host>
    </Engine>
  </Service>
</Server>

Nachdem beide server.xml Dateien angepasst und die zwei Tomcats gestartet sind, ist der erste Tomcat unter folgender URL erreichbar:

http://localhost:8080/

Der zweite Tomcat dagegen unter der URL:

http://localhost:8180/

Loadbalancing mit Apache2 + Tomcat 6 + mod_jk

Die Nachfolgende Beschreibung zeigt wie ein Apache2 Webserver als Loadbalancer fuer zwei oder mehr Tomcats zu konfigurieren ist. Die Beschreibung bezieht sich wieder auf einen Linux Debian 4.1.X Server mit 2.6.X Kernel, auf dem ein apache2 Webserver, Tomcat 6.0.16 und Java 5.0 installiert und betriebsbereit ist.

Die folgende Graphik veranschaulicht die Systemarchitektur die aufgebaut werden soll.

34_balancer2

34_balancer2

Wie der Apache2 Webserver mit dem Tomcat ueber das mod_jk Modul verbunden wird, habe ich bereits in einem vorhergehenden Blogeintrag beschrieben. Darauf werde ich hier nicht mehr eingehen.

apache2 + tomcat 6 + mod_jk

Ein Loadbalancer macht nur Sinn wenn mehr als ein Tomcat die gleiche Applikation ausliefert. Der Loadbalancer kann die Last auch zwischen 2 Tomcats verteilen die auf unterschiedlichen Maschinen laufen. Wie zwei oder mehr Tomcats auf der selben Maschine eingerichtet werden, zeigt der folgende Blogeintrag:

N Tomcats auf einer Maschine

Wenn Sie die zwei oberen Blogeintraege gelesen und verstanden haben, dann ist es nur noch ein kleiner Schritt zum Loadbalancer. Als erstes muss die /etc/apache2/workers.properties angepasst werden. Hier ist ein Bespiel:

# Tomcat an Java configuration
#

workers.tomcat_home=/opt/tomcat/
workers.java_home=/opt/java/
ps=/

worker.list=loadbalancer

worker.mainworker.type=ajp13
worker.mainworker.port=8009
worker.mainworker.cachesize=20
worker.mainworker.lbfactor=1
worker.mainworker.host=localhost

worker.tomcat2.type=ajp13
worker.tomcat2.port=8010
worker.tomcat2.cachesize=20
worker.tomcat2.lbfactor=1
worker.tomcat2.host=localhost

worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=mainworker,tomcat2
worker.loadbalancer.method=Session

Die worker.list verweist auf den worker “loadbalancer”, welcher vom Typ “lb” (loadbalancing) ist und auf die zwei worker “mainworker” und “tomcat2″ verweist. Der lbfactor gibt an wieviel Last ein worker uebernehmen soll. Wenn der mainworker einen lbfactor von 5 haette und der “tomcat2″ nur einen von 1, dann wuerde der “mainworker” fuenf mal soviele Anfragen zugewiesen bekommen wie der “tomcat2″.
Eine vollstaendige Dokumentation des workers ist hier zu finden:

http://tomcat.apache.org/connectors-doc/reference/workers.html

Die worker Namen “mainworker” und “tomcat2″ muessen natuerlich in den jeweiligen <tomcat>/conf/server.xml Dateien zu finden sein. Der eine Tomcat muss dafuer folgende Zeile in seiner server.xml enthalten:

<Engine name="Catalina"   defaultHost="localhost"   jvmRoute="tomcat2"  >

und der andere dementsprechend die Zeile:

<Engine name="Catalina"   defaultHost="localhost"   jvmRoute="mainworker"  >

Zum Schluss muss noch in der Datei /etc/apache2/sites-enabled/000-default der JkMount-Eintrag auf die gewuenschte Applikation angepasst werden:

JkMount /testApp loadbalancer

Wenn alles korrekt konfiguriert ist kann die Applikation durch folgende URL augerufen werden:

http://localhost/testApp/

Der Apache Webserver wird die Anfrage an eines der zwei Tomcats weiterleiten. Wenn der eine Tomcat runtergefahren wird, ist die Applikation immer noch auf dem zweiten Tomcat verfuegbar. Der Webserver leitet dann alle Anfragen automatisch an den einzig noch verbliebenen Tomcat, so das der Benutzer davon nichts mitkriegt.
Auf diese Art und Weise koennen Applikation ausfallsicherer gemacht werden.