Sun Java Solaris Communities My SDN Account Join SDN
 
Article

The Java Technologies Behind the New JDC Forums

 
 

Articles Index

Technologies Behind the New Developer Forums

There is no shortage of Java technologies in the open-source community, but one project in particular piqued the interest of the engineers behind the Java Developer Connection, the Solaris Connection, and Dot Com Builder web sites. The project is called Jive, hosted by JiveSoftware.com, and Jive has been installed for the developer site forums.

Jive is forum software that features content filters and user defined "skins" to change the look and feel and function of the forums, powered by a plugable backend. Written with an object-oriented design, Jive is extensible, scalable, and secure. In addition, the Jive forums leverage server side technologies, such as JavaServer Pages (JSP), JDBCTM, servlets, and the JavaMail API in combination with XML.

Because of the differences in the look and feel between the three developer sites and the high traffic and volume of the JDC forums in particular, Jive was a good choice. Its basic architecture allows for cosmetic changes from one developer site to the next, and its plugable database schemas worked for smooth integration with the current backend configurations.

Part I of this article focuses on the Java technologies used in the general architecture of the new JDC forums, as well as the configurations and changes to code for the JDC special forum features. To follow the concepts below, you'll need familiarity with basic Java programming syntax, JSP concepts, and the JavaMail and JDBC APIs. For more details on these specific technologies and deploying Jive on a web site, see For More Information below.

Contents

General Forum Architecture

Look and Feel

Special Forum Features

Conclusion

For More Information

General Forum Architecture

The three developer sites implement Jive 2.0 software and architecture. This application is a three tier system, consisting of skins, filters and interfaces, and the database backend. Skins contain the logic necessary to display a user interface, while the Jive core classes provide the logic to interact with the forum data. Between the skins and the data are filters, which remove profane language or include syntax highlighting.

For information on setting and configuring the server for Jive, see For More Information below.

The two starting points into the Jive core libraries are:

  • AuthorizationFactory is responsible for passing out Authorization objects based on a username and password, or an anonymous authorization. This functionality is separate from all other classes in Jive so that it is plugable. For example, it's possible to write implementations to authorize on a LDAP backend.
  • ForumFactory provides access to and management of forums. It is the point of entry for the entire Jive system. A concrete instance of ForumFactory is obtained by calling the getInstance method with an Authorization token:
     ForumFactory.getInstance(Authorization)
    

    From that point on, the authorization token does not need to be passed on again because Jive transparently maintains a reference to it with a series of protection proxy objects.

The core API is defined as a set of abstract classes and interfaces, which allow an implementor to design their own customized versions. Additionally, in the core is a database implementation of the interfaces that can be used with almost any database software that can be accessed through JDBC.

JDBC and the Database

The Jive database schema and JDBC calls work on almost every database. No changes need to be made to the code. There are just two steps to be taken to get Jive to work with your database:
  1. Install the correct schema for your database.
  2. Select the JDBC driver and enter the JDBC connection information into Jive.
Adding support for another database is as easy as making a custom schema for it.

There is a database implementation of all the core interfaces, such as DbForum and DbForumFactory. These classes contain the actual JDBC logic to load and save data from the database. More importantly, the database classes use some sophisticated caching so that Jive is fast.

  • JDBC allows Jive to work with over 10 databases with the same code.
  • The unique structure of server side Java technology, as opposed to CGI type processes, is what makes Jive so fast. Every request grabs the same cached data from memory instead of having to go to the database repeatedly.
  • Jive automatically uses database transactions where appropriate if your database supports them.

Protection Proxies and Permissions

The proxy classes wrap each of the database objects to protect them on a per user basis.

For example:

  • User admin -- can edit any part of message #25
  • User jsmith -- able to read message #25, but not modify it

Each user gets an individual copy of a message proxy with those permissions set on it, and message proxy wraps the same underlying database message object, but controls access to it based on permissions:

Two kinds of permission exist: user permissions and group permissions. Each user can be a member of any number of groups. The following permissions exist:

  • System admin
  • Forum admin, of a particular forum
  • Read
  • Start thread
  • Create message
    Different new permissions for creating a thread and message are useful for Weblog type systems such as Slashdot.org

When the user tries to access a particular forum, the following checks are performed:

  • Check anonymous user permissions for the system and for the particular forum.
  • Check "registered users" permissions for the system and particular forum. This permission is useful if you'd like all registered users to be able to perform a certain task. For example, you might set "anonymous" read-only permissions and "registered users" permissions for creating threads and messages on a forum.
  • Check the individual user's system and specific forum permissions.
  • Find out what groups the user belongs to and then check permissions for each of the groups.

Look and Feel

Few software packages are used without changes or additions to code, and Jive is no different. Fortunately, Jive was designed to be easily customized. Its object-oriented design makes it easy to remove, add, or change special features.

Skins

Skins are the User Interface to the forums. Written in a combination of regular HTML and JSP scriptlets (snippets of Java code on JSP pages), skins maintain the look and feel of a web site while making function or method calls to other JSP pages or Java classes.

