Verweise

Framework Interceptoren
Validatoren
Typ Konvertierung
Freemarker Handbuch

Startseite

Web Anwendungen mit Struts 2 und Freemarker

Dieses Tutorial richtet sich an Programmierer, die bereits Web Anwendungen (Servlets) in Java programmiert haben und nun den Einsatz von Struts und Freemarker als Framework erwägen. Ich erkläre nur eine kleine Auswahl von Funktionen - gerade so viel, wie man für eine einfache Web-Anwendung benötigt. Der Rest ergibt sich aus den Online Dokumentationen der beiden Produkte.

Sowohl für Struts als auch für Freemarker gibt es bereits jede Menge Anleitungen im Internet. Die meisten Anleitungen von Struts enthalten jedoch nur JSP Beispiele. Ich zeige Ihnen auf dieser Seite anhand eines konkreten Beispiels, wie sie Struts zusammen mit Freemarker einsetzen.

Was ist Freemarker?

Freemarker erzeugt Text Dokumente (z.B. HTML), indem es Vorlagen mit den Werten von Variablen ausfüllt:

    Hallo ${username}, willkommen auf unserer Webseite.

Durch den Einsatz von Freemarker trennen Sie die Programmlogik von der Darstellungsschicht und sie trennen außerdem die Darstellungsschicht vom Web-Design. Die Darstellungsschicht ist Freemarker, sie verwendet Templates, die von einem Web-Designer erstellt wurden und sie verwendet die Ausgabedaten der Programmlogik, die Sie in Java schreiben.

Was ist Struts 2?

Große Web-Anwendungen enthalten typischerweise immer wieder sehr ähnlichen Programmcode für

Struts kombiniert all diese Funktionen in ein Gesamtpaket und gibt dabei Vorgaben, wie diese Kompontenten heissen sollen und wie sie zusammen arbeiten.

Wie wirkt sich Struts auf die Performance aus?

Struts wirkt sich nur unwesentlich auf die Performance aus, darüber braucht man sich keine Gedanken zu machen. Auch die hier vorgestellte Template Engine arbeitet sehr gut und ist kaum zu verbessern. Allerdings würde ich sehr große Dokumente, z.B. lange Listen mit tausenden von Zeilen doch lieber ohne Template Engine erzeugen, weil es schneller geht mit weniger RAM machbar ist. Struts unterstützt diese Vorgehensweise.

Was brauche ich mindestens?

Um eine kleine Struts Anwendung zu entwickeln, brauchen Sie mindestens

Laden Sie sich StrutsTest.zip herunter. Das ist ein Netbeans Projekt, es kann aber auch in allen gängigen IDE's importiert werden. Die folgenden Erklärungen beziehen sich auf dieses Beispiel-Projekt.

Grundgerüst einer Struts Anwendung

Struts Anwendungen bestehen aus mehreren Packages, die jeweils mehrere Aktionen enthalten. Jede Aktion hat eine entsprechende Klasse.

http://server/context-pfad/package/Aktion --> Klasse de.example.package.Aktion

Die Klassen-Namen müssen nicht unbedingt mit den entsprechenden Teilen der URL überein stimmen, aber so ist es einfacher. Letztendlich ist die Zuordnung eine Frage der Konfiguration.

Die Aktions-Klasse hat im einfachsten Fall nur eine einzige Methode execute() welche irgend etwas macht und schließlich einen Ergebniscode zurück liefert. Struts gibt vor, daß der Ergebniscode im Normalfall "success" ist, ein einfacher String.

Nach Ausführung der execute() Methode lädt Struts je nach Ergebnis das ein oder andere Template.

Aktions Klassen nehmen Eingabeparameter (z.B. aus der URL oder aus Formularen) mit Hilfe von setter Methoden an, welche Struts vor der execute() Methode aufruft. Für das Ausfüllen von Templates liefern Aktions Klassen Werte mit Hilfe von getter Methoden. Mehr dazu später.

Die wichtigsten Konfigurationsdateien

