Ein weiteres Servlet-Beispiel

(Auszug aus "Java und XSLT" von Eric M. Burke)

   

   

In unserem nächsten Beispiel wird das Servlet DOM und XSLT verwenden, um die Webseiten zu erzeugen. Damit erreichen wir unser Ziel der Trennung von Daten und Präsentation, was es uns ermöglicht, die HTML-Ausgabe beliebig anzupassen, ohne den Java-Code ändern zu müssen. Obwohl der Einsatz von XML den Code des kleinen Beispiels komplexer macht, wird der Nutzen die Kosten schnell überwiegen, wenn die Anwendungen komplizierter werden. Dasselbe gilt für den Einsatz von Enterprise JavaBeans. Bei einem einfachen Beispiel scheint die Konfiguration sehr aufwendig, aber mit zunehmender Komplexität der Anwendung werden die Vorteile dieser Architektur schnell deutlich.

Unser Programm besteht aus zwei Webseiten, auf denen der Nutzer persönliche Informationen eingeben kann. Auf der ersten Seite wird nach Name, Telefonnummer und E-Mail-Adresse gefragt, auf der zweiten Seite werden die eingegebenen Daten zur Bestätigung nochmals dargestellt. Die erste Seite prüft die Daten und zwingt den Nutzer, alle benötigten Felder auszufüllen.

Design

Das Ziel dieser kleinen Anwendung ist es, die Nutzung von XSLT in einem Servlet zu demonstrieren. Dabei werden JAXP und DOM benutzt, um dynamisch XML-Daten zu generieren, welche mit XSLT-Stylesheets in HTML-Daten transformiert werden. Das Design ist in der folgenden Abbildung zu sehen.

Design der Anwendung Persönliche Informationen

Abbildung: Design der Anwendung Persönliche Informationen

Wie die Abbildung zeigt, ist PersonalDataServlet eine Unterklasse von HttpServlet. Es überschreibt sowohl doGet( ) als auch doPost( ). Wenn ein Nutzer die Website das erste Mal besucht, führt die HTTP GET-Anfrage dazu, daß die Methode doGet( ) aufgerufen wird, welche ein Formular erzeugt, in dem der Nutzer seine Informationen eingeben kann. Wenn dieser den Senden-Button auf der Webseite betätigt, validiert die Methode doPost( ) die Formularfelder und stellt die Bestätigungsseite dar, falls alles in Ordnung war. Wenn ein oder mehrere Felder nicht ausgefüllt sind, wird die Seite mit einer Fehlermeldung nochmals dargestellt.

Die Klasse PersonalData enthält die Daten, die der Nutzer eingegeben hat, und wird über HttpSession lokalisiert. Jeder Besucher erhält seine eigene Kopie von HttpSession und damit auch von PersonalData. Um diese Daten nach XML zu konvertieren, wird die separate Hilfsklasse PersonalDataXML verwendet.

Obwohl viele Leute den Code für die Erzeugung von XML direkt in eine Methode wie getXML( ) in der Klasse PersonalData plazieren würden, gab es einen triftigen Grund, eine separate Hilfsklasse zu verwenden. Auf diese Weise ist es weit einfacher, eine neue Technologie wie JDOM zu unterstützen, ohne bestehenden Code zu ändern. In diesem Fall könnte man einfach die neue Klasse PersonalDataJDOM hinzufügen. So bleibt die Klasse PersonalData schlank, da sie lediglich die Daten zu speichern hat.

Die erste Webseite ist in folgender Abbildung zu sehen. Zwingend benötigte Felder werden mit einem Stern (*) markiert. Der HTML-Code wird mit editPersonalData.xslt erzeugt.

Formular Persönliche Informationen ohne Einträge

Abbildung: Formular Persönliche Informationen ohne Einträge

Die folgende Abbildung zeigt denselben Bildschirm, nachdem der Nutzer den Senden-Button betätigt hat. Wenn Daten fehlen, wird eine rote Fehlermeldung ausgegeben, und die benötigten Felder werden fett gedruckt. Jede andere Meldung wird auch in Rot ausgegeben. Diese Seite wird ebenfalls mit editPersonalData.xslt erzeugt.

Formular Persönliche Informationen mit Fehlermeldungen

Abbildung: Formular Persönliche Informationen mit Fehlermeldungen

Bestätigungsseite

Abbildung: Bestätigungsseite

Nachdem alle Daten korrekt eingegeben wurden, wird die Bestätigung aus der Abbildung Bestätiungsseite dargestellt. Anders als die bisherigen Seiten wird diese mit confirmPersonalData.xslt erzeugt. Um die Darstellung einer dieser Seiten zu ändern, muß nur das entsprechende Stylesheet bearbeitet werden.

XML und Stylesheets

Die Strukturierung Ihrer XML-Daten kann erheblichen Einfluß darauf haben, wie die Ausgaben Ihrer Webanwendung konfiguriert werden können. In unserem Beispiel wird dieselbe XML-Datei für alle Webseiten verwendet, wie man im folgenden Beispiel sieht.

Beispiel: Beispielhafte XML-Ausgabe

<?xml version="1.0" encoding="UTF-8"?>
<page>
  <!-- Das nächste Element ist optional: -->
  <!-- <requiredFieldsMissing/> -->
  <personalData>
    <firstName required="true">Eric</firstName>
    <lastName required="true">Burke</lastName>
    <daytimePhone required="true">0636-1234567</daytimePhone>
    <eveningPhone/>
    <email required="true">burke_e@yahoo.com</email>
  </personalData>
</page>

Wie man sieht, ist der XML-Code ziemlich schlank gehalten. Es wird keine der Beschriftungen wie "Vorname:" hier spezifiziert. Dies ist die Sache des XSLT-Stylesheets. Selbst der Stern (*) wird ausgelassen, was dem XSLT-Autor die volle Kontrolle über das Aussehen der Webseite gibt. Die XML-Datei wird nur für Daten verwendet, während im Stylesheet Graphiken eingebunden, die Ausgaben in anderen Sprachen gemacht oder Teile der Seite, wie Kopf- und Fußzeilen, aus anderen Quellen eingebunden werden können.

Das Element <requiredFieldsMissing/> ist optional. Wenn es weggelassen wird, erzeugt das XSLT-Stylesheet keine Fehlermeldungen bei fehlenden Einträgen. Das ist vor allem dann nützlich, wenn die Seite das erste Mal mit leeren Feldern dargestellt wird, wobei natürlich keine Fehlermeldungen ausgegeben werden sollen. In unserem Servlet wird die Methode doGet( ) aufgerufen, wenn der Nutzer das erste Mal eine Seite anfordert, also werden die Fehlermeldungen hier deaktiviert.

Es soll noch erwähnt werden, daß diese XML-Datei nur für Dokumentations- und Testzwecke benötigt wird. In der Anwendungsumgebung werden die XML-Daten von den Klassen PersonalData und PersonalDataXML dynamisch erzeugt. Sie können zunächst weiter mit der statischen XML-Datei arbeiten, um mit Änderungen am XSLT-Code zu experimentieren.

