Java Discussion ForumList
Messages | Read Message
Subject |
Test message |
Author |
Joe User |
Date Posted |
1/23/2001 |
This is a test message. I'm just posting this to demonstrate how we
can write a discussion forum with J2EE.
|
Now that we know what we want our
discussion forum application to do and look like, we can start designing and
implementing the application. This means we have to determine how we're
representing and storing messages, and how we're going to retrieve them and
display them over the Web.
Object design
The basic data structure in our forum application is a message, so we'll
define a Java object skeleton to represent it, with "get" methods
corresponding to its public attributes:
package forum;
public class Message {
// return the content of a message body
public String getBody();
// return the message subject
public String getSubject();
// return the date posted
public String getPostDate();
// return the name of the author
public String getAuthorName();
}
We also need to represent a list of messages:
package forum;
public class MessageList {
// return the number of messages in the forum
public int getMessageCount();
// return a message at the given index
public Message getMessage(int i);
}
Database design
Web sites that need to store data persistently and reliably generally use a
relational database product (RDBMS) like Oracle or PostgreSQL for their data
storage. RDBMS'es have a lot of nice properties that make them well-suited for
web applications and multi-user systems. It frees application authors from
worrying about transaction isolation, and ensures that data can always be
restored to some valid state even if the power to the server is cut at a
critical moment. Imagine what could happen if you saved all your forum
messages to an ASCII text file and the server crashes when the message is half
written, or if two people try to post a message at the same time.
Let's assume that we're integrating our message forum into an existing data
model to support a multi-user web site. So we already have a table in place to
represent users:
CREATE TABLE USERS (
user_id integer primary key,
first_name varchar(100),
last_name varchar(100),
...
);
Next we'll create a table to represent the set of
messages stored in our discussion forum:
CREATE TABLE MESSAGES (
message_id integer primary key,
author_id integer references users(user_id),
post_date date,
subject varchar(100),
-- simplification: assume "short" messages so we don't need to use CLOB
body varchar(4000)
);
Implementation: Putting it together
Now that we know what objects we
need and how we're going to store them in the database, we're ready to start
using J2EE components to put it all together. We can build a first pass of
this system using just servlets, JSP, and JDBC, each of which pre-dates the
full J2EE specification. Here we will describe each API in detail.
Java Database Connectivity
Java Database Connectivity (JDBC) is an API
for connecting Java classes with relational databases. It defines an interface
for connecting to the database, issuing SQL statements, and parsing results.
Database and application server vendors generally provide JDBC drivers, which
are database-specific implementations of the JDBC API. A different JDBC driver
must be used for each database product.
Before the J2EE release, JDBC was part of the standard Java API, in package
java.sql
. The J2EE includes an enhanced JDBC interface, in
package javax.sql
. Some of the additional features include
scrolling cursors, which allows random access to the rows returned from a
query; batch updates; and a standard API for connection pooling.
This code shows how we can put JDBC into the Message and MessageList class
we defined above to retrieve messages from persistent storage:
Listing: Message.java
package forum;
import java.sql.*;
import forum.util.ConnectionManager;
/**
* Represents a message in the forum and retrieves it from persistent
* storage.
**/
public class Message {
// private data members
private String m_id;
private String m_body;
private String m_subject;
private java.util.Date m_date;
private String m_author_name;
private static java.text.SimpleDateFormat df =
new java.text.SimpleDateFormat("M/d/yyyy");
/** no-argument constructor */
public Message () { }
/**
* Create a new Message object from the
* input ResultSet. The ResultSet must be pointing to a valid row, and
* must contain fields named subject, post_date, first_name, and
* last_name. The "body" field is optional because we might not always
* want to pull in the body content (e.g., if we're pulling in lots
* of message headers to show in list format).
*
* @exception throws java.sql.SQLException if the input result set doesn't
* contain the required fields.
*/
public Message(ResultSet rs) throws SQLException {
fillAttributes(rs);
}
/**
* fill attribute values for this Message object from the
* input ResultSet. The ResultSet must be pointing to a valid row, and
* must contain fields named subject, post_date, first_name, and
* last_name. The "body" field is optional because we might not always
* want to pull in the body content (e.g., if we're pulling in lots
* of message headers to show in list format).
*
* @exception throws java.sql.SQLException if the input result set doesn't
* contain the required fields.
*/
private void fillAttributes(ResultSet rs) throws SQLException {
// loop over available columns to set optional attributes
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
String column = rsmd.getColumnName(i).toLowerCase();
if (column.equals("body")) {
m_body = rs.getString(i);
}
if (column.equals("message_id")) {
m_id = rs.getString(i);
}
}
// mandatory attributes
m_date = rs.getDate("post_date");
m_author_name = rs.getString("first_name") + " " + rs.getString("last_name");
m_subject = rs.getString("subject");
}
/**
* Create a new Message object with attributes values from a row in the
* database.
* @param messageId the value of the message_id column to pick out of
* the MESSAGES table
*/
public Message(String messageId) throws SQLException {
this();
m_id = messageId;
setMessageId(messageId);
}
/**
* Fills this Message's attributes with values from the database.
* @param messageId the value of the message_id column to pick out of
* the MESSAGES table
*/
public void setMessageId(String messageId) throws SQLException {
// hand-waving; assume connection management is centralized
Connection conn = ConnectionManager.getConnection();
PreparedStatement ps = conn.prepareStatement
("select messages.*, first_name, last_name "
+ "from messages, users "
+ " where message_id = ?"
+ " and author_id = user_id");
ps.setObject(1, messageId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
fillAttributes(rs);
}
conn.close();
}
/** @return the content of a message body */
public String getBody() {
return m_body;
}
/** @return the message subject */
public String getSubject() {
return m_subject;
}
/** @return the date posted */
public String getPostDate() {
return df.format(m_date);
}
/** @return the name of the author */
public String getAuthorName() {
return m_author_name;
}
}
Listing: MessageList.java
package forum;
import java.sql.*;
import java.util.ArrayList;
import forum.util.ConnectionManager;
/** A list of forum messages. */
public class MessageList {
private ArrayList m_list;
/**
* Creates a new MessageList by querying the database.
*/
public MessageList() throws SQLException {
m_list = new ArrayList();
// hand-waving; assume connection management is centralized
Connection conn = ConnectionManager.getConnection();
// query all fields except message body.
PreparedStatement ps = conn.prepareStatement
("select message_id, subject, post_date, first_name, last_name "
+ "from messages, users "
+ " where author_id = user_id");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
m_list.add(new Message(rs));
}
conn.close();
}
/** @return the number of messages in the forum */
public int getMessageCount() {
return m_list.size();
}
/** @return a message at the given index */
public Message getMessage(int i) {
return (Message)m_list.get(i);
}
}
There are many good tutorials for JDBC out there so
we won't explain the code above in detail. We're assuming that we have a
ConnectionManager class, which gives us JDBC Connections on demand. We'll
nearly always want to centralize the details of connecting to the database
(users, passwords, database URLs), and often this centralization comes with
the use of an off-the-shelf connection pool.
One interesting thing to note above is the public
Message(ResultSet)
constructor. This allows us to create a new
Message that takes its attribute values from an already-open query, which is
useful if we're not creating Messages one at a time, but rather creating
multiple Messages from a query already opened in another object like
MessageList.
Another interesting thing to note is that we took the code for opening the
database query out of the body of the Message(String messageId)
constructor and put it into a public method called setMessageId
.
This allows us to instantiate a Message as a "bean" from a JSP page (more on
that later), because bean classes are always instantiated via the default
no-argument constructor; querying the database is now a side effect of setting
the messageId
property on a Message bean.
Also note that we query for the raw date and format using Java's
SimpleDateFormat
class, instead of using any available RDBMS
functions for doing the same (e.g., Oracle's to_char
). This makes
our date formatting more flexible should we want to use a different date
format depending on the end-user's locale; it also makes our Java object more
re-usable because it is less dependent on any one vendor's RDBMS syntax.
Now that we have working classes for retrieving messages from the database,
we're ready to start serving them over the web using Java servlets and JSPs.
Java Servlets
Just as a Java applet is an application that runs in a
web client, a servlet is a Java class that runs in a web server. Servlets are
similar to CGI programs; they handle an incoming server request, do some
server-side processing, and return a response; the servlet API specifies a
standard API for getting information from the request, like the URL, cookies,
and form variables.
Each Java servlet request runs in its own thread under the same Java
virtual machine (JVM) process; processing all requests under the same process
allows servlets to save server-side state between requests, since all servlet
requests share the same memory address space. All of the usual thread-safety
pitfalls apply to any servlets that store state between requests.
The servlet specification also includes a standard convention for deploying
servlet classes and mapping them to URLs, through a deployment
descriptor file (web.xml
). A set of servlets, web pages, and
the classes they use, along with the deployment descriptor, comprise a web
application or "webapp." This standardized deployment layout is essential
for allowing cross-compatibility between application servers; you can develop
an application on Tomcat and then deploy it on WebLogic, for instance.
WebLogic may have a more sophisticated administration interface than Tomcat
but it uses the same web.xml deployment descriptor and the same file layout
under the hood.
The following code is a servlet that will read a message from the database
and display it to the user's browser, taking the message_id
field
from the messageId
URL variable:
package forum;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ShowMessage extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
Message m = null;
try {
m = new Message(req.getParameter("messageId"));
} catch (SQLException e) {
// wrap database exception to show standard error page
throw new ServletException(e);
}
resp.setContentType("text/html");
resp.setStatus(200);
PrintWriter out = new PrintWriter(resp.getWriter());
out.println("<h2>JSP Discussion Forum </h2>");
out.println("<a href=\"message-list\">List messages</a> | View message");
out.println("<hr>");
out.println("<table>");
out.println("<tr><th>Subject</th><td>" + m.getSubject() + "</td></tr>");
out.println("<tr><th>Author</th><td>" + m.getAuthorName() + "</td></tr>");
out.println("<tr><th>Posted on</th><td>" + m.getPostDate() + "</td></tr>");
out.println("</table>");
out.println(m.getBody());
out.close();
}
}
The above code creates a Message using the messageId
URL
variable; it then generates HTML output to the user's browser. To make the
above example code work, you'd need to create a webapp with all the necessary
class files (Message.class, MessageList.class, and ShowMessage.class) under
the webapp's WEB-INF/classes/forum
directory (because all the
classes are in the Java package "forum"); and, in the deployment descriptor
(WEB-INF/web.xml) map a URL to the forum.ShowMessage class with the following
lines:
<servlet>
<servlet-name>show-message</servlet-name>
<servlet-class>forum.ShowMessage</servlet-class>
</servlet>
<servlet-mapping
<servlet-name>show-message</servlet-name>
<url-pattern>/message-show</url-pattern>
</servlet>
Java Server Pages
The first thing most readers probably noticed about
the above example is that it's cumbersome to write web applications
exclusively with servlets. Generating HTML output as string literals is
verbose; and you can't change the HTML output without editing, compiling, and
deploying a Java class file. It's also cumbersome to add a new servlet to a
running server, since you'd have to edit the deployment descriptor and often
restart the servlet container.
Enter Java Server Pages (JSP). JSPs look like HTML pages that contain
snippets of Java code to be executed on the server, but they are really just
shorthand or syntactic sugar for servlets. The JSP container translates them
as needed to Java servlet classes, and compiles the resulting Java code. Since
each page is a new file on disk, and the .jsp files are in the same place as
static .html files, we don't need to edit the deployment descriptor to add a
new JSP page.
We could write the above servlet as a JSP:
<@ page import ="forum.Message" %>
<% Message m = new Message(req.getParameter("messageId")); %>
<h2>JSP Discussion Forum </h2>
<a href="message-list">List messages</a> | View message
<hr>
<table>
<tr><th>Subject</th><td><%= m.getSubject() %></td></tr>
<tr><th>Author</th><td><%= m.getAuthorName() %></td></tr>
<tr><th>Posted on</th><td><%= m.getPostDate() %></td></tr>
</table>
<%= m.getBody() %>
JSPs make it easier to separate content and presentation since the HTML
markup is out in the open, and the default state in a JSP is HTML output with
escapes into Java code instead of the other way around. There are different
notations for executing Java statement blocks (<% ... %>) and
interpolating the values of Java expressions into the output (<%= ...
%>).
J2EE also includes an API for creating new JSP tags and tag libraries. The
syntax for user-defined tags is also legal XML, so the procedures that handle
user-defined JSP tags serve a similar purpose to XML style sheets (XSLT). This
allows other J2EE application components, such as EJBs, to represent complex
data structures in XML, leaving the presentation-level HTML rendering to the
JSP tag extensions.
One such tag library included by default as part of the JSP standard
provides a way to instantiate and access Java classes within JSP without
writing any Java code directly; this assumes that the Java class that you're
accessing is a "bean." ("Beans" are just Java classes that follow a standard
naming convention for the methods that get and set their properties.) The
following code example shows the above servlet re-written entirely with JSP
tags, with no explicit Java code:
<jsp:useBean class="forum.Message" id="msg" scope="page">
<jsp:setProperty name="msg" property="*"/>
</jsp:useBean>
<h2>JSP Discussion Forum </h2>
<a href="message-list">List messages</a> | View message
<hr>
<table>
<tr><th>Subject</th><td><jsp:getProperty name="msg" property="subject"/></td></tr>
<tr><th>Author</th><td><jsp:getProperty name="msg" property="authorName"/></td></tr>
<tr><th>Posted on</th><td><jsp:getProperty name="msg" property="postDate"/></td></tr>
</table>
<jsp:getProperty name="msg" property="body"/>
In this example, we are implicitly creating an
instance of a Message with the jsp:useBean
tag; and each
jsp:getProperty
tag translates into a call to
msg.getXXXX
(note that when property="foo"
is
specified, the first letter of the property name is capitalized in the
corresponding method name, e.g., getFoo
). The most interesting
piece of magic is that jsp:setProperty property="*"
introspects
into the class, and calls the corresponding setXXXX
method, if
available, for every URL or form variable.
It is an ongoing debate whether JSP or servlets (combined with some other
templating system to break out static HTML) are better, and most web
applications use some combination of the two, since servlets can forward
requests on to JSPs and vice versa. A common pattern is using a controller
servlet to perform tasks common to a group of URLs (e.g.,
/path/*
), which does some common processing like user
authentication and then dispatches to a JSP to display a page.
Other J2EE APIs
So far we've only scratched the surface of J2EE with
JDBC, servlets, and JSP. And if we're building strictly a Web-based system,
that may be all we need to use. But there are many other APIs in J2EE that are
also useful, depending on the class of problem.
Enterprise Java Beans
The Enterprise Java Beans (EJB) API is a major
component of J2EE; EJB, servlets, JSP, and JDBC together form the core of the
J2EE APIs. is a component architecture for distributed Java applications in
heterogeneous computing environments. EJBs are JavaBeans classes that run
inside an "EJB container," like servlets are Java classes that run inside a
servlet engine. The EJB container is generally part of a comprehensive J2EE
application server product, which will also include a web server, servlet
engine, and JSP container.
EJBs are instantiated and accessed via Remote Method Invocation (RMI), so
you can call methods on objects from an application regardless of whether the
physical object lives in the same JVM or not. Since RMI in J2EE runs over the
Object Management Group's Internet Inter-ORB Protocol (IIOP, see http://omg.com ), EJBs may also be accessed from
non-Java client applications that are CORBA-compliant.
EJB provides a uniform standard for building a layer of abstraction between
business logic and enterprise information systems, which include RDBMS systems
like Oracle, Sybase, Microsoft SQL Server, etc.; ERP systems; legacy mainframe
systems; and others. EJBs also can be used to build a layer of abstraction
between server-side processing and client-side presentation. An EJB may be
invoked from a stand-alone "thin" Java client application, a Java servlet, a
JSP, or a CORBA application.
EJB Flavors
There are two flavors of EJBs: entity beans and session
beans. Session beans do not have any persistent state. They may have temporary
state, though; as the name implies, session beans are generally used to keep
track of objects that are valid for a particular user session only, such as
shopping carts or a temporary workspace. Session beans may access the same
enterprise information systems as entity beans, though, and may retrieve
entity bean instances.
Entity beans are persistently-stored objects. They can be thought of as
object views of a row in a database whose properties correspond with column
values. Entity beans that implement the EntityBean
interface must
provide methods for managing persistence which correspond to, and generally
issue, the SQL INSERT
, DELETE
, SELECT
,
and UPDATE
statements.
Entity bean persistence may either be managed by the bean itself, or by the
EJB container. When persistence is bean-managed, the bean author must write
the code to interface with the persistent store. Some EJB containers, though,
support container-managed persistence; and it is possible to provide
deployment tools to automatically generate SQL code for the above EJB methods.
It is important to remember, though, EJB is primarily for making
distributed objects; it is not an API for object-relational mapping despite
its support for persistent objects. As a general rule, EJB is not useful in
cases where there is no use for RMI or remote procedure calls. If a particular
solution does not require distributed objects, then EJB can add significant
complexity and cost without providing any truly new functionality.
Applying EJB to the discussion forum application
In the discussion
forum example, we could implement the Message
class as an entity
bean, and MessageList
as a session bean. The EJB API for entity
beans is a standardization so that classes like Message
that
access the database and map columns to object properties can be manipulated in
an application-independent way. This allows for the possibility of RAD tools
that can automate the generation of classes like Message
.
EJB is really only useful in the discussion forum example, though, if we
want to access Message
object instances on a different JVM or
machine than that which does the database query. For example, suppose we were
to extend the discussion forum application so that messages would be viewable
over both the web and from a standalone "thin" Java client application that
doesn't connect to the database directly. A Message entity bean's methods,
including the database queries, would run on the application server as they do
in the web-only system; but the thin client can call accesor methods on the
Message (actually, a stubbed interface) just as if it were an object local to
the client.
Java Transactions
The J2EE includes the Java Transactions API (JTA) for performing
distributed atomic transactions with enterprise information systems. This
allows the programmer to ensure that a series of operations will either be
entirely completed, or not done at all. Half-completed transactions are not
allowed.
While most RDBMS packages include facilities for transaction management,
they are limited to managing operations with that one RDBMS. The JTA can
provide distributed transactions that consist of operations on multiple
enterprise information systems.
Consider the following scenario:
- Customer logs in to RDBMS-powered, e-commerce web site
- Customer places order, order information stored in RDBMS
- Order information also updated in legacy order-fulfillment system
In the preceding example, should something go wrong with the
order-fulfillment operation, the customer's order should not be recorded in
the RDBMS. The JTA allows these operations to be treated as a single atomic
operation.
CORBA Compatibility
J2EE includes two-way compatibility between Java
and CORBA objects; either one may call the other. The Java 2 Standard Edition
(J2SE), a subset of J2EE, provides an Interface Definition Language (IDL)
compiler, which generates Java stubs for calling remote CORBA objects from
Java applications; and an Object Request Broker (ORB), which allows Java
classes, including EJBs, to be called remotely from CORBA.
Java Naming and Directory Interface
The Java Naming and Directory
Interface (JNDI) is an API for maintaining directories of name-to-object
bindings. It may be used as the primary means of storing user information in a
Java enterprise application, or it may be used in a helper role to the other
J2EE APIs for locating remote objects. For example, a developer who
instantiates an EJB within a client application might only know the name of
the EJB's remote interface and nothing about the actual class which will
handle the transport between the client application and the EJB container.
JNDI is used to look up the actual class given an interface name.
JNDI supports LDAP (Lightweight Directory Access Protocol), an industry
directory standard. This allows Java clients to connect to LDAP-compliant
directory servers (e.g., Netscape Directory Server), and other LDAP clients to
connect to Java enterprise applications. JNDI directories can issue events
when changes are made to directory bindings. Other objects can register
listeners to receive these events.
Java Message Service (JMS)
JMS is an asynchronous messaging service
for connecting different Java applications, or a distributed application that
runs on more than one virtual machine. Asynchronous messaging differs from the
synchronous messaging used in RMI and EJB, and has different applications. The
JMS API supports transactional message queues, which are implemented by
various J2EE applications servers.
JavaMail
JavaMail is an object-oriented API for parsing, constructing,
and sending MIME e-mail over the Internet. It consists of abstract classes for
sending e-mail messages, representing MIME multipart messages, and
representing e-mail folders. JavaMail would be useful in the discussion-forum
example for sending out e-mail alerts to subscribing users when new messages
are posted.
XML
XML (see