Code zum Diskussionsforum

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

Diese Seite enthält den kompletten Rest des Codes zum Diskussionsforum. Dies sind nur »simple« Dateien, die keine großen Erklärungen im Text verdienen. Der vollständige Quellcode kann von der Website zum Buch heruntergeladen werden.

Das folgende Beispiel stellt eine Standard-Implementierung des BoardSummary-Interface dar.

Beispiel: BoardSummaryImpl.java(1)

package com.oreilly.forum.domain;

import com.oreilly.forum.domain.*;
import java.util.*;

/**
 * Eine Implementierung des BoardSummary-Interface.
 */
public class BoardSummaryImpl implements BoardSummary {
  private long id;
  private String name;
  private String description;
  private List monthsWithMessages;
  
  /**
   * @param monthsWithMessages eine Liste von MonthYear-Objekten.
   */
  public BoardSummaryImpl(long id, String name, String description,
      List monthsWithMessages) {
    this.id = id;
    this.name = name;
    this.description = description;
    this.monthsWithMessages = monthsWithMessages;
        
  }
  
  public long getID( ) {
    return this.id;
  }
  
  public String getName( ) {
    return this.name;
  }
  
  public String getDescription( ) {
    return this.description;
  }
  
  /**
   * @return ein Iterator von <code>MonthYear</code>-Objekten.
   */
  public Iterator getMonthsWithMessages( ) {
    return this.monthsWithMessages.iterator( );
  }
}  

Das folgende Beispiel ist eine alternative Implementierung des BoardSummary-Interface. Diese Klasse wird von der »Schein-Daten«-Implementierung genutzt, was zu Testzwecken, wenn keine Datenbank verfügbar ist, sehr nützlich ist.

Beispiel: BoardSummaryImpl.java(2)

package com.oreilly.forum.fakeimpl;

import com.oreilly.forum.domain.*;
import java.util.*;

public class BoardSummaryImpl implements BoardSummary {
  private long id;
  private String name;
  private String description;
  // eine Liste von MonthYear-Objekten
  private List monthsWithMessages;
  
  public BoardSummaryImpl(long id, String name, String description) {
    this.id = id;
    this.name = name;
    this.description = description;
    this.monthsWithMessages = new ArrayList( );
  }
  
  public void messageAdded(Message msg) {
    DayMonthYear createDate = msg.getCreateDate( );

    // die monthsWithMessages-Liste wird aktualisiert
    Iterator iter = this.monthsWithMessages.iterator( );
    while (iter.hasNext( )) {
      MonthYear curMonth = (MonthYear) iter.next( );
      if (createDate.getMonth() == curMonth.getMonth( )
          && createDate.getYear() == curMonth.getYear( )) {
        return;
      }
    }
  
    this.monthsWithMessages.add(createDate);
  }

  public long getID( ) {
    return this.id;
  }

  public String getName( ) {
    return this.name;
  }

  public String getDescription( ) {
    return this.description;
  }

  public Iterator getMonthsWithMessages( ) {
    return this.monthsWithMessages.iterator( );
  }
}

Das folgende Beispiel ist eine Allzweck-Exception, die auftritt, wenn etwas mit der zugrundeliegenden Datenbank schiefgeht. Das verhindert das Einschleichen von anwendungsspezifischem Code in die Anwendung und ermöglicht die zukünftige Migration auf andere Datenquellen.

Beispiel: DataException.java

package com.oreilly.forum.adapter;

/**
 * Eine Exception, die angibt, daß ein Vorgang auf der zugrundeliegenden Datenquelle
 * gescheitert ist.
 */
public class DataException extends Exception {
  private Throwable rootCause;
  
  /**
   * DataException kapselt ein anderes Throwable-Objekt.
   */
  public DataException(Throwable rootCause) {
    super(rootCause.getMessage( ));
    this.rootCause = rootCause;
  }
  
  /**
   * Eine Exception mit der angegebenen Detailmeldung wird konstruiert.
   */
  public DataException(String message) {
    super(message);
  }
  