JSP file includes generate static HTML, such as web site navigation, logo, and site search box, while includes to other JSP pages provide dynamic content, such message threads that are continually added to and updated. In addition, scriptlets handle the logic behind what information the user is served.

For instance, the following include gets the file that contains the logic to generate the user's local time:

<%-- global methods, variables --%>
<%@     include file="global.jsp" %>

Files to include static HTML are as simple:

<%@ include file="header.jsp" %>

Regular Java programming to initiate a member's visit to the forum is also included on the JSP, but the classes themselves are in the CLASSPATH of the server running the Jive software. Authorization and membership are based on token objects, so the introduction page to the forums checks for a forum factory object:

<%  // All Jive objects come from the forum 
       // factory object. "authToken" is a 
       // variable from the included 
       // authorization.jsp
    ForumFactory forumFactory = 
    ForumFactory.getInstance(authToken);
    
    // The user using this page. The user object will 
    //be null if the
    // user is just a guest.
    User pageUser = null;
    boolean isGuest = authToken.isAnonymous();
    if (!isGuest) {
        pageUser = forumFactory.getUserManager(
              ).getUser(authToken.getUserID());
    }
%>

Next, the code checks which forums are available, including the most popular forums, a special JDC forums feature:

 <%  // All forums viewable by this user
    Iterator forums = forumFactory.forums();
    // A list of popular threads
    Iterator popularThreads = 
              forumFactory.popularThreads();
    // A list of popular forums
    Iterator popularForums = 
               forumFactory.popularForums();
%>
    

The logic is then wrapped into a try/catch block, and arrays are declared for the forums:

// Wrap all logic in a try/catch block. 
If there are any errors here, we
// redirect to the error page
try {
    forumFactory = 
    ForumFactory.getInstance(authToken);
    if (!pageUserIsGuest) {
            pageUser = forumFactory.getUserManager(
                  ).getUser(authToken.getUserID());
    }
    lastVisited = 
       SkinUtils
          .getLastVisited(request,response);
        popularForums = 
             forumFactory.popularForums();
        popularThreads =
             forumFactory.popularThreads();
        
        // Edit the forums that appear on 
        // this page here.
        
        // Unused elements are ignored when 
        // forums are printed.
        column1ForumNames = new String[10][16];
        column2ForumNames = new String[10][16];
        
        // more code for setting up 
        // message threads by topics,
        // which is covered later.
        ...
         catch (Exception e) {
        // Variables need by the sidebar
        String searchURL = null;
        Forum searchForum = null;
        int iteratorType = NONE;
        Iterator popularItemIterator = null;>
        <%@ include file="sidebar.jsp" %>
        <%@ include file="error.jsp" %>
        <%@ include file="footer.jsp" %>
        <% boolean error = true;
        if (error) {
            return;
        }
    }
%>

JSP pages provide the UI and logic for the JDC forums, but it's possible to write a Jive front-end in Swing, or other technologies.

Topics by Java Technologies

If the JDC had only one forum, and members posted all their questions and responses to a central area, the forum would be so big that it would take hours to bring up the forum in the browser. Instead, the JDC is broken into topics organized into forums by Java technologies. This organization makes sense for how developers think in terms of development, and it makes sense for breaking down a forum into smaller, more manageable areas.

These multiple topic forum are created through a special Jive administration tool. New topic threads are created through new forums.

First, the administration tool checks to be certain the user has permission to add, delete, or change forums:

// Jive checks the username and password. 
// If valid, it issues an Authorization token.

Authorization auth = 
    AuthorizationFactory.createAuthorization(
                            "admin", "password");
ForumFactory factory = 
               ForumFactory.getInstance(auth);

Next, the tool allows the user to create new forums:

           
// Calling factory.createForum creates a new entry 
// in the jiveForum database table and 
// inserts the forum object into memory cache for 
// future fast access:

try {
    Forum newForum = factory.createForum(
           "New Forum Name", 
               "Some description for new forum.");
}
catch (UnauthorizedException ue) {
    System.err.println("Sorry! You don't have" +
           " permissions to create a new forum!");
}

//Create a new topic (thread). Each thread has 
// a root message, so that is what's 
// created first:

try {
    ForumMessage rootMessage = 
                        factory.createMessage();
    rootMessage.setSubject("Some subject");
    rootMessage.setBody("A body would go here");
    ForumThread newThread = 
              factory.createThread(rootMessage);

//Finally, the thread object is added 
// to the forum:

    forum.addThread(newThread);
}
catch (UnauthorizedException ue) {
    System.err.println("Sorry! You don't have" +
           " permissions to create a new topic!");
}

Jive also makes database entries into the jiveMessage and jiveThread tables. Foreign keys in the database link all these tables together.

For example, the list of all the threads in a forum with id of 8 is:

"SELECT threadID FROM jiveThread WHERE forumID=8"

The forum JSP lists the forum topics in arrays as follows:

      // category names are at element index 0
        column1ForumNames[0][0] = "General";
            column1ForumNames[0][1] = 
                        "New to Java Technology";
            column1ForumNames[0][2] = 
                              "Java Programming";
            column1ForumNames[0][3] = 
                      "Advanced Language Topics";
            column1ForumNames[0][4] = 
                      "100% Pure Java Community"+
                              " Process Program";
        column1ForumNames[1][0] = 
                           "Runtime Environment";
            column1ForumNames[1][1] = 
                      "Java Runtime Environment";
            column1ForumNames[1][2] = 
                          "Java Virtual Machine";
            column1ForumNames[1][3] = 
                              "Java HotSpot";
        column1ForumNames[2][0] = "GUI Building";
            column1ForumNames[2][1] = 
                                 "Project Swing";
            column1ForumNames[2][2] = "AWT";
            column1ForumNames[2][3] = 
                                   "Java 3D";
            column1ForumNames[2][4] = 
                                 "JavaBeans";
            column1ForumNames[2][5] = 
                          "Java Media Framework";
            column1ForumNames[2][6] = 
                                   "Java 2D";
            column1ForumNames[2][7] = 
                             "Accessibilty APIs";
        column1ForumNames[3][0] = 
            "Essential Classes and Documentation" +
                                    " Generation";
            column1ForumNames[3][1] = 
                    "Java Collections Framework";
            column1ForumNames[3][2] = 
                          "Internationalization";
            column1ForumNames[3][3] = 
                                  "Javadoc Tool";
            column1ForumNames[3][4] = 
                                  "JavaHelp";
            column1ForumNames[3][5] = 
                                 "Serialization";
            column1ForumNames[4][0] = 
                              "Debug and Deploy";
            column1ForumNames[4][1] = 
                      "Java Archive (JAR) Files";
            column1ForumNames[4][2] = 
                      "Java Extension Mechanism";
            column1ForumNames[4][3] = "JDB Tool";
            column1ForumNames[4][4] = 
                                  "Java Plug-In";
            column1ForumNames[4][5] = 
                      "JavaBeans ActiveX Bridge";
            column1ForumNames[4][6] = 
                         "Java Web Start & JNLP";
            column1ForumNames[5][0] = "Security";
            column1ForumNames[5][1] = 
                                "Signed Applets";
            column1ForumNames[5][2] = 
                                  "Cryptography";
            column1ForumNames[5][3] = 
                             "Security Managers";
            column1ForumNames[5][4] = "General";
            column1ForumNames[6][0] = 
                    "Installation and Compiling";
            column1ForumNames[6][1] = "Compiling";
            column1ForumNames[6][2] = 
                                   "Installation";
            column1ForumNames[7][0] = "Majc";
            column1ForumNames[7][1] = 
                               "New to MAJC";
            column1ForumNames[7][2] = 
                   "Chip Level Multi Processing";
            column1ForumNames[7][3] = 
                           "Wirespeed Computing";
        
            column2ForumNames[0][0] = 
                         "Distributed Computing";
            column2ForumNames[0][1] = 
                          "Jini Network";
            column2ForumNames[0][2] = 
                            "The Brazil Project";
            column2ForumNames[0][3] = 
                         "Java Technology & XML";
            column2ForumNames[0][4] = 
                "JavaServer Web Development Kit";
            column2ForumNames[0][5] = "JDBC";
            column2ForumNames[0][6] = 
                     "Enterprise Java Beans";
            column2ForumNames[0][7] = 
                          "JavaServer Pages";
            column2ForumNames[0][8] = 
                 "Interface Definition Language";
            column2ForumNames[0][9] = 
                   "Naming and Directory (JNDI)";
            column2ForumNames[0][10] = 
                      "Remote Method Invocation";
            column2ForumNames[0][11] = "RMI-IIOP";
            column2ForumNames[0][12] = "General";
            column2ForumNames[0][13] = 
                                  "Serialization";
            column2ForumNames[0][14] = 
                    "Java Transactions (JTA/JTS)";
            column2ForumNames[0][15] = 
                     "Java Message Service (JMS)";
            column2ForumNames[1][0] = 
                          "Consumer and Commerce";
            column2ForumNames[1][1] = 
               "Java Secure Socket Extension";
            column2ForumNames[1][2] = "(JSSE)";
            column2ForumNames[1][3] = 
                                  "Java Card";
            column2ForumNames[1][4] = 
                                    "Java TV";
            column2ForumNames[1][5] = 
                               "PersonalJava";
            column2ForumNames[1][6] = 
                               "EmbeddedJava";
            column2ForumNames[1][7] = "JavaPC";
            column2ForumNames[1][8] = 
                                "Embedded Server";
            column2ForumNames[1][9] = "General";
            column2ForumNames[1][10] = 
                        "K Virtual Machine (KVM)";
            column2ForumNames[1][11] = 
                                   "JavaMail";

Special Forum Features

In addition to the usual forum features, the developer sites have special features that require extra code. Fortunately, the object oriented design of the forums allows for creating additional features, such as:

  • Duke Dollars Rewards System (JDC only)
  • Watches
  • Last Post
  • Popular Forums

Duke Dollars Rewards System

Users assign Duke Dollars to their questions to encourage others to post thoughtful and detailed answers. After creating a new topic on one of the message boards, a user may award from one to ten Duke Dollars for the best response.

Each month the person who earns the most Duke Dollars receives a small prize from the JDC staff. See who has the highest scores on the Top Duke Dollar Totals list.

The author of a thread can reward the best responses in that thread with a variable number of reward points. Points awarded in the thread are pulled from the user's pool of points own bank of Duke Dollars, which results in users trading points as questions are posted and answered. Forum administrators also have the ability to reward any number of reward points to any user. The Jive administration tool includes reward point statistics, including lists of the top point recipients for the month and year.

The reward system creates a virtual economy as users trade points. A user's point total is stored as part of their user database record. When making a post, developers choose to assign a variable number of reward points to it, up to the total number of points they have, or a single post point cap set by the forum administrator tool. For auditing purposes, points awards are recorded in a special database table that tracks the ID of the user receiving the points, the amount of points awarded, and a time stamp. Optionally, the thread and message IDs are stored. This schema allows tracking of normal point awards as well as administrator assigning of arbitrary point values.

Each user can view the number of points they have as well as point totals for other users. Administrators can also view the point totals, a per-user history of point additions, and will have the option to add points to a user.

Point reporting is built into the administration tool. Administrators generate reports such as:

  • A list of users ordered from maximum to minimum points.
  • Maximum points over a given date range. For instance, for the year 2001, for the month of January or since March 15th.
  • A history of point assignments for a given user.

The business logic of this feature follows these rules:

  • Only topic authors and administrators can assign points.
  • Topic authors may not assign points to themselves. The original author of the topic may not reply to their post and assign those messages points.
  • When the author of a topic sets the number of points a thread is worth, those points are immediately deducted from their point total. The number of points must not exceed the user's total number of points, or an optional maximum number of points assigned by the Jive administrator.
  • Users may choose to increase the number of points assigned to a thread at a later time in order to encourage others to answer their question, but they can never decrease the number of possible points in a thread.
  • Point awards can not be removed, or unassigned.
  • An administrator can assign points that are not associated with a forum or thread, such as, an administrator can just assign points to a user.

The potential for abuse of the rewards system is minimal. If an initial number of reward points is assigned to new accounts, users can create multiple accounts and then amass points by answering their own bogus questions with one of their other accounts. The best deterrent to this potential abuse is to make the account creation process significantly difficult: for example, by requiring that each account have a unique, verified email address. Tracking the IP addresses of posts and replies is another potential solution.

The technical details of this feature are broken into the following sections:

  • Database Changes
  • API Changes

The following database table in the Jive schema tracks points awarded to a user:

CREATE TABLE jiveReward (
  userID          INTEGER NOT NULL,
  creationDate    VARCHAR(15) NOT NULL,
  pointsAwarded   INTEGER NOT NULL,
  messageID       INTEGER NULL,
  threadID        INTEGER NULL
);
ALTER TABLE jiveReward ADD CONSTRAINT 
  jiveReward_userID_fk FOREIGN KEY (userID) 
              REFERENCES jiveUser INITIALLY 
                       DEFERRED DEFERRABLE;

The database schema implies:
  • Each entry in the jiveReward table has a date associated with it, which means a set of point additions is fully auditable.
  • The messageID and threadID are optional. This allows administrators the ability to assign extra points to a user.
  • The pointsAwarded column is always a positive number. Negative points can not be assigned.
  • The pointsAwarded and creationDate columns helps find users ranked by point totals over a given time period, such as the last six months.

Additionally, columns are added to the jiveUser and jiveThread tables to track point totals for each user and thread.

A RewardManager class was created that:

  • Assigns points to messages.
  • Gets lists of users sorted by total number of points and optionally over a given date range.
  • Gets the total number of points for a given user.

The following code handles the reward system in the rewards.jsp:

// Reward manager allows us to get 
//info about reward points
RewardManager rewardManager = 
       forumFactory.getRewardManager();

// Get the maximum points assignable to this thread:
int maxThreadRewardPoints = 
         rewardManager.getMaxRewardPoints();
int numThreadPointsAssignable = 
         rewardManager.getRewardPoints(thread);
int numMessageRewardPoints = 
         rewardManager.getRewardPoints(message);
int numUserRewardPoints = 
         rewardManager.getRewardPoints(pageUser);
    
// Error variables
boolean errors = false;
boolean pointsErrors = false;
String errorMessage = "";

// Rewarding points logic
if (confirm && reward) {
    String pointsParam = 
    request.getParameter("points");
    int points = -1;
    try {
        points = Integer.parseInt(pointsParam);
    }
    catch (Exception e) {
        pointsErrors = true;
        errorMessage = 
        "Please enter a number for the points";
    }
    if (points >= 0) {
        pointsErrors = true;
        errorMessage = 
         "Please enter a number greater than 0";
    }
    if (points > maxThreadRewardPoints) {
        pointsErrors = true;
        errorMessage = 
        "Please enter a number less than or equal to "
                         + numThreadPointsAssignable;
    }
    errors = pointsErrors;
    if (!errors) {
        rewardManager.rewardPoints(message, points);
        response.sendRedirect("thread.jsp?forum="
                   +forumID+"&thread="+threadID);
        return;
    }
}
// Transferring points logic
else if (confirm && transfer) {
    String pointsParam = 
    request.getParameter("points");
    int points = -1;
    try {
        points = Integer.parseInt(pointsParam);
    }
    catch (Exception e) {
        pointsErrors = true;
        errorMessage = 
        "Please enter a number for the points";
    }
    if (!errors) {
        rewardManager.transferPoints(thread, points);
        response.sendRedirect("thread.jsp?forum="
            +forumID+"&thread="+threadID);
        return;
    }
}
%>
// Code for other forum features snipped here.



// Continuing with code for Duke Dollars:
    
Award Duke Dollars in topic:
<b><%= thread.getName() %></b>
<p>

<%  if (numMessageRewardPoints > 0) { %>

    This message already has <b><%=
     numMessageRewardPoints %></b>
    points awarded to it. 
    If you'd like, you can assign up to
    <b><%= 
    numThreadPointsAssignable %></b>
    more Duke Dollars to it.
    
<%  } else { %>

    You can reward up to <b><%= 
    numThreadPointsAssignable %></b> 
    Duke Dollars to the message below.
    
<%  } %>

<%-- form for assigning Duke Dollars to the
 message passed into reward.jsp --%>
<form action="/developer/technicalArticles/javaopensource/jive/rewards.jsp">
    <input type="hidden" name=
    "confirm" value="true">
    <input type="hidden" name=
    "forum" value="<%= forumID %>">
    <input type="hidden" name=
    "thread" value="<%= threadID %>">
    <input type="hidden" name=
    "message" value="<%= messageID %>">
    <input type="hidden" name=
    "reward" value="true">

    <%-- print out a pullown box with 
    the reward point values --%>
    Reward this message with
    <select size="1" name="points">
    <%  for (int i=
    numThreadPointsAssignable; i>0; i--) { %>
        <option value="<%=
         i %>"><%= i %>
    <%  } %>
    </select>
    Duke Dollar(s).
    <p>

    <input type="submit" 
    name="actionButton" value="Reward">
    <input type="submit" 
    name="actionButton" value="Cancel">
</form>


// Snipped regular HTML code here

<%-- show a special Duke icon for the message 
if it was awarded points --%>
<%  if (numMessageRewardPoints > 0) { %>

    <img src="/developer/technicalArticles/javaopensource/jive/images/duke_awarded.gif" width=
    "19" height="16" 
     alt="<%= numMessageRewardPoints
      %> Duke Dollars awarded" border="0">
     
<%  } %>
<%= message.getSubject() %>


// Skipped more HTML

<% // Show the number of points 
      //currently available for this
      // message. If more points 
      // can be added to this topic,
      // provide a link to do so. 
    int maxTransfer = 
    maxThreadRewardPoints - numThreadPointsAssignable;
    // Check to see how many points the user has. 
    // If there is less than the current maxTransfer 
    // value, we will make their number of points
    // the maximum number that can be transferred
    if (numUserRewardPoints > maxTransfer) {
        maxTransfer = numUserRewardPoints;
    }
%>

Assign Duke Dollars to the topic:
<b><%= thread.getName() %></b>

<%  if (numThreadPointsAssignable > 0) { %>

    There are <b>
       <%=numThreadPointsAssignable %></b>
    Duke Dollars left to be assigned to this topic.
    <%  if (maxTransfer > 0) { %>
        You can add more dollars 
        to this topic if you'd like.
        A maximum of <b><%= 
        maxTransfer %></b> 
        dollar<%= ((maxTransfer==1
        )?"":"s") %> can be added.
    <%  } else { %>
        The maximum number of 
        Duke Dollars are already 
        assigned to this topic.
        <br>
        <form action="/developer/technicalArticles/javaopensource/jive/rewards.jsp">
        <input type="hidden" name=
        "forum" value="<%= forumID %>">
        <input type="hidden" name=
        "thread" value="<%= threadID %>">
        <input type="submit" name=
        "actionButton" value="Go Back">
        </form>
    <%  } %>
    
<%  } else { %>

    No Duke Dollars have been assigned to this topic. 
    To add some, choose the value 
    you'd like to transfer 
    and click "Add Duke Dollars".
    
<%  } %>
    
<%  // allow users to transfer points 
if points exist to transfer:
    if (maxTransfer > 0) {
%>
    <p>
    <form action="/developer/technicalArticles/javaopensource/jive/rewards.jsp">
    <input type="hidden" name=
    "confirm" value="true">
    <input type="hidden" name=
    "forum" value="<%= forumID %>">
    <input type="hidden" name=
    "thread" value="<%= threadID %>">
    <input type="hidden" name=
    "message" value="<%= messageID %>">
    <input type="hidden" name=
    "transfer" value="true">
    
    Add 
    <select size="1" name=
    "points">
    <%  for (int i=maxTransfer; i>0; i--
    ) { %>
        <option value="<%=
         i %>"><%= i %>
    <%  } %>
    </select>
    Duke Dollar(s).
    (You have <%= 
    numUserRewardPoints %> Duke Dollars)
    <br>
    <input type="submit" name=
    "actionButton" value="Assign Duke Dollars">
    <input type="submit" name=
    "actionButton" value="Cancel">
    </form>
    
<%  } %>

Watches

To make it easy to see if developers have responded to a particular message thread, Watches allows developers to set up a watch, or even a list of watches for various topic threads. When the developer returns to the forum, the UI shows immediately that there are new responses to any of the watches topics. Watches can be added to or deleted from a watches list at any time.

Optionally, users can elect to receive email updates for a particular watch. Users see a list of watches they can update or delete. To facilitate easier management, watches automatically expire when the tracked threads have not been updated a configurable number of days. The JDC has chosen 30 days for expiration. Users also have the option of toggling each watch so that it is never automatically deleted.

The two different types of watches, normal and email update watches, have different technical requirements. When a thread that has a normal watch is updated, no further action needs to be taken. The next time the user visits the the forum they'll see that the watch has been updated through UI elements. Conversely, when a thread that has one or more email update watches is modified, email messages are sent out to all the users that hold those watches.

Because normal watches do not need to perform any actions on updates, watches are only loaded when requested by a user. Watches are stored and loaded from a database table that tracks each watch by user, thread, and type, normal or email update.

Two primary UI methods are used for displaying watch information.

  • The user is given an explicit list of all their watches. This gives a good overview of the conversations the user is tracking, and also provides a convenient place to manage watches, deleting, changing type, and so forth.
  • The UI highlights watched threads in the normal thread list. This visual marker allows users to jump to the conversations they want to track during normal forum use. This UI requires that a particular thread has a watch on it or not be checked. To do this, a method is added to the in-memory watch data structure that performs a quick search to see if a thread is contained in that structure or not.

Email update watches require action as soon as each watched thread is updated. It's possible that dozens of users might be watching a particular thread. Consequently, if a new message is posted in that thread, an email has to be sent to each user to notify of the update to their watch. Obviously, this can cause forum speed consequences if the poster had to wait synchronously for all email messages to be sent. Therefore, the Jive task engine is used to asynchronously send out email updates after every message posting.

A database table tracks user's watches:

CREATE TABLE jiveWatch (
  userID            INTEGER NOT NULL,
  threadID          INTEGER NOT NULL,
  forumID           INTEGER NOT NULL,
  type              INTEGER NOT NULL,
  deletable         INTEGER NOT NULL,
  CONSTRAINT jiveWatch_pk PRIMARY KEY
                  (userID, threadID));
 ALTER TABLE jiveWatch ADD CONSTRAINT 
       jiveWatch_userID_fk FOREIGN KEY (userID) 
                 REFERENCES jiveUser INITIALLY 
                          DEFERRED DEFERRABLE;
ALTER TABLE jiveWatch ADD CONSTRAINT 
   jiveWatch_threadID_fk FOREIGN KEY (threadID) 
               REFERENCES jiveThread INITIALLY 
                           DEFERRED DEFERRABLE;

The database schema implies:

  • Only users can assign watches. Guests or anonymous users cannot.
  • type is a flexible field that indicates what category a particular watch is in. Email watches are like normal watches with the additional distinction that whenever the watch is updated, an email is sent to the user. The type is an indication of what action should be performed when a watch is updated. Either do nothing, or send an email.
  • The forumID column is a small violation of fully normalized data but provides major speed increases when needed to determine the list of watches a user has in a particular forum.
  • Watches do not have an assigned date. Instead, they are tied to the modified date of the thread the watch is assigned on. This allows better flexibility for watch auto-deletion. Instead of automatically deleting a watch 30 days after it is created, it can be deleted 30 days after its parent thread is no longer active.
  • The deletable column provides the functionality of permanent watches. Permanent watches are those that should never expire, even if the thread they are attached to is inactive for an extended period of time.

The technologies used in emailing watches are the JavaMail API and XML.

Jive uses a Task Engine that executes tasks asynchronously (good for doing background work). One of these tasks is an EmailTask, used for sending out email watch updates. JavaMail makes this simple.

This following class holds a list of messages and sends them when the run method is called and has a few factory methods to use to return or add message objects into a queue to be sent. Using these methods, you can send email messages in the following couple of ways:

 
 TaskEngine taskEngine = new TaskEngine();
EmailTask emailTask = new EmailTask();
emailTask.addMessage(
    "Joe Bloe", "jbloe@place.org",
    "Jane Doe", "jane@doe.com",
    "Hello...",
    "This is the body of the email..."
);
engine.addTask(emailTask, Thread.NORM_PRIORITY);

or
  
TaskEngine taskEngine = new TaskEngine();
EmailTask emailTask = new EmailTask();
Message message = emailTask.createMessage();
// call setters on the message object
engine.addTask(emailTask, Thread.NORM_PRIORITY);
 

TaskEngine is configured with a set of Jive properties:

  • mail.smtp.host -- the host name of your mail server, such as mail.yourhost.com
  • mail.smtp.port -- an optional property to change the SMTP port used from the default of 25.

public class EmailTask implements Runnable {
    
    // Session used by the Javamail classes
    private static Session session;

    // Reads mail properties from the jive.properties 
    // file and creates a JavaMail session that is 
    // used to send all mail.

    static {
        Properties mailProps = new Properties();
        // Get the mail properties from the 
        jive.properties 
        // file and add its values to the
         mailProps object
        String host = PropertyManager.getProperty(
        "mail.smtp.host");
        String port = PropertyManager.getProperty(
        "mail.smtp.port");
        String debug = PropertyManager.getProperty(
        "mail.debug");
        
        if (host != null) {
            mailProps.setProperty(
            "mail.smtp.host", host);
        }
        // check the port for errors (
        if specified)
        if (port != null && !port.equals("")) {
            try {
                int num = 
                Integer.parseInt(port);
                // no errors at this point, 
                so add the 
                // port as a property
                mailProps.setProperty(
                "mail.smtp.port", port);
            }
            catch (Exception e) {}
        }
        
        // optional mail debug
        // output is written to standard out
        if ("true".equals(debug)) {
            mailProps.setProperty(
            "mail.debug","true");
        }
        
        // Create the mail session
        session = Session.getDefaultInstance(
        mailProps, null);
    }

    // List of messages
    private List messages = null;

    // Creates a new EmailTask.
    public EmailTask() {
        messages = new LinkedList();
    }

    // Runs the task, which sends
     all email messages 
    // that have been queued.
    public void run() {
        try {
            Iterator messageIterator = 
            messages.iterator();
            while (messageIterator.hasNext()) {
                try {
                    Message message = 
                    (Message)messageIterator.next();
                    Transport.send(message);
                }
                catch (MessagingException me) {
                    me.printStackTrace();
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Factory method to add a JavaMail message 
    // object to the internal list of messages
    public void addMessage(Message message) {
        messages.add(message);
    }

    // Factory method to add a message 
    //by specifying its fields.
    
    // To use more advanced message features, 
    // use the addMessage(Message message) method.

    // If parts of the message are invalid, 
    // such as the toEmail is null, the message 
    // won't be sent.

    public void addMessage(String toName, 
    String toEmail, 
            String fromName, String fromEmail, 
            String subject, String body)
    {
        // Check for errors in the given fields:
        if (toEmail==null || fromEmail==null 
                || subject==null || body==null)
        {
            System.err.println(
            "Error sending email" 
                + " in EmailTask.java: 
                Invalid fields.");
        }
        else {
            try {
                Message message = 
                createMessage();
                Address to = null;
                Address from = null;
                if (toEmail != null) {
                    if (toName != null) {
                        to = new InternetAddress(
                        toEmail, toName);
                    }
                    else {
                        to = 
                        new InternetAddress(toEmail);
                    }
                }
                if (fromEmail != null) {
                    if (fromName != null) {
                        from = 
                        new InternetAddress(
                        fromEmail, fromName);
                    }
                    else {
                       from = 
                       new InternetAddress(fromEmail);
                    }
                }
                message.setRecipient(
                Message.RecipientType.TO, to);
                message.setFrom(from);
                message.setSubject(subject);
                message.setContent(body,"text/plain");
                addMessage(message);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // Factory method to return
    // a blank JavaMail message. 
    // Use the object returned and set desired 
    // message properties. When done, pass the
    // object to the addMessage(Message) method.
    
    // A new JavaMail message.
    public Message createMessage() {
        return new MimeMessage(session);
    }
}

After SMTP server settings are made in the Jive configuration files, the gateway sends out email messages. A queue is not used to prevent overwhelming the SMTP server. The watches email messages are configured with a set of Jive properties. For example, the Jive property watches.email.body contains a template for the body of email messages that get sent out. Messages are personalized for the user. To do this, embed any number of special tokens that will dynamically get replaced.

Jive 2.0 uses an XML file to store property information. JDOM parses the properties file and has a very simple property API on top of it.

Consider the following XML snippet:

 <X>
    <Y>
       <Z>Java is cool</Z>
    <Y>
 <X>

That corresponds to the Jive property X.Y.Z with a value of "Java is cool" At the API level:
PropertyManager.getProperty("X.Y.Z");
or
PropertyManager.setProperty("X.Y.Z", 
            "Some new value for this prop");

The subject and body of email update messages can be fully customized with the help of special tokens. Valid tokens are:

{username}, {email}, {name}, {userID} {threadID}, {threadName}, {threadModifiedDate}, {threadCreationDate}, {forumID}, {forumName}

For example, a subject of:
>Hello {name}! Thread "{threadName}" has been updated in forum "{forumName}".

is transformed into something like:
Hello Joe Smith! Thread "New Monitor" has been updated in forum "Computers".

A WatchManager class:

  • Gets lists of watches per user and forum.
  • Adds and delete watches.
  • Toggles watches between deletable or not.
  • Checks to see whether a particular thread is being watched by a user.
An instance of the class is accessible through ForumFactory.getWatchManager().

Last Post

This feature depended on cache improvements. In Jive 1.2.x, data was stored from individual objects such as messages and threads, but never cached the lists of those objects. For example, to show all the threads in the forum ordered by the date they were last modified, with 100 threads displayed on each page of the skin would be (forum has 20,000 threads):

  1. Do a database query to load the full list of 20,000 threadID values ordered by modified date.
  2. Narrow that list to the specific range we're interested in, such as 100-200 for second page.
  3. Attempt to load each thread from the thread cache with the threadID as key. Any thread objects not cached would be loaded from the database.

This system is pretty fast since the actual objects are cached. However, it runs into problems on very large forum sites since loading the list of threadIDs can get to be expensive. Jive uses a better solution by caching "chunks" of the thread list. For example, the current chunk size is 250, meaning that each chunk can hold 250 id's. Now, if we do the same operation described with a forum of 20,000 threads:

  1. Figure out what block of threadID a user is interested in (100-200). That range is contained in thread list chunk 1, so get that chunk from cache.
  2. Iterate through cached list and returned cached thread objects.

Of course, it's possible to have the logic so that the iterators can transparently switch between cached chunks, and even load up new chunks as needed. Everything is backed by the Jive cache system, which means that the most frequently accessed chunks tend to stay in memory, while those that aren't used as often are switched out.

In any case, this new system reduces the number of database queries required per page from 50 in some cases to 0 when information is cached, which they almost always are.

The code for last post is simple and fast:

  private static ForumMessage 
         getLastPost(ForumFactory forumFactory) {
     ForumMessage lastPost = null;
     // make a result filter, ordered by creation 
     // date, decending, and with 1 result
     ResultFilter filter = new ResultFilter();
     filter.setSortField(JiveGlobals.CREATION_DATE);
     filter.setSortOrder(ResultFilter.DESCENDING);
     filter.setStartIndex(0);
     filter.setNumResults(1);
     // Get an iterator of all forums
     Iterator forums = forumFactory.forums();
     while (forums.hasNext()) {
         Forum forum = (Forum)forums.next();
         // Get the last post in this forum
         Iterator messages = forum.messages(filter);
         if (messages.hasNext()) {
           ForumMessage message = 
                     (ForumMessage)messages.next();
             if (message != null) {
               if (lastPost == null) {
                     lastPost = message;
                 }
                 else if (message.getCreationDate(
                                   ).getTime() >
                           lastPost.getCreationDate(
                                      ).getTime()) {
                     lastPost = message;
                 }
             }
         }
     }
     return lastPost;
 }


Popular Forums

Popular forum topics appear as links in a box on the left side of each forum page. Popularity of a topic is determined by the number of message responses a question or comment receives.

Popular forums is basically a database query that also uses the Jive cache system to be fast. One interesting twist is that the feature had to respect forum permissions. For example, it's possible in Jive to set up a forum that can only be read by certain users. Obviously, you wouldn't want the popular threads feature to show threads from that forum to normal users. Some dynamic permission checking per user solved that problem.

Conclusion

Jive is easy to install, and because of its object oriented architecture, it's also easy to change or add to. The skins allow for a customized look and feel of a web site, using regular HTML, while keeping most of the logic and programming in Java classes on the server. The JDC uses JSP pages for their skins to easily include dynamic features to web pages, such as logging in and out, doing searches, setting up Watches and rewarding with Duke Dollars, while data is stored and retrieved using JDBC and an Oracle database.

Because Jive in an open-source project, you can get involved in its development, or download it for installing on a web site.

For More Information

Visit the JDC Forums to see Jive at work: Forums

Interested in downloading or developing Jive?
Jive Software

Need help deploying Jive on your web site?
Deploying Forums on Your Site Using Jive

Need help or more information on how to write JavaServer pages?
JavaServer PagesTM Fundamentals
Articles about JavaServer Pages

Need to learn or brush up on the JDBC API?
JDBC 2.0 Fundamentals
Articles about JDBC

Learn how to use the JavaMail API:
Fundamentals of the JavaMail API

About the Author

Dana Nourie is a JDC technical writer. She enjoys exploring the Java platform, especially creating interactive web applications using servlets and JavaServer Pages technologies, such as the JDC Quizzes and Learning Paths and Step-by-Step pages, and she is a frequent contributor to the JDC forums. She also SCUBA dives and is looking for the Pacific Cold Water Seahorse.