next up previous


TDDI48: Guest book lab (lab 2)

Jonas Kvarnström
jonkv@ida.liu.se

Version 1.1 (Thu, 05 Feb 1998 23:39:05 +0100)


Contents

Lab 2: Guest book server

In this lab, you will create a guest book , where people visiting your home page can register themselves as guests. Your guests will be informed by e-mail whenever you have made changes to your home page.

The lab will be implemented in several steps:

1.
Determine what information you want users to enter and write a database handler that stores user information in a file.
2.
Write a threaded guest book server and an applet submitting data to the server. Users will only be able to submit information, not change their information or remove themselves from your guest book. The applet and server will communicate using sockets and object serialization.
3.
Add the capability to create an HTML page containing a list of all guests.
4.
Add the capability to send a message to all registered guests.
5.
Make it possible for users to retrieve their own information, modify it and remove themselves from the list.
6.
Use a real database instead of a file. The server will connect to the database using JDBC (Java Database Connectivity).
7.
Create HTML pages dynamically using a servlet. The servlet - which will be ``linked in'' as a part of the web server - will query the database directly.
8.
Add search capabilities to your servlet.
9.
Let the applet and server communicate using RMI, Remote Method Invocation.

You will use the following parts of Java:

General information about the lab



Creating a project

For this lab, you will need to create a new empty project.



Packages

You will most likely benefit from putting your code in Java packages . A good split may be to put server code in the server package, client code (for the applets) in the client package and common code (such as objects that will be sent from the applet to the client or vice versa, or other code used by both clients and servers) in the common package.



Separating constants from the code

Whenever you write a program that is not completely trivial, it is important not to use ``magic constants'' directly in the code. Instead, create a separate class that contains the constants as final static fields, and use those fields in your other classes.

For example, you may want to create a class called common.Constants that contains (among other things) a field public static final int PORT = 8765; that contains the port number for your server. If you reference this port number in more than one place - for example, in the applet and in the server - and you need to change it, it is far easier to change it only in the Constants class. This class can also eventually contain fields for the name of the guest book data file you will use, the URL of the database you will use, and so on.



Error handling

You must always take care of all exceptions that can occur, and you must not use a simple catch-all clause such as ``catch (Exception e)'' - if a method call can throw more than one exception, you must explicitly handle each different exception that can be thrown.

You must always deal with the exceptions as well as you can, and attempt to recover gracefully. For example, you will write a server; it can't simply exit when there is an I/O exception - the server must be robust.



The <APPLET> tag

You will write at least two applets in this lab. You can read more about the different arguments to the APPLET tag in the JDK 1.1 documentation.



General information about Java programming

The Java Tutorial contains a lot of information that can be useful to you in this lab.

Exercise 1: File handling

In this exercise, you will write the file handling capabilities for the guest book.



Part 1: What information do you want?

The first thing you need to do in this exercise is to decide what information you want the user to be able to enter. You must allow the user to enter at least the following information:

However, you are free to add any other information that you may want.

 You must decide on a unique key field for the user information - a field that uniquely identifies the user. A simple solution would be to use the e-mail address, but you are free to choose another field. You could even add two separate account and password fields and check the password whenever the user wants to modify or remove his/her information.

You will need to create a new user information class capable of containing the information you want; when the user submits his/her information, the guest book applet will send a complete user information object over the network. In the lab description, we will call this class UserInfo, but you are of course free to call it whatever you want. The new class should be in the common package, since it needs to be created by the applet (client) and sent to the server.



Part 2: Creating a user database

In the following exercises, the guest book server will need to save user information somewhere. In this exercise, you will write a simple file database that stores a set of UserInfo objects.

One way to do this is simply to store a sequence of UserInfo objects in a file, re-reading the entire file whenever you are searching for a certain user and re-writing the entire file whenever you are adding or modifying a UserInfo object. This is certainly not very efficient. It will be sufficient for an initial implementation, but you would probably like to replace this with a more intelligent implementation some time in the future.

Therefore, it is very important to distinguish between the operations that you need to perform on the database and the implementation of those operations. The operations you will need in this lab are the following:

Exactly how this is done is completely irrelevant to the user of the database, as long as the five operations are available. Therefore, you will separate all database handling code into a class implementing the UserDatabase interface :

package server;

import java.util.Enumeration;

