XSP Einführung

Autor:
Thomas Bayer
Orientation in Objects GmbH
Thomas Bayer
Thomas Bayer
Datum:Juli 2002

Abstract

Dieser Artikel beschreibt die Erstellung von eXtensible Server Pages mit Cocoon und XSP. Grundlagen im Umgang und der Konfiguration von Apache Cocoon werden vorausgesetzt.

Server Pages

Server Pages haben die Entwicklung von Web Anwendungen stark vereinfacht. Im Gegensatz zur sehr technischen Entwicklung von CGI Anwendungen oder der Programmierung von Servlets, ist die Erstellung einer Server Page recht einfach. Eine Server Pages ähnelt im Aufbau einem HTML Dokument. Sie enthält Template Text, beispielsweise in der Form von HTML Markup, welcher später in das resultierende Dokument kopiert wird. Daneben kann eine Server Page Logik in Form von Code enthalten. Wird eine Server Page aufgerufen, so wird dieser Code ausgeführt und dessen Ausgabe in das resultierende Dokument eingebaut.

Der in der Seite enthaltende Code kann auf Datenbanken oder Server wie Enterprise JavaBeans Anwendungsserver zugreifen.

Server Pages gibt es für die verschiedensten Programmiersprachen, Beispielsweise die JavaServer Pages (JSP) für Java oder Active Server Pages (ASP) für Visual Basic.

eXtensible Server Pages (XSP)

Eine eXtensible Server Page ist zusätzlich zu den bisherigen Server Pages, ein gültiges XML Dokument. Für das Erstellen von XSPs gelten die gleichen Regeln wie für das Erstellen gewöhnlicher XML Dokumente. Für das Editieren kann ein gewöhnlicher Text Editor oder ein XML Editor verwendet werden. Im Gegensatz zu einer normalen Server Page liefert die XSP Seite kein HTML sondern ein XML Dokument zurück, welches in einem weiteren Schritt zu HTML transformiert werden kann. Eine XSP kann ohne jeglichen HTML Markup erstellt werden.

Der von einer XSP erzeugte Markup wird nicht als komplettes Dokument sondern in Form von SAX Ereignissen weitergeben.

Für die Erstellung von eXtensible Server Pages wird meistens Java verwendet. Cocoon bietet aber auch Unterstützung für andere Sprachen wie PHP oder JavaScript an.

Hello World Server Page

Das Beispiel hello.xsp zeigt eine einfache eXtensible Server Page. Die Datei ist ein gültiges XML Dokument. Im root Element wird der Namespace xsp für Server Pages angegeben. Das Attribut language verrät Cocoon, welche Sprache in der Server Page verwendet wird. Innerhalb der XSP Seite bestimmen wir mit den Elementen seite und para den späteren Aufbau des Ergebnisdokumentes. Im para Element finden wir einfachen Text und das xsp Element <xsp:expr>. Mit <xsp:expr> können wir einen Ausdruck angeben, dessen Wert in die spätere Seite eingebaut wird. In unser Seite verwenden wir einen Ausdruck, der ein neues Objekt vom Typ java.util.Date erzeugt. Den vollständigen Namen der Klassen müssen wir im Gegensatz zu normalen Java Quelldateien nicht angeben, da er transparent importiert wird, dazu später mehr.

Damit wir die Server Page im Cocoon ausführen können, müssen wir Sie in der Cocoon Installation hinterlegen und die Sitemap entsprechend anpassen.

Im Root Verzeichnis der Installation, in dem sich auch die Sitemap befindet legen wir ein Verzeichnis seminar und darin das Verzeichnis xsp an. Im xsp Verzeichnis erstellen wir die Datei, die Beispiel hello.xsp zeigt. Damit die Seite aufgerufen werden kann, muss die Sitemap passend erweitert werden. Beispiel 10.2 zeigt eine Pipeline, die für alle Dateien mit der Endung xsp unterhalb des seminar Contextes zuständig ist. Nach der Modifikation in der Sitemap können wir die Server Page mit folgender URL im Browser aufrufen: http://localhost:8080/cocoon/seminar/hello.xsp. Je nach dem wie wir Cocoon konfiguriert haben, wird geprüft, ob sich etwas an der Sitemap geändert hat und falls notwendig, wird die Sitemap neu übersetzt. Je nach Konfiguration müssen wir Cocoon neu starten oder ein zweitesmal auf Reload klicken. Dieser Aufwand kann während der Entwicklung mit folgender Einstellung in der Datei cocoon.xconf im Hauptverzeichnis des Cocoon webapp Verzeichnises vermiden werden:

Beispiel 10.1. Parameter für Sitemap Reloading

<sitemap
  file="sitemap.xmap"
  reload-method="synchron"
  check-reload="yes"
  logger="sitemap"/>

Es wird bei jedem Aufruf geprüft, ob sich etwas an der Sitemap geändert hat. Falls dies der Fall ist, wird zuerst die Sitemap neu übersetzt und geladen und erst anschließend die Anfrage ausgeführt.

Beispiel 10.2. Pipeline für die Sitemap

<map:match pattern="seminar/*.xsp">
  <map:generate type="serverpages" src="seminar/xsp/{1}.xsp" />
  <map:serialize />
</map:match>

Abbildung Abbildung 10.1 zeigt die Ausgabe unserer Server Page im Browser. Betrachtet man den Quellcode der Seite erkennt man die Elemente seite und para wie im Listing Beispiel 10.4 ersichtlich. Die Pipeline, für diese Server Page enthält noch keinen Transformer. Wenn wir einen Transformer für HTML in die Pipeline einbauen und anstatt des XML- ein HTML-Serializer verwenden, liefert der Aufruf unserer Server Page eine Web Seite zurück.

Beispiel 10.3. hello.xsp

<?xml version="1.0" encoding="UTF-8"?>
<xsp:page language="java" xmlns:xsp="http://apache.org/xsp">
    <seite>
        <para>
            Jetzt ist es:
            <xsp:expr>new Date()</xsp:expr>
        </para>
    </seite>
</xsp:page>

Abbildung 10.1. Ergebnis von hello.xsp

XSP Hello World

Beispiel 10.4. Quelltext der Ausgabe von hello.xsp

<seite
  xmlns:xml="http://www.w3.org/XML/1998/namespace"
  xmlns:xsp="http://apache.org/xsp">
    <para>Jetzt ist es: Sat Jan 05 13:36:10 CET 2002</para>
</seite>

Ausdrücke

Für das Einbinden von Ergebnissen aus Berechnungen und Methodenaufrufen steht das Element <xsp:expr> zur Verfügung. Ausdrücke werden ausgewertet, das Ergebnis in einen String umgewandelt und ausgegeben. Es können alle Java Ausdrücke verwendet werden.

Nach dem Ausdruck darf kein Semikolon stehen. Beispiel Beispiel 10.5 zeigt verschiedene Ausdrücke in einer XSP Seite.

Beispiel 10.5. Ausdrücke

<?xml version="1.0" encoding="UTF-8"?>
<xsp:page language="java"
          xmlns:xsp="http://apache.org/xsp">
  <seite>
    <para>
      <xsp:expr>7 * 6</xsp:expr>
      <xsp:expr>System.getProperty("java.class.path")</xsp:expr>
      <xsp:expr>new Date()</xsp:expr>
    </para>
  </seite>
</xsp:page>

Logik Blöcke

Im Gegensatz zu Ausdrücken, kann ein Logic Block mehrere Anweisungen aufnehmen. Die Anweisungen müssen mit einem Semikolon abgeschlossen werden. Ein Logik Block gibt im Gegensatz zu einer Expression keinen Wert zurück. Das Beispiel Beispiel 10.6 enhält einen Logik Block, in welchem eine Variable tiere angelegt wird. Diese Variable wird von der folgenden Expression ausgegeben.

Beispiel 10.6. Logic Blöcke