Das folgende Beispiel zeigt das XSLT-Stylesheet, welches das HTML-Formular erzeugt. Typischerweise sind die Stylesheets wesentlich länger als die XML-Daten selbst. Ein einfacherer Ansatz der Servlet-Entwicklung würde diese Logik im Source Code des Servlets als eine Reihe von println( )-Anweisungen unterbringen und so das Servlet viel größer und unflexibler machen.

Beispiel: editPersonalData.xslt

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output method="xml" indent="yes" encoding="ISO-8859-1" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
    <!--
        ***************************************************************
        ** Top-Level-Template. Erzeugt das Gerüst der XHTML-Seite
        ************************************************************ -->
    <xsl:template match="/">
        <html>
            <head><title>Persönliche Informationen</title></head>
            <body>
                <xsl:apply-templates select="page/personalData"/>
            </body>
        </html>
    </xsl:template>
    <!--
        ***************************************************************
        ** Template für das Element <personalData>.
        ************************************************************ -->
    <xsl:template match="personalData">
        <h3>Persönliche Informationen</h3>
        <xsl:if test="../requiredFieldsMissing">
            <div style="color: red; font-size: larger;">Fehler: Ein oder mehrere Felder wurden nicht ausgefüllt.</div>
        </xsl:if>
        <i>Felder mit einem (*) müssen ausgefüllt werden.</i>
        <form action="/chap6/personalData" method="post">
            <table border="0" cellpadding="2" cellspacing="0">
                <!-- Wähle alle direkten Nachkommen, wie firstName, lastName, daytimePhone usw... -->
                <xsl:apply-templates/>
            </table>
            <div align="center">
                <hr/>
                <input type="submit" name="submitBtn" value="Senden"/>
            </div>
        </form>
    </xsl:template>
    <!--
        ***************************************************************
        ** Gebe für jedes Feld eine neue Zeile in der Tabelle aus.
        ************************************************************ -->
    <xsl:template match="firstName|lastName|daytimePhone|eveningPhone|email">
        <tr>
            <xsl:if test="@required='true' and ../../requiredFieldsMissing and .=''">
                <xsl:attribute name="style">
                    <xsl:text>color:red; font-weight:bold;</xsl:text>
                </xsl:attribute>
            </xsl:if>
            <td>
                <xsl:choose>
                    <xsl:when test="name( )='firstName'">Vorname:</xsl:when>
                    <xsl:when test="name( )='lastName'">Nachname:</xsl:when>
                    <xsl:when test="name( )='daytimePhone'">Telefon dienstlich:</xsl:when>
                    <xsl:when test="name( )='eveningPhone'">Telefon privat:</xsl:when>
                    <xsl:when test="name( )='email'">E-Mail:</xsl:when>
                </xsl:choose>
            </td>
            <td>
                <input type="text" name="{name( )}" value="{.}"/>
            </td>
            <td>
                <xsl:if test="@required='true'">*</xsl:if>
            </td>
        </tr>
    </xsl:template>
</xsl:stylesheet>

Die ersten drei Zeilen von editPersonalData.xslt enthalten den Code, der den XSLT-Prozessor veranlaßt, unter Nutzung der Transitional-DTD XHTML 1.0 zu erzeugen. Unser Ergebnis verwendet das Element <i>...</i>, so daß wir nicht die strikte DTD für XHTML verwenden können. Das Top-Level-Template paßt wie gewöhnlich auf das Muster "/" und gibt den Rahmen des XHTML-Dokuments aus.

Das nächste Template paßt auf das Element <personalData> und erzeugt eine Überschrift, gefolgt von einer optionalen Fehlermeldung. Über das Element <xsl:if> wird festgestellt, ob das Element <requiredFieldsMissing/> vorhanden ist, und gegebenenfalls wird die Fehlermeldung erzeugt.

<xsl:template match="personalData">
    <h3>Persönliche Informationen</h3>
    <xsl:if test="../requiredFieldsMissing">
        <div style="color: red; font-size: larger;">Fehler: Ein oder mehrere Felder wurden nicht ausgefüllt.</div>
    </xsl:if>

Dieses Template erzeugt schließlich das <form>-Element, welches auch HTTP POST als Submit-Typ festlegt. Das Attribut action gibt an, daß das Formular die Daten an unser Servlet schicken wird. Die Aktion (um die Aktion des Formulars nicht fest im XSLT-Stylesheet zu verankern, kann sie als Stylesheet-Parameter übergeben werden) des Formulars entspricht dem URL-Muster, das wir im Anwendungsdeskriptor spezifizieren:

<i>Felder mit einem (*) müssen ausgefüllt werden.</i>
<form action="/chap6/personalData" method="post">

Schließlich erzeugt das Template eine Tabelle, so daß alle Überschriften und Textfelder ordentlich ausgerichtet sind. Wie in den vorhergehenden Beispielen, erzeugt ein Template die Tabelle, während ein anderes die einzelnen Zeilen erzeugt.

<table border="0" cellpadding="2" cellspacing="0">
    <!-- Wähle alle direkten Nachkommen, wie firstName, lastName, daytimePhone usw... -->
    <xsl:apply-templates/>
</table>
<div align="center">
    <hr/>
    <input type="submit" name="submitBtn" value="Senden"/>
</div>
</form>
</xsl:template>

Da diese Instanz von <xsl:apply-templates/> das select-Attribut nicht verwendet, werden alle Kind-Elemente ausgewählt. Das nächste Template paßt auf alle möglichen Elementtypen und wird einmal für jedes Auftreten von <firstName>, <lastName> usw. instantiiert:

<xsl:template match="firstName|lastName|daytimePhone|eveningPhone|email">

Dieses Template erzeugt ein <tr>-Element. Wenn das entsprechende Element das Attribut required="true" besitzt, die XML-Daten ein <requiredFieldsMissing/> enthalten und der Wert dieses Elements ein leerer String ist, dann wird die Ausgabe fett und rot dargestellt. Das ist ein Hinweis für den Nutzer, daß ein benötigtes Feld ausgelassen wurde. Die Schriftbreite und -farbe werden als style-Attribute des <tr>-Elements wie folgt festgelegt:

<tr>
  <xsl:if test="@required='true' and ../../requiredFieldsMissing and .=''">
    <xsl:attribute name="style">
      <xsl:text>color:red; font-weight:bold;</xsl:text>
    </xsl:attribute>
  </xsl:if>

Das Template erzeugt nun das erste <td>-Element, das die Beschriftung des aktuellen Feldes enthält. Leider besitzt XSLT für solche Situationen keinen Lookup-Mechanismus, aber mit <xsl:choose> läßt sich das Problem lösen:

<td>
    <xsl:choose>
        <xsl:when test="name( )='firstName'">Vorname:</xsl:when>
        <xsl:when test="name( )='lastName'">Nachname:</xsl:when>
        <xsl:when test="name( )='daytimePhone'">Telefon dienstlich:</xsl:when>
        <xsl:when test="name( )='eveningPhone'">Telefon privat:</xsl:when>
        <xsl:when test="name( )='email'">E-Mail:</xsl:when>
    </xsl:choose>