  /**
   * @return eine Referenz auf die gekapselte Exception oder null.
   */
  public Throwable getRootCause( ) {
    return this.rootCause;
  }
}

Das folgende Beispiel ist eine simple Tool-Methode, die mit Datumsangaben zu tun hat.

Beispiel: DateUtil.java

package com.oreilly.forum.domain;

import java.util.*;

/**
 * Verschiedene Tool-Funktionen für Datumsangaben. Die Methoden werden synchronisiert,
 * weil die Calendar-Instanz von mehreren gleichzeitig genutzt wird.
 */
public final class DateUtil {
  
  private static Calendar cal = Calendar.getInstance( );
  /**
   * @return Tag des Monats für ein gegebenes Datum.
   */
  public synchronized static int getDayOfMonth(Date date) {
    cal.setTime(date);
    return cal.get(Calendar.DAY_OF_MONTH);
  }
  
  /**
   * @return Zahlwert des Monats für ein gegebenes Datum.
   */
  public synchronized static int getMonth(Date date) {
    cal.setTime(date);
    return cal.get(Calendar.MONTH);
  }
  
  /**
   * @return Zahlwert des Jahre in einem gegebenen Datum.
   */
  public synchronized static int getYear(Date date) {
    cal.setTime(date);
    return cal.get(Calendar.YEAR);
  }
  
  private DateUtil( ) {
  }
}

Das folgende Beispiel ist eine Hilfsklasse, die einen Tag, einen Monat und ein Jahr gruppiert und unter anderem zu Sortierzwecken Vergleiche unterstützt.

Beispiel: DayMonthYear.java

package com.oreilly.forum.domain;

import java.util.Date;

/**
 * Repräsentiert einen Tag, einen Monat und ein Jahr.
 */
public class DayMonthYear extends MonthYear {
  private int day;
  
  public DayMonthYear( ) {
    this(new Date( ));
  }
  
  public DayMonthYear(Date date) {
    super(date);
    this.day = DateUtil.getDayOfMonth(date);
  }
  
  public DayMonthYear(int day, int month, int year) {
    super(month, year);
    this.day = day;
  }
  
  public int getDay( ) {
    return this.day;
  }
  
  public boolean equals(Object obj) {
    if (obj instanceof DayMonthYear) {
      DayMonthYear rhs = (DayMonthYear) obj;
      return super.equals(obj) && this.day == rhs.day;
    }
    return false;
  }
  
  public int hashCode( ) {
    return super.hashCode( ) ^ this.day;
  }
  
  public int compareTo(Object obj) {
    DayMonthYear rhs = (DayMonthYear) obj;
    int comparison = super.compareTo(obj);
    if (comparison == 0) {
      if (this.day < rhs.day) {
        return -1;
      } else if (this.day > rhs.day) {
        return 1;
      }
    }
    return comparison;
  }
  public String toString( ) {
    return getMonth() + "/" + getDay() + "/" + getYear( );
  }
}

Das folgende Beispiel erlaubt die Ausführung des Diskussionsforums ohne eine Datenbank. Diese Klasse wurde vor der Implementierung der Datenbank geschrieben und ist nur zu Testzwecken nützlich.

Beispiel: FakeDataAdapter.java

package com.oreilly.forum.fakeimpl;

import com.oreilly.forum.*;
import com.oreilly.forum.adapter.*;
import com.oreilly.forum.domain.*;
import java.util.*;

public class FakeDataAdapter extends DataAdapter {
  // eine Liste von BoardSummary-Objekten
  private List allBoards;
  private static long nextMessageID = 0;
  private Map messageMap = new HashMap( );
  
  public FakeDataAdapter( ) throws DataException {
    this.allBoards = new ArrayList( );
    
    BoardSummary bs0 = new BoardSummaryImpl(0L,
      "Java-Programmierung",
      "Allgemeine Programmierungsfragen zu Java");
    BoardSummary bs1 = new BoardSummaryImpl(1L,
      "XSLT-Stylesheet-Techniken",
      "effektive XSLT-Stylesheets schreiben.");
    this.allBoards.add(bs0);
    this.allBoards.add(bs1);
    
    this.postNewMessage(0L, "Erster Eintrag in Java-Programmierung",
      "burke_e@yahoo.com", "Beispieltext für eine Nachricht");
    
    }
  