<?xml version="1.0" encoding="UTF-8"?>
<xsp:page language="java" xmlns:xsp="http://apache.org/xsp">
  <seite>
    <para>
      <xsp:logic>int i = 3; String tiere = 3 + " Ameisenbären";</xsp:logic>
      <xsp:expr>tiere</xsp:expr>
    </para>
  </seite>
</xsp:page>

Schleifen und Bedingungen können wie Beispiel 10.7 zeigt innerhalb von <xsp:logic> eingesetzt werden. Leider können wir das < Zeichen nicht ohne Weiteres in einer XSP Seite verwenden, da es sich um ein XML Dokument handelt und das Zeichen als Anfang eines Elementes interpretiert werden würde. Wir können uns mit einer CDATA Section oder mit einer Entity wie im Beispiel behelfen.

Beispiel 10.7. Logic Blöcke

<xsp:logic>
  for( int i = 1; i &amp;lt; 10; i++) {
    <xsp:content>Hallo</xsp:content>
    if ( i == 5) {
      <xsp:content>Mitte</xsp:content>
    }
  }
</xsp:logic>

Bei größeren Code Blöcken bietet sich die Verwendung einer CDATA Section an. Beispiel 10.8 zeigt einen Abschnitt als CDATA Section. Das Kleinerals-Zeichen kann jetzt wieder normal geschrieben werden.