In der Datei WEB-INF/web.xml trägt man einen Filter ein:

    <filter>
        <filter-name>struts</filter-name>
        <filter-class>
            org.apache.struts2.dispatcher.FilterDispatcher
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Dieser Filter fängt alle für Struts relevanten URL ab. Struts benötigt kein Servlet, sie dürfen aber eigene Servlets hinzufügen, wenn Sie möchten.

Als Nächstes legt man im Source-Verzeichnis (default Package) eine Datei mit dem Namen struts.xml an. Beim Compilieren wird sie automatisch nach WEB-INF/classes/struts.xml kopiert.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
       "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
       "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.i18n.reload" value="true" />
    <constant name="struts.configuration.xml.reload" value="true" />

    <package name="package1" extends="struts-default" namespace="/package1">

        <action name="ShowFamily" class="de.example.package1.ShowFamily">
            <result name="success" type="freemarker">ShowFamily.ftl</result>
        </action>

    </package>    
</struts>

Der struts.devMode Parameter aktiviert einige Log Meldungen, die während der Entwicklung nützlich sein könnten. Einige Warnungen sind für die Weiterentwicklung von Struts selbst und brauchen von Ihnen nicht beachtet zu werden.z.B.:

     WARNUNG: Could not find property [.freemarker.Request]

Der struts.i18n.reload Parameter aktiviert das automatische Neu-Laden von Sprachdateien, wenn diese verändert werden.

Der struts.configuration.xml.reload Parameter aktiviert das automatische Neu-Laden dieser Konfigurationsdatei, wenn sie verändert wird.

Die nächsten Zeilen bestimmen, wie die Packages der Anwendung heissen und welche Aktionen sich darin befinden. Dort wird auch bestimmt, welche Template-Datei abhängig vom Ergebnis der Aktion geladen werden soll. Das obige Beispiel definiert nur ein Paket mir Name "package1", darin eine Aktion mit Name "ShowFamily", welche wiederum nur ein Ergebnis haben kann, nämlich "success", welches die Anzeige des Templates "ShowFamily.ftl" veranlasst.

Richtige Struts Anwendungen haben aber viele Aktionen. Durch Einsatz von Jokerzeichen kann man die struts.xml vereinfachen. Das folgende Beispiel zeigt, wie man alle Aktionen eines Packages in einem Rutsch den Klassen zuweist. Voraussetzung dafür ist, daß die Klassen genau so heissen, wie der entsprechende Teil in der URL.

        <action name="*" class="de.example.package1.{1}">
            <result name="input" type="freemarker">{1}.ftl</result>
            <result name="success" type="freemarker">{1}.ftl</result>
        </action>

Die Beispielanwendung hat vier Aktionen mit den Namen

Einige Einstellungen für Freemarker können in der Datei freemarker.properties im Source-Verzeichnis (default Package) vorgenommen werden. Beim Compilieren wird sie automatisch nach WEB-INF/classes/freemarker.properties kopiert. Wenn die Datei fehlt, werden Standardwerte angenommen. Fangen Sie erstmal mit den Standardvorgaben an:

template_update_delay=5
template_exception_handler=rethrow
#template_exception_handler=de.example.MyTemplateExceptionHandler

Der template_update_delay Parameter legt fest, wie lange Template Dateien höchstens ge-cached werden, und zwar in Sekunden. Für die Entwicklung ist ein kleiner Wert sinnvoll, in der Produktion wollen Sie sicher einen viel größeren Wert einsetzen, z.B. 3600 für eine Stunde.

Der template_exception_handler=rethrow veranlasst Freemarker dazu, Fehler (Exceptions) beim Ausfüllen der Templates im Web Browser anzuzeigen. In der Produktion möchten Sie ihren Kunden warscheinlich solche Meldungen nicht zumuten, also stellen Sie dort den MyTemplateExceptionHandler ein. Schauen Sie sich den Source Code dieser Klasse an. Unabhängig davon, was Ihr TemplateExceptionHandler macht, wird die Exception in jedem Fall von Freemarker geloggt.

Wo müssen die Templates liegen?

Struts erwartet Templates normalerweise im Hauptverzeichnis der *.war Datei, also dort, wo auch die Unterverzeichnisse WEB-INF und META-INF liegen. Dort wird für jedes Package ein gleichnahmiges Unterverzeichnis erwartet, in dem alle Templates des Packages liegen.

