Die Java-Klasse


... [ Die Schablone ] ... [ Die Java-Klasse ] ... [ Ausblick ] ... [ Inhaltsverzeichnis ] ...

Übersicht: Die Java-Klasse


Wie funktioniert Velocity?

Im vorherigen Kapitel wurde demonstriert, wie eine Velocity-Schablone erstellt wird. Die in der Schablone angeforderten Daten sollen nun von der Java-Klasse in die Schablone eingetragen und das Ergebnis ausgegeben werden. Dafür sind, egal wofür die Java-Klasse genutzt wird (Servlet, Anwendung, o.ä.), folgende Schritte notwendig:
  1. Velocity muss initialisiert werden
  2. ein "context"-Objekt wird erstellt
  3. dem "context"-Objekt werden Daten zugewiesen
  4. eine Schablone wird ausgewählt
  5. die Schablone wird mit dem "context"-Objekt verknüpft
Sehr vereinfacht könnten die fünf Punkte folgendermaßen in der Klasse implementiert werden:

00 StringWriter sw = new StringWriter();
01 Velocity.init();
02 VelocityContext context = new VelocityContext();
03 context.put("name", new String("Velocity") );
04 Template template =  Velocity.getTemplate("schablone.vm");
05 template.merge(context,sw);
In Zeile 01 wird Velocity initialisiert. Die beiden darauffolgenden Zeilen erstellen und füllen ein "context"-Objekt. Danach wird eine Schablonen-Variable deklariert (Zeile 04) und ihr eine Schablone zugewiesen ("Schablone.vm"). Für die Ausgabe wird ein "StringWriter"-Objekt (00) angelegt, welches zusammen mit dem "context"-Objekt an die Methode "merge" des "template"-Objektes übergeben wird. Diese Methode verknüpft die Schablone mit dem "context"-Objekt und gibt das Ergebnis an den StringWriter weiter.


Die Initialisierung von Velocity

Seit Velocity 1.2 gibt es zwei Möglichkeiten, um Velocity zu initialisieren:
  1. das "Singleton"-Modell
  2. das "Separate Instanz"-Modell
Die Entwicklung hat sich dadurch aber nicht wesentlich verändert, da derselbe Kern des Velocity-Codes benutzt wird. Dennoch gibt es wesentliche Unterschiede zwischen den beiden Varianten.

das "Singleton"-Modell
Bei diesem Modell wird in der Java-Virtual-Maschine nur eine Instanz der Velocity-Engine erstellt. Das bedeutet, dass für alle Benutzer dieselben Konfigurationsdateien, Schablonen etc. benutzt werden. Eine Implementierung dieses Modells sieht folgendermaßen aus:


01 import org.apache.velocity.app.Velocity;
02 import org.apache.velocity.Template;
03
04 ...
05
06 Velocity.setProperty( Velocity.RUNTIME_LOG_LOGSYSTEM, this);
07 Velocity.init();
08 ...
09 Template t = Velocity.getTemplate("foo.vm");
Zeile 01 gibt an, dass das Singleton-Modell genutzt wird. In Zeile 06 werden Eigenschaften gesetzt, die bei der Initialisierung von Velocity (Zeile 07) berücksichtigt werden. Zeile 09 zeigt die Zuweisung einer Schablone bei diesem Modell.

das "Separate Instanz"-Modell
Dieses Modell gibt es erst seit Velocity 1.2 und ermöglicht, wie der Name schon sagt" mehrere Instanzen von Velocity in der Java-Virtual-Maschine anzulegen. Dadurch wird es ermöglicht, dass verschiedene Schablonen, Konfigurationsdateien etc. genutzt werden können. Die Implementierung in der Java-Klasse sieht etwas anders aus, wie das folgende Beispiel zeigt:


01 import org.apache.velocity.app.VelocityEngine;
02 import org.apache.velocity.Template;
03
04 ...
05
06 VelocityEngine ve = new VelocityEngine();
07 ve.setProperty( VelocityEngine.RUNTIME_LOG_LOGSYSTEM, this);
08 ve.init();
09
10 ...
11
12 Template t = ve.getTemplate("foo.vm");
In Zeile 01 wird angegeben, dass es sich um das "Separate-Instanz"-Modell handelt. Zuerst wird im Beispiel eine Instanz der VelocityEngine erstellt (Zeile 06). Dieser Instanz werden Eigenschaften zugeordnet (07), bevor die Methode "init()" aufgerufen wird (08). Zeile 12 zeigt, wie bei diesem Modell eine Schablone zugewiesen wird.


