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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
<?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}&month={
message/@month}&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&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>
Copyright © 2002 O'Reilly & Associates. All rights reserved.