Chapter  24

Writing Web Services for Jeeves

by David Edgar Liebke


CONTENTS

Jeeves is a Java-based Web server development toolkit that includes a fully functional HTTP Web server. This chapter describes how to write servlets that extend the function of the HTTP server using the Web server toolkit.

The chapter begins by introducing the HTTP server's architecture. You will go through the process of administering the server. This background knowledge lays the foundation for the rest of the chapter.

In the second part of the chapter, you learn how to write servlets that extend Jeeves' functions. This section begins with an introduction to the servlet API, which is at the heart of Jeeves' functionality and extensibility. You then learn about Jeeves' rich collection of tools that enhance servlet development.

Finally, two examples show you what can be done with servlets and Jeeves. The first is a database servlet that puts a Web front end on any database that supports the Java Database Connectivity (JDBC) interface. The second takes advantage of Java's object serialization to create a simple example of an autonomous agent system.

What Is Jeeves?

Jeeves is often described simply as a Java Web server, but it is much more. Jeeves is a server development toolkit. At the center of the Jeeves toolkit is a package of generic server classes. With these classes, any developer can quickly build connection-oriented servers.

Another important component of the toolkit is the servlet. Servlets are Java objects that comply with the servlet API and are used to add functions to Web servers. The servlet API is Sun's proposed standard for extending Web server functions with Java.

Note
In addition to Jeeves, Acme Serve is a basic Web server that complies with the servlet API. It is available at http://www.acme.com/.

In addition to the servlet API and the generic server classes, the Jeeves toolkit includes security classes, administrative classes, utility classes, and a set of servlets that provide basic Web server functions.

The Jeeves HTTP server was developed from this toolkit and is a fully functioning Web server that provides all the features common to other Web servers.

Tip
For more information about Jeeves, check out http://www.javasoft.com/products/jeeves.

The Jeeves HTTP Server

This part of the chapter introduces you to the Jeeves HTTP server. It begins with an architectural overview and then moves on to basic server installation and administration.

By the end of this section, you'll have a basic understanding of the server architecture and administrative design. This lays the foundation for extending the server with servlets, which you will learn how to do in the second part of this chapter.

Architectural Overview

The Jeeves HTTP server is built on the framework provided by the generic server classes discussed earlier. This framework is the core of the Jeeves server development toolkit. Here is a brief description of the workings of a generic Jeeves server, followed by a description of the specific workings of the HTTP server.

An object of the sun.server.Server class waits in a loop for connection requests. Connections are placed on a queue while the server determines if there are handler objects of the sun.server.ServerHandler class available in the handler thread pool.

If none are available and the maximum number of handler threads has not been reached, the server starts a new handler thread. If, on the other hand, the number of handlers exceeds the minimum needed, and some have been idle for a period longer than the specified timeout parameter, the idle handlers expire.

In the case of the HTTP server, once the server receives an HTTP request, it is queued for servicing by the pool of HTTP server handler threads. The HTTP handler then authorizes and applies name translation rules to the request, and passes the request on to the appropriate servlet.

Servlets provide the core function of the Jeeves HTTP server, as well as providing a means for extending that function. The HTTP server includes a set of core servlets that provide common Web server functions.

For instance, the FileServlet fulfills HTTP GET requests, returning the requested file to the client. The Invoker servlet is used to dynamically invoke servlets that have been explicity requested by a client using an URL of the form:

http://ServerHostName/servlet/<servletName>

The Invoker supports only local servlets but will soon be able to dynamically load servlets from across a network. The SSInclude servlet parses server-side include files (files with an .shtml extension) and calls any servlet that was referenced.

