Discussion Forum Code

This appendix contains all of the remaining code from the discussion forum example presented in "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 tutorial's companion website at http://www.anonymous.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.anonymous.forum.domain; import com.anonymous.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.anonymous.forum.fakeimpl; import com.anonymous.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.anonymous.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.anonymous.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.anonymous.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.anonymous.forum.fakeimpl; import com.anonymous.forum.*; import com.anonymous.forum.adapter.*; import com.anonymous.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 Developing", "General developing 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.anonymous.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.anonymous.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.anonymous.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" />
</head>
<body>
<div >
<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 >
<xsl:apply-templates select="message"/>
</div>
</body>
</html>
</xsl:template>
<!-- ********************************************************** ** Show details for the <message> element *******************************************************-->
<xsl:template match="message">
<div>
<div >
<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 >
<xsl:text>In response to </xsl:text>
<a href="viewmsg?msgid={@id}">
<xsl:value-of select="subject"/>
</a>
</div>
</xsl:template>
</xsl:stylesheet>