    /**
     * @param msgID muß ein gültiger Nachrichten-Identifikator sein.
     * @return die Nachricht mit der angegebenen ID.
     * @throws DataException, wennn msgID nicht existiert oder ein Datenbankfehler
     * auftritt.
     */
    public Message getMessage(long msgID) throws DataException {
      Message msg = (Message) this.messageMap.get(new Long(msgID));
      if (msg != null) {
        return msg;
      }
      throw new DataException("ungültige msgID");
    }
  
    /**
     * Wenn zum angegebenen Board und Monat keine Nachrichten vorliegen, wird ein
     * leerer Iterator zurückgegeben.
     * @return ein Iterator von <code>MessageSummary</code>-Objekten.
     * @throws DataException, wenn boardID unzulässig ist oder ein Datenbankfehler
     * auftritt.
     */
    public Iterator getAllMessages(long boardID, MonthYear month)
        throws DataException {
      // das hier ist langsam, reicht aber für eine Schein-Implementierung.
      List msgs = new ArrayList( );
      Iterator iter = this.messageMap.values().iterator( );
      while (iter.hasNext( )) {
        MessageSummary curMsg = (MessageSummary) iter.next( );
        if (curMsg.getBoard().getID( ) == boardID
            && month.containsInMonth(curMsg.getCreateDate( ))) {
          msgs.add(curMsg);
        }
      }
      return msgs.iterator( );   
    }
  
    /**
     * Einer existierenden Nachricht wird eine Antwort hinzugefügt.
     *
     * @throws DataException, wenn ein Datenbankfehler auftritt oder der
     * Parameter unzulässig ist.
     */
    public Message replyToMessage(long origMsgID, String msgSubject,
        String authorEmail, String msgText) throws DataException {
      MessageSummary origMsg = getMessage(origMsgID);
      long msgID = getNextMessageID( );
          
      Message msg = new MessageImpl(msgID, new DayMonthYear( ), origMsg.getBoard( ),
        msgSubject, authorEmail, msgText, origMsgID);
          
      this.messageMap.put(new Long(msg.getID( )), msg);
      return msg;
    }
  
    /**
     * Eine neue Nachricht wird eingetragen.
     *
     * @return die neu erzeugte Nachricht.
     * @throws DataException, wenn ein Datenbankfehler auftritt oder ein
     * Parameter unzulässig ist.
     */
    public Message postNewMessage(long boardID, String msgSubject,
        String authorEmail, String msgText) throws DataException {
      BoardSummary boardSum = getBoardSummary(boardID);
      long msgID = getNextMessageID( );
          
      Message msg = new MessageImpl(msgID, new DayMonthYear( ), boardSum,
        msgSubject, authorEmail, msgText, -1);
      this.messageMap.put(new Long(msg.getID( )), msg);
          
      ((BoardSummaryImpl) boardSum).messageAdded(msg);
          
      return msg;
    }
  
    /**
     * @return ein Iterator von <code>BoardSummary</code>-Objekten.
     */
    public Iterator getAllBoards( ) throws DataException {
      return this.allBoards.iterator( );
    }
  
    public BoardSummary getBoardSummary(long boardID)
        throws DataException {
      Iterator iter = getAllBoards( );
      while (iter.hasNext( )) {
        BoardSummary curBoard = (BoardSummary) iter.next( );
        if (curBoard.getID( ) == boardID) {
        return curBoard;
      }
    }
    throw new DataException("Unzulässige boardID: " + boardID);
  }

  private synchronized static long getNextMessageID( ) {
    nextMessageID++;
    return nextMessageID;
  }
}

Das folgende Beispiel ist eine Implementierung des Message-Interface.

Beispiel: MessageImpl.java

package com.oreilly.forum.domain;

import java.util.*;

/**
 * Eine Implementierung des Message-Interface.
 */
public class MessageImpl extends MessageSummaryImpl implements Message {
  private String text;
  
