Java Servlet Programming in OS/2

From EDM2
Jump to: navigation, search

Written by Fernando Lozano

(Note: The source code for this article is msql4.zip here. Ed.)

Introduction

Unlike Java Applets, which after all the hype proved to be just toys in the corporate world, Java Servlets are rapidly capturing the attention of Information Technology on many companies.

But what are Java Servlets? They are server-side Java applications, just like Applets are client-side (browser) applications. Servlets work in collaboration with some kind of server to provide a service to client applications.

We'll focus on a special kind of servlet, the HttpServlet. It's a Servlet specialized to work with a Web Server. You can think about a Java Servlet as an easier replacement for a Java CGI application. The standard Java Extension API for Servlets provides Java classes the ability to perform the same operations that the standard CGI interface permits any external application to do:

  • Reply to a Web Browser request for an URL
  • Get the request details, such as the client IP address or user-agent
  • Get form data send by the browser
  • Get and set cookies
  • Send an HTML page or other kind of document (such as an GIF image) as the reply to the browser
  • Send HTTP headers to the browsers, causing it to redirect to another URL or not store the reply on its cache

So far I could just write a JAVA CGI application to do the work. But the Java VM startup time is too long to provide a quick reply to browser requests. Besides, I must write a CMD wrapper so my web server can run my Java application as a CGI application.

The Java Servlet API supposes the web server contains a Java VM so it won't take the time to start a new VM for each browser request. But better than that, the Servlet API provides means by which your application can preserve state between browser requests. (Remember, the CGI interface is stateless!) So, time-consuming operations like connecting to a data base can be performed just one time during Servlet initialization instead of for every request.

Programming Java Servlets is also much easier than programming Java or C/C++ CGI applications. This is because the Servlet API provides methods for easy decoding of form data, URL information and so on.

Configuring Java Servlet support

Let's use Lotus Domino GoWebserver (it's just the old known IBM Internet Connection Server, now sold using the Lotus brand). I downloaded release 4.6.2.2 from IBM Visual Age Developer's Domain: http://www7.software.ibm.com/vad.nsf/

Lotus GoWebserver is still free, only the secure (SSL) components expire after 60 days and need registration. Servlet support is available since ICS release 4.2.1, but you should check the supported release of both the JDK and the Servlet API if you do not want to upgrade your web server. I am using JDK 1.1.6 and GoWebserver 4.6.2.2 supports the Servlet API version 1.0.