The CgiServlet provides backward compatibility for the large body of existing CGI programs. The ImageMapServlet uses server-side image maps. Finally, there is the Admin Servlet that works together with the Admin applet to help with administrative tasks (you'll learn more about this in the next section).

Installing and Running the Jeeves HTTP Server

First, you need a host that has the Java runtime installed. Once you have an appropriate host, installing Jeeves is simply a matter of unzipping the distribution and running the httpd program found in the bin directory under the main installation directory. You can test whether the server is up by pointing a Web browser at the following URL:

http://ServerHostName:8888/

The server's default port is 8888; you will learn how to change this and other defaults in this chapter.

Tip
You must have at least version 1.0.2 of the JDK to run Jeeves.

Caution
If you are running Jeeves on a Windows 95 machine, you must make sure that the logs directory is under the main installation directory. Unzip tends not to extract directories that have no files, as is the case with the logs directory.

Administering the Jeeves Web Server

There are two ways to administer the Jeeves Web server. You can use the administration Web page, which includes an applet that enables you to modify many of the server's parameters dynamically. Or you can change the configuration files, listed in Table 24.1, by hand.

You can adjust many parameters. Some of these changes take effect immediately and others do not take effect until the server is restarted. In the following section, you will proceed step-by-step through the administration process, using both the administration Web page and the configuration files.

Note
Some properties can be changed only by hand. For instance, the server allows you to change the welcome page property from the default of index.html but you cannot do this from the administration Web page. You must change the httpd.properties file using a text editor.

Table 24.1  Jeeves Configuration Files

File NameProperties
httpd.propertiesServer name, port number, minimum threads, maximum threads, timeout, ramcache, keepalive, keepalive timeout, and location of other property files
rules.propertiesTranslation rules for invoking servlets
alias.propertiesTranslation rules for path aliases
servlet.propertiesServlet codebase, servlet code, and initArgs
mime.propertiesMIME configuration
acl.propertiesAccess control file

Administering Jeeves from a Java-Enabled Browser

You can manage the Jeeves Web server remotely using any Java-enabled Web browser. Once the server is running, you can access the administration Web page by pointing your browser at the following URL:

http://ServerHostName:8888/admin/admin.html

You are prompted for a user name and password. The default administration account name and password are admin. Once authorized, you see the page shown in Figure 24.1.

Figure 24.1 : HTTP configuration using the Admin applet.

The window on the left includes a list of administrative tasks, including HTTP configuration, log configuration, file aliasing, servlet aliasing, servlet loading, MIME configuration, user configuration, group configuration, access control list (ACL) configuration, and resource protection.

Modifying Basic Web Server Parameters

Jeeves has many tunable parameters. Figure 24.1 shows the parameters you can modify from the administration Web page. There are other properties, such as the server user (UNIX version only) and server host name, that can be changed only from the httpd.properties file.

Jeeves lets you set the number of handler threads that are started and how long an idle thread remains before being destroyed. In the httpd.properities file, you'll find the following thread properties: server.min.threads, server.max.threads, and server.timeout.

Jeeves uses connection keepalive to improve performance by keeping the connections to client browsers open even after the request has been fulfilled. This reduces the overhead of bringing the connection up and down for multiple requests from the same client.

The keepalive count property determines the number of hits from a single client that are received before the connection is brought down. The keepalive timeout determines the time in seconds the connection stays up after a request has been fulfilled.

Configuring Web Server Logging

You can specify where log files are stored and the level of logging detail for the Access, Error, and Event logs. These changes are made from the Log Configuration screen (see Figure 24.2) or the httpd.properties file.

Figure 24.2 : Log Configuration using the Admin applet.

The Access log is in Common Log format, which lets you use existing log-analyzing scripts on them. All the log files reside in the $JEEVES-HOME/logs directory.

Creating File Aliases

You can map virtual paths in the requested URL to an arbitrary real path name on the server's disk. These changes take effect immediately if done from the File Aliasing screen (see Figure 24.3); otherwise, you can make the changes to the alias.properties file.

Figure 24.3 : File aliasing using the Admin applet.

Configuring MIME

The mime.properties file and the Mime Section of the administration Web page allows you to map Mime types to file extensions. This information is sent from the server to the client browser.

The browser uses this information to figure out what to do with the file it is about to receive from the server. For example, in the case of a file with a .mov extension, the browser should start up a QuickTime viewer. You can also change the mime.properties file by hand.

Loading Servlets into the Web Server

To execute servlets, you must map the servlet name to a class that lies somewhere in the server's CLASSPATH environment variable. You can do this from the Servlet Loading screen of the administration Web page (see Figure 24.4).

Figure 24.4 : Servlet loading using the Admin applet.

Remote servlets can also be loaded by specifying their URL to the location field of the administration page.

When you use the Web page, the changes take effect immediately. When you use the servlets.properties file, you can map the servlet called myservlet to the MyServlet class in the mypackage package with the following entry:

myservlet.code=mypackage.MyServlet

When you change the servlet.properties file, the changes do not take effect until the server is restarted. You can now invoke myservlet with the following URL:

http://<server_host>/servlet/myservlet

The virtual path /servlet is mapped to the Invoker, which then calls the referenced servlet.

Creating Servlet Aliases

Servlets can also be mapped to arbitrary document names. When you use the Servlet Aliasing screen (see Figure 24.5), all changes are dynamic. Otherwise, change the rules.properties file. Again, changes to the configuration files take effect after the server has been restarted.

Figure 24.5 : Servlet aliasing using the Admin applet.

To map myservlet to myservlet.html, put the following line into the rules.properties file:

/myservlet.html=myservlet

HTTP Server Security

Jeeves uses an extensible, access-control list framework for controlling requests for files and servlets. Only the Basic HTTP authentication scheme is allowed, but Jeeves accepts the configuration of different authentication schemes when they become available.

Access control lists can be associated with any file, directory, or servlet. If a file or directory is not explicitly protected by an ACL, it inherits the protection of its parent directory.

If there is no ACL for the entire directory structure, access is granted. If a servlet is not explicitly protected by an ACL, a default is used. If it doesn't exist, access is granted. Servlets can also use their own access-control list, using the security classes available in Jeeves.

Jeeves also makes use of security realms. Realms are used to set broad security policies. When users, groups, or access-control lists are added to the Web server, they are assigned to a realm.

People who have common security needs can be put into a Single realm, such as the adminRealm. When you want to protect a resource, you associate it with an access-control list in a realm. Jeeves comes with two built-in realms, adminRealm and defaultRealm.

Servlet Security

The four basic types of servlets are core servlets, local servlets, signed network servlets, and unsigned network servlets. These servlets are treated differently with respect to security.

The core servlets and local servlets are thought to be trusted and are granted full access to the server's resources. Signed network servlets are granted a limited subset of privileges, as determined by the site administrator. Unsigned servlets are not trusted and are only executed in a restrictive environment, called the server sandbox.

Protecting Web Resources

Using the Resource Protection section of the administration Web page (see Figure 24.6), you can assign schemes and realms to Web resources. These resources include documents and servlets.

Figure 24.6 : Protecting resources with the administration Web page.

Adding Users to Security Realms

You can add users to different realms. The easiest way to add users is through the Users screen of the administration Web page (see Figure 24.7). Simply select the realm, and enter the user's name and password.

Figure 24.7 : Adding a user to a realm with the Admin applet.

Creating Groups of Users

You can group together users who should share the same privileges using the Groups section of the administration Web page (see Figure 24.8). Select the realm and group you want to change and enter the user you want to add. You can also create new groups by entering the new group name in the field above the user name.

Figure 24.8 : Adding a user to a group with the Admin applet.

Creating and Modifying ACLs

You can create new ACLs in a realm or add entries to an existing realm from the ACL screen on the administration Web page (see Figure 24.9). To add an entry to an existing ACL, select it from the center window and click the Add ACL Entry button.

Figure 24.9 : Creating and modifying ACLs with the administration Web page.

A new window appears. On the far left is a select box with a plus sign and a minus sign. Choose the plus sign to grant privileges or choose the minus sign to restrict them.

The next select box lets you choose whether you are modifying the privileges of a group or of an individual user. There is a text field to enter the group or user name. Finally, select the privileges you want (such as GET, POST, or PUT) for that user or group.

To add a new ACL, choose the Create ACL button. A box appears prompting you for the ACL name. To delete an ACL, select it from the list and choose the Delete ACL button.

Extending Jeeves' Functionality with Servlets

Now that you have a basic understanding of the HTTP server architecture and are familiar with the administrative design, you can move on to extending the server's function with servlets.

Creating servlets is like creating applets. Servlets are basically applets without graphical front ends. Both servlets and applets can be loaded dynamically from across a network.

Both applets and servlets are small programs that extend the function of a browser or server, respectively. Both can run on any platform that supports Java and the applet or servlet APIs, respectively.

In this part of the chapter, you are introduced to the Servlet API, and you'll write some generic servlets. You then learn about the additional classes provided by the Jeeves server development toolkit that make extending Web server functions even simpler.

Next, you build a servlet that accesses any database that supports the Java Database Connectivity (JDBC) interface, letting users insert data and search for data from a Web page.

Finally, you use servlets to build a simple example of an autonomous agent system. You'll write a couple of simple agency servlets that provide an environment for roaming autonomous agents.

You'll create an agent that is transported to a remote agency where it gathers some information from a JDBC database. The agency asks the agent where it lives and sends it home.

The agent's home agency stores the agent in a file for later processing. Finally, you'll restore the agent and extract the information it has gathered.

Employing the Servlet API

The Servlet API is Sun's proposed standard for extending Web server function with Java. Other servers that have Java APIs include Netscape, Oracle, and the World Wide Web Consortium's Jigsaw server, which, like Jeeves, is written entirely in Java.

Each of the above servers uses a different API. This reduces Java's inherent platform independence by requiring developers to write different Java programs for each of these servers.

A single Java Web server API will greatly simplify development of Web services. It is not yet clear whether servlets will become the standard that applets have become. But servlets have a lot going for them, not the least of which is the support of Sun, the developer of Java.

The classes associated with the servlet API are in a single package, java.servlet. This package includes four Java interfaces: ServletContext, ServletRequest, ServletResponse, and ServletStub; and three classes: Servlet, ServletInputStream, and ServletOutputStream.

The server developer must use the four interfaces to make the server comply with the servlet API. You then use the server's implementations of these interfaces along with the Servlet, ServletInputStream, and ServletOutputStream classes to write servlets.

In this section, you learn the basics behind the classes and interfaces that make up the servlet package.

Extending the Servlet Class

The Servlet class provides the basic function necessary to create servlets. This class includes the methods shown in Table 24.2.

Table 24.2  Methods in the Servlet Class

Method NameDescription
service(ServletRequest, ServletResponse) Services a single request from a client.
init() Called by system when servlet is first loaded.
GetInitParameter(String) Gets the named initialization parameter.
GetServletContext() Returns the servlet context object.
log(String) Logs a message to the servlet log.
GetServletInfo() Returns a string containing information about the servlet.
destroy() Destroys servlet and cleans up after it.
SetStub(ServletStub) Sets servlet stub; this is done by the system.

The first step in writing Servlets is extending the Servlet class and overriding the service method. The following servlet prints "Hello World" to the client browser.

The first step is to get the OutputStream from the ServletResponse object and create a PrintStream with it. Next, you set the response status to OK using the static variable SC_OK from the ServletResponse class.

Then, you set the content type of the response to text/plain and write out the headers. Finally, print the Hello World string to the PrintStream.

import java.servlet.*;

public class SimpleServlet extends Servlet {
     public void service( ServletRequest req, ServletResponse res ) {
     PrintStream ps = new PrintStream(res.getOutputStream());
     res.setStatus(ServletResponse.SC_OK);
     res.setContentType("text/plain");
     res.writeHeaders();
     ps.println("Hello World");
     ps.flush();
     }
}

After you compile the class, load it into the HTTP server using either the servlet.properties file or the servlet loading screen of the administration Web page, as was discussed in the first part of this chapter. You can access the servlet with the following URL:

http://<server>/servlet/<servlet_name>

where <servlet_name> is the name you assigned to your servlet when you loaded it into the HTTP server.

Sending Information with the ServletResponse Interface

The ServletResponse interface allows you to send information to the client's browser. It includes methods for getting an output stream directed at the client, setting the header information, sending errors to the client, and setting the status of the response. Table 24.3 shows more of ServletResponse's methods.

ServletResponse also includes a list of static integer variables used in setting the response status. The previous example used the SC_OK variable. Other responses include SC_CREATED, SC_NO_CONTENT, SC_MOVED_PERMANENTLY, SC_MOVED_TEMPORARILY, SC_BAD_REQUEST, SC_UNATHORIZED, SC_FORBIDDEN, SC_NOT_FOUND. For a complete list of methods and variables, see the servlet API documentation.

Table 24.3  ServletResponse Methods

MethodDescription
getOuputStream() Returns the output stream for writing responses.
SendError(int, String) Sends an error message to the client.
sendRedirect(String) Sends a redirect response to the client using a specified redirect URL.
SetContentLength(int) Sets the content length for this response.
SetContentType(String) Sets the content type for this response.
SetDateHeader(String, long) Sets the date header field.
SetHeader(String, String) Sets the value of a header field.
setIntHeader(String, int) Sets the value of an integer header field.
WriteHeaders() Writes the status line and message headers for this response to the output stream.
SetStatus(int) Sets the status code and a default message for this response.

Receiving Information with the ServletRequest Interface

In addition to the ServletResponse object, Servlets get a ServletRequest object as an argument. This object lets the servlet get information directly from the client making the request, as well as from the server that called the servlet.

ServletRequest includes methods for getting an input stream from the client, gathering header information, and extracting path and query information from the requested URL.

Table 24.4 shows the ServletRequest methods. For complete information, see the servlet API documentation.

Table 24.4  ServletRequest Methods

Method NameDescription
getAuthType() Returns the authentication scheme of the request or null if none.
GetContentLength() Returns the size of the request entity data or -1 if not known.
getContentType() Returns the MIME type of the request entity data.
GetDateHeader(String, long) Returns the value of a date header field.
getHeader(String) Returns the value of a header field.
getHeader(int) Returns the nth header field.
GetHeaderName(int) Returns the name of the nth header field.
GetInputStream() Returns an input stream for reading request data.
GetIntHeader(String, int) Returns the value of an integer header field.
getMethod() Returns the method with which the request was made.
GetPathInfo() Returns optional extra path information following the servlet path and preceding the query. string.
GetPathTranslated() Returns extra path information translated to a real path.
GetProtocol() Returns the protocol and version of the request.
GetQueryParameter(String) Returns the value of the specified query string parameter.
GetQueryParameters() Returns a hash table of query string parameter values.
GetQueryString() Returns the query string part of the servlet URL.
GetRemoteAddr() Returns the IP address of the agent that sent the request.
GetRemoteHost() Returns name of the host making the request.
GetRemoteUser() Returns the name of the user making the request or null if not known.
GetRequestPath() Returns the part of the request URI that corresponds to the servlet path, plus optional path information.
GetRequestURI() Returns the request URI.
GetServerName() Returns the host name of the server.
GetServerPort() Returns the port number on which this request was received.
GetServletPath() Returns the part of the request URI that refers to the servlet being invoked.

Getting Information with the ServletContext Interface

The getServletContext method from the Servlet class returns a ServletContext. This object lets you find out information about the environment in which the servlet is running.

The getServerInfo method returns the name and version of the server running. The getServlet method returns a servlet with its name, and the getServlets method returns an enumeration of all the available servlets in this context.

Using the Jeeves Development Toolkit

In addition to the servlet API, Jeeves includes additional classes that simplify the development of Web services. For example, there are several prebuilt servlets that extend the function of the generic Servlet class. These include a form servlet that processes HTML form input and a filter interface that lets you embed servlets in HTML pages using server-side includes.

Sun also includes several sample servlets to get you started. Other classes in the toolkit let you generate HTML, set servlet security, and do other useful tasks.

Processing Form Input with the FormServlet

The FormServlet greatly simplifies the processing of HTML form input. To process data from a form, extend the FormServlet class and override the sendResponse method.

The sendResponse method doesn't take a ServletRequest object as an argument. Instead, it gets a hash table containing the values from the HTML form. The following example shows how to get the value from the form field called field_name.

 Public class SimpleFormServlet extends FormServlet {
     public void sendResponse(ServletResponse res, Hashtable table) {
     String field_value = table.get("field_name");
     ...
     }
}

Using the Filter Interface to Embed Servlets in HTML Pages

Using the Jeeves Filter interface, you can create servlets that can be embedded in HTML pages using a server-side include statement. The following server-side include statement calls myServlet and passes the name1 and name2 parameters to it in the form of a hash table:

<SERVLET CODE="myServlet" name1="value1" name2="value2">

The following servlet inserts the date into the Web page it is embedded in:

import java.servlet.*;
import java.io.*;
import java.util.*;

public class SSIServlet extends Servlet implements Filter {
     public void service(InputStream is, OutputStream os, Hashtable params)
     throws java.io.IOException
     {
     Date now = new Date();
     PrintStream ps = new PrintStream(os);
     ps.println(today);
     ps.flush();
     }
}

Notice that the service method accepts different arguments from normal. Instead of ServletRequest and ServletResponse, the arguments to service are an InputStream, OutputStream, and Hashtable. The hash table is used to store the parameters passed to the servlet by the server-side include statement.

Generating HTML with the Jeeves HTML Classes

Sun provides a package of classes that generate HTML. The package includes the HtmlContainer and HtmlElement interfaces, as well as the HtmlContainerImpl, HtmlPage, HtmlTag, HtmlTagPair, and HtmlText classes.

Start by creating a new HtmlPage object. Then, using the addTag, addTagPair, and addText methods, insert the necessary HTML. The following example shows a simple use of these classes:

import java.servlet.*;
import sun.server.html
import java.io.*;

public class HtmlServlet extends Servlet {
     pubic service(ServletRequest req, ServletResponse res)
     throws Exception {
     res.setContentType("text/html");
     res.setStatus(ServletResponse.SC_OK);
     OutputStream os = res.getOutputStream();
     
     HtmlPage page = new HtmlPage("A Simple HTML page");
     page.addTagPair("H1", "This is a Simple HTML Page");
     page.addTag("p");
     page.addText("This page was generated by the sun.server.html package");
     
     page.write(os);
     os.flush();
     }
}

Building a Database Servlet

In this section, you build a Servlet that processes an HTML form and either searches for or inserts data into a JDBC database. This servlet could be used by a site to register new users, process orders, survey users, or enable users to search one of your publicly accessible databases.

Getting the Information from the Users

The first step is to process the form data with a FormServlet. This is a generic registration form that asks the user's name, title, company, and address. The following is a sendResponse method that returns the information supplied by the user in an HTML table:

public void sendResponse(ServletResponse res, Hashtable table)
throws IOException
{
     PrintStream ps = new PrintStream(res.getOutputStream());
     log("MyServlet running");
     res.setStatus(ServletResponse.SC_OK);
     res.setContentType("text/html");
     res.writeHeaders();
     
     String name = (String) table.get("name");
     String title = (String) table.get("title");
     String company = (String) table.get("company");
     String address = (String) table.get("address");

     ps.println("<HTML><HEAD>");
     ps.println("<TITLE>Registration Information</TITLE>");
     ps.println("</HEAD><BODY>");
     ps.println("<H2>This is the information you submitted</H2>");
     ps.println("<TABLE Border>");
     ps.println("<CAPTION>Your Registration Information</CAPTION>");
     ps.println("<TR><TD><B>Name</B></TD><TD>" +name +"</TD></TR>");
     ps.println("<TR><TD><B>Title</B></TD><TD>" +title +"</TD></TR>");
     ps.println("<TR><TD><B>Company</B></TD><TD>" +company +"</TD></TR>");
     ps.println("<TR><TD><B>Address</B></TD><TD>" +address +"</TD></TR>");
     ps.println("</TABLE>");
     ps.println("</BODY></HTML>");
     ps.flush();
}

Connecting Your Servlet to a JDBC Database

The next step is to connect to a SQL database. The Java Database Connectivity package makes this possible. The servlet also uses a very simple SQL generator.

The generator class takes an object of the DBRecord class as an argument and returns a string containing SQL commands. DBRecord is a container class that holds the table's name, its primary key, and the names and values of the fields to be changed.

To increase the performance of the database queries, the servlet connects to the database in its init() method. This makes the servlet connect to the database as soon as it is loaded.

By telling Jeeves to load the servlet when the server starts up, you can reduce the overhead associated with reestablishing the connection for each request. See the previous section on loading servlets to learn how to make Jeeves load the servlet at startup.

 Public class JDBCServlet extends FormServlet {
     Connection con;
     Statement stmt;
     
     public void init() {
     try {
     Class.forName("imaginary.sql.iMsqlDriver");
     String url = "jdbc:msql://pandora.scripps.edu:1112/userreg";
     connection = DriverManager.getConnection(url, "guest", "");
     } catch (Exception e) {
     System.out.println(e.getMessage());
     e.printStackTrace();
     }
     }
     public void destroy() throws Exception {
     ocn.close();
     }

}

The JDBCServlet's init() method made the connection to an msql database with the imaginary.sql.iMsqlDriver. The connection could have been made to any database with a JDBC driver. Even databases that use only ODBC could be used with an ODBC-JDBC bridge.

Note
The Weblogic Company has both pure Java JDBC drivers for most of the major RDBMSs and ODBC-JDBC bridges. To find out more, check out http://www.weblogic.com/.

Inserting Data in the Database

The next step is to override the sendResponse method. You put the information submitted with the form into a DBRecord object, and then you generate a string containing SQL commands with the SQLgen object.

Use that string in the executeQuery method of the JDBC Statement class. The Query returns a ResultSet object containing the response to your query, in the case of a search.

Listing 24.1 shows how data is inserted in a JDBC database. When the data is inserted in the database, the user gets an acknowledgment.

Caution
Since the Jeeves Web server is multithreaded, a servlet may be called by several handler threads simultaneously. You must make your servlets thread-safe by using synchronized methods and blocking when necessary.


Listing 24.1  JDBCSERVLET.JAVA-The sendResponse Method of the
JDBCServlet
Public void sendResponse(Servlet res, Hashtable table)
throws Exception
{
     Statement stmt;
     SQLgen dbaction;
     ResultSet rs;

     // Create a new DBRecord object and fill its fields with
     // the values obtained from the HTML form.
     DBRecord rec = new DBRecord();
     rec.setTable("userreg");
     String name = (String) table.get("name");
     if (name.length() > 0) {
     rec.setField(new DBField("name", name);
     }
     String title = (String) table.get("title");
     if (title.length() > 0) {
     rec.setField(new DBField("title", title);
     }
     String company = (String) table.get("company");
     if (company.length() > 0) {
     rec.setField(new DBField("company", company);
     }
     String address = (String) table.get("address");
     if (address.length() > 0) {
     rec.setField(new DBField("address", address);
     }
     String action = (String) table.get("action");
     if (action.equals("Insert")) {
     rec.setActionInsert();
     }
     
     // Create a generator object with the completed DBRecord object.
     dbaction = new SQLgen(rec);
     // Create a JDBC Statement object.
     stmt = con.createStatement();
     // Execute the SQL query generated by the SQLgen object
     rs = stmt.executeQuery(dbaction.getSQL());
     stmt.close();

     // Create a new PrintStream to output an acknowledgment of the users
     // registration
     PrintStream ps = new PrintStream(res.getOutputStream());
     log("JDBCServlet running");
     res.setStatus(ServletResponse.SC_OK);
     res.setContentType("text/html");
     res.writeHeaders();
     ps.println("<HTML><HEAD>");
     ps.println("<TITLE>Registration Complete</TITLE>");
     ps.println("</HEAD><BODY>");
     ps.println("<H2>Thank you for registering</H2>");
     ps.println("</BODY></HTML>");
     ps.flush();
}

Searching the Database

You can make your servlet search the database and insert data into it by adding the following code fragments to the above sendResponse method. The first piece checks to see if the form's action was set to search.

If it is, the action field of the DBRecord object is also set to search. The second piece returns the search results in the form of an HTML table.

 if (action.equals("Search")) {
     rec.setActionInsert();
}

...

if (action.equals("Search")) {
     ps.println("<TABLE Border>");
ps.println("<TR><TD><B>Name</TD><TD>Title</TD><TD>Company</TD><TD>Address</TD></TR>");
     while(rs.next()) {
     String name = rs.getString(1);
     String title = rs.getString(2);
     String company = rs.getString(3);
     String address = rs.getString(4);
     
     ps.println("<TR><TD>" +name +"</TD><TD>" +title +"</TD><TD>" );
     ps.println(company +"</TD><TD>" + address +"</TD></TR>")
     }
     ps.println("</TABLE>");
 }

Following is the complete code listing for the JDBC Serlvet. This simple example shows the power of using Java on the server-side. Combining servlets with the JDBC makes a strong argument for server-side Java.

Listing 24.2 shows a simple autonomous agent system that takes advantage of another of Java's powerful features, object serialization.


Listing 24.2  JDBCSERVLET.JAVA-Complete Code for the JDBC Servlet
import java.sql.*;
import java.io.*;
import java.util.*;
import sun.server.http.*;
import java.servlet.*;
import db.*;

public class JDBCServlet extends FormServlet {
     Connection con;
     Statement stmt;
     
     public void init() {
     try {
     Class.forName("imaginary.sql.iMsqlDriver");
     String url = "jdbc:msql://pandora.scripps.edu:1112/userreg";
     con = DriverManager.getConnection(url, "guest", "");
     } catch (Exception e) {
     System.out.println(e.getMessage());
     e.printStackTrace();
     }
     }
     public void destroy(){
     try {
               con.close();
     } catch (Exception e) {
     System.out.println(e.getMessage());
     e.printStackTrace();
     }

     }

     public void sendResponse(ServletResponse res, Hashtable table)
     throws Exception
     {
     Statement stmt;
     SQLgen dbaction;
     ResultSet rs;

     // Create a new DBRecord object and fill its fields with
     // the values obtained from the HTML form.
          DBRecord rec = new DBRecord();
     rec.setTable("userreg");
     String name = (String) table.get("name");
     if (name.length() > 0) {
     rec.setField(new DBField("name", name));
     }
     String title = (String) table.get("title");
     if (title.length() > 0) {
     rec.setField(new DBField("title", title));
     }
     String company = (String) table.get("company");
     if (company.length() > 0) {
     rec.setField(new DBField("company", company));
     }
     String address = (String) table.get("address");
     if (address.length() > 0) {
     rec.setField(new DBField("address", address));
     }
     String action = (String) table.get("action");
     if (action.equals("Insert")) {
     rec.setActionInsert();
     }
          if (action.equals("Search")) {
     rec.setActionInsert();
     }

     
     // Create a generator object with the completed DBRecord object.
          dbaction = new SQLgen(rec);
          try {
          // Create a JDBC Statement object.
     stmt = con.createStatement();
     // Execute the SQL query generated by the SQLgen object
     rs = stmt.executeQuery(dbaction.getSQL());
     stmt.close();

     // Create a new PrintStream to output an acknowledgment of the users
     // registration
          PrintStream ps = new PrintStream(res.getOutputStream());
          log("JDBCServlet running");
     res.setStatus(ServletResponse.SC_OK);
     res.setContentType("text/html");
     res.writeHeaders();
     ps.println("<HTML><HEAD>");
     ps.println("<TITLE>Registration Complete</TITLE>");
     ps.println("</HEAD><BODY>");
          ps.println("<H2>Thank you for registering</H2>");
          if (action.equals("Search")) {
     ps.println("<TABLE Border>");
     ps.println("<TR><TD><B>Name</TD><TD>Title</TD>");
                ps.println("<TD>Company</TD><TD>Address</TD></TR>"); 
                while(rs.next()) {
     name = rs.getString(1);
     title = rs.getString(2);
     company = rs.getString(3);
     address = rs.getString(4);
     
     ps.println("<TR><TD>" +name +"</TD><TD>" +title +"</TD><TD>" );
                   ps.println(company +"</TD><TD>" + address 
               +"</TD></TR>");

     }
     ps.println("</TABLE>");
     }
               if (action.equals("Insert")) {
          ps.println("<H2>This is the information you submitted</H2>");
     ps.println("<TABLE Border>");
     ps.println("<CAPTION>Your Registration Information</CAPTION>");
     ps.println("<TR><TD><B>Name</B></TD><TD>" +name +"</TD></TR>");
     ps.println("<TR><TD><B>Title</B></TD><TD>" +title +"</TD></TR>");
     ps.println("<TR><TD><B>Company</B></TD><TD>" +company +"</TD></TR>");
     ps.println("<TR><TD><B>Address</B></TD><TD>" +address +"</TD></TR>");
     ps.println("</TABLE>");
     }

     ps.println("</BODY></HTML>");
     ps.flush();

     } catch (Exception e) {
     System.out.println(e.getMessage());
     e.printStackTrace();
     }

}
}

Building a Simple Autonomous Agent System with Jeeves

This example shows how servlets can be used to create an environment in which small, self-contained Java programs can roam the Internet, gathering and processing information before returning home for debriefing. This example has five components: the agent, the remote agency servlet, the home agency servlet, the agent launcher, and the agent debriefing program.

The agent launcher instructs the agent of the information it is to retrieve from a particular JDBC database and then transports it to the destination. The remote agency servlet provides an environment where the agent can execute its run method, which then queries the specified database.

After that, the remote agency asks the agent where it lives and then sends it on its way. The home agency servlet then accepts the agent and stores it in a file for later debriefing. Finally, the agent debriefing program restores the agent and requests the information it was sent to retrieve.

This is a simple system designed only for illustration. A real system needs to use strict security measures, which Jeeves does provide through a servlet-specific, access-control list.

A real system also needs a way of advertising services that are accessible to agents. This way, agents can find new services themselves.

Using Object Serialization to Transport Agents Across the Internet

Java's object serialization provides transportation for your agent. Object serialization is the process of turning any Java object or graph of objects (an object and all the objects it is made up of) into a stream of information. This stream can be transported using any OutputStream or InputStream and restored to its original state when it reaches the other side.

Object serialization can be used to give persistence to an object by serializing an object into a file for later retrieval. This is done to the JDBCAgent in the following example.

Serializing an object is as easy as creating an ObjectOutputStream and calling its writeObject method with the object to be serialized as the argument.

ObjectOutputStream oos = ObjectOutputStream(socket.getOutputStream());
oos.writeObject(myobject);

Deserializing an object is just as straightforward. First, create an ObjectInputStream and call its readObject method with the object to be deserialized as the argument.

ObjectInputStream ois = ObjectInputStream(socket.getInputStream());
ois.readObject(myobject);

Note
The object serialization classes are not included with the JDK as of release 1.0.2. They can be downloaded with the Remote Method Invocation package from the Javasoft Web site.

Note
For more information on object serialization, see Sun's white paper: Java Object Serialization Specification, available at the Javasoft Web site (http://www.javasoft.com/).

Building the Remote Agency

Start by building the remote agency. The RemoteAgencyServlet class extends the Servlet class. Its sole purpose in Listing 24.3 is to deserialize the agent, execute the agent's run method, ask the agent where it lives, and send it on its way. Again, in a real system, you need to use careful security procedures.

Caution
This agency accepts any Java object that conforms to a simple interface and executes its run method. The servlet also bypasses all of Jeeves' built-in security procedures used to protect the server from remote servlets.
This is extremely dangerous in the real world. You should use strict access control on this servlet. At the same time, this example shows the power of servlets and Java.

Note
Jeeves provides an elaborate access-control mechanism that can be used to protect the server from dangerous agents. You can also create servlet-specific authentication procedures with the security classes.


Listing 24.3  REMOTEAGENCYSERVLET.JAVA-The RemoteAgencyServlet Class
import java.io.*;
import java.servlet.*;
import java.net.*;

public class RemoteAgencyServlet extends Servlet
{
     public String getServletInfo() {
          return "Remote Agent Servlet";
     }
     public void service(ServletRequest req, ServletResponse res)
     throws Exception
     {
     // Load the agent from the client's agent launcher
     ObjectInputStream ois = new ObjectInputStream(req.getInputStream());
     // Cast the incoming object to AgentInterface, which is a simple that
     // all agents must implement.
     AgentInterface agent = (AgentInterface) ois.readObject();
     // Call the agents run method.
     agent.run();
     
     // Now that the agent has completed its task, find out where it lives.
     URL agenthome = agent.getAgentHome();
     int port = agenthome.getPort();
     String host = agenthome.getHost();
     String file = agenthome.getFile();
     // Now that you know where the agent lives send it on its way.
     // Open up a connection to the agents home, which is a servlet compliant
     // web server.
     Socket socket = new Socket(host, port);
     PrintStream ps = new PrintStream(socket.getOutputStream());
     // Request to HomeAgentServlet from the web server
     ps.println("POST" +file);
     ps.flush();
     // Create an ObjectOutputStream
     ObjectOutputStream oos = new 
     ObjectOutputStream(socket.getOutputStream());
     oos.writeObject(agent);
     oos.flush();
     }
}  

Creating a Generic Agent Interface

Using agents from the AgentInterface interface allows new agents to be created to run on any servlet that knows the interface. This agent interface is simple, but other methods could be added.

The current interface has only three methods. The run method causes the agent to carry out the mission for which it was sent. The getAgentHome method is used by the remote agency to determine where to send the agent.

Finally, the getAuthentication method is used by the server to authenticate the agent. In this example, getAuthentication returns null.

import java.net.URL;
public interface AgentInterface {
     public void run();
     public URL getAgentHome();
     public String getAuthentication();
}

Implementing a Database Search Agent

The JDBCAgent uses the AgentInterface. This agent, shown in Listing 24.4, queries a JDBC database using much of the same code as the earlier database example. It has methods for specifying the remote host, database, JDBC driver, and the agent's home.


Listing 24.4  JDBCAGENT.JAVA-The JDBCAgent Class
import java.sql.*;
import java.io.*;
import java.util.*;
import java.net.URL;
import db.*;

public class JDBCAgent implements AgentInterface
{
     Connection con;
     Statement stmt;
     DBRecord rec;
     JDBCInfo jdbcinfo;
     Vector v;
     URL agenthome;
     
     public void run() {
     try {
     String url = jdbcinfo.getURL();
     String driver = jdbcinfo.getDriver();
     String user = jdbcinfo.getUser();
     String password = jdbcinfo.getPassword();

     Class.forName(driver);
     con = DriverManager.getConnection(url, user, password);
     stmt = con.createStatement();
     SQLgen dbaction = new SQLgen(rec);
     ResultSet rs = stmt.executeQuery(dbaction.getSQL());
     
     while(rs.next()) {
     Hashtable hashtable = new Hashtable();
     hashtable.put("name", rs.getString(1));
     hashtable.put("id", rs.getString(2));
     hashtable.put("company", rs.getString(3));
     hashtable.put("location", rs.getString(4));
     v.addElement(hashtable);
     }
     stmt.close();
     } catch (Exception e) {
     System.out.println(e.getMessage());
     e.printStackTrace();
     }
     }
     
     public Vector getResultVector() {
     return v;
     }
     public void setQuery(DBRecord rec) {
     this.rec = rec;
     }
     public void setJDBCInfo(JDBCInfo jdbcinfo) {
     this.jdbcinfo = jdbcinfo;
     }
     public void setAgentHome(URL agenthome) {
     this.agenthome = agenthome;
     }
     public URL getAgentHome() {
     return agenthome;
     }
     public String getAuthentication() {
     return null;
     }
}

Building the Home Agency

The HomeAgencyServlet stores returning agents until the user is ready to debrief them. This servlet deserializes an incoming agent and then stores it in a file (see Listing 24.5).


Listing 24.5  HOMEAGENCYSERVLET.JAVA-The HomeAgency Class
import java.io.*;
import java.servlet.*;
import java.net.*;

public class HomeAgencyServlet extends Servlet
{
     public String getServletInfo()
     {
     return "Agent Home Servlet";
     }

     public void service(ServletRequest req, ServletResponse res) 
     throws Exception
     {
     // create ObjectInputStream from the InputStream originating a the  RemoteAgencyServlet.
          ObjectInputStream ois = new 
          ObjectInputStream(req.getInputStream());
          // Read in the Agent object from the stream.
     AgentInterface agent = (AgentInterface) ois.readObject();
     // open a file to store the agent in until debriefing
     FileOutputStream fos = new 
FileOutputStream("/agents/storage/agent99");
     // Create an ObjectOutputStream pointing to the file
     ObjectOutputStream oos = new ObjectOutputStream(fos);
     // Write the agent to the file.
     oos.writeObject(agent);
     oos.flush();
     fos.close();
     }
}

Launching the Agent

The AgentLauncher sets the agent's mission and then transports it to the RemoteAgencyServlet. It uses a class called JDBCInfo to set the specific database information.

JDBCInfo is a container class that holds information on which database driver to use, the URL, and the user name and password for the database. The AgentLauncher then places the query in a DBRecord object.

Finally, it contacts the agency servlet on a remote HTTP server and uses object serialization to transport the agent to the remote agency (see Listing 24.6).


Listing 24.6  AGENTLAUNCHER.JAVA-The AgentLauncher Class
import java.io.*;
import java.util.*;
import java.net.*;
import db.*;

public class AgentLauncher {
     public static void main(String[] argv) throws Exception {
     JDBCAgent jdbcagent = new JDBCAgent();
     
     JDBCInfo jdbcinfo = new JDBCInfo();
     jdbcinfo.setDriver("imaginary.sql.iMsqlDriver");
     jdbcinfo.setURL("jdbc:msql://agency.agentworld.com:1112/agentdb");
     jdbcinfo.setUser("agent99");
     jdbcinfo.setPassword("");
     jdbcagent.setJDBCInfo(jdbcinfo);
     
     DBRecord rec = new DBRecord();
     rec.setTable("agents");
     rec.setPrimaryKey("recordnumber");
     rec.setField(new DBField("location", "North America"));
     rec.setActionSelect();
     jdbcagent.setQuery(rec);
     
     URL homeurl = new 
URL("http://pandora.scripps.edu/servlet/homeagency");
     jdbcagent.setAgentHome(homeurl);

     Socket socket = new Socket("buddha", 8888);
     PrintStream ps = new PrintStream(socket.getOutputStream());
     ps.println("POST /servlet/RemoteAgency");
     ps.flush();
     ObjectOutputStream oos = new ObjectOutputStream(ps);
     oos.writeObject(jdbcagent);
     oos.flush();
     }
}

Debriefing the Agent

The AgentDebriefing class, shown in Listing 24.7, restores the agent from the file that it is stored in and requests the information it was sent to gather. This program is run by the user when the agent has returned. The HomeAgency could have been designed to send mail, letting the user know when an agent returns.


Listing 24.7  AGENTDEBRIEFING.JAVA-The AgentDebriefing class
import java.io.*;
import java.util.*;
import java.net.Socket;
import db.*;

public class AgentDebriefing {
     public static void main(String[] argv) throws Exception {
     JDBCAgent jdbcagent = new JDBCAgent();
     FileInputStream fis = new FileInputStream("/agents/storage/agent99");
     ObjectInputStream ois = new ObjectInputStream(fis);
     JDBCAgent agent = (JDBCAgent) ois.readObject();
     
     Vector v = agent.getResultVector();
     for (Enumeration e=v.elements(); e.hasMoreElements();) {
     Hashtable hash = (Hashtable) (e.nextElement());
     System.out.println("Name: " +hash.get("name"));
     System.out.println("id: " +hash.get("id"));
     System.out.println("Company: " +hash.get("company"));
     System.out.println("location: " +hash.get("location"));
     }
     }
}