  /**
   * Eine neue Instanz dieser Klasse wird konstruiert.
   */
  public MessageImpl(long id, DayMonthYear createDate,
      BoardSummary board, String subject, String authorEmail,
      String text, long inReplyTo) {
    super(id, createDate, board, subject, authorEmail, inReplyTo);
    this.text = text;
  }
  
  /**
   * @return der Text dieser Nachricht.
   */
  public String getText( ) {
    return this.text;
  }
}

Das folgende Beispiel ist eine Implementierung des MessageSummary-Interface.

Beispiel: MessageSummaryImpl.java

package com.oreilly.forum.domain;

import java.util.*;

/**
 * Implementierung des MessageSummary-Interface.
 */
public class MessageSummaryImpl implements MessageSummary {
  private long id;
  private BoardSummary board;
  private String subject;
  private String authorEmail;
  private DayMonthYear createDate;
  private long inReplyTo;
  
  public MessageSummaryImpl(long id, DayMonthYear createDate,
      BoardSummary board, String subject, String authorEmail,
      long inReplyTo) {
    this.id = id;
    this.createDate = createDate;
    this.board = board;
    this.subject = subject;
    this.authorEmail = authorEmail;
    this.inReplyTo = inReplyTo;
  }
  
  public long getInReplyTo( ) {
    return this.inReplyTo;
  }
  
  public long getID( ) {
    return this.id;
  }
  
  public DayMonthYear getCreateDate( ) {
    return this.createDate;
  }
  
  public BoardSummary getBoard( ) {
    return this.board;
  }
  
  public String getSubject( ) {
    return this.subject;
  }
  
  public String getAuthorEmail( ) {
    return this.authorEmail;
  }
  
  public boolean equals(Object obj) {
    if (obj instanceof MessageSummaryImpl) {
      MessageSummaryImpl rhs = (MessageSummaryImpl) obj;
      return this.id == rhs.id;
    }
    return false;
  }
  
  public int hashCode( ) {
    return (int) this.id;
  }
  
  /**
   * Sortiert nach Erstellungsdatum gefolgt vom Betreff der Nachricht.
   */
  public int compareTo(Object obj) {
    if (this == obj) {
      return 0;
    }
    MessageSummaryImpl rhs = (MessageSummaryImpl) obj;
  
    int comparison = this.createDate.compareTo(rhs.createDate);
    if (comparison != 0) {
      return comparison;
    }
  
    comparison = this.subject.compareTo(rhs.subject);
    if (comparison != 0) {
      return comparison;
    }
  
    return 0;
  }
}
  

Das folgende Beispiel gruppiert einen Monat und ein Jahr und unterstützt Sortierung.

Beispiel: MonthYear.java

package com.oreilly.forum.domain;

import java.io.Serializable;
import java.util.*;

/**
 * Repräsentiert einen Monat und ein Jahr.
 */
public class MonthYear implements Comparable, Serializable {
  private int month;
  private int year;
  
  /**
   * Ein neues Objekt, das den gegenwärtigen Zeitpunkt darstellt, wird konstruiert.
   */
  public MonthYear( ) {
    this(new Date( ));
  }
  
  /**
   * Ein neues Objekt wird mit dem übergebenen Datum konstruiert.
   */
  public MonthYear(Date date) {
    this(DateUtil.getMonth(date), DateUtil.getYear(date));
  }
  
  /**
   * Ein neues Objekt mit dem übergebenen Monat und Jahr wird konstruiert.
   * @param month ein null-basierter Monat, wie in java.util.Calendar.
   */
  public MonthYear(int month, int year) {
    this.month = month;
    this.year = year;
  }
  
  /**
   * Vergleiche dieses MonthYear-Objekt mit einem anderen.
   */
  public int compareTo(Object obj) {
    MonthYear rhs = (MonthYear) obj;
    // erst wird das Jahr verglichen
    if (this.year < rhs.year) {
      return -1;
    } else if (this.year > rhs.year) {
      return 1;
    }
    // dann der Monat
    if (this.month < rhs.month) {
      return -1;
    } else if (this.month > rhs.month) {
      return 1;
    }

    return 0;
  }

