Book HomeJava and XSLTSearch this book

Appendix A. Discussion Forum Code

This appendix contains all of the remaining code from the discussion forum example presented in Chapter 7, "Discussion Forum". These are the "simple" files that did not merit a lot of explanation in the text. All of the source code can be downloaded from this book's companion web site at http://www.oreilly.com/catalog/javaxslt.

BoardSummaryImpl.java(1) (shown in Example A-1) provides a default implementation of the BoardSummary interface.

Example A-1. BoardSummaryImpl.java(1)

package com.oreilly.forum.domain;

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

/**
 * An implementation of the BoardSummary interface.
 */
public class BoardSummaryImpl implements BoardSummary {
    private long id;
    private String name;
    private String description;
    private List monthsWithMessages;

    /**
     * @param monthsWithMessages a list of MonthYear objects.
     */
    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 an iterator of <code>MonthYear</code> objects.
     */
    public Iterator getMonthsWithMessages( ) {
        return this.monthsWithMessages.iterator( );
    }
} 

BoardSummaryImpl.java(2) (shown in Example A-2) is an alternate implementation of the BoardSummary interface. This class is used by the fake data implementation, which is useful for testing purposes when a database is not available.

Example A-2. 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;
    // a list of MonthYear objects
    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( );

        // update the monthsWithMessages list
        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( );
    }
} 

DataException.java (shown in Example A-3) is a generic exception that occurs when something goes wrong with the underlying database. This prevents database-specific code from creeping into the application, making it possible to migrate to other data sources in the future.

Example A-3. DataException.java

package com.oreilly.forum.adapter;

/**
 * An exception that indicates some operation with the back-end
 * data source failed.
 */
public class DataException extends Exception {
    private Throwable rootCause;

    /**
     * Wrap a DataException around another throwable.
     */
    public DataException(Throwable rootCause) {
        super(rootCause.getMessage( ));
        this.rootCause = rootCause;
    }

    /**
     * Construct an exception with the specified detail message.
     */
    public DataException(String message) {
        super(message);
    }

    /**
     * @return a reference to the root exception or null.
     */
    public Throwable getRootCause( ) {
        return this.rootCause;
    }
} 

DataUtil.java (shown in Example A-4) is a simple utility method that deals with dates.

Example A-4. DateUtil.java

package com.oreilly.forum.domain;

import java.util.*;

/**
 * Misc utility functions for dates. Methods are synchronized because
 * the same Calendar instance is shared.
 */
public final class DateUtil {

    private static Calendar cal = Calendar.getInstance( );

    /**
     * @return the day of the month for a given date.
     */
    public synchronized static int getDayOfMonth(Date date) {
        cal.setTime(date);
        return cal.get(Calendar.DAY_OF_MONTH);
    }

    /**
     * @return the month number for a given date.
     */
    public synchronized static int getMonth(Date date) {
        cal.setTime(date);
        return cal.get(Calendar.MONTH);
    }

    /**
     * @return the year number for the given date.
     */
    public synchronized static int getYear(Date date) {
        cal.setTime(date);
        return cal.get(Calendar.YEAR);
    }

    private DateUtil( ) {
    }
}

DayMonthYear.java (shown in Example A-5) is a helper class that groups a day, month, and year together. It also supports comparisons for sorting purposes.

Example A-5. DayMonthYear.java

package com.oreilly.forum.domain;

import java.util.Date;

/**
 * Represents a day, month, and year.
 */
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( );
    }
}

FakeDataAdapter.java (shown in Example A-6) allows the discussion forum to be executed without any database. This class was written before the database was implemented, and is useful for testing purposes only.

Example A-6. 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 {
    // a list of BoardSummary objects
    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 Programming",
                "General programming questions about Java.");
        BoardSummary bs1 = new BoardSummaryImpl(1L,
                "XSLT Stylesheet Techniques",
                "Writing effective XSLT stylesheets.");
        this.allBoards.add(bs0);
        this.allBoards.add(bs1);

        this.postNewMessage(0L, "First subject in Java Prog",
                "burke_e@yahoo.com", "Sample message text");

    }

    /**
     * @param msgID must be a valid message identifier.
     * @return the message with the specified id.
     * @throws DataException if msgID does not exist or a database
     * error occurs.
     */
    public Message getMessage(long msgID) throws DataException {
        Message msg = (Message) this.messageMap.get(new Long(msgID));
        if (msg != null) {
            return msg;
        }
        throw new DataException("Invalid msgID");
    }

    /**
     * If no messages exist for the specified board and month, return
     * an empty iterator.
     * @return an iterator of <code>MessageSummary</code> objects.
     * @throws DataException if the boardID is illegal or a database
     * error occurs.
     */
    public Iterator getAllMessages(long boardID, MonthYear month)
            throws DataException {
        // this is slow, but works fine for a fake implementation
        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( );
    }

    /**
     * Add a reply to an existing message.
     *
     * @throws DataException if a database error occurs, or if any
     * parameter is illegal.
     */
    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;
    }

    /**
     * Post a new message.
     *
     * @return the newly created message.
     * @throws DataException if a database error occurs, or if any
     * parameter is illegal.
     */
    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 an iterator of <code>BoardSummary</code> objects.
     */
    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("Illegal boardID: " + boardID);
    }

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