Die Struts Library enthält außerdem noch die Datei org/apache/struts2/dispatcher/error.ftl, die zur Anzeige von Fehlermeldungen verwendet wird. Ich habe diese Datei zu den Templates der Beispielanwendung kopiert, um meinen Schmetterling einfügen zu können.

Wenn Ihre Anwendung mehrsprachig sein soll, können Sie für jedes Template pro Sprache eine Datei anlegen, z.B.

Der Web-Browser liefert bei jedem Request üblicherweise eine Liste von bevorzugten Sprachen mit, die der Benutzer selbst konfigurieren kann. Struts lädt automatisch die entsprechende Datei. Fall keine passende Datei vorhanden ist, oder der Browser keine entsprechende Information liefert, wird die Standard Datei verwendet.

Wenn Sie Templates woanders speichern wollen, müssen Sie einen eigenen Template Loader schreiben. Die Beispielanwenung enthält als Kopiervorlage den MyTemplateLoader. Sie können ihn modifizieren, um Templates z.B. aus einer Datenbank zu laden, oder von einem anderen Webserver (Content Management System). Auf diese Weise sind Templates veränderbar, ohne die Applikation neu ausliefern zu müssen. Lesen Sie sich die Javadoc von freemarker.cache.TemplateLoader durch, es gibt da einen wichtigen Hinweis für den Fall, daß eine Datei nicht gefunden wurde.

Diesen eigenen Template Loader aktivieren Sie, indem Sie einen eigenen FreemarkerManager schreiben (siehe MyFreemarkerManager aus der Beispielanwendung), welcher den neuen Template Loader in der Methode getTemplateLoader() liefert. Den neuen FreemarkerManager wiederum aktivieren Sie durch folgende Zeile in struts.xml:

    <constant name="struts.freemarker.manager.classname"
        value="de.example.MyFreemarkerManager" />

Beschreibung der Aktion ShowFamily

Bildschirmfoto der Aktion ShowFamily

Das Template für die Aktion ShowFamily enthält eine Variable für den Familiennamen ${familyName} und eine Schleife, in der die Familienmitglieder ${familyMembers} einer fiktiven Familie angezeigt werden sollen. Es gibt ein Template in deutsch und eins in englisch, welches in diesem Fall die Standard-sprache ist.

Inhalt der Datei package1/ShowFamily_de.ftl:

<html>
    <body>
         <img src="butterfly.png"><br>

         Die Mitglieder der Famile ${familyName} sind:
         <p>

         <#list familyMembers as member>
             ${member}<br>
         </#list>

         <p>
         <a href="?request_locale=en">english</a>

    </body>
</html>

Dieses Template demonstriert, wie sie die Freemarker Direktive "list" benutzen können, um den Inhalt von Arrays (nämlich die Vornamen) aufzulisten.

Es demonstriert auch, wie sie dem Benutzer das Umschalten der Sprache für seine aktuelle Sitzung ermöglichen.

Schauen Sie sich die Aktions-Klasse ShowFamily.java an:

import com.opensymphony.xwork2.ActionSupport;

public class ShowFamily extends ActionSupport {

    private final String name="Henkel";
    private final String[] members={"Michael", "Lisa", "Saskia", "Meinolf"};

    public String execute()  throws Exception {
        System.out.println("execute()=success");
        return "success";
    }
    
    public String[] getFamilyMembers() {
        System.out.println("getFamilyMembers()="+members);
        return members;
    }

    public String getFamilyName() {
        return name;
    }

}

Jede Aktions Klasse muss ActionSupport erweitern. Ich habe Hinweise gefunden, daß die Angabe "extends ActionSupport" optional sei, doch dann funktionieren z.B. Validatoren nicht mehr (mehr dazu später).

Die Klasse beginnt mit einer String-Konstante für den Familien-Namen, sowie einem String-Array mit den Vornamen. In einer echten Anwendung würden diese Daten natürlich aus einer Datenbank geladen werden.

Die execute() Methode liefert einfach nur das Ergebnis "success" zurück. Eine echte Applikation würde an dieser Stelle die Namen aus einer Datenbank laden und im Fehlerfall das Ergebnis "error" zurück melden. Sie können als Rückgabewert jeden beliebigen String verwenden. Hauptsache, es gibt eine passende Template-Zuweisung im action-Abschnitt der struts.xml.