Das "context"-Objekt

Im Kapitel "Die Schablone" wurde beschrieben, wie Variablen in der VTL angegeben werden. Die Variablen bekommen ihre Werte entweder direkt in der Schablone zugewiesen (über die Direktive "#set") oder aus der Java-Klasse. Für den zweiten Fall ist das "context"-Object zuständig. Dieses speichert Variablen-Bezeichner und deren Werte (Objekte) in einer Art Hashtable ab. Dafür stehen folgende zwei Methoden zum Hinzufügen und Auslesen von Objekten zur Verfügung:

public Object put (String key, Object value)
public Object get (String key)
Der Programmierer kann beliebig verschiedene und beliebig viele Objekte in das "context"-Objekt stecken, während der Designer der Schablone mittels VTL auf die mit "key" gekennzeichneten Objekte zugreifen kann. Er kann dabei auch (wie im Kapitel "die VTL" beschrieben) auf Eigenschaften und Methoden der Objekte zugreifen.
Wenn dem "context"-Objekt Listen-Objekte (z.B. Hashtable, LinkedList etc.) hinzugefügt werden, können diese in der VTL mittels "#foreach" ausgelesen werden.
Beim Hinzufügen von Elementen sollte stets darauf geachtet werden, dass das Objekt gültig ist und keine "null" hinzugefügt wird.

In der Java-Klasse muss, wie veranschaulicht wurde, u.a. Velocity initialisiert und ein "context"-Objekt erstellt werden. Alle weiteren Punkte zur Implementierung von Velocity (siehe Kapitel "Wie funktioniert Velocity?") hängen davon ab, wofür Velocity genutzt wird. Im Folgenden soll dieses anhand von zwei Beispielen erläutert werden.


Beispiel: Velocity in Servlets

Der Einsatz von Velocity in Servlets gehört zum Haupteinsatz von Velocity. Bevor eine Einführung in die Velocity-Servlet-Programmierung erfolgt, soll an dieser Stelle in aller Kürze erläutert werden, was ein Servlet ist.
Ein Servlet ist eine serverseitige Anwendung, die von einem Web-Brower aufgerufen wird. Es liefert eine Ergebnisausgabe zurück, die der Web-Browser darstellen soll. Servlets werden auf einem Web-Server abgelegt. Zur Ausführung von Velocity-Servlets wird der Einsatz eines Tomcat-Servers empfohlen. Aus diesem Grund wird im Folgenden nur auf die Benutzung eines solchen Servers eingegangen. Der Server besitzt ein Verzeichnis, das alle Servlets enthält ("/webapps"). In diesem Verzeichnis wird ein neues Verzeichnis erstellt (z.B. "/velexample"), das später das Velocity-Servlet enthalten soll.
Folgende Unterverzeichnisse und Dateien sind im Verzeichnis "velexample" zu erstellen:

velexample =>Standardverzeichnis für Schablonen, Eigenschaftsdatei, Logfile, etc. (Root-Verzeichnis des Servlets)
velexample/WEB-INF => beinhaltet die Datei "web.xml" (wird später weiter vertieft)
velexample/WEB-INF/classes => beinhaltet die selbst erzeugten Java-Klassen
velexample/WEB-INF/lib => beinhaltet die Velocity-Library

Die Erstellung von Velocity-Servlets ist sehr einfach, da in der zu erstellenden Java-Klasse nur eine einzige Methode implementiert werden muss. Die Hauptarbeit übernimmt die Klasse "VelocityServlet", von der geerbt wird. Diese wiederum erbt von der Klasse "HttpServlet" aus dem "javax"-Paket, welches die Erstellung von Servlets ermöglicht.
Die folgende Grafik verdeutlicht die Struktur:

Im Unterkapitel "Wie funktioniert Velocity?" wurde bereits beschrieben, dass fünf Punkte realisiert werden müssen, um Velocity zu implementieren. Wie dieses bei der Servlet-Entwicklung geschieht soll an dieser Stelle erläutert werden.
Bevor ein Servlet ausgeführt werden kann, muss der Server gestartet werden. Beim Starten ruft dieser automatisch die init-Methoden der ihm zugeordneten Servlets auf (der Servlets, die sich in der Verzeichnisstruktur von ("/webapps" befinden). Die init-Methode der Klasse "VelocityServlet" initialisiert Velocity als Singleton. Das bedeutet, dass Velocity-Servlets immer nach dem "Singleton"-Modell initialisiert werden. Damit ist der erste von fünf Schritten zur Benutzung von Velocity abgehakt. Erfolgt eine Anfrage an ein Servlet wird eine der Methoden "doGet" oder "doPost" der Klasse "HttpServlet" aufgerufen, welche in der Klasse "VelocityServlet" überschrieben werden. Beide Methoden machen nichts anderes, als die Methode "doRequest" aufzurufen.

die "doRequest"-Methode
Die Methode "doRequest" ist folgendermaßen implementiert:

01  protected void doRequest(HttpServletRequest request, HttpServletResponse response )
02       throws ServletException, IOException
03  {
04      try
05      {
06         Context context = createContext( request, response );
07         setContentType( request, response );
08         Template template = handleRequest( request, response, context );
09          if ( template == null )
10          {
11              return;
12          }
13          mergeTemplate( template, context, response );
14          requestCleanup( request, response, context );
15      }
16      catch (Exception e)
17      {
18          error( request, response, e);
19      }
20  }
Zuerst erstellt die Methode ein "context"-Objekt (Zeile 06). Nachdem in Zeile 07 der Typ des Contents gesetzt wird, erzeugt die Methode in Zeile 08 ein neues Template, indem die Methode "handleRequest" aufgerufen wird. Diese Methode wird im Folgenden noch weiter behandelt. Zeile 13 ruft die Methode auf, die für das Verknüpfen von Schablone und "context"-Objekt zuständig ist. Wie man erkennt, werden in dieser Methode zwei weitere Schritte durchgeführt, die für die Implementierung von Velocity nötig sind ("context"-Objekt erstellen, Verknüpfung von Schablone und "context"-Objekt).
Die fehlenden zwei Schritte werden in der Methode "handleRequest" durchgeführt. Diese ist die einzige Methode, die in der zu erstellenden Java-Klasse implementiert werden muss. Dabei kann man zwischen zwei verschiedenen Versionen unterscheiden:

public Template handleRequest (Context)
public Template handleRequest (HttpServletRequest, HttpServletResponse, Context
Das übergebene "context"-Objekt beinhaltet bisher noch keine Daten, die in der Schablone ausgegeben werden sollen. Das Hinzufügen dieser Daten geschieht in der Methode "handleRequest". Der Rückgabewert der Methode soll eine Schablone sein. Mit Hilfe des Methodenaufrufs "getTemplate" (implementiert in der Klasse "VelocityServlet"), wird eine Schablone ausgewählt, welche zurückgegeben werden soll. Damit sind alle fünf Schritte eingebunden und die Erstellung eines Velocity-Servlets ist fertig.

ein Beispiel
Das folgende Beispiel zeigt ein Velocity-Servlet:

01 public class SampleServlet extends VelocityServlet
02 {
03  public Template handleRequest( HttpServletRequest request,
04                                 HttpServletResponse response,
05                                 Context context )
06  {
07      String p1 = "Bill";
08      String p2 = "Bob";
09      Vector vec = new Vector();
10      vec.addElement( p1 );
11      vec.addElement( p2 );
12      context.put("list", vec );
13      Template template = null;
14      try
15      {
16          template =  getTemplate("sample.vm");
17      }
18      catch( ResourceNotFoundException rnfe )
19      {
20        // couldn't find the template
21      }
22      catch( ParseErrorException pee )
23      {
24        // syntax error : problem parsing the template
25      }
26      catch( Exception e )
27      {}
28      return template;
29  }
30 }
In Zeile 07 und 08 werden zwei Variablen angelegt, die in Zeile 10 und 11 einem "Vector"-Objekt zugefügt werden. Danach wird das "Vector"-Objekt dem "context"-Objekt zugefügt (Zeile 12). Es erhält den Schlüsselbezeichner "list". Dieses ist der Name, unter dem das Vector-Objekt in der Schablone angesprochen werden kann. In Zeile 16 wird schließlich eine Schablone ausgewählt. Die weiteren Zeilen überprüfen, ob bei der Auswahl der Schablone Fehler aufgetreten sind.
Die Schablone zu dem Servlet könnte folgendermaßen aussehen:

01 <html>
02 <head><title>Sample velocity page</title></head>
03 <body bgcolor="#ffffff">
04 <center>
05 <h2>Hello from velocity!</h2>
06 <i>Here's the list of people</i>
07 <table cellspacing="0" cellpadding="5" width="100%">
08    <tr>
09        <td bgcolor="#eeeeee" align="center">
10            Names
11        </td>
12    </tr>
13    #foreach ($name in $list)
14    <tr>
15        <td bgcolor="#eeeeee">$name</td>
16    </tr>
17    #end
18 </table>
19 </center>
20 </html>
Wie man erkennt, enthält die Schablone einige HTML-Tags. Dieses sollte auch nicht verwundern, da das Servlet Daten an einen Web-Browser sendet. Zwischen den HTML-Tags sorgen VTL-Anweisungen für das Eintragen von Daten. Im Beispiel wird eine Tabelle erzeugt (Zeile 07), die die Überschrift "Names" besitzt. In den weiteren Zeilen der Tabelle werden die Namen ausgegeben, die in der Java-Klasse dem "Vector"-Objekt zugefügt wurden, indem eine "#foreach"-Schleife das "Vector"-Objekt ausliest. Dieses geschieht in Zeile 13 bis 16.
Das Ergebnis im Web-Browser sieht so aus:



Methoden in der Klasse "VelocityServlet"
Neben der "doRequest"-Methode können weitere Methoden der "VelocityServlet"-Klasse überschrieben werden:

Context createContext(HttpServletRequest, HttpServletResponse )
Wird in der "handleRequest"-Methode aufgerufen. Erzeugt das "context"-Objekt und fügt die übergebenen Objekte hinzu, bevor es zurückgegeben wird.

void setContentType( HttpServletRequest,HttpServletResponse )
Wird in der "doRequest"-Methode aufgerufen. Setzt den Typ des Inhalts.

void mergeTemplate( Template, Context, HttpServletResponse ) Wird in der "doRequest"-Methode aufgerufen. Erzeugt die Ausgabe des Servlets aus Schablone und "context"-Objekt. Diese Methode greift auf einen Pool von Klassen zu. Nur in Ausnahmesituationen sollte sie überschrieben werden.

void requestCleanup( HttpServletRequest, HttpServletResponse , Context )
Wird in der "doRequest"-Methode aufgerufen. Die Methode soll genutzt werden, um Ressourcen freizugeben. Die default-Implementation bearbeitet nichts.

protected void error( HttpServletRequest, HttpServletResponse, Exception )
Wird in der "doRequest"-Methode aufgerufen. Dient zur Implementation von Fehlermeldung. Die default-Implementation gibt eine HTML-Fehlermeldungen mit Fehlerinformationen zurück.

Properties loadConfiguration( ServletConfig )
Wird in der "initVelocity"-Methode aufgerufen, welche von der "init"-Methode aufgerufen wird. Sie ermöglicht es, Velocity an die eigenen Anforderungen anzupassen. Darunter wird das Einbinden einer eigenen Velocity-Eigenschaften-Datei oder die Einstellung von Pfadangaben der Schablone etc. verstanden. Standardmäßig enthält die Methode nur das Setzen des Pfades für die Schablone. Als Eigenschaftendatei für Velocity wird die Standarddatei "velocity.properties" benutzt.
Als Beispiel soll nun erläutert werden, wie man eine eigene Eigenschaften-Datei einbindet, da dieses häufig benötigt wird, um Velocity auf die eigenen Bedürfnisse anzupassen. Die Methode "loadConfiguration" ist um folgende Zeilen Code zu erweitern:

01      String propsFile = config.getInitParameter(INIT_PROPS_KEY);
02      if ( propsFile != null )
03      {
04          String realPath = getServletContext().getRealPath(propsFile);
05          if ( realPath != null )
06          {
07              propsFile = realPath;
08          }
09          p.load( new FileInputStream(propsFile) );
10      }
Zuerst werden der Name der Datei und der Pfad ermittelt (Zeile 01), in dem sich die Eigenschaften-Datei befinden soll. Sie heißt meistens "velocity.properties", kann aber auch einen anderen Namen haben. Die Angaben über Pfad und Name werden in der Datei "web.xml" des "WEB-INF"-Verzeichnisses eines Servlets eingetragen. Dieses wird später näher erläutert.
Der bisher ermittelte Pfad enthält nur die Angabe innerhalb des Servlet-Verzeichnisses. Liegt unser Servlet z.B. im Verzeichnis "velexample" und die Eigenschaften-Datei (z.B. "myProperty.file") liegt auch in diesem Verzeichnis, würde in Zeile 01 als Pfad "/" und als Dateiname "myProperty.file" ausgegeben werden. Es muss nun noch ermittelt werden, wie der reale Pfad heißt (04) (der "gesamte" Pfad (z.B. "c:\tomcat\webapps\velexample")). Danach kann der Inhalt der Eigenschaften-Datei den Velocity-Eigenschaften zugefügt (09) werden.

web.xml
Nun stellt sich die Frage, woher die Methode in Zeile 01 wissen kann, in welcher Datei sich die Eigenschaften für das Servlet befinden. Dieses muss der Entwickler in der Datei "web.xml" eintragen. Sie befindet sich im Verzeichnis "WEB-INF" des Servlet-Verzeichnisses. Der Eintrag für ein Servlet könnte so aussehen:

01 <servlet>
02     <servlet-name>SampleServlet</servlet-name>
03     <servlet-class>SampleServlet</servlet-class>
04     <init-param>
05       <param-name>properties</param-name>
06       <param-value>/velocity.properties</param-value>
07     </init-param>
08     <load-on-startup>1</load-on-startup>
09 </servlet>
Interessant ist vor allem die Zeile 06, in der der Pfadname ("/") und der Dateiname ("velocity.properties") angegeben werden. Dieses liest der Befehl "config.getInitParameter(INIT_PROPS_KEY);" aus.

Velocity in Anwendungen

Die Entwicklung von Velocity-Anwendungen kommt der Entwicklung von Velocity-Servlets nahe. Der einzige Unterschied ist, dass die Initialisierung von Velocity vom Entwickler übernommen werden muss und nicht wie bei den Servlets von einer anderen Klasse ("VelocityServlet") übernommen wird.
Die Initialisierung kann auf verschiedene Arten erfolgen:

  • wenn die Standardwerte der Velocity-Eigenschaften-Datei benutzt werden sollen, reicht ein einfaches "Velocity.init()" aus, um Velocity zu initialisieren.
  • wenn einige neue Eigenschaften hinzugefügt werden sollen, können diese mit "Velocity.setProperty(String key, Object o)" gesetzt werden; danach muss nur noch "Velocity.init()" aufgerufen werden
  • wenn eine eigene Eigenschaften-Datei benutzt werden soll, erfolgt die Initialisierung über "Velocity.init(String filename)"
  • statt einer Datei kann auch ein "java.util.Properties"-Objekt übergeben werden, welches gesetzte Eigenschaften enthält: "Velocity.init(Properties p)"

  • Bei allen drei Arten von init() ist darauf zu achten, dass die gesetzten Eigenschaften die Standardeigenschaften ergänzen bzw. überschreiben. Nachdem Velocity initialisiert wurde können in der Java-Klasse verschiedene Methodenaufrufe des "Velocity"-Objektes erfolgen:

    evaluate( Context context, Writer out, String logTag, String instring )
    evaluate( Context context, Writer writer, String logTag, InputStream instream )

    Diese Methode verknüpft einen Input (String oder Stream) mit dem "context"-Objekt und gibt das Ergebnis an das Ausgabeobjekt ("Writer") weiter. Der "logTag"-String ist ein Kommentar, der den Aufruf der Methode in der Logdatei mit diesem Kommentar dokumentiert. Diese Methode ist dann sinnvoll einzusetzen, wenn ein Input (z.B. aus einer Datenbank o.ä.) VTL-Anweisungen enthält.

    invokeVelocimacro( String vmName, String namespace, String params[], Context context, Writer writer )
    Diese Methode ermöglicht es, Makros aus einer Schablone aufzurufen und das Ergebnis an das Ausgabeobjekt ("Writer") weiterzuleiten. "vmName" beinhaltet den Namen der Schablone, "namespace" den Namen des Makros und params[] ist die Parameterliste, die das Makro beim Aufruf erwartet. Zusätzlich wird das "context"-Objekt übergeben.

    mergeTemplate( String templateName, Context context, Writer writer )
    Diese Methode verknüpft die Schablone mit dem "context"-Objekt und gibt das Resultat an das Ausgabeobjekt ("Writer") weiter.

    boolean templateExists( String name )
    Diese Methode prüft, ob eine Schablone gefunden werden kann.

    ein Beispiel
    Nachdem die wichtigsten Methoden aufgezählt wurden, soll nun ein Beispiel die Benutzung von Velocity-Anwendungen verdeutlichen:
    
    01 import java.io.StringWriter;
    02 import org.apache.velocity.app.Velocity;
    03 import org.apache.velocity.VelocityContext;
    04 public class Example
    05 {
    06     public static void main( String args[] )
    07     {
    08         Velocity.init();
    09         VelocityContext context = new VelocityContext();
    10         context.put("name", "Velocity");
    11         context.put("project", "Jakarta");
    12         StringWriter w = new StringWriter();
    13         Velocity.mergeTemplate("testtemplate.vm", context, w );
    14         System.out.println(" template : " + w );
    15         String s = "We are using $project $name to render this.";
    16         w = new StringWriter();
    17         Velocity.evaluate( context, w, "mystring", s );
    18         System.out.println(" string : " + w );
    19     }
    20 }
    
    In diesem Beispiel reichen die Standardeinstellungen von Velocity aus. Deshalb wird in Zeile 08 die "init"-Methode ohne weitere Parameter aufgerufen. In Zeile 09 wird ein neues "context"-Objekt erstellt. Diesem werden zwei Einträge hinzugefügt (Zeile 10+11) bevor ein "StringWriter"-Objekt erstellt wird. Danach wird eine Schablone mit dem "context"-Objekt verknüpft (13) und das Ergebnis ausgegeben (14). Ein String von VTL-Anweisungen (15) wird nun mittels der Methode "evaluate" mit dem "context"-Objekt verknüpft (17) und ebenfalls ausgegeben (18).
    Betrachtet man das Beispiel aufmerksam, fällt auf, dass alle fünf Punkte zur "Implementierung von Velocity in einer Java-Klasse" erfüllt werden. In Zeile 13 wird die Schablone "testtemplate.vm" eingebunden. Diese beinhaltet folgenden Text:
    
    Hi!  This is $name from the $project project.
    

    Die Ausgabe sieht nach dem Start der Anwendung so aus:
    
    template : Hi!  This is Velocity from the Jakarta project.
    string : We are using Jakarta Velocity to render this.
    

    Exceptions
    Während des Ablaufs der Anwendung können drei verschiedene Exceptions auftreten:
    ResourceNotFoundException: Gibt an, dass die Schablone nicht gefunden werden konnte.
    ParseErrorException: Gibt an, dass beim Parsen in der VTL-Syntax Fehler aufgetreten sind.
    MethodInvocationException: Gibt an, dass bei einem Methodenaufruf eines Objektes, das sich im "context"-Objekt befindet, ein Fehler aufgetreten ist.
    Alle drei Exceptions können im Paket "org.apache.velocity.exception" gefunden werden. Bei einem Auftreten senden sie eine Meldung an die Logdatei.


    ... [ Die Schablone ] ... [ Die Java-Klasse ] ... [ Ausblick ] ... [ Inhaltsverzeichnis ] ...