Beispiel 10.8. CDATA Section für Code

 for( int i = 1; i &lt; 10; i++) { 

Innerhalb der CDATA Sections können wir keine XML Elemente verwenden, da diese als normaler Text und hier als Java Code interpretiert werden würden. Das Listing in Beispiel 10.9 erzeugt einen Fehler beim Übersetzen des erzeugten Java Codes. Vor der Verwendung eines XML Elments im Logic Block müßte die CDATA Section geschlossen werden. Danach kann wieder eine neue Section eröffnen werden.

Beispiel 10.9. Fehler

for( int i = 1; i &lt; 10; i++) {
  <xsp:content>Hallo</xsp:content> if (i == 5) {
    <xsp:content>Mitte</xsp:content>
  }
}

Das Tag <xsp:content> ermöglicht die Ausgabe von Template Text innerhalb von Logic Blöcken. Innerhalb eines <xsp:content> Elementes können Ausdrücke mit <xsp:expr> ausgegeben werden. Der Logic Block in Beispiel 10.10 gibt die Zahlen 1 bis 9 aus.

Beispiel 10.10. Logic Blöcke

<xsp:logic>
  for( int i = 1; i &amp;lt; 10; i++) {
    <xsp:content>
        <xsp:expr>i</xsp:expr>
    </xsp:content>
  }
</xsp:logic>

Falls die Ausgabe wie in Listing Beispiel 10.11 als Inhalt eines Elementes ausgegeben werden soll, können wir uns das <xsp:content> Element ersparen.

Beispiel 10.11. Ausgabe

<zahl>1</zahl>
<zahl>2</zahl>
<zahl>3</zahl>
<zahl>4</zahl>
<zahl>5</zahl>
<zahl>6</zahl>
<zahl>7</zahl>
<zahl>8</zahl>
<zahl>9</zahl>

Wir dürfen innerhalb eines Logik Blockes beliebige XML Elemente verwenden. Innerhalb dieser Elemente können Ausdrücke mit <xsp:expr> ausgegeben werden. Wie Beispiel 10.12 zeigt können damit recht elegant Server Pages erstellt werden.

Beispiel 10.12. XML Elemente in Logik Blöcken

<xsp:logic>
    for( int i = 1; i &amp;lt; 10; i++) {
    <zahl>
        <xsp:expr>i</xsp:expr>
    </zahl>
    }
</xsp:logic>

Aufbau einer Server Page

Damit wir den Aufbau einer eXtensible Server Page besser nachvollziehen können, betrachten wir eine einfache XSP Seite und die aus der Server Page erzeugte Java Datei.

Den Aufbau und die Struktur einer XSP Seite zeigt Beispiel 10.13. Zunächst handelt es sich um ein valides XML Dokument. Den Anfang macht der XML Prolog. Danach folgt das root Element des XML Dokumentes. Bei einer standard XSP Seite ist dies das Element <xsp:page>. In der vereinfachten Form kann in späteren Versionen anstatt dieses root Elements ein vom Benutzer definiertes verwendet werden. Die Angabe des Namespaces xsp für das core XSP Logicsheet ist im <xsp:page> Element zwingend vorgeschrieben. Das core Logicsheet xsp.xsl übersetzt den Markup einer XSP Seite in Java Quellcode. Für zusätzliche Tag Bibliotheken in Form von Logicsheets können weitere Namespaces definiert werden. Das <xsp:page> Element darf folgende direkte Kind Elemente enthalten:

  • Ein <xsp:structure> Element

  • xsp:logic Blöcke

  • Nur ein benutzerdefiniertes Element

Die nachfolgende Abschnitte beschreiben diese Elemente genauer.

Beispiel 10.13. Aufbau einer Server Page

<?xml version="1.0"?>
<xsp:page language="java" xmlns:xsp="http://apache.org/xsp">
    <xsp:structure>
        <xsp:include>java.math.*</xsp:include>
    </xsp:structure>
    <xsp:logic>
        public String nachricht = "Hossa";
        public BigInteger getBig() { return new
            BigInteger("10000000000000000000");
        }
    </xsp:logic>
    <page>
        <xsp:expr>nachricht + getBig()</xsp:expr>
        <xsp:logic>
            for(int i = 0; i &amp;lt; 10; i++) {
              <foo />
            }
        </xsp:logic>
    </page>
</xsp:page>

Zunächst möchten wir untersuchen, was mit der Server Page geschieht. Die XSP Seite wird mit Hilfe des xsp Core Logicsheets in eine Textdatei umgewandelt, die Java Code enthält. Unter der Tomcat Servlet Engine Installation finden wir diese Textdatei im work Verzeichnis, in dem auch übersetzte JSP Seiten abgelegt werden. Den Code zur XSP Seite aufbau.xsp finden wir unter:

%TOMCAT_HOME%\work\localhost\cocoon\cocoon-files\org\apache\cocoon\www\seminar\xsp\aufbau_xsp.java

Unterhalb des work Verzeichnisses finden wir Verzeichnisse für den Server, den Servlet Context und Unterverzeichnisse, die so aufgebaut sind wie das package Statement der erzeugten Seite. Beispiel 10.14 zeigt den leicht vereinfachten generierten Code. Nach diversen Imports für Java, Cocoon und XML Packages kommen die benutzerdefinierten Import Statements. Wir sehen, daß aus dem Inhalt des <xsp:include> Elementes der aufbau.xsp Seite ein import java.math.* erzeugt wurde. Die Java Datei enthält eine öffentliche Klasse, die von XSPGenerator erbt und einen an den Dateinamen angelehnte Bezeichnung aufbau_xsp bekommen hat. XSPGenerator ist eine abstrakte Klasse des Cocoon Frameworks.

Der static Initializer der Klasse enthält einen Timestamp mit der Zeit, zu der die Java Datei erzeugt wurde. Diese Zeitangabe wird benutzt, um bei erneuten Seitenabrufen zu prüfen, ob sich in der Zwischenzeit etwas am Quellcode der XSP Seite verändert hat..

Unterhalb des Kommentars User Class Declarations finden wir den Inhalt des Logik Blockes, der ein direktes Kind von <xsp:page> war. Wir können so der generierten Klasse Methoden und Instanzvariablen einbauen.

Die generate() Methode ist für die Generierung der Ausgabe der Server Page zuständig. Innerhalb der Methode finden wir viele Statements, die über einen SAX Content Handler Elemente erzeugen.

Den Inhalt des in der XSP Seite benutzten <xsp:expr> Elementes finden wir im folgenden Statement.

XSPObjectHelper.xspExpr(contentHandler, nachricht + getBig());

Der Logik Block mit der for Schleife wurde einfach an der passenden Stelle in die Java Datei eingebaut. Das foo Element innerhalb der for Schleife wird über die SAX Nachrichten startElement und endElement erzeugt.

Die aus den XSP Seiten generierten Java Dateien sollten nicht per Hand manipuliert werden, da mit jeder Veränderung der XSP Quelle beim nächsten Aufruf die Datei mit einer neu erzeugten überschrieben wird und die Änderung dann verloren gingen. Vor dem Editieren warnt uns auch ein Kommentar.

Bei Entwickeln von Anwendungen mit XSPs hat man normalerweise keine Berührung mit diesen generierten Java Dateien. Bei hartknägigen Fehlern kann ein Blick in den generierten Java Code helfen, den Fehler zu finden.

Beispiel 10.14. Aus XSP generierter Java Code

package org.apache.cocoon.www.seminar.xsp;
// Importieren diverser Java, Cocoon und XML Klassen
/* User Imports */
import java.math.*;
/** * Generated by XSP. Edit at your own risk, :-) */
public class aufbau_xsp extends XSPGenerator {
  static {
    dateCreated = 1010489086383L;
    dependencies = new File[]{ };
  }
  /* Built-in parameters available for use */
  // context - ServletContext
  // request - HttpServletRequest
  // response - HttpServletResponse
  // parameters - parameters defined in the sitemap
  /* User Class Declarations */
  public String nachricht = "Hossa";
  public BigInteger getBig() {
    return new BigInteger("10000000000000000000");
  }
  /** * Generate XML data. */
  public void generate()
    throws SAXException, IOException, ProcessingException {
    this.contentHandler.startDocument();
    AttributesImpl xspAttr = new AttributesImpl();
    this.contentHandler.startPrefixMapping(
           "xml", "http://www.w3.org/XML/1998/namespace");
    this.contentHandler.startPrefixMapping(
           "xsp","http://apache.org/xsp");
    this.contentHandler.startElement("", "page", "page", xspAttr);
    xspAttr.clear();
    XSPObjectHelper.xspExpr(contentHandler, nachricht + getBig());
    for (int i = 0; i &lt; 10; i++) {
      this.contentHandler.startElement("", "foo", "foo", xspAttr);
      xspAttr.clear();
      this.contentHandler.endElement("", "foo", "foo");
    }
    this.contentHandler.endElement("", "page","page");
    this.contentHandler.endPrefixMapping("xml");
    this.contentHandler.endPrefixMapping("xsp");
    this.contentHandler.endDocument();
  }
}

Attribute und Methoden auf Klassenebene

In die aus der XSP generierte Generator Klasse können wir Instanzvariablen und Methoden einbauen lassen. Logik Blöcke mit <xsp:logic>, die direkte Kinder des <xsp:page> Tags sind, werden auf Klassenebene in den Code eingebaut. Wir können in den Logik Blöcken Code für Instanzvariablen, Konstanten oder Methoden unterbringen. Im Beispiel 10.13 wird die Instanzvariable nachricht und die Methode getBig auf Klassenebene in den resultierenden Code einbebettet.

Jede XSP darf nur ein vom Benutzer definiertes root Element innerhalb des <xsp:page> Tags besitzen. Der Inhalt dieses Tags wird in die generate Methode der generierten Klasse übernommen. Verwenden wir Logik Blöcke innerhalb des benutzerdefinierten Root Elementes, so wird der Code nicht auf Klassenebene sondern in die generate Methode eingebaut.

Dynamische Attribute und Elemente

Die Elemente, die wir in den bisherigen XSP Seiten erzeugt haben, wurden einfach als Markup eingebaut. Die Namen der Elemente waren alle vorgegeben. Den Namen eines Elementes können wir auch zur Zeit des Seitenabrufes bestimmen. Zunächst betrachten wir eine Alternative basierend auf einem Logic Block. Beispiel 10.15 zeigt den Logic Block, der abhängig von der Variablen einheit entweder ein Meter oder ein Kilo Element erzeugt. Es wird zur Laufzeit entschieden, welches Element erzeugt wird. Diese Technik mit dem if Statement ist nicht besonders elegant und führt zu unübersichtlichem und schlecht wartbarem Code. Die Namen für das zu erzeugende Element sind bereits vorgegeben. Das Element wird entweder Meter oder Kilo heißen.

Beispiel 10.15. Abhängiges Element

<xsp:logic>
    String einheit = "Meter";
    int menge = 7;
    if ( "Meter".equals(einheit)) {
      <Meter>
        <xsp:expr>menge</xsp:expr>
      </Meter>
    } else {
      <Kilo>
        <xsp:expr>menge</xsp:expr>
      </Kilo>
    }
</xsp:logic>

Diese Lösung stößt an Ihre Grenzen, wenn wir den Elementnamen aus einer Variable oder dem Request verwenden möchten. Nehmen wir an, wir möchten über einen Request Parameter einheit den Namen eines Elementes bestimmen. Die Seite möchten wir im Browser über folgende URL aufrufen:

http://127.0.0.1:8080/cocoon/seminar/dyn-element.xsp?einheit=Meter

Über das SAX API können wir die notwendigen Ereignisse, um ein Element zu erzeugen an einen ContentHandler schicken. Die Sitemap Komponenten Generator, Transformer und Serializer im Cocoon verwenden auch SAX Ereignisse um miteinander zu kommunizieren. Wie Beispiel 10.16 zeigt können wir ein Element mit dem Namen des Request Parameters einheit erzeugen.

Beispiel 10.16. Dynamisches Element mit SAX API

<xsp:logic>
  String elementName =
    <xsp-request:get-parameter name="einheit" />;
  AttributesImpl attr = new AttributesImpl();
  contentHandler.startElement(
      "", elementName, elementName, attr);
  this.characters("7");
  this.contentHandler.startElement(
      "", elementName, elementName, attr);
</xsp:logic>

Wie das SAX API in Server Pages eingesetzt werden kann beschreibt ???. Produktivität und Übersichtlichkeit bleiden bei diesem Ansatz auf der Strecke. Mit <xsp:element> und <xsp:attribute> können Elemente und Attribute, mit dynamischen Namen, wesentlich einfacher erzeugt werden. Die folgende Zeile Erzeugt ein Element mit dem Namen hossa unter Verwendung von <xsp:element>.

<xsp:element name="hossa" />

Die Bezeichnung hossa ist fest als Attributwert kodiert. Wir möchte für den Namen wieder den Wert eines Requestparameters oder ein Ausdruck verwenden. Es wäre schön, wenn folgendes möglich wäre:

<xsp:element name="<xsp-request:get-parameter name='einheit'/>" />

Dieser Versuch scheitert daran, daß wir innerhalb eines Attributes keine XSP Tags bzw. keine XML Elemente verwenden. können. Damit wir trotz dieser Einschränkung dynamische Werte verwenden können, gibt es das Tag <xsp:param>, mit dem wir einen Wert oder Ausdruck in einem Childelement übergeben können.

Der folgende Codeausschnitt erfüllt die gleiche Aufgabe wie die Lösung mit dem SAX API unter Verwendung von <xsl:element> und <xsp:param>.

Beispiel 10.17.

<xsp:element>
    <xsp:param name="name">
        <xsp-request:get-parameter name="einheit" />
    </xsp:param>
    7
</xsp:element>

Attribute mit dynamischen Namen können mit <xsp:attribute> erzeugt werden. Im Beispiel 10.18 wird dem Element gewicht ein Attribut hinzugefügt. Der Name des Attributes bestimmt der Wert des Übergabeparameters einheit..

Beispiel 10.18. <xsp:attribute>

<gewicht>
    <xsp:attribute>
        <xsp:param name="name">
            <xsp-request:get-parameter name="einheit" />
        </xsp:param>
        7
    </xsp:attribute>
</gewicht>
Zum Geschaeftsbreich Competence Center
Artikel
Das weiterführende Cocoon Logicsheet Tutorial beschreibt die Verwendung und Entwicklung von Logicsheets für XSP