MessageImpl.java (shown in Example A-7) is an implementation of the Message interface.

Example A-7. MessageImpl.java

package com.oreilly.forum.domain;

import java.util.*;

/**
 * An implementation of the Message interface.
 */
public class MessageImpl extends MessageSummaryImpl implements Message {
    private String text;

    /**
     * Construct a new instance of this class.
     */
    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 the text of this message.
     */
    public String getText( ) {
        return this.text;
    }
} 

MessageSummaryImpl.java (shown in Example A-8) is an implementation of the MessageSummary interface.

Example A-8. MessageSummaryImpl.java

package com.oreilly.forum.domain;

import java.util.*;

/**
 * Implementation of the 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;
    }

    /**
     * Sorts by create date followed by message subject.
     */
    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;
    }
} 

MonthYear.java (shown in Example A-9) groups a month and year together. It also supports sorting.

Example A-9. MonthYear.java

package com.oreilly.forum.domain;

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

/**
 * Represents a month and a year.
 */
public class MonthYear implements Comparable, Serializable {
    private int month;
    private int year;

    /**
     * Construct a new object representing the current instant in time.
     */
    public MonthYear( ) {
        this(new Date( ));
    }

    /**
     * Construct a new object with the given date.
     */
    public MonthYear(Date date) {
        this(DateUtil.getMonth(date), DateUtil.getYear(date));
    }

    /**
     * Construct a new object with the given month and year.
     * @param month a zero-based month, just like java.util.Calendar.
     */
    public MonthYear(int month, int year) {
        this.month = month;
        this.year = year;
    }

    /**
     * Compare this MonthYear object to another.
     */
    public int compareTo(Object obj) {
        MonthYear rhs = (MonthYear) obj;
        // first compare year
        if (this.year < rhs.year) {
            return -1;
        } else if (this.year > rhs.year) {
            return 1;
        }
        // then month
        if (this.month < rhs.month) {
            return -1;
        } else if (this.month > rhs.month) {
            return 1;
        }

        return 0;
    }

    /**
     * @return true if the specified date occurs sometime during this month.
     */
    public boolean containsInMonth(DayMonthYear date) {
        return date.getMonth( ) == this.month
                && date.getYear( ) == this.year;
    }

    /**
     * @return the month number, starting with 0 for January.
     */
    public int getMonth( ) {
        return this.month;
    }

    /**
     * @return the year number.
     */
    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;
    }
}

The viewMsg.xslt XSLT stylesheet (shown in Example A-10) displays a web page for a single message.

Example A-10. viewMsg.xslt

<?xml version="1.0" encoding="UTF-8"?>
<!--
***********************************************************
** viewMsg.xslt
**
** Shows details for a specific message.
***********************************************************
-->
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="utils.xslt"/>
  <xsl:param name="rootDir" select="'../docroot/'"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" 
    indent="yes" 
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
    
   <!--
  **********************************************************
  ** Create the XHTML web page
  *******************************************************-->
  <xsl:template match="/">
    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
        <title>View Message</title>
        <link href="{$rootDir}forum.css" 
            rel="stylesheet" type="text/css"/>
      </head>
      <body>
        <div class="box1">
          <h1>View Message</h1>
          <div>
            <xsl:value-of select="message/board/name"/>
          </div>
        </div>
        <!-- ===== Quick Actions ====== -->
        <h3>Quick Actions</h3>
        <ul>
          <li>Return to 
                           <!-- long line wrapped -->
          <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> messages for <xsl:value-of select="message/board/name"/>
          </li>
          <li>Return to the <a href="home">home page</a>
          </li>
          <li>
            <a href="postMsg?mode=replyToMsg&amp;origMsgID={message/@id}">Reply</a> 
              to this message</li>
        </ul>
        <h3>Message</h3>
        <div class="box2">
          <xsl:apply-templates select="message"/>
        </div>
      </body>
    </html>
  </xsl:template>
  
  <!--
  **********************************************************
  ** Show details for the <message> element
  *******************************************************-->
  <xsl:template match="message">
    <div>
      <div style="font-weight: bold;">
        <xsl:value-of select="subject"/>
      </div>
      <xsl:text> posted by </xsl:text>
      <a href="mailto:{authorEmail}">
        <xsl:value-of select="authorEmail"/>
      </a>
      <xsl:text> on </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>
  
  <!--
  **********************************************************
  ** Show a link to the message that this one is in
  ** response to.
  *******************************************************-->
  <xsl:template match="inResponseTo">
    <div style="text-indent: 2em;">
      <xsl:text>In response to </xsl:text>
      <a href="viewMsg?msgID={@id}">
        <xsl:value-of select="subject"/>
      </a>
    </div>
  </xsl:template>
</xsl:stylesheet>


Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.