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",
"[email protected]", "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.