public interface UserDatabase {

    /**
     * Read the entire user database and return an Enumeration containing
     * all UserInfo objects.
     */
    public Enumeration getUsers() throws IOException;

    /**
     * Returns the unique UserInfo object corresponding to the key of
     * the given UserInfo object, or null if no such user exists.
     */
    public common.UserInfo getUser(common.UserInfo info) throws IOException;

    /**
     * If there is no user with the same key as *info*, add *info*
     * to the user database. Otherwise, throw an IOException.
     */
    public void append(common.UserInfo info) throws IOException;
    
    /**
     * If there is a user with the same key as *info*, remove the
     * old user information object and add the new information to
     * the user database. Otherwise, throw an IOException.
     */
    public void update(common.UserInfo info) throws IOException;

    /**
     * If there is a user with the same key as *info*, remove the
     * old user information object.  Otherwise, throw an IOException.
     */
    public void remove(common.UserInfo info) throws IOException;
}

As long as you only use the methods available in UserDatabase, you can begin by writing a very inefficient (but correct!) implementation - called FileDatabase, for example. Later on, you can write a more efficient replacement for the FileDatabase and simply plug it in, since the replacement will also implement the UserDatabase interface.

As you can see, all UserDatabase methods except getUsers() take UserInfo objects as arguments, even though most methods are only interested in the key field. This is because the UserDatabase interface should not impose any restrictions on what key field we use; the methods get the entire UserInfo object and can use any field as a key. Of course, in an implementation of UserDatabase, all methods will use the same key field.

You will need to create a new class implementing the UserDatabase interface; in the lab description, it will be called server.FileDatabase, since it is a user database that uses an ordinary file.

In the UserDatabase interface above, we use a java.util.Enumeration for returning the set of users. An Enumeration has two methods: hasMoreElements() returns true if there are more elements in the sequence, and nextElement() returns the next element in the sequence (see also the TestDatabase class below). This is a nice way to hide the implementation details of your getUsers method: You may read the entire database into memory at once, or you may read each UserInfo object from the file from within the nextElement() method each time it is called.

In this implementation, you can simply create a Vector vec of all users and return vec.elements().

Note that since the server you will write will be threaded, different threads can call methods in the same FileDatabase simultaneously. Therefore, you will need to use synchronization to make sure that the file handler performs atomic operations on the database - that is, the following scenario must not be possible:

1.
Thread A: Calls append; append A reads the entire file into a Vector in memory.
2.
Thread B: Calls append; append B reads the entire file into a Vector in memory.
3.
Thread A: Append A continues, appends user A to its Vector, and writes the Vector to the file.
4.
Thread B: Append B continues, appends user B to its Vector, and writes the Vector to the file.
5.
Information about user A is lost!

How you perform this synchronization is up to you. You can read more about threads and thread synchronization in the Java Tuturial.

(You are allowed to assume that there is only one UserDatabase object accessing the same database file at the same time.)

Storing objects in files

In previous versions of Java, you had to invent your own file formats for writing information to disk. In Java 1.1, however, entire objects (and any ``sub-objects'' they refer to) can be serialized - converted to a stream of bytes - and saved to disk or sent to another program through the network. The serialization protocol is standardized and gives the same results in all Java implementations. You can use serialization in the file database.

Any object that should be serialized must implement the Serializable interface.

To serialize an object (send/write it as a sequence of bytes), you use the writeObject() method in an ObjectOutputStream; this output stream is a filter that is connected to another OutputStream, such as a FileOutputStream or the output stream of a network socket.

You can write any number of objects to an ObjectOutputStream, and you can also write ``composite'' objects. This means that you will need to decide whether you want to write one object at a time, a single array of objects, a Vector of objects, ... You may also want to write some additional information to the stream, such as the number of registered users.

To deserialize the object (receive/read a sequence of bytes and convert it to an object), you use the readObject() method in the corresponding ObjectInputStream.

You can read more about object serialization in the serialization specification in the JDK 1.1 documentation.

As always, you will have to take care of any errors and exceptions that can occur, and you are not allowed to ignore errors silently within the database handler - they should be signaled to the caller as IOExceptions. This means that you may have to catch other exceptions such as ClassNotFoundException in the FileDatabase methods and ``manually'' throw IOExceptions (with readable error message strings) instead.



Part 3: Testing your user database