The Apache OS/2 web server also supports Servlet, through the JServ module. I am not aware of other OS/2 web servers with Servlet support, if you know about one please mail me as lozano@blnet.com. Few other commercial web servers have native Servlet support (two exceptions are O'Reilly WebSite Pro 2.2 and the Novonyx Fastrack Server for Netware) but Sun provides on its own JSDK (Java Servlet Development Kit) support for Netscape Fasttrack and Enterprise servers, besides Apache. You can also buy from third-parties, such as IBM WebSphere, Servlet support for Microsoft IIS and other servers. The Servlet Domain is a nice source of information on Servlet support, the URL is: http://www.servletcentral.com/

I installed GoWebserver in E:\WWW, and put all related files inside this directory, so I have E:\WWW\HTML, E:\WWW\CGI-BIN and E:\WWW\SERVLETS.

Before being able to run servlets on Lotus GoWebserver you need to edit the file \MPTN\ETC\servlet.cnf and change the following lines (please replace "e:\WWW" with the directory you chose during installation):

  ServletLog      e:\WWW\LOGS\servlet-log
  JavaPath        e:\java11\bin
  JavaLibPath     e:\java11\dll
  JavaClassPath   e:\java11\lib\classes.zip;e:\www\cgi-bin\icsclass.zip;
                  e:\www\servlets\public

Restart GoWebserver and check its error log to see if everything is ok. Now let's run one of the sample Servlets provided with GoWebserver to see if we really have Servlet support operational.

First, enter the directory E:\WWW\SERVLETS\PUBLIC. Then try the following command:

  [E:\www\servlets\public] javac ReqInfoServlet.java

You should have a clean compile and the file ReqInfoServlet.class should be generated. If something goes wrong, check the release number of your JDK (it must be at least 1.1.4 for GoWebserver 4.6.2.2), check your CLASSPATH in CONFIG.SYS (it should have at least the same contents of the JavaClassPath in servnet.cnf) and check if you put in \MPTN\ETC\servet.cnf the correct paths for your installation of JDK 1.1.x (tip: it is NOT "\javaOS2").

Let's test the compiled servlet. Open your favorite web browser and try the following URL:

  http://localhost/servlet/ReqInfoServlet

(You did configure "localhost" in your HOSTS database, didn't you?) The result should be a page like this:

  Request protocol is HTTP/1.0
  Request scheme is http
  Remote host is null
  Remote address is 127.0.0.1
  Receiving server is localhost on port number 80

  Header data is:
  name = CONNECTION; value = Keep-Alive
  name = ACCEPT; value = image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
  name = USER-AGENT; value = Mozilla/3.0 (OS/2; I)
  name = AUTHORIZATION; value = Basic YWRtaW46YWRtaW4=
  name = HOST; value = localhost

  Request parameters are:

If it didn't work, recheck all previous steps of installing JDK 1.1.6, LotusGoWebserver 4.6.2.2 and editing servlet.cnf.

A simple Servlet

Now that we have the ability to run servlets, let's see how to build one.

At first, we need to import the classed from the Servlet API:

    import javax.servlet.*;
    import javax.servlet.http.*;

As you will most of the time send text (or HTML text) back to the browser, we need the java.io core package:

    import java.io.*;

A servlet is just a Java class that inherits from one of the Servlet classes let's use the specialized HttpServlet:

    public class Sample1 extends HttpServlet
    {

Our servlet should implement at least one method: service(). This method is called for every request the web server receive from the browser asking for the servlet URL:

        public void service(HttpServletRequest req, HttpServletResponse res)
                    throws IOException
        {

The service() method have to be declared as above. Its parameters provide access to the browser request and allow us to build our reply to the request. The first thing we have to do is to is to tell what kind of information we are about to send to the browser:

            res.setContentType("text/html");

So we are about to send ASCII text but the browser should interpret HTML tags inside it. To actually send the HTML text to the browser, we need an output stream, which we get from the Response argument:

            PrintWriter pw = new PrintWriter(res.getOutputStream());

And now just dump plain HTML on the stream:

             pw.println("<HTML>");
             pw.println("<HEAD>");
             pw.println("<TITLE>Sample Servlet #1</TITLE>");
             pw.println("</HEAD>");
             pw.println("<BODY>");
             pw.println("<H1>Sample Servlet #1<HR></H1>");
             pw.println("<P>Hi There!");
             pw.println("</BODY>");
             pw.println("</HTML>");

Now let's finish with the output stream, and we are done!

 
             pw.flush();
             pw.close();
         }
     }

Here is our complete servlet example:

    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
 
    public class Sample1 extends HttpServlet
    {
        public void service(HttpServletRequest req, HttpServletResponse res)
                    throws IOException
        {
            res.setContentType("text/html");
            PrintWriter pw = new PrintWriter(res.getOutputStream());
 
            pw.println("<HTML>");
            pw.println("<HEAD>");
            pw.println("<TITLE>Sample Servlet #1</TITLE>");
            pw.println("</HEAD>");
            pw.println("<BODY>");
            pw.println("<H1>Sample Servlet #1<HR></H1>");
            pw.println("<P>Hi There!");
            pw.println("</BODY>");
            pw.println("</HTML>");
 
            pw.flush();
            pw.close();
        }
    }

Save it as Sample1.java and compile with the command:

  [E:\fernando] javac Sample1.java

Then copy the generated .class file to the servlets directory of your web server:

  [E:\fernando] copy Sample1.class \www\servlets\public

Now you can run the servlet using the following URL:

  http://localhost/servlet/Sample1

And the result should be a page that resembles the following:

   Sample Servlet #1
   ----------------------------------
 
   Hi There!

If something goes wrong and you have to recompile your servlet, be sure to restart also Lotus GoWebserver. Each servlet is loaded only once by the web server, so it won't get the new .class file unless your stop the web server and restart it.

More about the Servlet API

Although most servlet tutorials will instruct you to use the service() method, just as we have already done, it is not advisable to do so. The service() method is called for any request received for the servlet URL. This request could be a GET, a POST, a PUT, a DELETE and others can be defined by newer HTTP standards.

The HttpServlet class has request-specific methods that are the preferable way to code our servlets. The main ones are doGet() and doPost(). The GET request is sent by the browser when it just wants to load a page, and the POST request is sent by the browser when there is too much form data to be kept as part of the URL. Actually, it's the HTML coding of the form that instructs the browser to use GET or POST when the Submit button is pressed, and I recommend you always use POST for HTML forms.

Let's rewrite out example to use the doGet() method instead of service():

    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
 
    public class Sample2 extends HttpServlet
    {
        public void doGet(HttpServletRequest req, HttpServletResponse res)
                    throws IOException
        {
            res.setContentType("text/html");
            PrintWriter pw = new PrintWriter(res.getOutputStream());
 
            pw.println("<HTML>");
            pw.println("<HEAD>");
            pw.println("<TITLE>Sample Servlet #2</TITLE>");
            pw.println("</HEAD>");
            pw.println("<BODY>");
            pw.println("<H1>Sample Servlet #2<HR></H1>");
            pw.println("<P>Hi There!");
            pw.println("</BODY>");
            pw.println("</HTML>");
 
            pw.flush();
            pw.close();
        }
    }

Save it as Sample2.java, compile it and copy Sample2.class to the servlet public directory of your web server. Load it with the URL: http://localhost/servlet/Sample2

And see it's result. Note that we just changes "service" for "doGet" and changed "Sample 1" to "Sample 2". Very easy!

But before we can write an useful servlet, we need to know how to get form data. Create the following simple form, which has just an text entry field and a submit button:

   <HTML>
   <HEAD>
   <TITLE>Form for Sample Servlet #3</TITLE>
   </HEAD>
   <BODY>
   <H1>Form for Sample Servlet #3<HR></H1>
   </BODY>
   <FORM method="post" action="/servlet/Sample3">
   Please enter your name:
   <BR>
   <INPUT type="text" name="name" size=30>
   <P>
   <INPUT type="submit" value="Ok"><HR>
   </FORM>
   </HTML>

A servlet can obtain form data using the getParameter() method of the HttpRequest class. You refer to each form element value by using it's name attribute. As our text field has name="name", we can call getParameter as:

  String name = req.getParameter ("name");

Here is the simple Sample3.java servlet that receives the form data and displays a greeting message:

    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
 
    public class Sample3 extends HttpServlet
    {
        public void doPost(HttpServletRequest req, HttpServletResponse res)
                    throws IOException
        {
            res.setContentType("text/html");
            PrintWriter pw = new PrintWriter(res.getOutputStream());
 
            pw.println("<HTML>");
            pw.println("<HEAD>");
            pw.println("<TITLE>Sample Servlet #3</TITLE>");
            pw.println("</HEAD>");
            pw.println("<BODY>");
            pw.println("<H1>Sample Servlet #3<HR></H1>");
 
            pw.println("<P>Hi, " + req.getParameter("name") + "!");
            pw.println("<BR>Nice to meet you!");
 
            pw.println("</BODY>");
            pw.println("</HTML>");
 
            pw.flush();
            pw.close();
        }
    }

Have you seen how easy is to work with form data? So now let's do more interesting things, using database-driven servlets!

Getting data from a database

As some readers may be suspecting, I'll use mSQL as the database back-end for the rest of this article. I could use any other database: Oracle, Sybase, Solid, DB2 and others have OS/2 versions and JDBC drivers. I could also used other web servers, but GoWebserver + mSQL gives to me and you the following benefits:

  • Both are FREE!
  • Small download, even 28.8K modem users can get the two packages and start working
  • Small footprint, no one will need Pentium 266 + 64Mb RAM to follow my examples.

You have to look at my two previous articles, "mSQL Revisited" and "Accessing databases using Java and JDBC" to get the background I need and to know how to build the work environment for the rest of this tutorial.

Instead of using mSQL 2.0.1B by Dirk Ohme, you may download and install the new mSQL port for OS/2, release 2.0.4.1 Beta3 by Yuri Dario. It won't have the easy install script (just instructions in the file README.EMX) but although being labeled a beta and a "developer's release", it is already more stable than Dirk's port. Visit The mSQL PC Home Page at

http://www.blnet.com/msqlpc

To download the new mSQL 2.0.4.1 and to know about new developments.

Besides all steps necessary to compile and run Java applications using the mSQL JDBC driver, you need to change the CLASSPATH the servlet gets when started from the web server. Edit \MPTN\ETC\servlet.cnf as follows:

  JavaClassPath   e:\java11\jdbc\msql-jdbc_1.0b3\imaginary.zip;
                  e:\java11\lib\classes.zip;
                  e:\www\cgi-bin\icsclass.zip;e:\www\servlets\public

And don't forget to restart your web server.

If you can already run the previous servlets and you can also run the samples from the JDBC article, we can start with a simple servlet that just lists all entries from our address book. Here is the source code:

    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    import java.net.URL;
    import java.sql.*;
 
    public class AddrBook extends HttpServlet
    {
        public void doGet(HttpServletRequest req, HttpServletResponse res)
                    throws IOException
        {
            // we will send a HTML page
            res.setContentType("text/html");
            PrintWriter pw = new PrintWriter(res.getOutputStream());
 
            // start the page and the included table
            pw.println("<HTML>");
            pw.println("<HEAD>");
            pw.println("<TITLE>Address Book Servlet</TITLE>");
            pw.println("</HEAD>");
            pw.println("<BODY>");
            pw.println("<H1>Address Book<HR></H1>");
            pw.println("<TABLE border align=\"center\">");
            pw.println("<TR>");
            pw.println("<TH>Name");
            pw.println("<TH>Company");
            pw.println("<TH>Title");
            pw.println("<TH>E-mail");
 
            try {
                // connect to mSQL database pim
                Class.forName ("com.imaginary.sql.msql.MsqlDriver");
                String url = "jdbc:msql://localhost:1114/pim";
                Connection con = DriverManager.getConnection (url, "borg", "");
 
                // execute the query
                Statement stmt = con.createStatement ();
                ResultSet cur = stmt.executeQuery (
                    "SELECT company, first_name, last_name, title, e_mail "
                    + "FROM addr_book "
                    + "ORDER BY last_name, first_name"
                );
 
                // list the returned rows
                while (cur.next ()) {
                    pw.println ("<TR>");
                    pw.println ("<TD>" + cur.getString ("last_name")
                        + ", " + cur.getString ("first_name"));
                    pw.println ("<TD>" + cur.getString ("company"));
                    pw.println ("<TD>" + cur.getString ("title"));
                    pw.println ("<TD>" + cur.getString ("e_mail"));
                }
 
                // clean-up
                stmt.close ();
                con.close ();
            }
            catch (Exception e) {
                pw.println("<TR><TD colspan=4>");
                pw.println (e.getMessage ());
 
                // this one will go to the web server error log
                e.printStackTrace ();
 
                pw.println("</TABLE>");
                pw.println("</BODY>");
                pw.println("</HTML>");
 
                pw.flush();
                pw.close();
            }
 
            // finish the HTML page
            pw.println("</TABLE>");
            pw.println("</BODY>");
            pw.println("</HTML>");
 
            pw.flush();
            pw.close();
        }
    }

If you have deleted the PIM database we've used in the previous articles, the source code of this article, available on EDM/2 web site, has a SQL script that creates the table, sequence, index and populate then with some test data.

You can see that this application is very simple: I basically got the code inside the main() method of the PimSearch app presented in the June 1998 issue and put inside the servlet doGet() method, changing "System.out" by "pw". That's all: a complete servlet that lists records from an mSQL database.

But I won't recommend coding a database-driven servlet the way this example shows. It has too much code in just one method, and has redundant code (see how we finish the page two times). Worse yet, imagine your complete address book, with dozens or even hundred of names, being loaded by your web browser through a 28.8K modem. So, let's rewrite out app to be better structured and also to be better adapted to the Internet environment.

The natural structure for an address book is to show only the names with a given starting letter. So here is the revised AddrBook servlet:

   import javax.servlet.*;
   import javax.servlet.http.*;
   import java.io.*;
   import java.net.URL;
   import java.sql.*;
 
   public class AddrBook2 extends HttpServlet
   {
 
       public void printRowOfLetters(PrintWriter pw, char letter,
                 char start, char end)
       {
           pw.println("<TT>");
           char l = start;
           while (l <= end )
           {
               if (l == letter) {
                   pw.print(" " + letter + " ");
               }
               else {
                   pw.print(
                       " <A href=\"/servlet/AddrBook2?letter="
                       + l + "\">" + l + "</A> "
                   );
               }
               ++l;
           }
           pw.println("</TT>");
       }
 
       public void printLinks(PrintWriter pw, char letter)
       {
           pw.println("<P><BIG><B>");
           printRowOfLetters(pw, letter, 'A', 'M');
           pw.println("<BR>");
           printRowOfLetters(pw, letter, 'N', 'Z');
           pw.println("</B></BIG>");
       }
 
       public void printHeader (PrintWriter pw, String letter)
       {
           pw.println("<HTML>");
           pw.println("<HEAD>");
           pw.println("<TITLE>Address Book Servlet</TITLE>");
           pw.println("</HEAD>");
           pw.println("<BODY>");
           pw.println("<H1>Address Book<HR></H1>");
 
           printLinks(pw, letter.charAt(0));
 
           pw.println("<P>");
           pw.println("<TABLE border align=\"center\">");
           pw.println("<TR>");
           pw.println("<TH>Name");
           pw.println("<TH>Company");
           pw.println("<TH>Title");
           pw.println("<TH>E-mail");
       }
 
       public void printFooter (PrintWriter pw)
       {
           pw.println("</TABLE>");
           pw.println("</BODY>");
           pw.println("</HTML>");
 
           pw.flush();
           pw.close();
       }
 
       public void printRows (PrintWriter pw, ResultSet cur)
                   throws java.sql.SQLException
       {
           boolean empty = true;
 
           while (cur.next ()) {
               empty = false;
               pw.println ("<TR>");
               pw.println ("<TD>" + cur.getString ("last_name")
                   + ", " + cur.getString ("first_name"));
               pw.println ("<TD>" + cur.getString ("company"));
               pw.println ("<TD>" + cur.getString ("title"));
               pw.println ("<TD>" + cur.getString ("e_mail"));
           }
 
           if (empty) {
               pw.println ("<TR><TD colspan=4>No records found");
           }
       }
 
       public void doGet(HttpServletRequest req, HttpServletResponse res)
                   throws IOException
       {
           res.setContentType("text/html");
           PrintWriter pw = new PrintWriter(res.getOutputStream());
 
           String letter = req.getParameter("letter");
           printHeader(pw, letter);
 
           try {
               // connect to mSQL database pim
               Class.forName ("com.imaginary.sql.msql.MsqlDriver");
               String url = "jdbc:msql://localhost:1114/pim";
               Connection con = DriverManager.getConnection (url, "borg", "");
 
               // execute the query
               Statement stmt = con.createStatement ();
               ResultSet cur = stmt.executeQuery (
                   "SELECT company, first_name, last_name, title, e_mail "
                   + "FROM addr_book "
                   + "WHERE last_name LIKE '" + letter + "%' "
                   + "ORDER BY last_name, first_name"
               );
 
               printRows (pw, cur);
 
               // clean-up
               stmt.close ();
               con.close ();
           }
           catch (Exception e) {
               pw.println("<TR><TD colspan=4>");
               pw.println (e.getMessage ());
 
               // this one will go to the web server error log
               e.printStackTrace ();
               printFooter(pw);
           }
 
           printFooter(pw);
       }
   }

When building servlets, I like to structure the code mirroring the structure of the generated web page. This case, our page has a header, a footer and a table displaying selected rows from the database, so we have the methods printHeader(), printRows() and printFooter().

The header also contains a lot of links that just provide a mean to switch to another letter, and this set of links is generated by printLinks(), which is called from inside printHeader(). It could be a nice idea to use a set of nice bitmaps, each one representing a notebook tab, just like Lotus Organizer. I'm sure the reader will have no trouble to adapt my code to this design.

Now I feel compelled to confess I am not a proficient Java programmer. If anyone knows a clearer and easier way to write the code, I'll be glad do see it and share with other EDM2 readers.

Look how we pass parameters to the servlet using the URL and the GET method. To use this servlet, we need to specify an URL like:

http://localhost/servlet/AddrBook2?letter=A

And when you select another letter, a similar URL will be generated by the form. This URL is looks like the one generated by search engines, for example AltaVista, and the fact that the parameters are inside the URL makes it easy to link a set of related servlets, each one generating a different page that is part of a bigger application.

Object-Oriented Programming for the Web

We could just go on and write more methods and more Servlets until we get the complete Address Book Web application working. But this way we'd not be taking advantage of Java features and would not know how they could be used for real-life projects.

Unfortunately, most programmers in IT departments work this way. They don't use "object-oriented programming", they just do "programing using objects". I do not pretend to teach all the dos and don'ts of object-oriented programming, but I think I can give you useful tips and techniques.

The first way to take advantage of OO is to build a super-class for all our Address Book servlets. Why? All then will share some common visual elements: a title and a navigation bar. Real servlets would have the company logo, copyright messages, webmaster e-mail and the like. We can put all these elements in a common superclass from which all servlets inherits the standard page layout. So, let's create AddrBookServlet class.

We need a form to insert or edit Address Book entries, and a servlet to actually make the changes to the database (when the form is submitted). And to finish our application, we need a way to remove an entry. This could be done by the same servlet that shows the entries list, as the user will have the immediate visual feedback of the entry disappearing.

Why don't we do the same to the form? Because an entry form can have many validation rules, and nothing guarantees that the inserted (or edited) entry will stay on the current letter.

Then we have three classes to derive from AddrBookServlet: AddrBook3 (the listing class), AddrBookForm and AddrBookEntry (the one that does insert or update).

This is the external, user-interface structure of our application, but a nice OO design will separate data from application logic. This way we design classes for the data entities we use. The only one is an AddressBook entry, which we call Entry. If an Entry had any processing on its own, we would create an abstract type (class) Entry and another class to fetch and store the Entry on the database, but as an Entry has only its data fields, we'll use this class to interface to the database for only one entry (inserting, updating and deleting).

Limit these data classes to the real business functions of your application. This way, if someday I write a traditional GUI Address Book application, or if I create another application (what about a complete contact management?) I'll be able to reuse the same data classes.

We do not just manipulate one isolated Entry. We need to manipulate sets of entries. How would we get all the entries starting with letter 'E'? And if we implemented a view by company, someone has to filter and sort the entries. My preferred approach is to define one or more collection classes to do these tasks, so I have EntryCollection class.

But the data needs to be presented to the user and somehow the data the user types must get into the data classes. So we need UI classes specific for web. I defined the HttpEntry class with methods to render an Entry in HTML as a form or table row, and with the ability to initialize itself from HTML form data.

Note that we have three layers of classes in this design: application-specific classes, data entity classes and data visualization classes. A layered approach is at the heart of all maintainable and expandable software, and you can note that my layers resembles the CVM (Controller/View/Model) design from Smalltalk and nice OO methodologies. Have you ever wondered that the same principles worked outside the GUI world?

Let's summarize our Address Book Web application design:

  Data            Entry       EntryCollection
                     |
                     |
  View            HttpEntry

  Application                   AddrBookServlet
                                      |
                      +---------------+--------------+
                      |               |              |
                  AddrBook3   AddrBookForm    AddrBookEntry

This diagram shows the specialization (inheritance) relationship of the classes. We could build diagram showing other aspects of our design, like the "contains" relationships and the "use" relationships. The bigger your application the more you benefit from these additional views of your design.

Design and implementation considerations

I agree that, for this simple application, such an object-oriented design may be overkill. Most programmers like to just sit and type the code (which is a bad idea anyway) but they may be much more comfortable using a traditional procedural design. I won't argument that my design is better than any other, but as we are talking about Java, an OO programming language, I felt compelled to talk about object-oriented design. There are so few sources of info on the subject that uses workable examples that you can build, run and actually USE, and even fewer for web application.

Some people may argue that, as servlets do not reload for every browser request (as CGI applications does), we do not need to break the application in many small apps, one for each web page. They would prefer to have just one servlet for all operations, and that only servlet would get as a parameter the operation to be performed.

I do not like this monolithic design. Java was made so your application is made of small collaborating classes. This is useful for network computing, distributed applications (as the classes are loaded only on demand) and also for easing the burden of maintenance.

If we decide to change the standard lay-out of the pages, I just need to edit and recompile the AddrBookServlet class, as long as the new design fits on the Header-Body-Footer structure of the original layout design. I do not have to recompile all dependent classes (as I would need to do if I used C++), just drop the new AddrBookServlet.class on my web server servlets directory and presto, the existing servlets inherits the layout changes.

But that's too much about theory. Let's see the code!

The Data layer

Let's see the classes on the first layer: Entry and HttpEntry. These only encapsulate SQL statements against the database (using JDBC) and provide an interface based on the application data entities and the operations supported by then.

  /*
   * Entry - encapsulates an Address Book entry (i.e., an Address
   * Book record or row)
   */

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

  public class Entry
  {
      public String id;
      public String first_name;
      public String last_name;
      public String title;
      public String company;
      public String e_mail;

      protected void emptyEntry ()
      {
          id = "";
          first_name = "";
          last_name = "";
          title = "";
          company = "";
          e_mail = "";
      }

      public Entry ()
      {
          emptyEntry();
      }

      private void entryFromRow (ResultSet res)
             throws java.sql.SQLException
      {
          /*
           * As we are inserting spaces instead of empty strings or
           * NULL values, we need to remove these extra spaces.
           * THIS IS A HACK!
           */
          id = res.getString("id").trim();
          last_name = res.getString("last_name").trim();
          first_name = res.getString("first_name").trim();
          company = res.getString("company").trim();
          title = res.getString("title").trim();
          e_mail = res.getString("e_mail").trim();
      }

      public Entry (ResultSet res)
             throws java.sql.SQLException
      {
          entryFromRow(res);
      }

      public Entry (Connection con, String myId)
             throws java.sql.SQLException
      {
          emptyEntry();
          id = myId;

          if (myId.length() > 0) {
              Statement stmt = con.createStatement();
              ResultSet res = stmt.executeQuery(
                  "SELECT id, company, first_name, last_name, title, e_mail "
                  + "FROM addr_book "
                  + "WHERE id = " + myId
              );

              if (res.next()) {
                  entryFromRow(res);
              }

              stmt.close();
          }
      }

      public void validate ()
                  throws java.lang.Exception
      {
          if (last_name.length() == 0 || e_mail.length() == 0) {
              Exception e = new Exception(
                  "You should provide at least "
                  + "a last name and an e-mail"
              );
              throw(e);
          }

          /*
           * mSQL JDBC driver gets lost if it gets an empty string
           * from the database and you get no rows. THIS IS A HACK!
           * The correct solution would be to insert NULL values for
           * the empty fields
           */
           if (first_name.length() == 0) first_name = " ";
           if (title.length() == 0) title = " ";
           if (company.length() == 0) company = " ";
      }

      public static void delete (Connection con, String id)
              throws java.sql.SQLException
      {
          Statement stmt = con.createStatement ();

          stmt.executeUpdate(
              "DELETE FROM addr_book "
              + "WHERE id = " + id
          );
      }

      public void insert (Connection con)
             throws java.sql.SQLException
      {
          Statement stmt = con.createStatement ();

          ResultSet res = stmt.executeQuery("SELECT _seq FROM addr_book");
          res.next();
          id = res.getString("_seq");

          stmt.executeUpdate(
              "INSERT INTO addr_book "
              + "(id, first_name, last_name, title, company, e_mail) "
              + "VALUES (" + id + ", "
              + "'" + first_name +"', '" + last_name + "', "
              + "'" + title +"', '" + company + "', "
              + "'" + e_mail +"')"
          );
          stmt.close();
      }

      public void update (Connection con)
                 throws java.sql.SQLException
      {
          Statement stmt = con.createStatement ();
          stmt.executeUpdate(
              "UPDATE addr_book SET "
              + "first_name = '" + first_name + "', "
              + "last_name = '" + last_name + "', "
              + "title = '" + title + "', "
              + "company = '" + company + "', "
              + "e_mail = '" + e_mail + "' "
              + "WHERE id = " + id
          );
          stmt.close();
      }

  }

Note two interesting things about the Entry class: first, we use the validate() method to test if the data provided to initialize an instance (object) of this class. We defined that any address book entry should have at least a last name and an e-mail address. So far so good. But, instead of returning a boolean value, we return nothing and throw an exception if the instance is not valid.

Java follows the philosophy that error and exception processing should not be part of the regular course of action of the application. Note that nowhere our program checked status values for errors. We check for empty result sets, but this is a NORMAL occurrence in our code. This leads to cleaner code and less work testing for errors. This also allows a single point for displaying error messages.

If you know some part of the code may get run-time errors, put it inside an try block and do all error processing in the catch block. And if your code may identify errors, throw Exception objects for then and let the try/catch blocks handles them: for example, displaying an error message (instead of many pieces of code everywhere for displaying these messages).

The other thing is a hack. Every real application has its hacks, so our wouldn't be different. Here's the cause of the hack: if the returned rows from any select contains empty strings, the JDBC driver returns no rows! All you get is an empty result set. If you run the same query through the mSQL monitor, you get all the rows that match the select statement. Too bad, you can't leave blank fields on the insert/edit form.

We could change validate() to force all fields to have some content, but my requirements are for allowing empty fields. The correct value for an empty field in relational databases is to have a NULL value, not an empty string, so I guess that's why this bug had its way inside mSQL JDBC driver.

The real solution for the problem would be to insert or update the empty fields with NULL values (ex: update addr_book ser company = NULL where ...") but I'd have to write too much code for generating the correct SQL statement, because a non-empty value is delimited by quotes, while a NULL value has no delimiter. It's easier to put a white space if the field is empty. This is what we do inside validate(). You shouldn't insert or update any data to the database without validate() before, so this is a nice place for the hack.

After this hack, I may get many field containing only white space from the database. It will be not nice to have an input field containing an space. This could also lead to bad formatting of HTML. So I play safe trim() white space from the fields when initializing the object from database.

See how an object-oriented design can help even when hacking: I had a clear place to put the hack, and I can be assured the hack will not impact any other code. Better yet, I won't start altering many lines of code spread through many modules because I need to insert and remove white space.

Another important thing about the Entry class is the static method delete(). This is because I do not need an in-memory initialized Entry for deleting, I just need to be able to identify the entry about to be deleted. Why should I instantiate an object that will just be discarded? Also thing about fetching data from the database that will not be used.

That's enough about the Entry class. Let's see the EntryCollection class:

  /*
   * EntryCollection - encapsulates the result of a database query
   * against Address Book entries
   */

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

  public class EntryCollection
  {
      private Statement stmt;
      private ResultSet res;

      public EntryCollection (Connection con, String firstLetter)
             throws java.sql.SQLException
      {
              stmt = con.createStatement ();
              res = stmt.executeQuery(
                  "SELECT id, company, first_name, last_name, title, e_mail "
                  + "FROM addr_book "
                  + "WHERE last_name LIKE '" + firstLetter + "%' "
                  + "ORDER BY last_name, first_name"
              );

      }

      public Entry next ()
             throws java.sql.SQLException
      {
          if (res.next()) {
              return new Entry(res);
          }
          else {
              return null;
          }
      }

      public void close ()
             throws java.sql.SQLException
      {
          res.close();
      }

  }

The EntryCollection class is much simpler, so I guess it does not need any comments.

The View Layer

Our view layer is very small. Just one class: HttpEntry.

   /*
    * HttpEntry - initializes or render an Entry using HTTP
    */
 
   import javax.servlet.*;
   import javax.servlet.http.*;
   import java.io.*;
 
   public class HttpEntry extends Entry
   {
 
       public HttpEntry()
       {
           emptyEntry();
       }
 
       public HttpEntry(Entry entry)
       {
           id = entry.id;
           first_name = entry.first_name;
           last_name = entry.last_name;
           title = entry.title;
           company = entry.company;
           e_mail = entry.e_mail;
       }
 
       public HttpEntry(HttpServletRequest req)
       {
           // as the user may have typed extra spaces, we remove then
           id = req.getParameter("id").trim();
           first_name = req.getParameter("first_name").trim();
           last_name = req.getParameter("last_name").trim();
           title = req.getParameter("title").trim();
           company = req.getParameter("company").trim();
           e_mail = req.getParameter("e_mail").trim();
       }
 
       public String getName ()
       {
           return(last_name + ", " + first_name);
       }
 
       public void printForm (PrintWriter pw)
       {
           pw.println("<P><HR>");
           pw.println("<FORM method=\"post\" "
               + "action=\"/servlet/AddrBookEntry\">");
           pw.println("<INPUT type=\"hidden\" name=\"id\" "
               + "value=\"" + id + "\">");
           pw.println("<TABLE>");
           pw.println("<TR><TD>Last Name");
           pw.println("<TD><INPUT type=\"text\" name=\"last_name\" "
               + "value=\"" + last_name + "\">");
           pw.println("<TR><TD>First Name");
           pw.println("<TD><INPUT type=\"text\" name=\"first_name\" "
               + "value=\"" + first_name + "\">");
           pw.println("<TR><TD>Company");
           pw.println("<TD><INPUT type=\"text\" name=\"company\" "
               + "value=\"" + company + "\">");
           pw.println("<TR><TD>Title");
           pw.println("<TD><INPUT type=\"text\" name=\"title\" "
               + "value=\"" + title + "\">");
           pw.println("<TR><TD>E-mail");
           pw.println("<TD><INPUT type=\"text\" name=\"e_mail\" "
               + "value=\"" + e_mail + "\">");
           pw.println("</TABLE>");
           pw.println("<P>");
           pw.println("<INPUT type=\"submit\" ");
           if (id.length() > 0) {
               pw.println("value=\"Update Entry\">");
           }
           else {
               pw.println("value=\"Insert Entry\">");
           }
           pw.println("<INPUT type=\"reset\" value=\"Clear Form\">");
           pw.println("</FORM>");
       }
 
       public void printTableRow (PrintWriter pw)
       {
               pw.println("<TR>");
               pw.println("<TD>" + getName());
               pw.println("<TD>" + company);
               pw.println("<TD>" + title);
               pw.println("<TD>" + e_mail);
       }
 
       public void printTableRow (PrintWriter pw, String editURL,
                   String deleteURL)
       {
               pw.println ("<TR>");
               pw.println (
                   "<TD>" + "<A href=\"" + editURL + "id=" + id + "\">"
                   + getName() + "</A>"
               );
               pw.println("<TD>" + company);
               pw.println("<TD>" + title);
               pw.println("<TD>" + e_mail);
               pw.println(
                   "<TD align=\"center\">" + "<A href=\"" + deleteURL
                   + "id=" + id + "\">" + "[X]</A>"
               );
       }
 
   }

This is a utility class that helps writing applications that manipulate an address book entry. It would be used by any web application, enabling code reuse. The data layer may be used by non-web applications. Here's the heart of OO design: build for reuse.

See that I have methods for initializing an HttpEntry from a web form (a HttpServletRequest) and for displaying it as a table row or as a form. We also need to initialize it from a regular entry. This helps the view interface simpler, because it won't need to include constructors that know about databases. This would also allow us to store presentation properties on the view (a different color, for example).

If we displayed partial contents of an entry, we would need additional methods. When you build a new application that needs a different HTML presentation change (or extends) this class, do not build this logic inside your new application, else you won't enable code reuse and consequently will turn maintaining or enhancing the code.

Of course, you would design many view classes for a single entity, instead of just one class as I did.

Bigger applications would also benefit from one or more HttpCollectionView classes. My design isn't pure as some view logic is inside the application.

The Application Layer

Now the application layer. The first class is a generic AddrBookServlet from which all other servlet classes inherits the standard page layout and the navigational links. If later I decide to include a search function on the address book, I'd have to modify only this class to get the search link on every page generated by the Address Book Web Application.

  /*
   * AddrBookServlet - standard page layout for all AddrBook servlets
   */
 
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
 
  public class AddrBookServlet extends HttpServlet
  {
 
      private void printRowOfLetters (PrintWriter pw, char letter,
                  char start, char end)
      {
          pw.println("<TT>");
          char l = start;
          while (l <= end )
          {
              if (l == letter) {
                  pw.print(" " + letter + " ");
              }
              else {
                  pw.print(
                      " <A href=\"/servlet/AddrBook3?letter="
                      + l + "&id=\">" + l + "</A> "
                  );
              }
              ++l;
          }
          pw.println("</TT>");
      }
 
      private void printLinks (PrintWriter pw, char letter)
      {
          pw.println("<P><CENTER><TABLE width=\"100%\">");
 
          pw.println("<TR><TD>");
          pw.println("<BIG><B>");
          printRowOfLetters(pw, letter, 'A', 'M');
          pw.println("<BR>");
          printRowOfLetters(pw, letter, 'N', 'Z');
          pw.println("</B></BIG>");
          pw.println("<TD>");
          pw.println("<A href=\"/servlet/AddrBookForm?id=\">");
          pw.println("Insert new entry</A>");
          pw.println("</TABLE></CENTER>");
      }
 
      protected void printHeader (PrintWriter pw, String title, String letter)
      {
          pw.println("<HTML>");
          pw.println("<HEAD>");
          // the servlet-generated pages should not be stored on cache
          pw.println("<META http-equiv=\"pragma\" content=\"no-cache\">");
          pw.println("<META http-equiv=\"expires\" content=\"10-JAN-1980\">");
          pw.println("<TITLE>" + title + "</TITLE>");
          pw.println("</HEAD>");
          pw.println("<BODY>");
          pw.println("<H1>" + title + "<HR></H1>");
 
          printLinks(pw, letter.charAt(0));
      }
 
      protected void printFooter (PrintWriter pw)
      {
          pw.println("</TABLE>");
          pw.println("</BODY>");
          pw.println("</HTML>");
 
          pw.flush();
          pw.close();
      }
 
  }

Now the main servlet: the Address Book Listing, which I named AddrBook3 so you do not have to worry about renaming each version of the listing.

  /*
   * AddrBook3 - lists address book entries by letter and deletes
   * the selected (clicked) entry
   */
 
   import javax.servlet.*;
   import javax.servlet.http.*;
   import java.io.*;
   import java.net.URL;
   import java.sql.*;
 
   public class AddrBook3 extends AddrBookServlet
   {
 
       protected void printHeader (PrintWriter pw, String title, String letter)
       {
           super.printHeader(pw, title, letter);
 
           pw.println("<P>");
           pw.println("<TABLE border width=\"100%\">");
           pw.println("<TR>");
           pw.println("<TH>Name");
           pw.println("<TH>Company");
           pw.println("<TH>Title");
           pw.println("<TH>E-mail");
           pw.println("<TH>Remove");
       }
 
       private void printRows (PrintWriter pw, Connection con, String letter)
                   throws java.sql.SQLException
       {
           EntryCollection entries = new EntryCollection(con, letter);
           Entry entry = entries.next();
 
           if (entry == null) {
               pw.println("<TR><TD colspan=5>No records found");
           }
           else {
               while (entry != null)
               {
                   HttpEntry httpEntry = new HttpEntry(entry);
                   httpEntry.printTableRow(pw, "/servlet/AddrBookForm?",
                       "/servlet/AddrBook3?letter=" + letter + "&");
                   entry = entries.next();
               }
           }
           entries.close();
       }
 
       public void doGet (HttpServletRequest req, HttpServletResponse res)
                   throws IOException
       {
           res.setContentType("text/html");
           PrintWriter pw = new PrintWriter(res.getOutputStream());
 
           String letter = req.getParameter("letter");
           String id = req.getParameter("id");
           printHeader(pw, "Address Book Servlet", letter);
 
           try {
               Class.forName("com.imaginary.sql.msql.MsqlDriver");
               String url = "jdbc:msql://localhost:1114/pim";
               Connection con = DriverManager.getConnection(url, "borg", "");
 
               if (id.length() > 0) {
                   Entry.delete(con, id);
               }
 
               printRows(pw, con, letter);
 
               con.close();
           }
           catch (Exception e) {
               pw.println("<TR><TD colspan=5>");
               pw.println("Unexpected error while listing Address Book");
               pw.println("Entries");
               pw.println("<P>" + e.getMessage ());
 
               // this one will go to the web server error log
               e.printStackTrace();
               printFooter(pw);
           }
 
           printFooter(pw);
       }
 
   }

This is an example of bad design as the application (servlet) code implements view functionality. I'll leave as an exercise to the reader implementing a better design (tip: look at the printHeader() and printRows() methods).

This servlet implements two functions of our application: browsing (listing address book entries by initial letter) and deleting entries. I did this way to have immediate feedback as the entry disappears from the list. I could also have a separate AddrBookDelete servlet and make this servlet just redirect the browser to the listing servlet instead to get the same visual effect.

Now the AddrBookForm servlet:

 
    /*
     * AddrBookForm - displays an empty form for inserting or a
     * filled for updating a given address book entry
     */
 
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    import java.net.URL;
    import java.sql.*;
 
    public class AddrBookForm extends AddrBookServlet
    {
 
        public void doGet(HttpServletRequest req, HttpServletResponse res)
                    throws IOException
        {
            res.setContentType("text/html");
            PrintWriter pw = new PrintWriter(res.getOutputStream());
            String id = req.getParameter("id");
 
            printHeader(pw, "Address Book Form", " ");
 
            try {
                Class.forName ("com.imaginary.sql.msql.MsqlDriver");
                String url = "jdbc:msql://localhost:1114/pim";
                Connection con = DriverManager.getConnection (url, "borg", "");
 
                Entry entry = new Entry (con, id);
                HttpEntry httpEntry = new HttpEntry (entry);
 
                httpEntry.printForm(pw);
                printFooter(pw);
            }
            catch (Exception e) {
                pw.println("<P><HR><P>Unexpected error while generating the");
                pw.println("Entry form");
                pw.println("<P>" + e.getMessage());
 
                // this one will go to the web server error log
                //e.printStackTrace();
                printFooter(pw);
            }
        }
 
    }

We connect to the database, create (instantiate) the objects and let then do the work. Ideally all useful programs would be that simple. But the form does not perform the insertions or updates. Let's see who really does these:

 
   /*
    * AddrBookEntry - inserts or updates an address book entry using the
    * data received from a HTML form
    */
 
   import javax.servlet.*;
   import javax.servlet.http.*;
   import java.io.*;
   import java.net.URL;
   import java.sql.*;
 
   public class AddrBookEntry extends AddrBookServlet
   {
 
       public void doPost(HttpServletRequest req, HttpServletResponse res)
                   throws IOException
       {
           res.setContentType("text/html");
           PrintWriter pw = new PrintWriter(res.getOutputStream());
 
           printHeader(pw, "Address Book Form Results", " ");
 
           try {
               HttpEntry entry = new HttpEntry(req);
               entry.validate();
 
               Class.forName ("com.imaginary.sql.msql.MsqlDriver");
               String url = "jdbc:msql://localhost:1114/pim";
               Connection con = DriverManager.getConnection (url, "borg", "");
 
               if (entry.id.length() > 0) {
                   pw.println(
                       "<P><HR>Entry <B>" + entry.getName()
                       + "</B> updated successfully."
                   );
                   entry.update(con);
               }
               else {
                   entry.insert(con);
                   pw.println(
                       "<P><HR>Entry <B>" + entry.getName()
                       + "</B> inserted successfully."
                   );
               }
 
               con.close();
               printFooter(pw);
           }
           catch (Exception e) {
               pw.println("<P><HR><P>Error while inserting or updating entry:");
               /*
               pw.println(
                   + "<UL><LI>id ='" + req.getParameter("id") + "'"
                   + "<LI>last name ='" + req.getParameter("last_name") + "'"
                   + "<LI>first name ='" + req.getParameter("first_name") + "'"
                   + "</UL>"
               );
               */
               pw.println("<P>" + e.getMessage());
               pw.println("<P>Please press the [Back] button to correct");
 
               // this one will go to the web server error log
               //e.printStackTrace();
               printFooter(pw);
           }
       }
 
   }

Just let the instantiated objects to the work and displays a feedback message. Why not returning to the listing? Because the inserted or updated entry would show under a different letter, and I am too lazy to write the code to direct to the correct listing. Well, if the users ask too much...

And we are done! We have three servlets and four supporting, reusable classes that makes a complete address book web application. Enjoy!

How to Run the Application

Copy all the .class files generated by compiling the Java classes presented above to the public servlets directory of your web server (E:\WWW\SERVLETS\PUBLIC) and type the following URL on the browser:

http://localhost/servlet/AddrBook3?letter=A&id=

A last tip: most web servers have an error log, and the standard error from servlets is generally directed to this error log. Lotus GoWebserver also shows the error log in real time inside it's window. Although this is terrible for a production server, it is very nice for a development server as you can insert debug messages and watch them on the error log window. How to write to the standard error?

  import java.io.*
  ...
  System.err.println("This should appear in the web server error log!");

If you have some problems and start inserting debug code in your applets, do not forget to restart the web server, so the modified classes get loaded.

Before we finish, a little home work for the reader: extend the Address Book to allow a search by part of a name. You should be able to do this by just changing the AddrBookServlet class (so the new function appears on the links all pages shows), defining (or extending or inheriting) the EntryCollection class and building one or two servlets for the search function.

Maybe you can even imagine a design clever than mine and build a generic AddrBook view superclass from which the "letter" view, the search results view and a view by company could be derived. And please do not repeat my bad design, create an HttpEntryCollection View-layer class!

Oh, I almost forgot! Please visit The mSQL PC Home Page at: http://www.blnet.com/msqlpc/

And help me share knowledge about mSQL, databases and web programming in general!