</td>

Das ist immer noch besser, als die Beschriftungen fest in den XML-Code oder das Servlet zu programmieren, denn wir können das Stylesheet ändern, ohne etwas neu kompilieren zu müssen. Man kann z.B. die Beschriftungen in eine andere Sprache übersetzen, ohne daß der Java-Code betroffen wäre, was Webdesignern eine große Flexibilität bietet.

Die nächste Spalte der Tabelle enthält das Eingabefeld:

<td>
   <input type="text" name="{name( )}" value="{.}"/>
</td>

In den XHTML-Ausgaben sieht das Ergebnis wie folgt aus: <input type="text" name="firstName" value="Eric"/>. Schließlich wird in der letzten Spalte der Stern dargestellt, falls das Attribut required="true" ist:

    <td>
      <xsl:if test="@required='true'">*</xsl:if>
    </td>
  </tr>
</xsl:template>

Das nächste Stylesheet, confirmPersonalData.xslt, ist im folgenden Beispiel zu sehen. Es ist kürzer, denn es zeigt lediglich eine Zusammenfassung der Nutzereingaben der vorherigen Seite. Da es weder Fehlermeldungen noch ein HTML-Formular ausgeben muß, aber die gleiche Grundstruktur wie editPersonalData.xslt hat, ist eine detaillierte Beschreibung nicht nötig.

Beispiel: confirmPersonalData.xslt

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output method="xml" indent="yes" encoding="ISO-8859-1" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
    <!--
     ***************************************************************
     ** Top-Level-Template. Erzeugt das Gerüst der XHTML-Seite.
     ************************************************************ -->
    <xsl:template match="/">
        <html>
            <head>
                <title>Persönliche Daten - Zusammenfassung</title>
            </head>
            <body>
                <xsl:apply-templates select="page/personalData"/>
            </body>
        </html>
    </xsl:template>
    <!--
     ***************************************************************
     ** Template für das Element <personalData>.
     ************************************************************ -->
    <xsl:template match="personalData">
        <h2>Vielen Dank!</h2>
        <h3>Ihre Informationen...</h3>
        <table border="0" cellpadding="2" cellspacing="0">
            <!-- Wähle alle direkten Nachkommen wie firstName, lastName, daytimePhone usw... -->
            <xsl:apply-templates/>
        </table>
        <p><a href="/chap6/personalData">Hier klicken, um diese Informationen zu ändern...</a></p>
    </xsl:template>
    <!--
     ***************************************************************
     ** Gebe für jedes Feld eine neue Zeile in der Tabelle aus.
     ************************************************************ -->
    <xsl:template match="firstName|lastName|daytimePhone|eveningPhone|email">
        <tr>
            <td>
                <xsl:choose>
                    <xsl:when test="name( )='firstName'">Vorname:</xsl:when>
                    <xsl:when test="name( )='lastName'">Nachname:</xsl:when>
                    <xsl:when test="name( )='daytimePhone'">Telefon dienstlich:</xsl:when>
                    <xsl:when test="name( )='eveningPhone'">Telefon privat:</xsl:when>
                    <xsl:when test="name( )='email'">E-Mail:</xsl:when>
                </xsl:choose>
            </td>
            <td>
                <b><xsl:value-of select="."/></b>
            </td>
        </tr>
    </xsl:template>
</xsl:stylesheet>

Wahl des Designs

Die beiden Stylesheets, editPersonalData.xslt und confirmPersonalData.xslt, haben viel gemeinsam. Um die Dinge einfach zu halten, wurden sie als unabhängige Stylesheets implementiert. Das ist natürlich nur eine von mehreren Möglichkeiten. Man hätte z. B. nach gemeinsamen Funktionalitäten suchen, diese auslagern und mit <xsl:import> oder <xsl:include> einbinden können. Dieser Ansatz hätte hier jedoch nicht funktioniert, da die beiden Stylesheets zwar ähnlich strukturiert sind, die Templates aber unterschiedliche Ausgaben produzieren. Wenn eine Webpräsenz jedoch komplexer wird, stößt man häufig auf gemeinsame Designelemente, wie Navigationsleisten, die nicht an mehreren Plätzen gespeichert werden sollten.

Ein anderer Ansatz wäre es, die beiden Stylesheets in eines zusammenzufassen und mit einem Parameter festzulegen, ob es im Modus »ändern« oder »bestätigen« arbeiten soll. In diesem Fall würde das Servlet den Parameter über die Klasse Transformer von JAXP und das Element <xsl:param> übergeben. Im Stylesheet selbst müßte man eine Menge von <xsl:choose>- bzw. <xsl:if>-Elementen verwenden, um die Ausgabe abhängig vom Parameter zu kontrollieren. Der JAXP-Code würde ungefähr so aussehen:

javax.xml.transform.Transformer trans = ...
trans.setParameter("personalDataMode", "edit");

Obwohl dieser Ansatz seine Berechtigung hat, macht er für dieses spezielle Beispiel keinen Sinn, da jedes Template eine andere Ausgabe liefert. Es hätte in einer komplizierteren Lösung resultiert, als einfach zwei getrennte Stylesheets zu schreiben. Hat man aber mehrere fast identische Webseiten, die sich nur geringfügig unterscheiden, ist die Verwendung eines Stylesheet-Parameters die elegantere Lösung, denn man muß nur die wenigen unterschiedlichen Abschnitte bedingt abarbeiten.

Quellcode

Das erste Stück Quellcode, das wir uns anschauen wollen, wird im folgenden Beispiel gezeigt. Die Klasse PersonalData ist lediglich ein Datenspeicher und enthält keinerlei XML- oder Datenbank-Code. Indem Sie einfache Klassen wie diese schreiben, können Sie schnell eigenständige Tests entwerfen, die jede Funktionseinheit auf Korrektheit prüfen. Es wäre sehr schwer, diesen Code außerhalb der Umgebung eines Browsers zu testen, wenn er Teil des Servlets wäre.

Beispiel: PersonalData.java

package chap6;

/**
 * Eine Hilfsklasse, die persönliche Informationen speichert. Sie
 * beinhaltet keinen Code zur Erzeugung von XML. Diese Klasse garantiert,
 * daß ihre Daten nicht leer sind und keinen unnötigen Whitespace
 * enthalten.
 */
public class PersonalData {
  private String firstName;
  private String lastName;
  private String daytimePhone;
  private String eveningPhone;
  private String email;
  
  public PersonalData( ) {
      this("", "", "", "", "");
  }
  
  public PersonalData(String firstName, String lastName,
        String daytimePhone, String eveningPhone, String email) {
      this.firstName = cleanup(firstName);
      this.lastName = cleanup(lastName);
      this.daytimePhone = cleanup(daytimePhone);
      this.eveningPhone = cleanup(eveningPhone);
      this.email = cleanup(email);
  }
  