  /**
   * @return true, wenn das angegebene Datum in diesem Monat liegt.
   */
  public boolean containsInMonth(DayMonthYear date) {
    return date.getMonth( ) == this.month
      && date.getYear( ) == this.year;
  }

  /**
   * @return den Zahlwert des Monats, angefangen mit 0 für Januar.
   */
  public int getMonth( ) {
    return this.month;
  }

  /**
   * @return die Jahreszahl.
   */

  public int getYear( ) {
    return this.year;
  }

  public boolean equals(Object obj) {
    if (obj instanceof MonthYear) {
      MonthYear rhs = (MonthYear) obj;
      return this.month == rhs.month
        && this.year == rhs.year;
    }
    return false;
  }

  public int hashCode( ) {
    return this.month ^ this.year;
  }
}
  

Das folgende XSLT-Stylesheet zeigt die Webseite für eine einzelne Nachricht an.

Beispiel: viewMsg.xslt

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 ***********************************************************
 ** viewMsg.xslt
 **
 ** Zeigt die Details einer bestimmten Nachricht an.
 *********************************************************** -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
  <xsl:import href="utils.xslt"/>
  <xsl:param name="rootDir" select="'../docroot/'"/>
  <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
  <!--
   **********************************************************
   ** Die XHTML-Webseite wird erzeugt.
   ******************************************************* -->
  <xsl:template match="/">
    <html>
      <head>
        <title>Nachricht anzeigen</title>
        <link href="{$rootDir}forum.css" rel="stylesheet" type="text/css"/>
      </head>
      <body>
        <div class="box1">
          <h1>Nachricht anzeigen</h1>
          <div><xsl:value-of select="message/board/name"/></div>
        </div>
        <!-- ===== Quick Actions ====== -->
        <h3>Quick Actions</h3>
        <ul>
          <li>Zurück zu
            <!-- eine lange, umbrochene Zeile -->
            <a href="viewMonth?boardID={message/board/@id}&amp;month={message/@month}&amp;year={message/@year}">
              <xsl:call-template name="utils.printLongMonthName">
                <xsl:with-param name="monthNumber" select="message/@month"/>
              </xsl:call-template>,
              <xsl:value-of select="message/@year"/>
            </a> Nachrichten für <xsl:value-of select="message/board/name"/>
          </li>
          <li>Zurück zur <a href="home">Startseite</a></li>
          <li>Auf diese Nachricht <a href="postMsg?mode=replyToMsg&amp;origMsgID={message/@id}">antworten</a></li>
        </ul>
        <h3>Nachricht</h3>
        <div class="box2"><xsl:apply-templates select="message"/></div>
      </body>
    </html>
  </xsl:template>
  <!--
   **********************************************************
   ** Details zum <message>-Element anzeigen.
   ******************************************************* -->
  <xsl:template match="message">
    <div>
      <div style="font-weight: bold;"><xsl:value-of select="subject"/></div>
      <xsl:text> erstellt von </xsl:text>
      <a href="mailto:{authorEmail}"><xsl:value-of select="authorEmail"/></a>
      <xsl:text> Datum: </xsl:text>
      <xsl:call-template name="utils.printShortMonthName">
        <xsl:with-param name="monthNumber" select="@month"/>
      </xsl:call-template>
      <xsl:text> </xsl:text>
      <xsl:value-of select="@day"/>
      <xsl:text>, </xsl:text>
      <xsl:value-of select="@year"/>
      <xsl:apply-templates select="inResponseTo"/>
    </div>
    <pre><xsl:value-of select="text"/></pre>
  </xsl:template>
  <!--
   **********************************************************
   ** Ein Link zu der Nachricht, auf die diese die Antwort ist,
   ** wird ausgegeben.
   ******************************************************* -->
  <xsl:template match="inResponseTo">
    <div style="text-indent: 2em;">
      <xsl:text>Als Antwort auf </xsl:text>
      <a href="viewMsg?msgID={@id}"><xsl:value-of select="subject"/></a>
    </div>
  </xsl:template>
</xsl:stylesheet>
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