You can test your file database code with the following code. Note that since your code should detect duplicates, you may need to delete your database file each time you run the test program.

package server;

import common.UserInfo;
import java.util.Vector;
import java.util.Enumeration;

public class TestDatabase {

    public static void main(String[] args) throws IOException {

        /*
         * Declare it only as a UserDatabase -- that way, you are certain
         * that you don't use any methods that are only available in
         * FileDatabase and not in "general" UserDatabases.  One argument
         * to the FileDatabase constructor may be the name of the database
         * file.
         */
        UserDatabase db = new FileDatabase(/* Your code here */)

        UserInfo user1 = new UserInfo(/* Your code here */);
        UserInfo user2 = new UserInfo(/* Your code here */);
        UserInfo user3 = new UserInfo(/* Your code here */);
        UserInfo user4 = new UserInfo(/* Your code here */);

        db.append(user1);
        db.append(user2);
        db.append(user3);
        db.append(user4);

        try {
            db.append(user1);
            // We tried to append the same user again.  If we get this far,
            // we succeeded -- so we have a duplicate.  This is wrong!
            System.out.println("BUG: Failed to detect duplicate user");
            return;
        } catch (IOException e) {
            System.out.println("OK: Detected duplicate user");
        }

        // Here, you should change some non-key fields in user1

        // user1.something = "whatever";

        db.update(user1);

        // And we remove user3...

        db.remove(user3);

        // Then we print the list of users

        Enumeration users = db.getUsers();

        while (users.hasMoreElements() {
            // We only know that we have Objects; they may not be
            // UserInfo objects.  If we tried to cast nextElement()
            // to an UserInfo object here, we could get a
            // ClassCastException.
            Object obj = users.nextElement();

            if (obj instanceof UserInfo) {
                // Yes, it really was a UserInfo object
                printUser((UserInfo)obj);
            } else {
                // For some reason, someone has put something that is not
                // a UserInfo object in our Vector...
                throw new IOException("BUG: Unknown object in user database: " + obj);
            }
        }
    }

    public static void printUser(UserInfo user) {
        System.out.println(/* Your code here */);
    }
}



Comments about the exercise

In this exercise, you have learned how to use files in Java, and specifically how to write objects to and read objects from files using the ObjectOutputStream and ObjectInputStream classes. Java has many other file handling classes. Some of them are the following:

All the stream classes read and write single bytes , even if they return Strings or chars (remember that a Java char is 16 bits). There are also corresponding Reader and Writer classes, introduced in Java 1.1, that are much better at handling true 16-bit characters without loss of information.

Exercise 2: Guest book applet/server

In this exercise, you will write a guest book applet , which can be put on your home page and which will allow visitors to register themselves as guests. You will also write a threaded guest book server , which stores a set of registered users in the file database you wrote in the previous exercise. When a guest submits his/her information, the applet will contact the server and ask it to store the information the user has entered.

You can read more about Java clients and servers using sockets in the custom networking part of the Java Tutorial, especially the All About Sockets section.



Applets and security

Due to security restrictions, Java applets (such as the guest book applet you will write) are only allowed to contact the server computer from which they were downloaded - they are is not even allowed to contact ports on the computer on which they are running.

In this lab, the guest book server you write will be running on your own computer. The applet can only contact it if it is downloaded from the same computer. Therefore, you must have a web server running on your own computer.

In this course, we are using the Java Web Server (previously called Jeeves) which is written completely in Java. How you use the server is described in more detail below.

The server is already installed on the lab computers. If you are doing your labs at home, you must install a web server supporting servlets. You can download the Java Web Server from here (use version 1.0.3; it is free for educational use). Note that you will be writing servlets that are pre-configured in the Java Web Server installed in the PC lab rooms. If you want to do this at home, you must configure everything yourself - unfortunately, we do not have the time to answer your questions about installing software on your own computers. If you have problems, use the computers in the PC lab rooms.

Starting the Java Web Server

Normally, if you were running a 'permanent' web server, the Java Web Server would run as a Windows NT service - that is, it would start automatically when you turn on the computer.

In this course, since the web server should not be permanent, you must start it manually. Unfortunately, this is more complicated than it should be.

To start the server, you must first start a command prompt (the Windows Start menu, then Programs , then Command Prompt ). In the command prompt window, you type the following:

        c:
        cd \tddi48\javawebserver1.0.3\bin
        httpdnjr

All log messages written by the web server will be shown in the command prompt window. Note that as the server starts up, it will write 10-20 lines of messages; this is normal.

After this, you must start a web browser (such as HotJava or Netscape Navigator) and open the address http://localhost:9090 - this takes you to the administration interface for the web server. Log in as administrator (user name 'admin', password 'admin'). You will see three services - the Web Service, the Proxy Service and the Administration Service. Single-click on the Web Service (port 8080) and click the "Start" button.

Stopping the Java Web Server

To stop the Java Web Server completely, press Ctrl-C in the command prompt window. You can also stop and restart it from the administration interface as described above.



Part 1: A network communication protocol

(This section describes approximately how your network communication protocol will work, but you will not implement it until part 3.)

The information that the user enters must be sent to the server in some way, and the server must be able to reply with a status message (or error message) of some kind.

To be able to do this, we must first create a two-way network connection between the client and the server. This is done using sockets , which you can read about in the custom networking part of the Java Tutorial.

Once we have a socket connection, we can use its getInputStream() and getOutputStream() methods to get ordinary input and output streams that we can read from and write to. But what should we read and write?

In previous versions of this lab, when we used Java 1.0.2, we sent the user information as text strings separated by some field delimiter, but this was very cumbersome. Fortunately, object serialization, which you used in the previous exercise, works as well with socket input/output streams as with files. This is what you will use in the guest book applet/server.

The details of your network communication protocol are up to you. However, the following things will be necessary:



Part 2: Graphical design for the guest book applet

Create a new applet - the guest book applet - in the client package. In the lab description, we will refer to this applet as GuestBookApplet.

You will need to design the user interface for your guest book applet. There should be a text field for the name, one for the e-mail address, and so on. Make sure that the components you use are appropriate - for example, the ``want to be informed'' field is boolean and should therefore be a checkbox, and since the general comments may be long, that field should be a TextArea and not a TextField. Use a layout manager (for example, GridBagLayout) to position the fields in the applet!

There should be at least five action buttons available in the applet:

You may want also want to have a ``clear all information'' button, and give the fields default values (such as the string ``http://'' in the web page field).

You should also include an information field where the applet can show information, such as error messages, to the user.



Part 3: Communicating with the guest book server

Write a multi-threaded guest book server that can accept user information from a network socket and store it in a user database.

Since classes implementing UserDatabase are only required to enforce threadsafe operations within the same UserDatabase object, you should create your FileDatabase only once, when the server is created, and use it in all server threads. You also want to be able to reuse your guest book server class with different kinds of UserDatabase without making changes to the server. Therefore, the constructor of the guest book server should take a UserDatabase as an argument.

You can then create a FileDatabaseServer class as follows:

package server;

public class FileDatabaseServer {
    public static void main(String[] args) {
        new ThreadedServer(new FileDatabase(/* your code here */));
    }
}

In this exercise, you will implement the functionality for the ``submit'' button in the applet. When the user presses ``submit'', the following should happen:

1.
The client (applet) opens a network connection (a socket) to the server.
2.
The server accepts the connection and starts a new thread.
3.
The new thread gets its input from the socket's input stream. It checks which command the user sends (in this case, ``submit'', but you must make sure that you return a proper error code/message if another command was sent.
4.
Since the command was ``submit'', the thread attempts to append the user to the database. If the user is already present, FileDatabase will signal an error (an IOException), and the error message from this exception should be returned to the applet (by writing to the socket's output stream). Otherwise, some kind of ``user added'' code/message should be returned to the applet.
5.
The applet receives the reply from the socket and displays it to the user in some way.

A note on object streams

Note that the applet and the server threads cannot create their ObjectInputStreams and ObjectOutputStreams in the same order. The reason for this is that when you create an ObjectOutputStream, it wants to send a header before the constructor returns, and when you create an ObjectInputStream, it waits for a header before it returns. Therefore, if you start with creating two streams of the same kind, they will both be either trying to write a header or waiting for a header.



Part 4: Testing your guest book

To test the guest book, create a JAR (Java Archive) file containing all classes in your project; this can be done easily using Project/JAR in Visual Café. Create an HTML page that contains your applet and put it in the z:\public_html directory (if you don't have one, you must create one); this is where the Java Web Server will look for its documents. The HTML page should refer to your client.GuestBookApplet class and use the JAR archive that you just created (the ARCHIVE attribute of the APPLET tag); this archive must also be placed in z:\public_html.

You can read more about JAR in the JAR documentation in the JDK 1.1 documentation.

Start the guest book server on your computer - you may want to do this from a command line prompt.

Try to add a few users. Add users with the same key (e-mail address, or whatever field you used as a key); this should fail. Add several users with different keys. Look at the database file you created (using the Windows Explorer); the file should grow when you add more users.

If you update and recompile your code, don't forget to re-create the JAR archive!

Exercise 3: Generating a guest book page

Now users can add themselves to the guest book, but there is no way for anyone, even yourself, to see the list of guests.

In this exercise, you should change the threaded server so that every time a user submits new information, you generate an HTML page that contains a list of all registered users. Web pages and E-mail addresses should be clickable hyper links (e-mail addresses have ``mailto:'' URLs; for example, mailto:jonkv@ida.liu.se).

Note that since users may enter characters that are used by HTML, such as ``<'' and ``&'', you may want to convert them to their corresponding character names, such as ``&lt;'' and ``&amp;''. Otherwise, users can ``sabotage'' your guest book page by entering HTML codes in one of the text fields.

You should also change the applet so that when the user clicks the ``View guest book'' button, the browser will display the guest book; see the java.applet.Applet and java.applet.AppletContext classes for instructions on how to make the browser show another web page.

Exercise 4: Sending a message when the page changes

Although you may simply be interested in knowing who reads your home page, the main reason for users to register themselves may be that they can get a message whenever you update your page. This will be implemented in this exercise.

The server will not send mail automatically when your web page changes. It could do this, by creating a new background thread that continuously checks your home page for changes, but this would mean that mails would be sent every time you change your home page, even if you only made a minor cosmetic change. Instead, you will add a new command to your server for sending mail to all registered users that indicated that they want to be notified when your home page changes; this command must take the actual message to be sent as an argument. (Previously, you had implemented the ``submit'' command, which had a UserInfo as an ``argument''.)

Since you don't want ordinary users to access this command, you will also need to create a new applet that can send the ``send mail'' command to the server. This applet should have at least a text field for the subject, a text area for the actual message, and a button that sends the information to the server, telling it to send the message to all users.

Of course, you will also need to create a new HTML page containing your applet. As in the previous lab, you can put your code in a JAR archive.



Sending a mail message

Unfortunately, Java currently does not have any built-in methods for sending e-mail. This may change in the future with the JavaMail API, but currently, we must connect to an SMTP server ``manually''. You can do this with the class defined below. Note that this implementation is very ugly: It does not check any error messages sent by the SMTP server, and therefore we don't know whether our mails were accepted or not.

In this case, you can send mail messages to the mail.student.liu.se SMTP server.

package common;

import java.lang.*;
import java.io.*;
import java.net.*;

public class MailSender
{
    private String serverAddress;
    
    /**
     * Create a new mail sender for the given server address; you can create
     * this object once in your server and re-use it in all threads.
     */
    public MailSender(String serverAddress) {
        this.serverAddress = serverAddress;
    }
    
    /**
     * @param from The sender's mail address
     * @param to The recipient's mail address
     * @param subject The subject of the message
     * @param message The actual message
     *
     * @return null if OK, otherwise a String (an error message).
     */
    public String send(String from, String to, String subject, String message) {
        Socket socket;
    
        // Port 25 is the SMTP port.
        try {
            socket = new Socket(serverAddress, 25);
        } catch (IOException e) {
            return "Cannot connect to mail host '" + serverAddress + "': " + e;
        }

        PrintStream out;
    
        try {
            out = new PrintStream(socket.getOutputStream());
        } catch (IOException e) {
            return "Cannot get output stream to mail host '" + serverAddress + "': " + e;
        }

        try {
            out.println("HELO " + InetAddress.getLocalHost().getHostName().toString());
            out.println("MAIL FROM: " + from);
            out.println("RCPT TO: " + to);
            out.println("DATA");
            out.println("From: " + from);
            out.println("To: " + to);
            out.println("Subject: " + subject);
            out.println("");
            out.println(message);
            out.println(".");
            out.println("QUIT");
        } catch (IOException e) {
            return "Error sending mail: " + e;
        }
        return null;
    }
}

Exercise 5: Retrieving, changing and removing user data

Users can add themselves to the guest book database, but they cannot change their data (for example, if their web page address changes), and they cannot remove themselves from the database. You will add those functions in this exercise. As always, you must use proper error / exception handling and show comprehensible error messages to the user of the applet.



Part 1: Retrieving user data

If someone wants to change their guest book data, they will first want to retrieve their old data and make changes to that. Fortunately, in the user database, you have a key that uniquely identifies each user.

When the user fills in his/her key (possibly the e-mail address) in the guest book applet and presses the ``Retrieve information'' button, the applet should ask the server for the UserInfo object that corresponds to the given key. The applet should then fill in all text fields and other components correspondingly.



Part 2: Changing user data

Assuming you completed the previous exercises correctly, there should be an error message when the user presses the submit button and a user with the same key is already registered. Now, you will make it possible for users to change their information.

When the user fills in all information in the guest book applet and presses the ``Update information'' button, the applet should ask the server to update the information for the user with the given key. Of course, if there is no user with the given key, an error message should be shown.



Part 3: Removing user data

When the user fills in all information in the guest book applet and presses the ``Remove information'' button, the applet should ask the server to remove all information for the user with the given key. Of course, if there is no user with the given key, an error message should be shown.



Comments about the exercise

In a real guest book, users would naturally need to use some kind of password to remove or change their information, but we ignore security in this exercise.

Exercise 6: Using a real database

Until now, you have used a single database file for all users. This may work for a small guest book, but it is not realistic for a larger application.

In this exercise, you will replace your FileDatabase with a JDBCDatabase using the standard Java Database Connectivity API. You will not be using any of the Symantec-specific database classes that are included in Visual Café.



About Java Database Connectivity

Java Database Connectivity, JDBC, is a part of the Java 1.1 standard (the java.sql package). You can read more about it in the following places:



Part 1: Creating a database and a table

The first time you use the database server, you must create a new database. You can do this by starting the Sybase SQLAnywhere SQL Central and selecting the Database Utilities/Create database option. Name the database z:\javadb.db and press the Next (or Finish ) button until the dialog window disappears.

You now have a database, but it does not contain any tables. You will need to create at least one table that will contain the information you collect about your guests. To do that, connect to the database (View /Connect ):

You can add a table by doing javadb /javadb /Tables /Add Table . Give the table a name and press OK. Using Guests /Columns /Add Column , you can add new columns in the table. Read about different datatypes in the JDBC documentation.

Your table columns should always have types that are ``natural'' for the kind of information you want to represent - for example, you should make the ``wants to be notified'' column a boolean column, not a one-character column containing ``Y'' or ``N''.



Part 2: Writing the JDBCDatabase class

Now you should write the JDBCDatabase class. Like the FileDatabase class, the new class should implement the UserDatabase interface.

Make sure that your database class does not hard-code the database URL, user name, or password. You should either give them as arguments to the constructor or create a Connection outside the constructor.

You should also create a JDBCDatabaseServer analogous to your FileDatabaseServer.

Some example code

In the JDBC: Getting started document, there is some sample code showing how you can query a database.

The following code is a modified version of that sample code: Since you already know the names of the columns in your database, there is no need to find out the names of the columns using the ResultSetMetaData class.

Note that in your implementation, you may have to add more error handling code. You will also have to find out how to perform queries that update the database (since you will need to be able to remove and modify user information).

import java.net.URL;
import java.sql.*;

class SimpleSelect
{
    // This is my own class; you will have to decide how to handle
    // your errors...
    protected ErrorHandler err = new ErrorHandler(System.err);

    // We are using the Symantec JDBC driver.  (JDBC itself is
    // defined in the java.sql package, but anyone can write
    // JDBC drivers for different databases.)
    public final static String dbDriver = "symantec.itools.db.jdbc.Driver";

    public final static String tableName = "People";

    // This is the URL of our database.  We assume that the
    // database is running on the same computer as this program,
    // on port 8889.
    public final static String dbURL =
    "jdbc:dbaw://localhost:8889/SYBASE_SQLANY/javadb/javadb";

    public static void main (String args[]) {

        // Try to load the database driver.  We do this by getting the
        // Class object corresponding to the driver.  This causes the
        // class to be loaded into the Java virtual machine.  Its
        // static initializer methods are executed; one of them
        // registers the driver with the JDBC DriverManager -- so if
        // we succeed with this call, the DriverManager now knows that
        // the DBAnywhere driver is available.

        try {
            Class.forName(dbDriver);
        } catch (ClassNotFoundException e) {
            // You are allowed to fail and exit here, since this can
            // happen only when you *start* your threaded server.  You
            // are not allowed to terminate the program later, since
            // the server should stay up even if there are temporary
            // database problems.
            err.fatal("Database driver not found", e);
            return;
        }

        try {
            // Let all drivers log their messages to System.out, so
            // that we can see what is happening...

            DriverManager.setLogStream(System.out);

            // Try to connect to the database.  As you may remember, DBA
            // is the DataBase Administrator, and sql is the password we
            // used when we created the database in SQL Central.

            Connection con = DriverManager.getConnection(dbURL, "DBA", "sql");

            // If we were unable to connect, an exception would have
            // been thrown.  So, if we get here, we are successfully
            // connected to the database.

            // Check for, and display any warnings generated by the
            // connect.

            checkForWarning (con.getWarnings());

            // Get the DatabaseMetaData object and display some
            // information about the connection

            DatabaseMetaData dma = con.getMetaData();

            System.out.println("\nConnected to " + dma.getURL());
            System.out.println("Driver       " + dma.getDriverName());
            System.out.println("Version      " + dma.getDriverVersion());
            System.out.println();

            // Create a Statement object so we can submit SQL
            // statements to the driver.
            Statement stmt = con.createStatement();

            // Submit a query, creating a ResultSet object
            ResultSet rs = stmt.executeQuery("SELECT Name, Address FROM " + tableName);

            // Display all columns and rows from the result set
            dispResultSet (rs);

            // Close the result set
            rs.close();

            // Close the statement
            stmt.close();

            // Close the connection
            con.close();

        } catch (SQLException ex) {

            // A SQLException was generated.  Catch it and display the
            // error information.  Note that there could be multiple
            // error objects chained together.

            System.out.println ("\n*** SQLException caught ***\n");

            while (ex != null) {
                System.out.println("SQLState: " + ex.getSQLState());
                System.out.println("Message:  " + ex.getMessage());
                System.out.println("Vendor:   " + ex.getErrorCode());
                System.out.println();
                ex = ex.getNextException();
            }
        }
    }

    //-------------------------------------------------------------------
    // checkForWarning
    // Checks for and displays warnings.  Returns true if a warning
    // existed
    //-------------------------------------------------------------------

    private static boolean checkForWarning (SQLWarning warn)
         throws SQLException
    {
        boolean rc = false;

        // If a SQLWarning object was given, display the
        // warning messages.  Note that there could be
        // multiple warnings chained together

        if (warn != null) {
            System.out.println ("\n *** Warning ***\n");
            rc = true;
            while (warn != null) {
                System.out.println("SQLState: " + warn.getSQLState());
                System.out.println("Message:  " + warn.getMessage());
                System.out.println("Vendor:   " + warn.getErrorCode());
                System.out.println();
                warn = warn.getNextWarning();
            }
        }
        return rc;
    }

    //-------------------------------------------------------------------
    // dispResultSet
    // Displays all columns and rows in the given result set
    //-------------------------------------------------------------------

    private static void dispResultSet(ResultSet rs)
         throws SQLException
    {
        // Display data, fetching until end of the result set

        boolean more = rs.next();
        while (more) {

            // Loop through each column, getting the
            // column data and displaying

            System.out.println("Name:    " + rs.getString("Name"));
            System.out.println("Address: " + rs.getString("WWW"));
            System.out.println();

            // Fetch the next result set row

            more = rs.next();
        }
    }
}



Part 3: Testing your new system

In this exercise, you have added two (or possibly more) new classes to the system: The new JDBCDatabase database and the JDBCDatabaseServer. Since your previous classes were written to be easily extended by plugging in new databases, you should not have had to change any of your old code: You should still use exactly the same code for the applet, threaded server, and so on.

Now, you can test your new system. Try adding some guests, modifying their attributes, and so on. While you are doing this, you may want to look at the current contents of the database using SQL Central.

Exercise 7: Servlets

Until now, we have created a new HTML page for the guest book every time a guest is added or some information is changed. This may not be the best solution, since the web server may get a request for the guest book page at the same time we are updating it, meaning that the user will get an incomplete page. Instead, we should query the database dynamically whenever the user wants to view the guest book.

In this exercise, you will write a servlet that retrieves all information from the database and dynamically writes an HTML page that is sent to the user. This HTML page is never written to disk - it is sent directly from the servlet to the web browser.



Learning more about servlets

A servlet is to a server what an applet is to a client: Some Java code that executes within the ``main'' program, adding new capabilities. The Java Web Server uses servlets, and there are many other web servers that support servlets.

For those of you familiar with CGI programming, servlets are similar - but far more efficient, since they are linked into the web server, and safer to use, since they can be executed within a servlet sandbox similar to the applet sandbox in a web browser.

The following links may be useful:



Part 1: Writing the servlet

Create a new class called server.GuestBookServlet (the web server is preconfigured for a servlet of that name). Since your servlet will create an HTML page, it should extend javax.servlet.HttpServlet.

Most of the code you will need is already written. In JDBCDatabase, you have all the code you need for retrieving a set of users from the database; you have also written code that creates an HTML page containing those users.

The servlet method that is normally called by the server is doGet(); you should implement this method.

Due to the way the lab environment was installed, it is unfortunately impossible for the web server to access servlet code that is not in the server package. This means you cannot reuse your UserInfo class, since it is in the common package. Instead, you can let the code that generates the HTML page access the database directly.



Part 2: Testing the servlet

When you have completed your servlet, you must copy GuestBookServlet.class (and any other classes it refers to in the servlet package) to the c:\tddi48\javawebserver1.0.3\servlets\server directory. You can then try to load the page http://localhost:8080/dynamic-guestbook.html. This page is configured in the web server as an alias for the guest book servlet. You should get the same result by loading http://localhost:8080/servlets/guestbook.

If you need to recompile your servlet, you must remember to put the new class file in c:\tddi48\javawebserver1.0.3\servlets\server. The web server should detect that the class file has changed and reload the servlet, but in some cases, it may fail. In that case, you can shut down the server and restart it (from the administration interface, http://localhost:9090).

Exercise 8: Servlets with arguments

The previous exercise solved the problem of showing the entire HTML page, but it would also be nice to be able to show only some of the guests.

A servlet can take arguments, just like an applet or a CGI script. The arguments can be taken directly from an HTML form using the POST method, as follows:

<FORM METHOD=POST ACTION="/servlet/guestbook">
Name pattern to search for: <INPUT TYPE=text SIZE=40 NAME="namePattern"><p>
...and so on...
<INPUT TYPE="submit" VALUE="Query database">

When the user presses the button on the form, your servlet's doPost() method will be called. The method must do the following:

Exercise 9: Remote Method Invocation

Being able to use sockets explicitly is necessary in order to communicate with existing applications and non-Java applications, but now, both the applet and our threaded server are written in Java. Therefore, we can use Remote Method Invocation instead.

Remote Method Invocation, introduced in Java 1.1, lets a method call methods in objects that reside on another computer. Internally, this is handled using object serialization, but the details are hidden from the programmer, who only sees ordinary method calls.



Learning how to use RMI

You should read the Getting Started document in the JDK 1.1 documentation. Follow the instructions and create a distributed Hello World program. Make sure that you understand what happens when you run the program.

See also the complete RMI documentation.

The Exceptions In RMI part of the RMI specification also contains some useful information.



Using RMI in the guest book server

Make a copy of your guest book applet and guest book server code. Instead of using sockets explicitly, your new applet should make remote method calls directly into the server; therefore, the server no longer needs to be threaded.

If you get unexplainable exceptions, make sure that your applet and your server use the same version of the stub code generated by rmic and that the rmiregistry is running.

About this document ...

TDDI48: Guest book lab (lab 2)

This document was generated using the LaTeX2HTML translator Version 97.1 (release) (July 13th, 1997)

Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds.

The command line arguments were:
latex2html -t TDDI48: Lab 2 (Guest book lab) -prefix lab2b- -split 1 -no_footnode -custom_titles -local_icons -dir lab2b lab2.tex.

The translation was initiated by Jonas Kvarnstrom on 2/5/1998


next up previous
Jonas Kvarnstrom
2/5/1998