Chapter 15 of Professional Java Server Programming, © 1999 Wrox Press.

Part 1
Part 2
[ Part 3 ]
Table of Contents
    JSP and Java Beans
        index.jsp and common.jsp
        weeds.jsp
        error.jsp
        The Beans
    Serving images out of the database
        ViewWeed.java
        InsertImages.java
Conclusion

JSP and Java Beans


In WEEDS 2, I use Java Server Pages to define the look of my pages -- as a template filled in with information obtained from Java beans. Systems that embed a programming language in HTML, such as JSP, Netscape's LiveWire and Microsoft's ASP, make it convenient to separate the style and content of web pages. Many programmers feel it's easier to write web applications this way, compared to CGI scripts or Java Servlets, in which you must generate HTML inside your program and write a lot of repetitive code such as class and method declarations. With careful design, systems like JSP help divide the labor of making complex sites. The templates can be written by graphic designers with a good aesthetic sense, artistic ability and knowledge of HTML. Implementing database-driven sites also requires skill in programming, software engineering, and data modeling. Although it takes a programmer to plan the architecture of a site, most of the tricky programming can be encapsulated in Java Beans used by the template.


What exactly are Java Beans? The Java Beans specification (http://www.javasoft.com/beans/docs/spec.html) describes a set of standard design patterns to set and retrieve properties of Java objects, to pass events between objects, to create instances of objects, and to store objects using serialization. Also, the specification defines a format for storing metainformation about Java classes in a JAR file that allow visual GUI builders and other tools to import them and incorporate them automatically in projects. The Java Beans specification is modular, so you can use parts of the specification you need for your application (such as getting and setting properties) without needing to implement everything, such as metainformation.


Because AWT components in Java 1.1 use the Java Beans patterns, it's possible to write new GUI components using the Java Beans patterns and assemble them with visual tools to make GUI applications. Even so, Java Beans don't need to be graphical and are equally suitable for server programming. Enterprise Java Beans (EJB) builds on the Beans specification to define a way of making reusable components for server applications that can scale to the needs of large business. Clients access EJBs via RMI or IIOP -- clients never get a direct reference to an EJB, but rather, the creation of EJBs and all of the business methods are managed by an EJB Container, a server product supplied by a third party. A good introduction to EJB is at http://www.javasoft.com/products/ejb/white_paper.html


The aspect of Beans most useful for web applications is properties. Properties are like variables or constants which can be set or retrieved through a standard interface. They are only like variables because the authors of Beans can implement them any way they like. A property could be stored in a variable, but could also be stored in a database, or computed by a lengthy calculation. When you set a property, Beans can make changes in other objects -- a visual bean can redraw itself, for instance, if you change its appearance. Properties can be set and retrieved through two patterns called accessor methods. Suppose we have a Bean named pinto, with a property called weight of type double. So other objects can set the property, it defines a method with the signature


public void setWeight(double weight).

Other objects retrieve the property through the method


public double getWeight().

In general, the name weight can be replaced by any other name, and the type double can be replaced by another type. You can use accessor methods by hand in other objects, and sophisticated programs such as GUI builders can automatically detect and use them via Java introspection.


There are two ways to use Beans from JSP. The first is the use the <jsp:useBean /> <jsp:setProperty/>and <jsp:getProperty/> tags. These make it possible to use Beans without writing Java at all, and because they have a simple and limited function, they'll be useful for visual tools which let us drag and drop Beans into pages. However, it's still possible to access Beans using the ordinary Java syntax. For

instance, to insert the value of pinto's weight property, we can either type


<jsp:getProperty id="pinto" property="weight"/>

or


<%=pinto.getWeight() %>.

In this project I've used the second approach, because the Java syntax is shorter and I'm already familiar with it. Also, more seriously, I find the <jsp:useBean/> tag is limited. Although it can automatically create Beans in different scopes, that is, create beans that exist for just one page, or that persist throughout a session or application, it can only create Beans via an empty constructor, such as


pinto=new Pinto();

Unfortunately, this isn't flexible. For one thing, it's impossible to create Enterprise Java Beans this way. To use an EJB, you must look up its Home Interface, which contains methods for creating, finding and removing instances of the Bean, via JNDI. My WeedBean corresponds to an entry in the database. It always corresponds to the same entry, so I pass the id, the primary key of the weed, in its constructor:


weed=new Weed("12");


WeedBean plays a role similar to that of an Entity Bean in EJB -- it represents an object held in persistant storage. (A database) Although I could provide a method in WeedBean to set its id, say


weed.setId("12");

this breaks my decision to make a particular instance of WeedBean correspond to a particular weed. (Suppose Object A and Object B held a copy of the same WeedBean and Object A changed the WeedBean's id without telling Object B.)


Although Sun will add support for EJB in the next version of JSP, in the short term you have two choices to use EJBs or Beans with complicated constructors:

use Java expressions and scriplets, or, write a Bean with a simple constructor which

wraps the complicated Bean.


index.jsp and common.jsp

Let's take a look at the JSP file for the top page. We start out with a few definitions

index.jsp

<HTML lang="en">
<%@ page errorPage="error.jsp" %>
<%
	tapir.weeds.IndexBean b=new tapir.weeds.IndexBean();
     b.setBreak(8);
%>
<%@ include file="meta.jsp"%>

At the top, I declare an error handler-- if index.jsp throws an exception, the JSP engine will execute and pass the exception to error.jsp. I'll talk about error.jsp in a moment. Next we create an instance of this page's Bean. (I use the full name of the Bean because the <@ page import> directive didn't work in Sun's reference implementation, which was the only JSP 1.0 compliant engine available when I wrote this.) Next I configure the Bean, setting the break property. I could store the configuration of the Bean in a database, or as a serialized object in a file, but since the break property has to do with visual presentation, it makes sense to store its value in the JSP file, where most of the decisions about presentation are made.


Finally, I include the file meta.jsp, which is included by all of the JSP pages in this project except for error.jsp. It contains the HTML <HEAD> of the document, which contains HTML 4.0 metainformation tags which help search engines and other web robots.


meta.jsp

<HEAD><TITLE><%= b.getTitle() %></TITLE>
<META name="ROBOTS" content="<%= b.getRobotInfo() %>">
<META name="Author" content="<%= b.getAuthor() %>">
<META name="Date" content="<%= b.getDate() %>">
<META name="Copyright" content="<%= b.getCopyright() %>">
<META name="Keywords" content="<%= b.getKeywords() %>">
<META name="Description" content="<%= b.getDescription() %>">
<META name="Version" content="<%= b.getVersion() %>">
</HEAD>

In meta.jsp, the Bean properties return plain text. This is, in general, good. If we wanted to write a GUI or command-line application that accesses the weeds database, we could easily use the author property. Also, when Bean properties return plain text,

the JSP author (who isn't necessarily the same as the Bean author) has a great degree of choice. If, for instance, she wants to print the title centered in bold, she can type


<DIV ALIGN=CENTER><B><%= b.getTitle() %></B></DIV>

if the JSP author knows how to do more complicated programming, she could also process the String in embedded Java code. However, sometimes it makes sense for a Bean to return HTML which is included directly in the document, as we do next.

index.jsp

<BODY BGCOLOR=#FFFFFF>
<TABLE CELLSPACING=0 WIDTH=100%><TR><TD BGCOLOR=#A0FFA0>
<FONT COLOR=#000000><FONT SIZE=+2><%= b.getTitle() %></FONT></FONT>
</TABLE>
<TABLE CELLPADDING=3>
<TR>
<TD VALIGN=TOP>
<%= b.getWeedList() %>
<TD VALIGN=TOP>
<IMG SRC="mountains.jpg" HEIGHT=201 WIDTH=310>
</TABLE>

The weedList property is an HTML string that we insert into the middle of the document. It contains <B> tags to make the family headers bold, <BR> tags to mark the end of lines, and <IMG> tags pointing to single pixel .gifs for formatting, and a <TD> tag which breaks the table. As a result, the author of the JSP can make only limited changes in the appearance of the list: perhaps she could embed it in a <FONT> tag, but she couldn't decide to make the family headers italic instead of bold, or to change the indentation of the species tags. I had some other choices here: I could have had the Bean return an array of species names and written a Java scriptlet, embedded in the JSP, to format the text. However, this would defeat the goal of confining complicated Java to the Bean. If there were particular parameters that the JSP author would want to set, for instance, the indentation of the species names, the Bean could expose properties to allow that. But how could I, as the Bean author, imagine everything that a JSP author would want to change? Cascading style sheets (CSS) offer a ray of hope: if, for instance, weedList marked text with logical meanings with the SPAN tag, that is,


<SPAN class="familyHeader">Amaranthaceae</SPAN>
<SPAN class="species"><A HREF="weeds/1.html">
Amaranthus spinosus L.</A></SPAN>

a graphic designer could change the formatting of the page by changing the style sheet. I couldn't do that for this project, however, because Common Weeds is of interest to viewers in the third world who won't have the newest browsers. Finally, at the bottom

of the page, I include the copyright message, stored in copy.jsp, and end the page.


<%@ include file="copy.jsp"%>
</BODY>
</HTML>

The common name index, common.jsp is almost identical to index.jsp, except that it creates an instance of CommonBean rather than IndexBean, and has a more complicated method to give the computer a hint as to where to break the columns,


<%
	tapir.weeds.CommonBean b=new tapir.weeds.CommonBean();
        int[] breaks=new int[5];
        breaks[0]=22;
        breaks[1]=43;
        breaks[2]=61;
        breaks[3]=77;
        breaks[4]=100;
        b.setBreaks(breaks);
%>

weeds.jsp


The <jsp:useBean/> and related tags would have been adequate for index.jsp and common.jsp, since IndexBean and CommonBean both have zero-argument constructors. However, the constructor of WeedBean takes a single argument, the id number of the weed, and can't be created by <jsp:useBean/>.


weed.jsp

<HTML lang="en">
<%@ page errorPage="error.jsp" %>
<%
	tapir.weeds.WeedBean b=new tapir.weeds.WeedBean(request.getParameter("weed"));
%>
<%@ include file="meta.jsp"%>

The id number of the weed is passed through the weed form parameter, which I retrieve through the request object, an instance of javax.servlet.http.HttpServle­tRequest which is automatically defined in any Java server page.


weed.jsp

<BODY BGCOLOR=#FFFFFF>
<TABLE CELLSPACING=0 WIDTH=100%><TR><TD BGCOLOR=#A0FFA0 COLSPAN=3>
<FONT COLOR=#000000><FONT SIZE=+2><%= b.getTitle() %></FONT></FONT>
<TR><TD WIDTH=<%= b.getSmallImageWidth() %> ROWSPAN=2>
<A HREF="<%= b.getBigImageURL() %>">
<IMG ALIGN=LEFT BORDER=0 ALT="[<%= b.getPrincipalLatin() %> illustration]"
HEIGHT=<%= b.getSmallImageHeight() %> WIDTH=<%= b.getSmallImageWidth() %> SRC="<%= b.getSmallImageURL() %>">
</A>

All of the Bean properties in the snippet above are plain text. I was careful in weed.jsp to avoid nested tables, because nested tables slow the rendering of tables in web browsers. Many database-driven sites use complex table layouts to pack in a lot of information. That's good, but some browsers (particularly Netscape) won't draw tables until they've received enough information to lay them out correctly -- a problem that synergises with overloaded servers, congested backbones and slow modems. To avoid this, test your site over slow connections, avoid nested tables, and try breaking large tables into several tables.


<TD VALIGN=TOP>
<SMALL>
<%= b.getNumberNav() %>
</SMALL>

The numberNav property of WeedBean is a block of HTML that draws a navigation bar that lets users seek out a page by number. Bean properties that return HTML can work like widgets do in graphical user interfaces.


weed.jsp

<TD VALIGN=TOP>
<DIV ALIGN=RIGHT>
<A HREF="../index.html">top/latin</A> |
<A HREF="../common.html">common</A>
</DIV>
<TR><TD COLSPAN=2 VALIGN=TOP>
<B>Family <%= b.getFamily() %></B>
<BR>Latin name: <I><%= b.toHtml(b.getPrincipalLatin()) %></I>
<BR>Common names: <I><%= b.toCommaList(b.toHtml(b.getCommon())) %></I>
<%= b.toHtml(b.getTexts()) %>
<P><B>Additional resources:</B>
<UL>
<LI><A HREF="<%= b.getBigImageURL() %>">Large illustration</A>
</UL>
<%@ include="copy.jsp"%>
</TABLE>
</BODY>
</HTML>

We then fill in the description of the weeds. A Bean used in a JSP can provide useful functions, as well as properties. In this case, there are several toHtml() functions which convert types to HTML. For instance, getPrincipalLatin() returns a LanguageString which contains a string and the ISO digraph denoting the language of the string, which is "la" in the case of Latin. The toHtml(LanguageString l) method adds a SPAN tag which marks the language of the text,



<SPAN LANG="la">Ricinus communis L. </SPAN>

toHtml(LanguageString l[]) converts an array of LanguageStrings into an array of Strings. toCommaList(String s[]) inserts commas between the strings in a String array to produce a comma separated list. Similarly, toHtml(Text t[]) uses toHtml(Text t) to convert the array of text objects produced by getTexts(). Another convenient way to define a library of utility functions for a collection of JSPs is to use the <%@ page extends %> directive to make them subclass a class you define. It takes some thought, however, to use subclassing well in Java, since Java does not support multiple inheritance and, therefore, a JSP, like any Java class, can only inherit from one other class.

error.jsp

Many things can and will go wrong with a database-driven site. Not only will you encounter errors while prototyping, implementing, and debugging a site, but you'll probably have your database crash, have administrators set incorrect settings, have users incorrectly change URLs by hand, and other troubles while your site is in production. The default error pages provided by your web server and servlet engine are often ugly and uninformative. To make error messages useful, both to developers and end users, and to make your site have a consistent look, it's desirable to provide your own error pages when things go wrong.


[enlarge image]

JSP provides a simple method for handling errors. When an exception is thrown, and not caught, inside a JSP, the JSP engine forwards the request to the page designated in the <%@ page errorPage %> directive. The error page must contain the

<%@ page isErrorPage="true" %> directive, which ensures that, in addition to the usual request, and response objects, an exception object is defined which is the exception thrown by the failed JSP.


error.jsp

<HTML lang="en">
<%@ page isErrorPage="true" %>
<BODY BGCOLOR=#FFFFFF>
<TABLE CELLSPACING=0 WIDTH=100%><TR><TD BGCOLOR=#A0FFA0>
<FONT COLOR=#000000><FONT SIZE=+2>Common Weeds of the Carribean</FONT></FONT>
</TABLE>
<DIV ALIGN=RIGHT>
<A HREF="../index.html">top/latin</A> |
<A HREF="../common.html">common</A>
</DIV>
<IMG SRC="/weeds/error.gif" HEIGHT=300 WIDTH=276 ALIGN=RIGHT>
<FONT SIZE=+7>OOPS!</FONT>
<BR>
<FONT SIZE=+2>Either you punched in something invalid, or something went wrong with our web server.
Try reloading. If that doesn't help
send a note to <A HREF="mailto:paul@honeylocust.com">paul@honeylocust.com</A></FONT>
<BR CLEAR=ALL>
<B><U>Stack trace:</U></B>
<PRE>
<% exception.printStackTrace(new PrintWriter(out)) ;%>
</PRE>
</BODY>
</HTML>

Our error page is simple, with a look consistent with the rest of Common Weeds and the tapir.org site. The stack trace, generated by


<% exception.printStackTrace(new PrintWriter(out)) ;%>

is useful for developers. For a more elaborate site, you should divide exceptions into two categories: expected and unexpected. Expected errors are those that can be identified to a specific problem which is the fault of the user or the server. For instance, if the user replaces the id of the plant with something incorrect, or if the database crashes and you can't get a connection at all, these errors can be expected and you can return a comprehensible error message. However, you can't predict everything that can go wrong (and a error handling system that's too complex is itself a source of bugs.) For the next version of Common Weeds, I will define an Exception called WeedsException, which will be thrown when expected errors occur. The detail message of WeedsException will be a user-friendly error message to replace the current vague message. When error.jsp receives a WeedsException, it will print the friendly message and omit the stack trace.


Note that you can put any Java code you like in an error handler, so you could have your error handler send a system administrator a mail with the Java Mail API, send a message to your system log, or store the details of the incident in a database.

The Beans

I created one Bean for each JSP file. This way, the division of labor is clear. In some situations, it might be best to create one bean for several different pages, or use more than one Bean in a page -- it would make sense to implement a navigation bar or advertising banner as a Bean that is reused on multiple pages. Many of the same functions, such as site metainformation, are needed in all of the Beans, so I use inheritance and put common code in GeneralBean. IndexBean, CommonBean and WeedBean all extend GeneralBean.



GeneralBean.java

package tapir.weeds;
import tapir.weeds.representation.*;
import honeylocust.msxml.parser.ParseException;
import java.io.*;
import java.sql.*;
import java.util.*;
public class GeneralBean {
  Weeds d_weeds;
  public GeneralBean() throws ClassNotFoundException,SQLException {
    d_weeds=new Weeds();
  };

d_weeds is an instance of the class Weeds, which is the interface to the SQL database. We next declare accessors for the metainformation fields accessed in meta.jsp. These values are inherited by Beans that extend GeneralBean, but all can be overridden in subclasses if we wish.


GeneralBean.java

  public String getRobotInfo() {
    return "ALL";
  };
  public String getAuthor() {
    return "Olivia S. Direnzo and Paul A. Houle";
  };
  public java.util.Date getDate() {
    return new java.util.Date();
  };
  public String getCopyright() {
    return "&copy; 1998 Honeylocust Media Systems (http://www.honeylocust.com/)";
  };
  public String getDescription() {
    return "A collection of descriptions and illustrations of weeds observed in El Limon, a small village
                in the Dominican Republic during January 1998. ";
  };
  public String getKeywords() throws SQLException {
    return "El Limon,Weeds,Botany,xml";
  };
  public String getLanguage() {
    return "en";
  };
  public String getVersion() {
    return "1.5DEV";
  };

After this, we have a few utility functions. GetNumberNav() for instance, generates the HTML code for the numbered navigation bar to navigate between individual weed pages by number,


GeneralBean.java

  public String getNumberNav(int id) {
    StringBuffer out=new StringBuffer();
    for(int i=1;i<15;i++) {
      if (i!=id) {
	out.append("<A HREF=\""+i+".html\">");
      } else
	out.append("&lt;");
      out.append(i);
      if (i!=id) {
	out.append("</A>");
      } else
	out.append("&gt;");
      out.append(" | ");
    };
    return out.toString();
  };

GeneralBean also contains a utility function to convert objects of type LanguageString to HTML strings using HTML 4.0 language marking. This facility makes it possible for search engines and automatic web page translators to process your pages.


GeneralBean.java

  public String toHtml(LanguageString l) {
    if (l.getLanguage()==getLanguage()) {
      return l.toString();
    } else {
      return "<SPAN LANG=\""+l.getLanguage()+"\">"+l.toString()+"</SPAN>";
    };
  };

The next two methods are written in a functional style. When we make a list of plants by common name, which can be written in several languages such as Spanish, Portuguese, English and French, we generate a comma separated list of names. First we convert an array of LanguageStrings into an array of HTML strings with String[] toHtml(LanguageString l) and then use String toCommaList(String[] s) to convert that array of HTML strings into a comma-separated list. This style lets us write programs by composing existing functions such as toCommaList(toHtml(commonNames). If I invented a new kind of string, say a ColoredString I just need to write a toHtml(ColoredString s[]) function. Java isn't quite as suitable for functional programming as more exotic languages such as LISP, but functional programming is often comfortable and fast in Java.


GeneralBean.java

  public String[] toHtml(LanguageString l[]) {
    int len=l.length;
    String[] x=new String[len];
    for(int i=0;i<len;i++)
      x[i]=toHtml(l[i]);
    return x;
  };
  public String toCommaList(String s[]) {
    int len=s.length;
    StringBuffer out=new StringBuffer();
    for(int i=0;i<len;i++) {
      if (i!=0)
	out.append(",");
      out.append(s[i]);
    };
    return out.toString();
  };
};

Serving images out of the database

ViewWeed.java

To serve images out of the database, I use an ordinary Java Servlet. JSP wouldn't help us here, since JSP uses Writers rather than OutputStreams, so it is limited to generating text documents, rather than binary files. Other than that, serving images dynamically isn't much different from serving documents. The ViewWeed servlet examines the path property of the request object -- the part of the URL that comes after the name of the servlet. For example, in http://www0/servlets/tapir.org.ViewWeed/big/5.jpg, the path is "/big/5.jpg." I only need to implement the doGet() method of HttpServlet, since this servlet only handles GET requests. (It doesn't process elaborate forms) I catch all exceptions and display an error message containing the exception's stack trace to aid debugging.



ViewWeed.java

public class ViewWeed extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
	try {
	    String path=req.getPathInfo().substring(1);
	    if (path.endsWith("/")) {
		path=path.substring(0,path.length()-1);
	    };
	    if (path.length()>6 && path.substring(0,6).equals("small/")) {
		serveImage(res,"small",path.substring(6));
	    } else if (path.length()>4 && path.substring(0,4).equals("big/")) {
		serveImage(res,"big",path.substring(4));
	    } else {
		throw new Exception("Invalid path.");
	    }
	} catch(Throwable e) {
	    PrintWriter out = res.getWriter();
	    out.println("<HEAD><TITLE>Exception processing ViewWeed</TITLE></HEAD>");
	    out.println("<BODY><PRE>");
	    e.printStackTrace(out);
	    out.println("</PRE></BODY>");
	}
    }

Two things must be done to serve an image: (i) call res.setContentType() to specify the MIME type of the image and (ii) write the image data to the OutputStream obtained from res.getOutputStream(). I fetch the MIME type and the image, stored as a BLOB, out of the database .


ViewWeed.java

    public void serveImage(HttpServletResponse res,String type,String name) throws Exception{
	Weeds w=new Weeds();
	int index=name.indexOf(".gif");
	if (index==-1)
	    throw new Exception("bad filename <"+index+">");
	String f=name.substring(0,index);
	String g=w.getOldId(f);
	ResultSet rs=w.selectFromImages(g,type);
	String mimeType=rs.getString(1);
	byte[] img=rs.getBytes(2);
	res.setContentType(mimeType);
	OutputStream os=res.getOutputStream();
	os.write(img);
	os.close();
    };

InsertImages.java

There is a separate program for inserting images in the database. As disk space is cheap, I load the images from all weeds, identified and unidentified, out of a directory containing all of them: I copied the GifInfo class wholesale from the old Weeds of El Limon


InsertImages.java

ViewWeed.java

package tapir.weeds;
import tapir.weeds.representation.*;
import java.io.*;
public class InsertImages {
  public static void main(String argv[]) throws Exception {
    Weeds w=new Weeds();
    GifInfo g=new GifInfo();
    String dir=argv[0];
    for(int i=1;i<33;i++) {
      System.out.println(i);
      File f=new File(dir,i+".gif");
      int x[]=g.getDimensions(f);
      w.insertImage(Integer.toString(i),"big",f,"image/gif",x[0],x[1]);
      f=new File(dir,i+"half.gif");
      x=g.getDimensions(f);
      w.insertImage(Integer.toString(i),"small",f,"image/gif",x[0],x[1]);
    }
    System.exit(-1);
  }
}

If you need to put a lot of data in a TEXT or BLOB column, JDBC provides handy methods such as PreparedStatement.setBinaryStream() which copies text or binary data out of an InputStream and ResultSet.getBinaryStream() which lets provides an InputStream to read the column from.


Weeds.java

  public void insertImage(String species,String type,File file,
      String format,int width,int height) throws SQLException,FileNotFoundException {
    PreparedStatement ps=prepareStatement("INSERT INTO images (species,type,
           img,format,width,height) values (?,?,?,?,?,?)");
    ps.setString(1,species);
    ps.setString(2,type);
    storeFileInBlob(ps,3,file);
    ps.setString(4,format);
    ps.setInt(5,width);
    ps.setInt(6,height);
    ps.executeUpdate();
  };
  public void storeFileInBlob(PreparedStatement ps,int slot,File f) throws SQLException,FileNotFoundException {
    int length=(int) f.length();
    FileInputStream in=new FileInputStream(f);
    ps.setBinaryStream(slot,in,length);
  };

Conclusion

WEEDS 2, the software behind Common Weeds of the Caribbean, is a dynamic web application using Java Server Pages and servlets on the Apache web server with the MySQL database. Since I use load the database from XML, I've eliminated, for now, the need to edit the database via the web.. With a web crawler, I make a static copy of my dynamic site for final deployment on the web and on floppy disk. Although I've covered a lot of ground in this chapter, database-driven sites are a large topic, and I ought to mention a few things that I haven't discussed: since WEEDS 2 doesn't yet provide a web interface for changing the database, I haven't discussed how to safeguard the integrity of data when multiple users make changes simultaneously and, also, the design and implementation of web user interfaces for editing data.


Part 1
Part 2
[ Part 3 ]
Table of Contents

Chapter 15 of Professional Java Server Programming, © 1999 Wrox Press.
Produced by Honeylocust Media Systems, contact paul@honeylocust.com