Die Methoden getFamilyMembers() und getFamilyName() liefern Werte für die Variablen ${familyMembers} und ${familyName} in dem Template. Struts ruft diese Methoden nach execute() auf, wenn das Template befüllt wird.

Die Zuordnung von Template-Variable zu getter Methode ergibt sich automatisch durch deren Namen. Beachten Sie, daß Variablen-Namen in Templates immer mit einem kleinen Buchstaben beginnen, während der erste Buchstabe hinter getXxxxx() immer groß geschrieben wird.

Wenn das Template eine Variable enthält, für die es keine passende getter Methode gibt, wird eine Exception geloggt und im Web Browser angezeigt (es sei denn, sie unterdrücken Fehlermeldungen durch einen eigenen TemplateExceptionHandler).

Beschreibung der Aktion ShowParameter

Bildschirmfoto der Aktion ShowParameter

Bei dieser Aktion sollen zwei URL Parameter innerhalb der Webseite angezeigt werden. Das Template ShowParameter.ftl sieht so aus:

<html>
    <body>
         <img src="butterfly.png"><br>

         Parameter color  = ${color} <br>
         Parameter number = ${number} <br>

    </body>
</html>

Beim Aufruf der Aktion müssen Sie zwei Parameter angeben, wie im Bildschirmfoto.

Verglichen mit dem vorherigen Template ist dieses hier vieleicht schon etwas langweilig. Interessanter ist der Mechanismus, der die URL Parameter in die Aktionsklasse ShowParameter.java überträgt.

import com.opensymphony.xwork2.ActionSupport;

public class ShowParameter extends ActionSupport  {

    private String color=null;
    private Integer number=null;

    public String execute() throws Exception {
        System.out.println("execute()=success");
        return "success";
    }
    
    public void setColor(String s) {
        System.out.println("setColor("+s+")");
        color=s;
    }

    public String getColor() {
        System.out.println("getColor()="+color);
        return color;
    } 

    public void setNumber(Integer i) {
        System.out.println("setNumber("+i+")");
        number=i;
    }

    public Integer getNumber() {
        System.out.println("getNumber()="+number);
        return number;
    }

}

Neben den schon bekannten getter Methoden, hat diese Klasse zwei setter Methoden. Struts ruft für jeden URL Parameter ganz automatisch die passende setter Methode auf. Die Zuordnung ergibt sich einzig und allein aus den Namen der Parameter und Methoden.

Das ist schon genial einfach, aber schauen Sie genauer hin, denn es kommt noch besser. Die Methode setNumber() erwartet einen Integer, URL Parameter sind aber grundsätzlich Strings. Struts konvertiert den Datentyp sogar automatisch. Das klappt mit allen Standard Datentypen (wie Integer, Long, Date, auch java.sql.Date).

Dahinter steckt der ParametersInterceptor.

Struts enthält noch viele andere Interceptoren. Die meisten Interceptoren holen Eingabeparameter aus dem HTTP Request und übergeben sie der Aktionsklasse mittels setter Methoden. Auf diese Weise wird die Daten-Eingabe klar von der Daten-Verarbeitung getrennt.

Der ParametersInterceptor verhält sich etwas anders, als die anderen Interceptoren. Er sucht in der Aktionsklasse nach passenden setter Methoden und ruft diese auf, falls vorhanden. Alle anderen Interceptoren rufen hingegen setter Methoden mit fest definierten Namen auf, sofern die Aktionsklasse das zugehörige Aware-Interface implementiert.

Wenn Sie zum Beispiel bestimmte HTTP Header auslesen wollen, können Sie den ServletConfigInterceptor benutzen, indem ihre Aktionsklasse das ServletRequestAware Interface implementiert. Zum Beispiel so:

import com.opensymphony.xwork2.ActionSupport;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;