  /**
   * <code>eveningPhone</code> ist als einziges Feld optional.
   *
   * @return true, wenn alle benötigten Felder ausgefüllt sind.
   */
  public boolean isValid( ) {
    return this.firstName.length( ) > 0
           && this.lastName.length( ) > 0
           && this.daytimePhone.length( ) > 0
           && this.email.length( ) > 0;
  }
  
  public void setFirstName(String firstName) {
     this.firstName = cleanup(firstName);
  }
  
  public void setLastName(String lastName) {
     this.lastName = cleanup(lastName);
  }
  
  public void setDaytimePhone(String daytimePhone) {
     this.daytimePhone = cleanup(daytimePhone);
  }
  
  public void setEveningPhone(String eveningPhone) {
     this.eveningPhone = cleanup(eveningPhone);
  }
  
  public void setEmail(String email) {
     this.email = cleanup(email);
  }
  
  public String getFirstName( ) { return this.firstName; }
  public String getLastName( ) { return this.lastName; }
  public String getDaytimePhone( ) { return this.daytimePhone; }
  public String getEveningPhone( ) { return this.eveningPhone; }
  public String getEmail( ) { return this.email; }
  
  /**
   * Säubere den String-Parameter, indem null durch den leeren
   * String ersetzt bzw. unnötiger Whitespace gestrichen wird.
   */
  private static String cleanup(String str) {
    return (str != null) ? str.trim( ) : "";
  }
}

Obwohl die Klasse PersonalData nur ein Datenspeicher ist, kann sie doch auch einfache Validierungsaufgaben übernehmen. Der Standardkonstruktor initialisiert alle Felder mit Werten ungleich null:

public PersonalData( ) {
  this("", "", "", "", "");
  }

Darüber hinaus benutzen die set-Methoden die private Methode cleanup( ):

private static String cleanup(String str) {
     return (str != null) ? str.trim( ) : "";
}

Instanzen dieser Klasse verhindern also null-Referenzen und Whitespace, so daß dies weder im Servlet noch in den XML-generierenden Klassen nötig ist. Whitespace zu entfernen ist besonders deshalb wichtig, weil ein Nutzer sonst die Validierungsregeln einfach umgehen könnte, indem er in einem benötigten Feld die Leertaste betätigt. Außerdem enthält die Klasse PersonalData eine explizite Validierungsmethode, die prüft, ob die benötigten Felder eingegeben wurden:

public boolean isValid( ) {
    return this.firstName.length( ) > 0
         && this.lastName.length( ) > 0
         && this.daytimePhone.length( ) > 0
         && this.email.length( ) > 0;
}

Das einzige Feld, das nicht ausgefüllt werden muß, ist eveningPhone, also wird es hier nicht geprüft. Indem wir diese Methode in die Klasse aufnehmen, werden die Aufgaben des Servlets weiter eingeschränkt.

Im folgenden Beispiel wird die nächste Klasse PersonalDataXML vorgestellt. Sie konvertiert Objekte des Typs PersonalData in DOM-Document-Objekte. Indem wir nicht in eine XML-Textdatei, sondern nach DOM konvertieren, müssen wir nicht das reine XML in den XSLT-Prozessor eingeben. Statt dessen benutzen wir die Klasse javax.xml.transform.DOMSource, um direkt einen DOM-Baum zu übergeben.

Beispiel: PersonalDataXML.java

package chap6;

import javax.xml.parsers.*;
import org.w3c.dom.*;

/**
 * Konvertiert ein Objekt von PersonalData mit DOM in eine XML-Repräsentation.
 */
public class PersonalDataXML {
  
  /**
   * @param personalData soll nach XML konvertiert werden.
   * @param includeErrors falls true, wird ein zusätzliches Feld in den
   * XML-Code eingebaut, das den Browser veranlaßt, den Nutzer über
   * unausgefüllte Einträge zu benachrichtigen.
   * @return ein DOM-Document, das die Webseite enthält.
   */
  public Document produceDOMDocument(PersonalData personalData,
    boolean includeErrors) throws ParserConfigurationException {
      
    // verwende JAXP von Sun, um das DOM-Document zu erzeugen
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance( );
    DocumentBuilder docBuilder = dbf.newDocumentBuilder( );
    Document doc = docBuilder.newDocument( );
      
    // erzeuge die Wurzel des Dokuments <page>
    Element pageElem = doc.createElement("page");
    doc.appendChild(pageElem);
      
    // falls verlangt, erzeuge das Element <requiredFieldsMissing/>
    if (includeErrors && !personalData.isValid( )) {
      pageElem.appendChild(doc.createElement(
        "requiredFieldsMissing"));
    }
      
    Element personalDataElem = doc.createElement("personalData");
    pageElem.appendChild(personalDataElem);
      
    // verwende eine private Hilfsfunktion, um den aufwendigen
    // Code von DOM zu umgehen
    addElem(doc, personalDataElem, "firstName",
      personalData.getFirstName( ), true);
    addElem(doc, personalDataElem, "lastName",
      personalData.getLastName( ), true);
    addElem(doc, personalDataElem, "daytimePhone",
      personalData.getDaytimePhone( ), true);
    addElem(doc, personalDataElem, "eveningPhone",
      personalData.getEveningPhone( ), false);
    addElem(doc, personalDataElem, "email",
      personalData.getEmail( ), true);
  
    return doc;
  }
  /**
   * Eine Hilfsmethode, die die Klasse vereinfacht.
   *
   * @param doc: das DOM-Document, wird als Factory zum
   * Erzeugen der Elemente verwendet.
   * @param parent: das DOM-Element, das Elternelement des zugefügten Elementes wird.
   * @param elemName: der Name des zu erzeugenden XML-Elementes.
   * @param elemValue: der Textinhalt des neuen XML-Elementes.
   * @param required: falls true, wird das Attribut 'required="true"' eingefügt.
   */
  private void addElem(Document doc, Element parent, String elemName,
      String elemValue, boolean required) {
    Element elem = doc.createElement(elemName);
    elem.appendChild(doc.createTextNode(elemValue));
    if (required) {
      elem.setAttribute("required", "true");
    }
    parent.appendChild(elem);
  }
}

Der Code beginnt mit zwei import-Anweisungen. Das Paket javax.xml.parsers enthält das JAXP-Interface, und das Paket org.w3c.dom enthält die Standard-DOM-Interfaces und Klassen:

import javax.xml.parsers.*;
import org.w3c.dom.*;

Der wichtigste Teil dieser Klasse ist ihre öffentliche API, mit der die Konvertierung eines PersonalData-Objektes in ein DOM-Document-Objekt durchgeführt wird:

public Document produceDOMDocument(PersonalData personalData,
  boolean includeErrors) throws ParserConfigurationException {

Der Parameter includeErrors gibt an, ob das Element <requiredFieldsMissing/> in das Ergebnis der Operation aufgenommen werden soll. Wenn diese Klasse eine ParserConfigurationException auslöst, liegt das wahrscheinlich an einem falschen CLASSPATH. Das kommt häufiger vor, wenn noch eine ältere Version von JAXP vorhanden ist.

Wenn man JAXP benutzt, braucht es einige Zeilen Code, um die korrekte Implementierung der abstrakten Klasse DocumentBuilder zu erhalten. Durch die Verwendung der Factory-Klasse wird unser Code von herstellerspezifischen DOM-Implementierungen gekapselt:

// verwende JAXP von SUN, um das DOM-Dokument zu erzeugen
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance( );
DocumentBuilder docBuilder = dbf.newDocumentBuilder( );
Document doc = docBuilder.newDocument( );

Sobald das doc-Objekt erzeugt wurde, wird es verwendet, um alle restlichen Elemente der XML-Daten zu erzeugen. Zuerst wird das Element <page> erzeugt:

// erzeuge die Wurzel des Dokumentes <page>
Element pageElem = doc.createElement("page");
doc.appendChild(pageElem);

Das Element <page> wird, da es das Wurzelelement ist, als einziges direkt zum Dokument hinzugefügt. Alle restlichen Elemente werden als Kinder oder Nachkommen von <page> eingefügt. Trotzdem muß das doc-Objekt als Factory zum Erzeugen der restlichen Elemente verwendet werden:

// falls verlangt, erzeuge das Element <requiredFieldsMissing/>
if (includeErrors && !personalData.isValid( )) {
  pageElem.appendChild(doc.createElement(
    "requiredFieldsMissing"));
}

Da DOM recht aufwendig sein kann, werden die Nachkommen von <personalData> in einer Hilfsmethode namens addElem( ) erzeugt:

Element personalDataElem = doc.createElement("personalData");
pageElem.appendChild(personalDataElem);

// verwende eine private Hilfsfunktion, um den aufwendigen
// Code von DOM zu umgehen
addElem(doc, personalDataElem, "firstName",
  personalData.getFirstName( ), true);
...

Das Beispiel PersonalDataXML.java zeigt die komplette Implementierung der Methode addElem( ). Eine Beispielausgabe könnte wie folgt aussehen:

<firstName required="true">Eric</firstName>

In folgenden Beispiel wird schließlich PersonalDataServlet.java vorgestellt. Es ist ein einfacher Weg zur Entwicklung von Servlets, gut geeignet für kleine Programme wie dieses, allerdings mit einigen Erweiterungsproblemen, die wir noch in diesem Kapitel besprechen. Wir haben die Erzeugung von HTML und XML aus dem Servlet entfernt, es regelt aber immer noch die einkommenden Anfragen von Browsern. Mit zunehmender Größe Ihrer Applikation wird der Code entsprechend aufwendiger.

Beispiel: PersonalDataServlet.java

package chap6;

import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

/**
 * Ein Beispiel-Servlet, das zwei Seiten erzeugt. Auf der ersten Seite wird der
 * Nutzer aufgefordert, seine "persönlichen Informationen", wie Name, Telefonnummer
 * und E-Mail, einzugeben. Auf der zweiten Seite wird eine Bestätigung dieser
 * Information dargestellt. XSLT wird für die Erzeugung des HTML-Codes verwendet,
 * so daß dieses Servlet kein spezielles Aussehen erzwingt.
 */
public class PersonalDataServlet extends HttpServlet {
  private PersonalDataXML personalDataXML = new PersonalDataXML( );
  private Templates editTemplates;
  private Templates thanksTemplates;
  
  /**
   * Einmalige Initialisierung des Servlets.
   */
  
  public void init( ) throws UnavailableException {
    TransformerFactory transFact = TransformerFactory.newInstance( );
    String curName = null;
    try {
      curName = "/WEB-INF/xslt/editPersonalData.xslt";
      URL xsltURL = getServletContext( ).getResource(curName);
      String xsltSystemID = xsltURL.toExternalForm( );
      this.editTemplates = transFact.newTemplates(
        new StreamSource(xsltSystemID));
      
      curName = "/WEB-INF/xslt/confirmPersonalData.xslt";
      xsltURL = getServletContext( ).getResource(curName);
      xsltSystemID = xsltURL.toExternalForm( );
      this.thanksTemplates = transFact.newTemplates(
        new StreamSource(xsltSystemID));
    } catch (TransformerConfigurationException tce) {
      log("Stylesheet kann nicht kompiliert werden", tce);
      throw new UnavailableException("Stylesheet kann nicht kompiliert werden");
    } catch (MalformedURLException mue) {
      log("kann XSLT-Datei nicht finden: " + curName);
      throw new UnavailableException(
        "kann XSLT-Datei nicht finden: " + curName);
    }
  }
  
  /**
   * Bearbeitet HTTP GET-Anfragen, z.B. wenn der Nutzer eine
   * URL eingibt oder einen Link betätigt.
   */
  
  protected void doGet(HttpServletRequest request,
      HttpServletResponse response) throws IOException,
      ServletException {
    PersonalData personalData = getPersonalData(request);
    // der dritte Parameter 'false' bewirkt, daß beim Anzeigen
    // der Seite keine Fehlermeldungen ausgegeben werden.
    showPage(response, personalData, false, this.editTemplates);
  }
  /**
   * Bearbeitet HTTP POST-Anfragen, z.B. wenn der Nutzer den
   * Senden-Button betätigt, um seine persönlichen Daten abzuschicken.
   */
  protected void doPost(HttpServletRequest request,
      HttpServletResponse response) throws IOException,
      ServletException {
        
    // Hole das Objekt personalData und beschreibe es mit den Informationen,
    // die der Nutzer übermittelt hat.
    PersonalData pd = getPersonalData(request);
    pd.setFirstName(request.getParameter("firstName"));
    pd.setLastName(request.getParameter("lastName"));
    pd.setDaytimePhone(request.getParameter("daytimePhone"));
    pd.setEveningPhone(request.getParameter("eveningPhone"));
    pd.setEmail(request.getParameter("email"));
        
    if (!pd.isValid( )) {
      // zeige die Seite 'Ändern' mit einer Fehlermeldung
      showPage(response, pd, true, this.editTemplates);
    } else {
      // zeige die Seite 'Bestätigung'
      showPage(response, pd, false, this.thanksTemplates);
    }
  }
  /**
   * Eine Hilfsmethode, die die persönlichen Daten als HTML an den Browser
   * des Clients schickt. Dabei wird ein XSLT-Stylesheet auf den
   * DOM-Baum angewendet.
   */
  private void showPage(HttpServletResponse response,
      PersonalData personalData, boolean includeErrors,
      Templates stylesheet) throws IOException, ServletException {
    try {
      org.w3c.dom.Document domDoc =
        this.personalDataXML.produceDOMDocument(
        personalData, includeErrors);
      
      Transformer trans = stylesheet.newTransformer( );
      
      response.setContentType("text/html");
      PrintWriter writer = response.getWriter( );
      
      trans.transform(new DOMSource(domDoc), new StreamResult(writer));
    } catch (Exception ex) {
     showErrorPage(response, ex);
    }
  }
  /**
   * Falls ein Fehler auftritt, kann diese Methode den Stack-Trace im Browser darstellen.
   */
  private void showErrorPage(HttpServletResponse response,
      Throwable throwable) throws IOException {
    PrintWriter pw = response.getWriter( );
    pw.println("<html><body><h1>Ein Fehler ist aufgetreten</h1><pre>");
    throwable.printStackTrace(pw);
    pw.println("</pre></body></html>");
  }
  /**
   * Eine Hilfsmethode, die das Objekt PersonalData aus der 
   * HttpSession ermittelt.
   */
  private PersonalData getPersonalData(HttpServletRequest request) {
    HttpSession session = request.getSession(true);
    PersonalData pd = (PersonalData) session.getAttribute(
      "chap6.PersonalData");
    if (pd == null) {
        pd = new PersonalData( );
        session.setAttribute("chap6.PersonalData", pd);
    }
    return pd;
  }
}

Unser Servlet beginnt mit einer langen Liste von import-Anweisungen, da es sowohl die Servlet-API als auch das JAXP-Paket verwendet. Das Servlet selbst ist wie gewöhnlich eine Unterklasse von HttpServlet und besitzt drei private Membervariablen:

public class PersonalDataServlet extends HttpServlet {
 private PersonalDataXML personalDataXML = new PersonalDataXML( );
 private Templates editTemplates;
 private Templates thanksTemplates;

Da viele Clients eine Instanz unseres Servlets gleichzeitig benutzen, ist es wahrscheinlich, daß auf diese Daten nebenläufig zugegriffen wird. Deshalb muß sichergestellt werden, daß jede dieser Variablen in einer Multithread-Umgebung sicher ist. Instanzen von PersonalDataXML können in Multithread-Umgebungen problemlos verwendet werden, weil sie zustandslos sind, d.h., sie enthalten keine Daten, die nebenläufig geändert werden könnten. Die Instanzen von Templates sind kompilierte Repräsentationen der beiden Stylesheets und sind aufgrund ihres Designs auch sicher im Umgang mit Threads.

Aus den Kommentaren wird deutlich, daß die Methode init( ) eine einmalige Initialisierung des Servlets vornimmt. Der Servlet-Container ruft diese Methode auf, bevor das Servlet den ersten Client-Request entgegennimmt. Es wird sichergestellt, daß die Methode init( ) komplett ausgeführt wird, bevor ein anderer Thread Zugriff auf das Servlet erhält. Bei Fehlern während der Initialisierung wird eine Instanz von UnavailableException erzeugt:

public void init( ) throws UnavailableException {
  TransformerFactory transFact = TransformerFactory.newInstance( );
String curName = null;
...

Diese Exception ist im Paket javax.servlet definiert und besagt, daß das Servlet nicht erfolgreich geladen werden konnte. Die häufigste Ursache für diesen Fehler sind Konfigurationsprobleme. Die XSLT-Stylesheets könnten z.B. im falschen Verzeichnis installiert sein, oder eine JAR-Datei wurde nicht gefunden.

Als nächstes lädt die Methode init( ) die beiden Stylesheets in den Speicher. Da XSLT-Stylesheets im Dateisystem liegen, wird StreamSource verwendet, um sie einzulesen und JAXP zur Verfügung zu stellen. Natürlich wollen wir den absoluten Pfadnamen nicht fest einprogrammieren, denn dann läuft Ihr Code zwar auf Ihrem Rechner, nicht aber, wenn er auf einem Webserver installiert wird. Mit einem absoluten Windows-Pfadnamen wie C:\java\tomcat\webapps\chap6\WEB-INF würde das Servlet weder unter anderen Betriebssystemen laufen noch auf Windows-Rechnern, bei denen Tomcat in einem anderen Verzeichnis installiert ist. Am besten benutzt man einen relativen Pfadnamen wie .\WEB-INF, so daß das Stylesheet gefunden werden kann, unabhängig davon, wo die Webanwendung eingesetzt wird.

Der relative Pfadname ist relativ zu einer bestimmten Startposition, deshalb benutzen wir die Klasse ServletContext. Sie kann Ressourcen relativ zum Installationsverzeichnis der Webanwendung lokalisieren und ermöglicht es so, absolute Pfadnamen zu vermeiden und damit Ihren Code portabler zu gestalten. Um die Details der Abbildung von relativen auf absolute Pfadnamen kümmert sich der Servlet-Container.

Wir installieren chap6.war in das Verzeichnis webapps von Tomcat. Tomcat entpackt es ins Verzeichnis webapps\chap6, in dem sich Unterverzeichnisse befinden, die der Struktur der WAR-Datei entsprechen. Als erstes weisen wir mit dem folgenden Pfadnamen der Variablen curName den Namen der XSLT-Datei zu:

try {
  curName = "/WEB-INF/xslt/editPersonalData.xslt";

Nun gibt es zwei Möglichkeiten. Der ServletContext kann das XSLT-Stylesheet sowohl durch einen InputStream als auch durch eine URL repräsentieren. Wenn Sie einen InputStream verwenden, sieht der XSLT-Prozessor das Stylesheet als einen Strom von Bytes. Er kennt dabei nicht die Quelle des Datenstroms und kann somit auch keine URI-Referenzen auflösen. Das wird dann zum Problem, wenn Ihr Stylesheet ein anderes Stylesheet einbindet, denn dieses kann dann nicht lokalisiert werden. Um dieses Problem beim Einsatz von InputStream zu lösen, stellt das Interface javax.xml.transform.Source die Methode setSystemId( ) zur Verfügung. Diese erlaubt es dem XSLT-Prozessor, URI-Referenzen im Stylesheet aufzulösen (siehe XSLT-Verarbeitung mit Java).

In unserem Servlet umgehen wir dieses Problem, indem wir anstelle eines InputStream eine URL verwenden. Die URL wird in einen System-Identifier konvertiert und erlaubt es, eine Instanz von StreamSource zu erzeugen. Mit dieser wird schließlich eine Instanz von Templates für unser Stylesheet erzeugt:

URL xsltURL = getServletContext( ).getResource(curName);
String xsltSystemID = xsltURL.toExternalForm( );
this.editTemplates = transFact.newTemplates(
    new StreamSource(xsltSystemID));

Derselbe Prozeß wird für das zweite Stylesheet wiederholt, woran die Ausnahmebehandlung anschließt:

  curName = "/WEB-INF/xslt/confirmPersonalData.xslt";
  xsltURL = getServletContext( ).getResource(curName);
  xsltSystemID = xsltURL.toExternalForm( );
  this.thanksTemplates = transFact.newTemplates(
    new StreamSource(xsltSystemID));
  } catch (TransformerConfigurationException tce) {
  log("Stylesheet konnte nicht kompiliert werden", tce);
    throw new UnavailableException("Stylesheet konnte nicht kompiliert werden");
} catch (MalformedURLException mue) {
  log("XSLT-Datei wurde nicht gefunden: " + curName);
  throw new UnavailableException(
    "XSLT-Datei wurde nicht gefunden: " + curName);
  }
}

Die Methode log() schreibt eine Meldung in eine von Tomcats Logdateien, die sich im Verzeichnis TOMCAT_HOME\logs befinden. Die UnavailableException zeigt an, daß das Servlet nicht gefunden und somit nicht geladen werden konnte. Der Nutzer sieht eine Fehlerseite in seinem Browser.

Falls init( ) erfolgreich beendet werden kann, ist das Servlet bereit, Client-Requests entgegenzunehmen. Da in diesem Servlet die Methoden doGet( ) und doPost( ) implementiert wurden, unterstützt es die HTTP GET- und POST-Protokolle. Wenn der Nutzer die Anwendung aufruft, durch das Betätigen eines Links, der Eingabe einer URL oder dem Aufruf eines Bookmarks, sendet der Browser eine HTTP GET-Anfrage, die den Aufruf der Methode doGet( ) zur Folge hat:

protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws IOException,
    ServletException {
  PersonalData personalData = getPersonalData(request);
  // der dritte Parameter 'false' bewirkt, daß beim Anzeigen
  // der Seite keine Fehlermeldungen ausgegeben werden.
  showPage(response, personalData, false, this.editTemplates);
}

Zuerst holt doGet( ) eine Instanz von PersonalData, die dem entsprechenden Nutzer zugeordnet ist. Da dieser Code auch in der Methode doPost( ) Verwendung findet, wurde er in die Hilfsmethode getPersonalData( ) ausgelagert. Im Beispiel PersonalDataServlet.java finden Sie deren Implementierung. Im wesentlichen wird HttpSession verwendet, um die richtige Instanz von PersonalData zu finden. Wird das Objekt in der Sitzung nicht gefunden, wird eine neue Instanz erzeugt und abgelegt.

Die Methode doGet( ) ruft ihrerseits die Methode showPage( ) auf, die die eigentliche Arbeit erledigt und die Webseite an den Browser schickt. Die Methode showPage( ) hat folgende Parameter:

  • Die HttpServletResponse, die den Zugriff auf PrintWriter ermöglicht. Das Ergebnis der Transformation wird an diesen Writer geschickt.
  • Die Instanz von PersonalData, damit die Methode showPage( ) weiß, welche Daten dargestellt werden sollen.
  • Den Parameter false, der hier bedeutet, daß keine Fehlermeldungen auszugeben sind. Da doGet( ) beim ersten Darstellen der Seite aufgerufen wird, würden Nutzer sonst Warnungen über ungültige Daten erhalten, obwohl Sie noch gar nichts eingegeben haben.
  • Eine Referenz auf das entsprechende Stylesheet. In diesem Fall stellt das Stylesheet das HTML-Formular dar, so daß der Nutzer seine Informationen eingeben kann.

Nachdem der Nutzer das Formular ausgefüllt und an das Servlet abgeschickt hat, wird die Methode doPost( ) aufgerufen. Ihr Code ist ähnlich dem von doGet( ) (siehe Beispiel PersonalDataServlet.java). Der einzige Unterschied ist, daß die Daten durch die Klasse PersonalData validiert werden. Sind die Daten gültig, wird die Bestätigungsseite angezeigt. Ansonsten wird die aktuelle Seite noch einmal, aber mit Fehlermeldungen, dargestellt. Wie man im Code sehen kann, ist der einzige Unterschied zwischen den beiden Seiten, daß sie unterschiedliche Stylesheets benutzen.

Schließlich erzeugt die Methode showPage( ) unter Nutzung der Hilfsklasse PersonalDataXML eine Instanz eines DOM-Documents. Da die Erstellung des DOM-Baumes in eine Hilfsklasse ausgelagert wurde, bleibt der Code des Servlets schlank und übersichtlich:

private void showPage(HttpServletResponse response,
    PersonalData personalData, boolean includeErrors,
    Templates stylesheet) throws IOException, ServletException {
  try {
    org.w3c.dom.Document domDoc =
        this.personalDataXML.produceDOMDocument(
        personalData, includeErrors);

Danach erzeugt die Methode eine neue Instanz von Transformer. Sie erinnern sich bestimmt noch aus XSLT-Verarbeitung mit Java, daß Instanzen von Transformer sehr klein sind und lediglich Zustandsinformationen für die aktuelle Transformation enthalten. Weil Transformer-Instanzen in Multithread-Umgebungen nicht sicher sind, werden sie hier als lokale Variablen der Methode realisiert. Dadurch bekommt jeder Thread seine eigene Kopie:

Transformer trans = stylesheet.newTransformer( );

Als nächstes wird der Content-Type der HttpServletResponse konfiguriert, ein PrintWriter erzeugt und die Transformation ausgeführt. Das Ergebnis wird direkt an den PrintWriter gesendet:

    response.setContentType("text/html");
    PrintWriter writer = response.getWriter( ); 

    trans.transform(new DOMSource(domDoc), new StreamResult(writer));
  } catch (Exception ex) {
    showErrorPage(response, ex);
  }
}

Falls eine Exception ausgelöst wurde, wird die Methode showErrorPage( ) aufgerufen. Da eine Exception ihre Ursache unter anderem in einer fehlenden XML-Bibliothek haben kann, verwendet showErrorPage( ) kein XML oder XSLT, um ihre Ausgaben zu erzeugen. Denn sonst würde mit großer Sicherheit dieselbe Exception noch einmal auftreten. Statt dessen werden println( )-Aufrufe verwendet, um die HTML-Ausgaben zu erzeugen (siehe Beispiel PersonalDataServlet.java).

Anwendung

Die folgende Abbildung zeigt den kompletten Inhalt der WAR-Datei unseres Beispiels. Die Klasse SplashScreenServlet.class wird auch mit aufgelistet, denn dieses Beispiel ist nur eine Erweiterung des ersten Beispiels aus diesem Kapitel. Wie vorher werden die .class-Dateien im Verzeichnis classes abgelegt, damit der ClassLoader dieser Webanwendung auf sie zugreifen kann.

WAR-Datei vom PersonalDataServlet

Abbildung: WAR-Datei vom PersonalDataServlet

Die XSLT-Stylesheets werden im Verzeichnis WEB-INF/xslt abgelegt. Da der Inhalt des Verzeichnisses WEB-INF für Clients nicht zugänglich ist, sind die XSLT-Stylesheets für Leute, die die Website besuchen, nicht direkt sichtbar. Wenn Sie wollen, daß die Stylesheets öffentlich zugänglich werden, müssen Sie sie außerhalb des Verzeichnisses WEB-INF ablegen. Die Datei index.html ist z.B. die öffentlich sichtbare »Startseite« dieser Webanwendung. Sie enthält nur einen Link, auf den der Nutzer klicken kann, um das Servlet darzustellen. Obwohl die Stylesheets vor den Clients versteckt sind, kann der Java-Code auf sie zugreifen. Im Beispiel PersonalDataServlet.java wurden die Stylesheets in der Methode init( ) folgendermaßen lokalisiert:

curName = "/WEB-INF/xslt/editPersonalData.xslt";
URL xsltURL = getServletContext( ).getResource(curName);

Wie das Beispiel verdeutlicht, entspricht die Position der Stylesheets deren Position in der WAR-Datei. Deshalb wird das Servlet auch funktionieren, wenn die Webanwendung auf einem Webserver installiert wird.

Der Anwendungsdeskriptor aus dem folgenden Beispiel wurde um die neue Klasse PersonalDataServlet erweitert.

Beispiel: Erweiterter Anwendungsdeskriptor

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
   <servlet>
      <servlet-name>personalDataServlet</servlet-name>
      <servlet-class>chap6.PersonalDataServlet</servlet-class>
   </servlet>
   <servlet>
      <servlet-name>splashScreen</servlet-name>
      <servlet-class>chap6.SplashScreenServlet</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>personalDataServlet</servlet-name>
      <url-pattern>/personalData/*</url-pattern>
   </servlet-mapping>
   <servlet-mapping>
      <servlet-name>splashScreen</servlet-name>
      <url-pattern>/splash/*</url-pattern>
   </servlet-mapping>
</web-app>

Hinweise zum Kompilieren, Installieren und Ausführen

Im Umgang mit Java kann man meinen, die Hälfte aller Probleme hängt mit dem CLASSPATH zusammen. Um dieses Beispiel zu übersetzen, müssen die folgenden JAR-Dateien im CLASSPATH aufgelistet werden:

jaxp.jar

Java API for XML Processing (JAXP) 1.1

xalan.jar

Xalan XSLT-Prozessor (benutzen Sie den von JAXP)

crimson.jar

Crimson XML-Parser (benutzen Sie den von JAXP)

servlet.jar

befindet sich im Tomcat Servlet-Container

Natürlich muß auch das Verzeichnis mit unserem eigenen Quellcode im CLASSPATH aufgelistet werden. Wenn alles eingerichtet ist, können PersonalData.java, PersonalDataXML.java und PersonalDataServlet.java mit javac *.java kompiliert werden. Ob Sie auch SplashScreenServlet.java mit einbeziehen wollen, liegt bei Ihnen.

Wie schon erwähnt, kann die WAR-Datei mit dem jar-Befehl erzeugt werden. Um chap6.war zu erzeugen, müssen alle Dateien in die Verzeichnisstruktur aus der Abbildung WAR-Datei vom PersonalDataServlet gebracht werden. Dann kann in dem Verzeichnis, in dem index.html steht, das folgende Kommando ausgeführt werden:

jar -cvfM ../chap6.war

Dadurch wird chap6.war im übergeordneten Verzeichnis des aktuellen Arbeitsverzeichnisses abgelegt; der Slash (/) funktioniert unter Windows und Unix. Nachdem die WAR-Datei erzeugt wurde und man in ihr Verzeichnis gewechselt hat, kann man den Inhalt mit folgendem Kommando ansehen:

jar -tvf chap6.war

Es zeigt das Inhaltsverzeichnis der WAR-Datei, das der Struktur in der Abbildung WAR-Datei vom PersonalDataServlet entsprechen muß.

Die Tomcat-Installation ist einfach: Die Datei chap6.war muß nur ins Verzeichnis TOMCAT_HOME/webapps kopiert werden, wenn Tomcat nicht läuft. Jetzt können Sie versuchen, das Servlet zu starten. Wenn es nicht funktioniert, liegt es wahrscheinlich daran, daß noch jaxp.jar, xalan.jar und crimson.jar im Verzeichnis TOMCAT_HOME/lib installiert werden müssen, bevor Sie für die Webanwendung zugänglich sind.

Der schwierigste Aspekt hierbei ist, die richtigen Versionen dieser JAR-Dateien zu installieren. Abhängig von Ihrer Tomcat-Version können noch ältere Versionen von jaxp.jar und crimson.jar im Verzeichnis TOMCAT_HOME/lib gefunden werden. Die sicherste Lösung ist, JAXP 1.1 herunterzuladen. Es enthält alle drei JAR-Dateien, die dann ins Verzeichnis TOMCAT_HOME/lib kopiert werden können.

Wenn Sie alle diese Schritte ausgeführt haben, starten Sie Tomcat und greifen auf die folgende URL zu:

"http://localhost:8080/chap6/personalData"

Nun sollte die Seite "Persönliche Informationen" mit dem leeren Formular dargestellt werden und auf Ihre Eingabe warten.

Stylesheets mit Initialisierungsparametern lokalisieren

Wie Sie gerade gesehen haben, ist es einfach, Stylesheets irgendwo unterhalb des WEB-INF-Verzeichnisses in einer WAR-Datei zu plazieren. Das ist zwar eine ideale Lösung für einzelne Webanwendungen, es gibt aber Situationen, in denen dasselbe Stylesheet von einer ganzen Gruppe von Webanwendungen verwendet wird. In diesem Fall müßte das Stylesheet in mehreren WAR-Dateien eingebunden werden.

Idealerweise würde man das Stylesheet in ein gemeinsam benutztes Verzeichnis ablegen, den Namen dieses Verzeichnisses aber nicht fest in die Servlets einprogrammieren. Dies läßt sich einfach mit Initialisierungsparametern bewerkstelligen. Es handelt sich um Name/Wert-Paare als Strings, die im Anwendungsdeskriptor spezifiziert und über Servlet oder ServletContext ausgelesen werden können.

Servlet-Initialisierungsparameter sind an bestimmte Servlets und Context-Initialisierungsparameter an eine ganze Webanwendung gebunden. Um die Position der XSLT-Stylesheets anzugeben, ist es sinnvoll, Context-Parameter zu verwenden. Sie können im Anwendungsdeskriptor wie folgt angegeben werden:

<web-app>
  <context-param>
    <param-name>xslt_verzeichnis</param-name>
    <param-value>C:/dev/xslt</param-value>
  </context-param>
  <servlet>
   ...
  </servlet>
</web-app>

Die Werte dieser Parameter können mit den folgenden Methoden des Interface javax.servlet.ServletContext ausgelesen werden:

public interface ServletContext {
  // null zurückgeben, wenn der Parameter nicht existiert
  String getInitParameter(String name);
  Enumeration getInitParameterNames( );
  ...die restlichen Methoden sind hier ausgelassen
}

Um also die Position eines Stylesheets zu ermitteln, kann man den folgenden Code in der Methode init( ) eines Servlets verwenden:

public class MeinServlet extends HttpServlet {
  private String xsltDirectory;
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    this.xsltDirectory = config.getServletContext( ).getInitParameter(
      "xslt_verzeichnis");
    if (this.xsltDirectory == null) {
     throw new UnavailableException(
        "Context-Parameter xslt_verzeichnis wurde nicht angegeben");
    }
  }
  ...Rest des Codes wurde ausgelassen
}

Nun, da sich die tatsächliche Position des Stylesheets im Anwendungsdeskriptor befindet, kann diese geändert werden, ohne das Servlet bearbeiten zu müssen.

   

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Java & XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2002 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Java und XSLT" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de