Recent articles:
Popular archives:
Java: A platform for platforms
Sun's reorg may seem promising to shareholders but it's also a scramble for position. The question now is whether Sun can,
or wants to, maintain its hold on Java technology. Especially with enterprise leaders like SpringSource and RedHat investing
heavily in Java's future as a platform for platforms
Also see:
Discuss: Tim Bray on 'What Sun Should Do'
Re-architecting the Forum's backend improves the application in three ways:
Why JDBC?
JDBC is Java's database API that adapts arbitrary SQL databases to Java and provides a common interface for using them. JDBC
works by providing three Java interfaces -- Connection, Statement, and ResultSet -- which are implemented by vendor-specific drivers. The programmer just plugs in the correct driver and sends the correct
SQL to access the database of his/her choice -- the rest of JDBC usage is essentially uniform.
For the purpose of this application, we use the simple message database structure, shown next, which we will access via JDBC calls to the Windows NT 4.0 machine that hosts it.
| Thread | Sender | Subject | Article |
This database is a quasi-relational database comprised of a single table, Messages. It conforms to the first normal form requirements of flatness and atomicity of attributes, but doesn't have a real primary key. For that matter, it isn't optimized either; the database is just for demo purposes.
Here's a complete listing of all the source code we'll use in this article.
Extending the API
The 1.0 version of Forum's API, as used by Forum and implemented in the ForumComm client communications library, looks like this:
Hashtable loadAllThreads () -- Loads all threads defined in server's config file.Vector loadThreadArticles (String t) -- Loads all articles in thread t.boolean postArticle (String art, String t) -- Posts article art to thread t.
When we turn the server into a middleware layer, we obviously have to continue to expose the API calls that the 1.0 client expects. But because we're now dealing with a real database, we need to add functionality that a more advanced client might want. These additional calls are suggested by what the database can offer. The following API calls, among others, would be useful additions given the new backend:
Vector loadThreadSubjects (String t) -- Loads all subjects in thread t.
Vector loadSubjectArticles (String t, String s) -- Loads articles with subject s in thread t.
Vector loadUserArticles (String t, String u) -- Loads user u's posts to thread t.
To migrate, we have to support Forum 1.0 clients and future clients simultaneously. A good way to handle this is to merge the two APIs and let the client, whichever version it may be, use the calls of its choice. We'll call the merged version Forum API version 1.1.
Implementing the classes
The 1.0 Forum server is composed of two classes, ForumServer and ForumConnectionHandler. ForumServer listens to the network and threads off a new ForumConnectionHandler for each client request that comes in. Again, for a review of this process, see part 2 of our Forum application series.
First, we're going to update ForumConnectionHandler to create an instance of ForumConnectionHandlerComm and call its methods when a client request comes in, instead of hitting the local filesystem as before. We can clean up ForumServer as well, because it no longer needs several of its original methods. Then, we'll formalize the Forum 1.1 API by creating
a Java interface, ForumCommInterface, which any Forum 1.1 communications library should implement. Finally, we'll add a new class, ForumConnectionHandlerComm, which will implement the ForumCommInterface with JDBC database calls.
Class ForumConnectionHandler
An instance of ForumConnectionHandler receives an incoming client request and handles it by calling the appropriate method of ForumCommInterface. Note that with this setup it is possible to plug in different implementations of ForumCommInterface without affecting how the handler functions.
import java.net.*;
import java.util.*;
import java.io.*;
public class ForumConnectionHandler extends Thread {
// 1.0 requests
static final int LOAD_ALL_THREADS = 1;
static final int LOAD_THREAD_ARTICLES = 2;
static final int POST_ARTICLE = 3;
// new requests for 1.1
static final int LOAD_THREAD_SUBJECTS = 4;
static final int LOAD_SUBJECT_ARTICLES = 5;
static final int LOAD_USER_ARTICLES = 6;
long id;
Socket client;
Vector basicThreads;
InputStream in;
OutputStream out;
DataInputStream dIn;
DataOutputStream dOut;
public ForumConnectionHandler (long i, Socket c, ThreadGroup h, Vector bt) {
super (h, "Forum Connection Handler 1.1 " + i);
id = i;
client = c;
basicThreads = bt;
}
public void run() {
try {
in = new BufferedInputStream (client.getInputStream());
out = new BufferedOutputStream (client.getOutputStream());
dIn = new DataInputStream (in);
dOut = new DataOutputStream (out);
ForumCommInterface comm = new ForumConnectionHandlerComm ();
String t, type = "";
int request = -1;
Vector threadArts;
request = dIn.readInt();
switch (request) {
// 1.0 requests
case LOAD_ALL_THREADS:
Hashtable threads = comm.loadAllThreads ();
threadSetup (threads, basicThreads);
Enumeration en = threads.keys();
while (en.hasMoreElements())
dOut.writeUTF ((String) en.nextElement());
dOut.writeUTF("");
dOut.flush();
type = "LOAD_ALL_THREADS";
break;
|
This code is essentially the same as that of the 1.0 version, up to the first case statement. This case calls comm's loadAllThreads (), and then sends the result to the client according to the socket-based protocol understood by the client's communications
library ForumComm.
case LOAD_THREAD_ARTICLES:
t = dIn.readUTF();
threadArts = comm.loadThreadArticles (t);
Enumeration en2 = threadArts.elements();
while (en2.hasMoreElements())
dOut.writeUTF ((String) en2.nextElement ());
dOut.writeUTF ("");
dOut.flush ();
type = "LOAD_THREAD_ARTICLES for thread " + t;
break;
case POST_ARTICLE:
t = dIn.readUTF();
String art = dIn.readUTF();
comm.postArticle (art, t);
type = "POST_ARTICLE for thread " + t;
break;
// stubs for 1.1 requests
case LOAD_THREAD_SUBJECTS:
// Vector subjects = comm.loadThreadSubjects (String t);
break;
case LOAD_SUBJECT_ARTICLES:
// Vector sa = loadSubjectArticles (String t, String s);
break;
case LOAD_USER_ARTICLES:
// Vector ua = loadUserArticles (String t, String u);
break;
default:
type = "unknown request: " + request;
} // end switch
|
The last three cases (those of the new API additions) are left unimplemented. Their implementation follows that of the previous
case statements and is necessary to provide the additional Forum 1.1 features to a socket-based client.
System.out.println ("#" + id + ": " + type + " from " + client.getInetAddress());
} catch (Exception ex) {
ex.printStackTrace ();
}
finally {
try {
client.close();
} catch (IOException ex) {
System.out.println ("Socket close for connection #" + id + " failed.");
}
}
}
void threadSetup (Hashtable h, Vector t) {
// make sure that the basic threads are in place
Enumeration en = t.elements ();
while (en.hasMoreElements ()) {
String k = (String) en.nextElement ();
if (!h.containsKey (k))
h.put (k, new Vector ());
}
}
}
Any SQLExceptions thrown by the ForumConnnectionHandlerComm class are handled in the catch (Exception ex) clause. Here they're simply displayed to the server's console.
The threadSetup method, a new method for ForumConnectionHandler, makes sure that a minimum number of discussion threads are provided to the client even if the database is empty. These "basic
threads" are defined in the forum config file, forum.cfg, and passed in as the vector basicThreads when the handler is constructed. You can easily modify this particular thread-choice policy by changing or overriding threadSetup .
Class ForumServer
For the sake of brevity, we won't cover the changes I've made to this class. But take a moment to look over the modified code,
which is available here.
Interface ForumCommInterfaceForumCommInterface is used to provide a uniform interface to classes that expose the 1.1 Forum API. The implementation of this interface is
extremely straightforward; it consists of definitions for the methods declared in by the API.
Class ForumConnectionHandlerComm
This new class, ForumConnectionHandlerComm, requires a bit of explanation. Let's break it down piece by piece.
import java.net.*;
import java.util.*;
import java.io.*;
import java.sql.*;
class ForumConnectionHandlerComm implements ForumCommInterface {
// 1.0 Forum client sender/message delimiter for backwards compatibility
static final String DELIMITER = "\u0000";
// Connect Software's < www.connectsw.com > FastForward driver used here...
static final String DRIVER = "connect.microsoft.MicrosoftDriver";
static final String URL = "jdbc:ff-microsoft://machine.yourdomain.com:1433";
static final String LOGIN = "forum";
static final String PASSWD = "yourpassword";
static final String DB_TABLE = "Messages";
static {
try {
// try to load the ODBC driver
Class.forName (DRIVER);
// enable debugging by uncommenting:
// DriverManager.setLogStream (System.out);
} catch (ClassNotFoundException ex) {
ex.printStackTrace ();
}
}
Here, we initialize the driver for the database that we are accessing. You can approach the driver issue in a number of ways,
depending on the type of driver that you want to use. I have chosen a type 4 (pure Java) driver to avoid using ODBC, which
adds another level of indirection to the conversation with the database. The driver class is loaded in a static initializer because it only needs to be loaded once.