public class ShowUserAgent
       extends ActionSupport
       implements ServletRequestAware {

    private String useragent=null;
    
    public String execute()  throws Exception {
        System.out.println("execute()=success");
        return "success";
    }

    public void setServletRequest(HttpServletRequest request) {
        useragent=request.getHeader("user-agent");
    }

    public String getUseragent() {
        System.out.println("getUserAgent()="+useragent);
        return useragent;
    }
    
}

Sauberer ist es jedoch, einen eigenen Interceptor zu schreiben, der den (oder die) gewünschten HTTP Header aus dem HTTP Request holt und mittels setter Methode an die Aktionsklasse übergibt. So trennen Sie die Daten-Eingabe und Daten-Verarbeitung klar voneinander. Spätestens, wenn sie die gleichen Header in vielen Aktionsklassen benötigen, wird der Vorteil sichtbar.

Und damit kommen wir zur nächsten Beispielaktion, die demonstriert, wie das geht.

Beschreibung der Aktion ShowUserAgent

Bildschirmfoto der Aktion ShowUserAgent

Die Aktion ShowUserAgent demonstriert, wie man Textfragmente aus internationalisierten properties-Dateien benutzt und wie man eigene Interceptoren einsetzt.

Das Template package1/ShowUserAgent.ftl existiert nur einmal. Von diesem Template gibt es keine sprach-spezifischen Varianten, stattdessen wird der String "Dein User-Agent ist" aus der Datei ShowUserAgent.properties oder deren Sprachspezifischen Varianten geladen.

<html>
    <body>
         <img src="butterfly.png"><br>

         <@s.text name="your_useragent_is"/> ${useragent}

    </body>
</html>

Inhalt der Datei ShowUserAgent.properties und ShowUserAgent_en.properties:

your_useragent_is=Your User-Agent is

Inhalt der Datei ShowUserAgent_de.properties:

your_useragent_is=Dein User-Agent ist

Achtung: Wenn der Benutzer im Browser eine Sprache eingestellt hat, für die es kein passendes Template gibt, wird das Standard Template verwendet. Bei den Properties wird jedoch eine etwas andere Logik angewendet. Wenn keine passendes Property vorhanden ist, wird die Sprache des Betriebssystems verwendet, unter dem die Applikation läuft. Nur, wenn auch dafür keine spezifische Datei vorhanden ist, wird die Standardsprache verwendet.

Da mein Betriebssystem auf deutsch eingerichtet ist, muß ich somit auch englische Properties Dateien anlegen. Amsonsten würden englische Benutzer meiner Webseiten zwar englische Templates sehen, aber die Meldungen aus den Properties wären deutsch.

Anstelle einer Aktions-spezifischen properties Datei könnte auch eine package.properties verwendet werden, die dann für alle Klassen in diesem Package verwendet werden würde.

Source code der Aktionsklasse:

import com.opensymphony.xwork2.ActionSupport;
import de.example.HeaderAware;


public class ShowUserAgent extends ActionSupport implements HeaderAware {

    private String useragent=null;
    
    public String execute()  throws Exception {
        System.out.println("execute()=success");
        return "success";
    }

    public void setUserAgent(String s) {
        System.out.println("setUserAgent("+s+")");
        useragent=s;
    }

    public String getUseragent() {
        System.out.println("getUserAgent()="+useragent);
        return useragent;
    }

}

Die getter Methode getUseragent() liefert den Wert zurück, der zuvor durch die setter Methode setUseragent() gesetz wurde. An dieser Stelle kommt ein selbst geschriebener Interceptor mit Namen HeaderInterceptor, sowie das entsprechende Interface mit Namen HeaderAware zum Einsatz.

Der Interceptor kann dann mit Hilfe des HeaderAware Interfaces abfragen, ob die aktuelle Aktionsklasse dieses Interface implementiert, woraus sich wiederum ergibt, ob die Aktionsklasse eine setUserAgent() Methode hat. Das geht schneller, als zu testen, ob eine bestimmte setter Methode existiert.

Source code des HeaderAware Interfaces:

public interface HeaderAware {
    public void setUserAgent(String s);
}

Source Code des Interceptors:

import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class HeaderInterceptor implements Interceptor {

    public void destroy() {} // not used

    public void init() {} // not used

    public String intercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        HttpServletRequest request = ServletActionContext.getRequest();
        if (action instanceof HeaderAware) {
            ((HeaderAware)action).setUserAgent(
                request.getHeader("user-agent"));
        }
        return invocation.invoke();
    }

}

Jetzt müssen Sie noch erfahren, wie man Struts mitteilt, daß dieser Interceptor aufgerufen werden soll.

Eine Aktion erfordert typischerweise viele Interceptoren, wobei die meisten davon schon standardmäßig eingebunden sind. Die Standard-Interceptoren sind im "defaultStack" zusammen gefasst. Den können Sie nicht ändern, aber sie können einen neuen Stack erzeugen, welcher den defaultStack beinhaltet.

Dazu dienen die folgenden Zeilen in der Datei struts.xml:

    <package name="package1" extends="struts-default" namespace="/package1">

        <interceptors>
          <interceptor name="HeaderInterceptor"
               class="de.example.HeaderInterceptor"/>

          <interceptor-stack name="myDefaultStack">
            <interceptor-ref name="HeaderInterceptor"/>
            <interceptor-ref name="defaultStack"/>
          </interceptor-stack>

        </interceptors>
        <default-interceptor-ref name="myDefaultStack"/>

        <action ...>
        </action>

    </package> 

Die letzte Zeile (default-interceptor-ref...) teilt Struts mit, daß unser gerade erzeugter "myDefaultStack" nun der neue Standard Interceptor Stack sein soll.

Man könnte den HeaderInterceptor oder den myDefaultStack auch individuellen Aktionen zuweisen, anstatt dem ganzen Package. Das würde so gehen:

    <package name="package1" extends="struts-default" namespace="/package1">

        <action name="ShowUserAgent"
          class="de.example.package1.ShowUserAgent">
            <interceptor-ref name="HeaderInterceptor"/>
            <result name="success" type="freemarker">
                ShowUserAgent.ftl
            </result>
        </action>

    </package>

Beschreibung der Aktion ShowForm

Bildschirmfoto der Aktion ShowForm

Die Aktion ShowForm demonstriert, wie man Eingaben aus einem HTML Formular validiert und entgegen nimmt. Darüber hinaus sehen Sie hier, wie man in Templates Fehlermeldungen für nicht gesetzte Variablen verhindert. Das Template sieht so aus:

<html>
    <body>
         <img src="butterfly.png"><br>

         <form>
             ${fieldErrors.name!""}<br>
             Name <input type="text" name="name"><br>
             ${fieldErrors.datum!""}<br>
             Datum <input type="text" name="datum"><br>
             <input type="submit">
         </form>
         <p>
         name = ${name!"..."}<br>
         datum = ${datum!"..."}<br>
         <p>

         <#list fieldErrors?keys as key>
             <#if key_index == 0>
                 <b>Alle Fehlermeldungen:</b><br>
             </#if>
             Feld ${key}: ${fieldErrors[key]}<br>
         </#list>

    </body>
</html>

Das Formular enthält zwei Eingabefelder, für Name und Datum. Über den Eingabefeldern wird ggf.eine Fehlermeldung angezeigt. Falls keine Fehlermeldung existiert, wird ein Leerstring "" angezeigt. Auf diese Weise verhindert man Fehlermeldungen bezüglich nicht gesetzter Variablen.

Unter dem Formular werden die eingegebenen Werte einfach wieder angezeigt. Falls die Felder nocht nicht ausgefüllt sind, wird an dieser Stelle Ersatzweise "..." angezeigt.

Schließlich werden alle Fehlermeldungen nochmal zusammenhängend aufgelistet, um zu demonstrieren, wie man Hash-Tables auflistet. Hash-Tables muss man nämlich anders behandeln, als Listen und Arrays (wie in der ShowFamily Aktion).

Bei den Fehlermeldungen der Validatoren entspricht der Key des Eintrages dem Feldnamen des Formulars, und der Wert des Eintrages ist die Fehlermeldung:

FieldErrors?keys liefert eine Liste der keys der Hash-Table, welche mit der #list Direktive aufgelistet wird. Der Meldungstext wird dann mit ${fieldErrors[key]} angezeigt.

Innerhalb der Auflistung liefert die Spezial-Variable key_index die Postion des aktuellen Keys, beginnend bei 0. Das Template schreibt nur über die erste Fehlermeldung (also mit Nummer 0) eine Überschrift.

Die Aktionsklasse dazu sieht so aus:

package de.example.package1;

import com.opensymphony.xwork2.ActionSupport;
import java.sql.Date;

public class ShowForm extends ActionSupport  {

    private Date datum=null;
    private String name=null;

    public String execute() throws Exception {
        System.out.println("execute()=success");
        return "success";
    }
    
    public void setName(String s) {
        System.out.println("setName("+s+")");
        name=s;
    }

    public String getName() {
         System.out.println("getName()="+name);
        return name;
    } 

    public void setDatum(Date d) {
        System.out.println("setDatum("+d+")");
        datum=d;
    }

    public Date getDatum() {
        System.out.println("getDatum()="+datum);
        return datum;
    }

}

Die setter Methoden werden von Struts aufgerufen, um die Werte aus den Formularfeldern zu übergeben. Dann ruft Struts die execute() Methode auf, und dann ruft Struts die getter Methoden auf, um das Template auszufüllen.

Obwohl in dem obigen Source Code immer das Ergebnis "success" zurück gemeldet wird, gibt es noch einen zweiten Ergebniscode, nämlich "input". Dieser wird von Struts erzeugt, wenn ein Validator fehlschlägt. Die Beispiel-Anwendung setzt Validatoren ein, um die Eingaben aus dem Formular zu prüfen, bevor die Aktionsklasse aufgerufen wird.

Validatoren für die Aktion ShowForm werden durch die Datei ShowForm-validation.xml definiert:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
        "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>

    <field name="name">
        <field-validator type="requiredstring">
            <message key="nameMissing"/>
        </field-validator>
    </field>

    <field name="datum">
        <field-validator type="required">  
            <message key="dateMissing"/>
        </field-validator>
        <field-validator type="date">
            <param name="min">01.01.1990</param>
            <param name="max">31.12.2099</param>
            <message key="dateRange"/>
        </field-validator>
    </field>
    
</validators>

Das Feld "name" muss ausgefüllt werden und es darf kein Leerstring sein. Falls diese Prüfung fehlschlägt, wird die Meldung "nameMissing" angezeigt.

Das Feld "datum" muss ausgefüllt sein und das Datum muss im Bereich 1.1.1990 - 31.12.2099 liegen. Beachten Sie hierbei, daß die Syntax für min und max der System-Locale entsprechen muss. Auf einem englisch konfiguriertem Betriebssystem müssen Sie das Datum auch Englisch schreiben. Unabhängig davon gibt der Benutzer der Web-Seite das Datum immer in seiner eigenen Sprache ein (entsprechend der Einstellung seines Browsers) und es wird auch entsprechend seiner Sprache formatiert angezeigt. Stellen Sie in Ihrem Browser die bevorzugte Sprache von deutsch auf englisch um, dann sehen Sie den Unterschied.

Die Datei ShowForm.properties sieht so aus:

nameMissing=Missing name
dateMissing=Missing date
dateRange=date not in range ${min} - ${max}
invalid.fieldvalue.datum=Invalid date

Die Datei ShowForm_de.properties sieht so aus:

nameMissing=Sie müssen einen Namen eingeben
dateMissing=Sie müssen ein Datum eingeben
dateRange=Das Datum ist nicht im Bereich ${min} - ${max}
invalid.fieldvalue.datum=Ungültiges Datum

Die Meldung invalid.fieldvalue.datum wird von Struts aufgerufen, wenn die eingegebene Zeichenkette nicht in ein Datum umgewandelt werden kann, also syntaktisch falsch ist. Solche Meldungen gibt es für jede Datentyp-Konvertierung. Wenn Sie keine entsprechende Meldung in der Properties-Datei festlegen, erscheint eine englische Meldung aus Struts.

Nachwort

Wenn Sie denken, sie wüssten jetzt alles über Struts, dann liegen Sie ganz falsch. Aber Sie wissen genug, um eine vorzeigbare Anwendung mit Struts zu erstellen. Für alles Weitere lesen Sie die Online Dokus von Struts und Freemarker.