Search the Catalog
Java Cookbook

Java Cookbook

Solutions and Examples for Java Developers

By Ian Darwin
June 2001
0-596-00170-3, Order Number: 1703
882 pages, $44.95

Chapter 18
Web Server Java: Servlets and JSP

Introduction

This chapter covers Web Server Java, but you won't find anything about writing CGI programs in Java here. Although it would be entirely possible to do so, it would not be efficient. The whole notion of CGI programs is pretty much passe. Every time a CGI program is invoked, the web server has to create a new heavyweight process in which to run it; this is inefficient. If it's interpreted in Java, the program has to be translated into machine code each time; this is even more inefficient.

Today's trend is toward building functionality into the web server: Microsoft ASP, PHP3, Java servlets, and JavaServer Pages? (JSP[1]) are examples of this. None of these normally requires a separate process to be created for each request; the Java-based solutions run in a thread (see Chapter 24) inside the web server, and the Java bytecode need only be translated into machine code once in a long while, assuming a just-in-time (JIT) runtime system. Naturally, this book concentrates on the Java solutions.

We'll use two examples in this chapter. Consider the task of displaying a web page with five randomly chosen integer numbers (lottery players love this sort of thing). The Java code you need is simple:

// Part of file netweb/servlets_jsp/FiveInts.java 
Random r = new Random(  ); 
for (int i=0; i<5; i++) 
	System.out.println(r.nextInt(  )); 

But of course you can't just run that and save its output into an HTML file because you want each person seeing the page to get a different set of numbers. If you wanted to mix that into a web page, you'd have to write code to println( ) a bit of HTML. This would be a Java servlet.

The servlet code could get messy, however, since you'd have to escape double quotes inside strings. Worse, if the webmaster wanted to change the HTML, he'd have to approach the programmer's sanctified source code and plead to have it changed. Imagine if you could give the webmaster a page containing a bit of HTML and the Java code you need, and have it magically compiled into Java whenever the HTML was changed. Imagine no longer, says the marketer, for that capability is here now, with JavaServer Pages.

The second example is a dictionary (list of terms); I'll present this both as a servlet and as a JSP.

I won't talk about how you get your servlet engine installed, nor exactly how you install your servlet. If you don't already have a servlet engine, though, I'd recommend downloading Tomcat from http://jakarta.apache.org. Tomcat is the official reference implementation--so designated by Sun--for the servlet and JSP standard. It is also (as you can infer from the URL) the official servlet engine for the ever-popular Apache web server.

First Servlet: Generating an HTML Page

Problem

You want a servlet to present some information to the user.

Solution

Override the HttpServlet method service( ), or doGet( )/doPost( ).

Discussion

The abstract class javax.servlet.Servlet is designed for those who wish to structure an entire web server around the servlet notion. For example, in Sun's Java Web Server, there is a servlet subclass for handling plain HTML pages, another for processing CGI programs, and so on. Unless you are writing your own web server, you will probably not extend from this class, but rather its subclass HttpServlet, in the package javax.servlet.http. This class has a method:

public void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException; 

The service method is passed two arguments, request and response. The request contains all the information about the request from the browser, including its input stream should you need to read data. The response argument contains information to get the response back to the browser, including the output stream to write your response back to the user.

But the web has several HTTP methods for passing data into a web page. Unimportant for plain HTML pages, this distinction becomes of interest when processing forms, i.e., web pages with fill-in-the-blank or choice items. Briefly, the GET method of HTTP is used to pass all the form data appended to the URL. GET URLs look like this, for example:

http://www.acmewidgets.com/cgi-bin/ordercgi?productId=123456 

They have the advantage that the user can bookmark them, avoiding having to fill in the form multiple times. But there is a limit of about 1KB on the overall length of the URL. Since this must be a single string, there is an encoding that allows spaces, tabs, colons, and other characters to be presented as two hexadecimal digits: %20 is the character hexadecimal 20, or the ASCII space character. The POST method, by contrast, passes any parameters as input on the socket connection, after the HTTP headers.

The default implementation of the service( ) method in the HttpServlet class figures out which method was used to invoke the servlet. It dispatches to the correct method: doGet( ) if a GET request, doPost( ) if a POST request, etc., passing along the request and response arguments. So while you can, in theory, override the service( ) method, it's more common (and officially recommended) to override either doGet( ), doPost( ), or both.

The simplest HttpServlet is something like Example 18-1.

Example 18-1: HelloServlet.java

import java.io.*; 
import java.util.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 
  
/** Simple Hello World Servlet 
 */ 
public class HelloServlet extends HttpServlet{ 
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 
		PrintWriter out = response.getWriter(  ); 
		response.setContentType("text/html"); 
		out.println("<H1>Hello from a Servlet</h2>"); 
		out.println("<P>This servlet ran at "); 
		out.println(new Date().toString(  )); 
		out.println("<P>Courtesy of HelloServlet.java 1.2 "); 
	} 
} 

The program will give output resembling Figure 18-1.

Figure 18-1. Hello from a servlet

 

You can do much more with servlets. Suppose you wanted to print a dictionary--a list of terms and their meanings--from within a servlet. The code would be pretty much as it was in Figure 18-1, except that you'd need a doGet( ) method instead of a doPost( ) method. Example 18-2 is the code for TermsServlet.

Example 18-2: TermsServlet.java

/** A Servlet to list the dictionary terms. 
 */ 
public class TermsServlet extends HttpServlet { 
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { 
		PrintWriter out = resp.getWriter(  ); 
		out.println("<HTML>"); 
		out.println("<TITLE>Ian Darwin's Computer Terms and Acronyms</TITLE>"); 
		out.println("<BODY>"); 
		out.println("<H1>Ian Darwin's Computer Terms and Acronyms</h2>"); 
		out.println("<TABLE BORDER=2>"); 
		out.println("<TR><TH>Term<TH>Meaning</TR>"); 
  
		// This part of the Servlet generates a list of lines like 
		//	<TR> <TD>JSP <TD>Java Server Pages, a neat tool for ... 
		TermsAccessor tax = new TermsAccessor("terms.txt"); 
		Iterator e = tax.iterator(  ); 
		while (e.hasNext(  )) { 
			Term t = (Term)e.next(  ); 
			out.print("<TR><TD>"); 
			out.print(t.term); 
			out.print("<TD>"); 
			out.print(t.definition); 
			out.println("</TR>"); 
		} 
		out.println("</TABLE>"); 
		out.println("<HR></HR>"); 
		out.println("<A HREF="servlet/TermsServletPDF
">Printer-friendly (Acrobat PDF) version</A>"); 
		out.println("<HR></HR>"); 
		out.println("<A HREF="mailto:compquest@darwinsys.com/subject=Question
">Ask about another term</A>"); 
		out.println("<HR></HR>"); 
		out.println("<A HREF="index.html">Back to HS</A> <A HREF="../
">Back to DarwinSys</A>"); 
		out.println("<HR></HR>"); 
		out.println("<H6>Produced by $Id: TermsServlet.java,v 1.1 2000/04/06 
ian Exp $"); 
		out.print(" using "); 
		out.print(tax.ident); 
		out.println("</H6>"); 
	} 
}

Debugging Tip for Servlets

Several servlet engines (e.g., Allaire JRun) generate a lot of very small log files spread over many different directories. It is worth investing the time to learn where your particular servlet engine records stack traces, standard error and output, and other messages.

See also , which shows how a servlet or other server component can communicate with a network-based logging tool.

Servlets: Processing Form Parameters

Problem

You want to process the data from an HTML form in a servlet.

Solution

Use the request object's getParameter( ) method.

Discussion

Each uniquely named INPUT element in the FORM on the HTML page makes an entry in the request object's list of parameters. These can be obtained as an enumeration, but more commonly you request just one. Figure 18-2 shows a simple form that asks you how many random numbers you want generated, and makes up that many for you.

Figure 18-2. Random numbers HTML page

 

When I type the number 8 into the field and press the "Get Yours" button, I see the screen shot in Figure 18-3.

Figure 18-3. Random numbers servlet output

 

How does it work? The program obviously consists of both an HTML page and a Java servlet. The HTML page appears in Example 18-3; notice the FORM entry and the INPUT field.

Example 18-3: IntsServlet.htm

<HTML> 
<HEAD><TITLE>Random Numbers Page</TITLE></HEAD> 
<BODY BGCOLOR="white"> 
<H1>Random Numbers Page</h2> 
<P>This site will let you pick some random numbers for Lottery, lucky number 
or other purposes, all electronically.</P> 
<FORM METHOD=POST ACTION="/servlets/IntsServlet"> 
<H4>How Many Numbers Do You Want Today?</H4> 
<INPUT NAME=howmany SIZE=2> (default is 5) 
<BR> 
<INPUT TYPE="SUBMIT" VALUE="Get YOURS!"> 
</FORM> 
</BODY></HTML> 

Example 18-4 shows the Java for the servlet. Watch for the use of getParameter( ).

Example 18-4: IntsServlet.java

import java.io.*;
import java.util.Random;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class IntsServlet extends HttpServlet {
	protected final int DEFAULT_NUMBER = 5;
 
	/** Called when the form is filled in by the user. */
	public void doPost(HttpServletRequest req, HttpServletResponse resp)
	throws IOException {
		resp.setContentType("text/html");
		PrintWriter out = resp.getWriter(  );
	
		// The usual HTML setup stuff.
		out.println("<HTML>");
		out.println("<HEAD>");
		out.println("<BODY BGCOLOR=\"white\">");
 
		// HTML for this page
		out.println("<TITLE>Your Personal Random Numbers</TITLE>");
		out.println("<H1>Your Personal Random Numbers</h2>");
		out.println("<P>Here are your personal random numbers,");
		out.println("carefully selected by a");
		out.println("<A HREF=\"http://java.sun.com\">Java</A> program.");
		out.println("<OL>");
 
		// Figure out how many numbers to print.
		int n = DEFAULT_NUMBER;
		String num=req.getParameter("howmany");
		if (num != null && num.length(  ) != 0) {
			try {
				n = Integer.parseInt(num);
			} catch (NumberFormatException e) {
				out.println("<P>I didn't think much of ");
				out.println(num);
				out.println(" as a number.</P>");
			}
		}
 
		// Now actually generate some random numbers.
		Random r = new Random(  );
		for (int i=0; i<n; i++) {
			out.print("<LI>");
			out.println(r.nextInt(49));	// for Lotto 6/49

		out.println("</OL>");
 
		// Print a break and a back link.
		out.println("<HR></HR>");
		out.println("<A HREF=\"index.html\">Back to main Page</A>");
		out.println("</HTML>");
	}
}

See Also

The online source includes OrderServlet, a slightly longer example.

Cookies

Problem

You want the client (the browser) to remember some bit of information for you.

Solution

Bake a cookie, and serve it to the client along with your response.

Discussion

Cookies were invented by Netscape as a debugging technique, but have since become ubiquitous: all modern browsers, including MSIE, and text browsers such as Lynx accept and store them. A cookie is, at heart, a small piece of text--a name and value pair--that the server side generates and sends to the client. The browser remembers them (nontransient cookies are stored to your hard disk; Netscape creates a file called cookies or cookies.txt, for example). The browser then sends them back to the server on any subsequent visit to a page from the same site. The Cookie class is part of the javax.servlet.http package, so any servlet implementation will include it. The constructor is passed a name and value, but there are other parameters you can set. Most important is the expiry time, which is in seconds from the time you first send it. The default is -1; if the value is negative, the cookie is not saved to disk; it becomes a "transient cookie" that exists only until the browser exits and is then forgotten. For cookies that are stored to disk, the expiry time is converted to a base of January 1, 1970, the beginning of Unix time and of the modern computing era.

When the browser visits a site that has sent it a cookie or cookies, it returns all of them as part of the HTTP headers. You retrieve them all (as an array) using the getCookies( ) method, and iterate through them looking for the one you want.

for (int i=0; i<mySiteCookies.length; i++) {
	Cookie c = mySiteCookies[i];
	if (c.getName(  ).equals(name-you're-looking-for)) {
			someString = c.getValue(  );
			break;
	}
}

Suppose you want the user to pick a favorite color for the servlet to use as the background color for pages from that point on. Let's use a name of prefs.bgcolor for the color-coding cookie. The main servlet is CookieServlet, which checks for the cookie. If it was not set previously, it jumps off to an HTML page, which will eventually return here via another servlet. On the other hand, if the color cookie was previously set, CookieServlet (shown in Example 18-5) displays the welcome page with the user's color set.

Example 18-5: CookieServlet.java

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
/** Simple Cookie-based Page Color Display servlet demo.
 */
public class CookieServlet extends HttpServlet {
	/** The preferences cookie name */
	protected final static String PREFS_BGCOLOR = "prefs.bgcolor";
	/** Where to go if we have not yet been customized. */
	protected final static String CUSTOMIZER = "/ColorCustomize.html";
	/** The user's chosen color, if any */
	protected String faveColor = null;
 
	public void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {
 
		// Go through all the cookies we have, looking for a faveColor.
		Cookie[] mySiteCookies = request.getCookies(  );
		for (int i=0; i<mySiteCookies.length; i++) {
			Cookie c = mySiteCookies[i];
			if (c.getName(  ).equals(PREFS_BGCOLOR)) {
				faveColor = c.getValue(  );
				break;
			}
		}
 
		// if we did not find a faveColor in a cookie,
		// punt to customization servlet to bake one up for us.
		if (faveColor == null) {
			ServletContext sc = getServletContext(  );
 
			// Requires Servlet API 2.1 or later!
			// RequestDispatcher rd = 
			//	sc.getRequestDispatcher(CUSTOMIZER");
			//rd.forward(request, response);
 
			// Do it the old way
			response.sendRedirect(CUSTOMIZER);
		}
 
		// OK, we have a color, so we can do the page.
		PrintWriter out = response.getWriter(  );
		response.setContentType("text/html");
 
		out.println("<html><title>A Custom-Colored Page</title>");
		out.print("<body bgcolor=\"");
		out.print(faveColor);
		out.println("\">");
		out.println("<P>Welcome! We hope you like your colored page!</P>");
		out.println("</body></html>");
		out.flush(  );
	}
}

If the user has not yet set a color customization cookie, the CookieServlet passes control (by sending an HTTP redirect in the old API, or by use of a ServletDispatcher under the Servlet API 1.2 or later) to this HTML page.

<BODY BGCOLOR="pink">
<H1>Please choose a color</h2>
<FORM ACTION="/servlet/ColorCustServlet" METHOD=GET>
<SELECT NAME="color_name">
	<OPTION VALUE="green">Green</>
	<OPTION VALUE="white" SELECTED>White</>
	<OPTION VALUE="gray">Grey</>
</SELECT>
<INPUT TYPE="submit" VALUE="OK">
</FORM>

Finally, the HTML page will jump to the customization servlet (Example 18-6), which contains the code shown here to save the user's preference as a cookie, and then return to the CookieServlet by sending an HTTP "redirect," causing the browser to load the specified replacement page.

Example 18-6: ColorCustServlet.java

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
/** Color customization servlet */
public class ColorCustServlet extends HttpServlet {
 
	protected final static String DEFAULT_COLOR = "white";
	protected String faveColor = DEFAULT_COLOR;
 
	public void doGet(HttpServletRequest request, HttpServletResponse response)
	throws IOException {
		response.setContentType("text/html");
		PrintWriter out = response.getWriter(  );
 
		String cand=request.getParameter("color_name");
		if (cand != null) {
			faveColor = cand;
			Cookie c = new Cookie(CookieServlet.PREFS_BGCOLOR, faveColor);
			c.setMaxAge(60*60*24*365);
			response.addCookie(c);
		}
		response.sendRedirect("/servlet/CookieServlet");
	}
}

Of course, there are issues to consider when using cookies. Some users disable cookies out of justifiable fear that web sites will use them for gathering more information than a person might want to have known. In this case, our servlet would keep coming back to the customization page. It should probably have a warning to the effect that "cookies must be enabled to view this site." Or you could use other techniques, such as session tracking (see Session Tracking).

And realistically, you probably want to keep more than one preference item for a user. If you let them set the screen background, you also need to set the text color, for example. It's probably better to keep the preferences in a database on the server side, and just set a token that identifies the user (possibly the database primary key). Even then, remember that cookies can be altered! See Program: CookieCutter for a program to allow modification of the cookies stored on your hard drive.

Session Tracking

Problem

You want to keep track of one user across several servlet invocations within the same browser session.

Solution

Use an HttpSession object.

Discussion

HTTP was designed to be a stateless protocol: you would connect to a server, download a laboratory report, and that would be the end of it. Then people started getting clever, and began using it for interactive applications. For such purposes as a shopping cart in an online mall, and tracking answers during an online quiz or moves in an online game, the notion of an HTTP session has evolved to keep track of a particular browser. Sessions can be identified either by use of a cookie (see Cookies) or by a Session Identifier that is added to the URL. In either case the session ends when the user's browser program exits, but will otherwise stick around for a long time (there is probably a major denial-of-service attack hidden in here, so beware).

Using a session is fairly simple within the Servlet API. You request the HttpSession object from the HttpRequest that is passed into your service( ) or doGet( )/doPost( ) method. The session object behaves rather like a Hashtable except that the method names are putValue( ) and getValue( ). This allows you to store an arbitrary number of objects in the session and retrieve them later.

This program uses an HttpSession to keep track of a user's responses during a quiz about Java. There are some 20 categories; once you pick a category, you can answer all the multiple-choice questions in that topic. The first question looks like Figure 18-4.

Figure 18-4. Quiz servlet starting

 

After you've answered a few questions, it may look like Figure 18-5.

Figure 18-5. Quiz servlet several questions later

 

At the end of the quiz, you'll see the total number of questions that you answered correctly.

The Exam object (an object containing all the questions and answers, along with the number of correct answers) is loaded using an XamDataAccessor (the code for these two classes is not shown) and stored in a Progress object. Progress, an inner class inside the servlet, is a tiny data structure used to monitor your progress through one quiz. When you change topics, the Progress object is discarded and a new one created. The bulk of the code in Example 18-7 is taken up in checking and tracking your answers and in generating the HTML to show the results of your previous question (if any), as well as the question and possible answers for the current question.

Example 18-7: DoTestServlet.java

/** A Java Servlet to administer the tests over the Web.
 *	Saves exam and status session object to avoid having to reload it, 
 *  but also to keep the exam constant during a session!
 */
public class DoTestServlet extends HttpServlet {
 
	/** Where to find the exams du jour */
	protected final static String DIRECTORY =
		 "/home/ian/webs/daroadweb/quizzes-";
	/** The body color */
	protected final static String BGCOLOR = "white";
 
	/** An Inner Class to track the student's progress */
	class Progress {
		Exam exam;			// exam being taken
		boolean done;		// exam is finished.
		String category;	// name of exam, in effect
		int  curQuest;		// Question number working on, 0-origin
		int  correct;		// number gotten right on first try
	}
 
	/** Service is used to service each request. */
	public void service(HttpServletRequest request,
		HttpServletResponse response) throws IOException, ServletException {
 
		PrintWriter out = response.getWriter(  );
		HttpSession session;
		Progress progress;
		String reqCategory;
 
		// Set response type to HTML. Print the HTML header.
		response.setContentType("text/html");
		out.println("<HTML>");
 
		// Find the requested category
		reqCategory = request.getParameter("category");
		reqSubject  = request.getParameter("subject");	// unix or java
 
		// Request the user's session, creating it if new.
		session = request.getSession(true);
		if (session.isNew(  )) {
			// out.println("<B>NEW SESSION</B>");
			progress = new Progress(  );
			progress.category = reqCategory;
			session.putValue("progress", progress);
		} else {
			progress = (Progress) session.getValue("progress");
		}
 
		if (reqCategory != null && progress.category != null && 
			!reqCategory.equals(progress.category)) {
			
			// CHANGE OF CATEGORIES
			// out.println("<B>NEW PROGRESS CUZ " + 
			//	reqCategory + " != " +progress.category + "</B>");
			progress = new Progress(  );
			progress.category = reqCategory;
			session.putValue("progress", progress);
		}
		if (progress.exam == null) {
			XamDataAccessor ls = new XamDataAccessor(  );
			try {
				progress.exam = ls.load(DIRECTORY + subject + "/" +
					progress.category + ".xam");
			} catch (IOException ex) {
				eHandler(out, ex, "We had some problems loading that exam!");
			} catch (NullPointerException ex) {
				eHandler(out, ex, "Hmmm, that exam file seems to be corrupt!");
			}
		}
 
		// Now that we have "exam", use it to get Title. 
		out.print("<TITLE>Questions on ");
		out.print(progress.exam.getCourseTitle(  )); out.println("</TITLE>");
		out.print("<BODY BGCOLOR=\""); out.print(BGCOLOR); out.println("\">");
		out.print("<H1>");
		out.print(progress.exam.getCourseTitle(  ));
		out.println("</h2>");
 
		// Guard against reloading last page
		if (progress.done) {
			out.println("<HR><a href=\"/quizzes/\">Another Quiz?</a>");
			out.flush(  );
			return;
		}
 
		// Are we asking a question, or marking it?
		out.println("<P>");
		String answer =request.getParameter("answer");
		int theirAnswer = -1;
		if (answer != null) {
			// MARK IT.
			Q q = progress.exam.getQuestion(progress.curQuest);
			theirAnswer = Integer.parseInt(answer);
			if (theirAnswer == q.getAns(  )) {
 
				// WE HAVE A RIGHT ANSWER -- HURRAH!
				if (!q.tried) {
					out.println("<P><B>Right first try!</B>");
					progress.correct++;
				} else
					out.println("<P><B>Right. Knew you'd get it.</B>");
				q.tried = true;			// "Tried and true..."
 
				if (++progress.curQuest >= progress.exam.getNumQuestions(  )) {
					out.print("<P>END OF EXAM.");
					if (progress.correct == progress.curQuest) {
						out.println("<P><B>Awesome!</B> You got 100% right.");
					} else {
						out.print("You got ");
						out.print(progress.correct);
						out.print(" correct out of ");
						out.print(progress.curQuest);
						out.println(".");
					}
					out.println("<HR><a href=\"/quizzes/\">Another Quiz?</a>");
 
					// todo invalidate "progress" in case user retries 
					progress.done = true;
 
					// Return, so we don't try to print the next question!
					return;
 
				} else {
					out.print("Going on to next question");
					theirAnswer = -1;
				}
			} else {
				out.print("<B>Wrong answer</B>. Please try again.");
				q.tried = true;
			}
		}
 
		// Progress?
		out.print("<P>Question ");
		out.print(progress.curQuest+1);
		out.print(" of ");
		out.print(progress.exam.getNumQuestions(  ));
		out.print(". ");
		if (progress.curQuest >= 2) {
			out.print(progress.correct);
			out.print(" correct out of ");
			out.print(progress.curQuest);
			out.print(" tried so far (");
			double pct = 100.0 * progress.correct  / progress.curQuest;
			out.print((int) pct);
			out.println("%).");
		}
 
		// Now generate a form for the next (or same) question
		out.print("<FORM ACTION=/servlet/DoTestServlet METHOD=POST>");
		out.print("<INPUT TYPE=hidden NAME=category VALUE=");
			out.print(progress.category); out.println(">");
		out.println("<HR>");
 
		Q q = progress.exam.getQuestion(progress.curQuest);
		out.println(q.getQText(  ));
 
		for (int j=0; j<q.getNumAnswers(  ); j++) {
				out.print("<BR><INPUT TYPE=radio NAME=answer VALUE=\"");
				out.print(j);
				out.print("\"");
				if (j==theirAnswer)
					out.print(" CHECKED");
				out.print(">");
				out.print(q.getAnsText(j));
				out.println("</INPUT>");
			}
		out.println("<HR>");
 
		out.println("<INPUT TYPE=SUBMIT VALUE=\"Mark it!\"");
		out.println("</FORM>");
		out.println("</HTML>");
		out.close(  );
	}
 
	void eHandler(PrintWriter out, Exception ex, String msg) {
		out.println("<H1>Error!</h2>");
		out.print("<B>");
		out.print(msg);
		out.println("</B>");
		out.println("<pre>");
		ex.printStackTrace(out);
		out.flush(  );
		out.close(  );
	}
}

Debugging Tip for Servlets Using an HttpSession

Objects (such as Exam and Progress in the last example) are stored in the server for as long as your session lasts. If you change any such class so as to make it incompatible with the previous version, you will get mysterious "class cast errors" with the name of the class you changed. In these cases, you can simply close the browser (use File->Exit if using Netscape), and a new session object will be created. See also for another way to avoid these ClassCastException errors.

Generating PDF from a Servlet

Problem

You want to make a printer-friendly document using a format like Adobe PDF.

Solution

Use response.setContentType("application/pdf") and a third-party Java API that can generate PDF.

Discussion

Portable Document Format (PDF) is a file format created by Adobe Systems Inc. PDF gives you full control over how your document looks, much more so than HTML, XML, or even Java's printing routines (see Chapter 12). Adobe Acrobat is a set of programs for reading and writing PDF. Adobe itself does not publish a Java API for generating PDF from scratch, but it does publish the file format specification (Adobe Portable File Format Specification) and explicitly gives everyone permission to write software to generate and/or process PDF files. PDF is a good fit for processing by an object-oriented language like Java, as it's an object-based text format. As a result, there are several PDF APIs available for Java, both free and commercial:

Go to http://www.pdfzone.com and look in the Toolbox section for others.

Like Perl, SPDF has several names. Perl on a good day is the Practical Extraction and Report Language, but on a bad day it's the Purely Eclectic Rubbish Lister. A true geek has to admire that kind of whimsy. SPDF can be the Simple PDF API, but it can also be the Stupid PDF API. Mostly the latter, I fear. Example 18-8 is a simple servlet that takes the user's name from the HTML form in Figure 18-6 and generates a custom-made shopping coupon with the customer's info imprinted into it, and a unique serial number (for which a Date object provides a cheap stand-in here) to prevent multiple uses of the coupon. I suspect there is a real window of opportunity for such coupons in conjunction with online web sites and large discount retail stores. Unfortunately, I'm too busy writing this book to exploit this marvelous opportunity, so I'll just release the source code to SPDF. If you get rich from it, send me some of the money, OK?

Figure 18-6. PDF coupon servlet

 

I'm not showing the source code for SPDF in this book, as the present version is pretty crude. No font support. No graphics. Single-page documents. It may be released, however; check out http://www.darwinsys.com/freeware/spdf.html if you're interested. Think of SPDF as the Standby PDF API, which you can use while you decide which of the other PDF APIs you really want to use.

When you click on the "Get Yours" button, the servlet is run, generating a PDF file and sending it back to the browser. My Unix version of Netscape tries to save it to disk since I don't have Acrobat loaded; the filename MyCoupon.pdf is provided by the Content-disposition header that I added to the response object. See Figure 18-7.

Figure 18-7. PDF coupon servlet save dialog

 

My test MS-Windows system's copy of Netscape has Acrobat installed, and will run Acrobat as a Netscape Plug-in to display it; see Figure 18-8.

Figure 18-8. PDF coupon in Acrobat Reader

 

The basic SPDF API uses a PDF object to represent one PDF file. The PDF object has methods to set various things, to add pages to the object (and Page has methods to add text strings, moveTo operations, and others), and finally to write the file. Example 18-8 is the servlet that responds to the coupon request shown in Figure 18-6.

Example 18-8: PDFCouponServlet.java

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.darwinsys.spdf.*;
 
/** Simple PDF-based Coupon Printer Servlet
 */
public class PDFCouponServlet extends HttpServlet {
	public void doGet(HttpServletRequest request,
		HttpServletResponse response) throws IOException {
 
		PrintWriter out = response.getWriter(  );
		response.setContentType("application/pdf");
 
		// Tell browser to try to display inline, but if not,
		// to save under the given filename.
		response.setHeader("Content-disposition",
			"inline; filename=\"MyCoupon.pdf\"");
 
		PDF p = new PDF(out);
		Page p1 = new Page(p);
		p1.add(new MoveTo(p, 100, 600));
		p1.add(new Text(p, 
			"This coupon good for one free coffee in the student lounge."));
		String name = request.getParameter("name");
		if (name == null)
			name = "unknown user";
		p1.add(new Text(p,
			"Printed for the exclusive use of " + name));
		p1.add(new Text(p,
			"by Ian Darwin's PDFCoupon Servlet and DarwinSys SPDF software"));
		p1.add(new Text(p, "at " + new Date().toString(  )));
		p.add(p1);
		p.setAuthor("Ian F. Darwin");
 
		// Write the PDF file page
		p.writePDF(  );
	}
}

Most of the Java PDF APIs are roughly similar. Example 18-9 is the Terms servlet rewritten using PDFLib to generate a fancier PDF document with the same information as the HTML version.

Example 18-9: TermsServletPDF.java

import javax.servlet.*; 
import javax.servlet.http.*; 
import java.io.*; 
import java.util.*; 
import com.pdflib.*; 
  
/** Output the dictionary in fancy(?) PDF. 
 * This version uses "PDFlib", from PDFLib.GmbH (www.pdflib.com). 
 */ 
public class TermsServletPDF extends HttpServlet { 
	/** A printwriter for getting the response. */ 
	PrintWriter out; 
  
	/** Handle the get request. */ 
	public void doGet(HttpServletRequest request, 
		HttpServletResponse response) throws ServletException { 
  
		try { 
  
			out = new PrintWriter(response.getOutputStream(  )); 
  
			int font; 
			pdflib p = new pdflib(  ); 
  
			if (p.open_file("") == -1) { 
				warning(response, "Couldn't create in-memory PDF file", null); 
				return; 
			} 
  
			p.set_info("Title", "Dictionary Project"); 
			p.set_info("Author", "Ian F. Darwin, ian@darwinsys.com"); 
			p.set_info("Creator", "www.darwinsys.com/dictionary"); 
  
			p.begin_page(595, 842); 
  
			font = p.findfont("Helvetica", "host", 0); 
  
			p.setfont(font, 14); 
  
			// for now just use one term from the Iterator 
			Iterator e = new TermsAccessor("terms.txt").iterator(  ); 
			Term t = (Term)e.next(  ); 
			p.set_text_pos(50, 700); 
			p.show("Term: "); 
			p.continueText(t.term); 
			p.set_text_pos(70, 666); 
			p.show("Definition: "); 
			p.continueText(t.definition); 
			p.end_page(  ); 
  
			p.close(  ); 
  
			byte[] data = p.get_buffer(  ); 
  
			response.setContentType("application/pdf"); 
			response.getOutputStream(  ).write(data); 
		} catch (IOException e) { 
			warning(response, "pdflib IO error:", e); 
			return; 
		} catch (Exception e) { 
			warning(response, "pdflib error:", e); 
ode>
		} 
    } 

The end of the servlet in Example 18-10 demonstrates a way to provide a user-friendly wrapper around the occasional exception traceback. Method warning( ) can also be used to print a generic error message without a traceback by passing null as the exception argument.

Example 18-10: TermsServletPDF error handling

	/** Generic error handler. Must call before any use of "out" */ 
	protected void warning(HttpServletResponse response, 
		String error, Exception e) { 
		response.setContentType("text/html"); 
		try { 
			PrintWriter out = response.getWriter(  ); 
		} catch (IOException exc) { 
			// egad - we can't tell the user a thing! 
			System.err.println("EGAD! IO error " + exc + 
    " trying to tell user about " + error + " " + e); 
			return; 
		} 
	    out.println("<H1>Error</h2>"); 
		out.print("<P>Oh dear. You seem to have run across an error in "); 
		out.print("our dictionary formatter. We apologize for the inconvenience"); 
		out.print("<P>Error message is "); 
		out.println(error); 
  
		if (e != null) { 
			out.print("<P>Exception is: "); 
			out.println(e.toString(  )); 
			out.print("Traceback is: "); 
			out.print("<PRE>"); 
			e.printStackTrace(out); 
			out.print("</PRE>"); 
		} 
		System.out.print("DictionaryServletPDF: "); 
		System.out.println(error); 
		if (e != null) { 
			System.out.println(e.toString(  )); 
		} 
	} 
} 

HTML Meets Java: JSP

Problem

You have a web page that could use a jolt of Java.

Solution

Use the JavaServer Pages method of mixing HTML and Java.

Discussion

JavaServer Pages (JSP) shares some general syntax with Microsoft's ASP (Application Server Pages) and the free-software PHP (Programmable Hypertext Processor). They allow a mix of HTML and code; the code is executed on the server side, and the HTML plus the code results are printed as HTML. Because of Java's portability and JSP's full access to the entire Java API, JSP may be the most exciting web technology to come along since the online pizza demonstration. Example 18-11, for example, is the "five integers" code as a JSP.

Example 18-11: fiveints.jsp

<HTML>
<HEAD>
<TITLE>Your Personal Random Numbers</TITLE>
<H1>Your Personal Random Numbers</h2>
<P>Here are your personal random numbers,
carefully selected by a
<A HREF=\"http://java.sun.com\">Java</A> program.
<OL>
	<%
	java.util.Random r = new java.util.Random(  );
	for (int i=0; i<5; i++) {
		out.print("<LI>");
		out.println(r.nextInt(  ));
	}
	%>
</OL>
<HR></HR>
<A HREF=\"index.html\">Back to main Page</A>

Notice how much more compact this is than the servlet version in First Servlet: Generating an HTML Page. It should not surprise you to learn that JSPs are actually compiled into servlets, so most of what you know about servlets also applies to JSP. Let's look at another example that generates an HTML form and calls itself back when you activate the form, and also contains an HTML table to display the current month. Figure 18-9 and Example 18-12 show a JSP version of the CalendarPage program from .

Figure 18-9. CalendarPage.jsp in action

 

Example 18-12: CalendarPage.jsp

<%@page import="java.util.*,java.text.*" %>
 
<head>
	<title>Print a month page.</title>
	<meta name="version"
</head>
<body bgcolor="white">
<h1>Print a month page, for the Western calendar.</h2>
<P>Author Ian F. Darwin, ian@darwinsys.com
 
<%	// First get the month and year from the form.
	boolean yyok = false;	// -1 is a valid year, use boolean
	int yy = 0, mm = 0;
	String yyString = request.getParameter("year");
	if (yyString != null && yyString.length(  ) > 0) {
		try {
			yy = Integer.parseInt(yyString);
			yyok = true;
		} catch (NumberFormatException e) {
			out.println("Year " + yyString + " invalid");
		}
	}
	Calendar c = Calendar.getInstance(  );
	if (!yyok)
		yy = c.get(Calendar.YEAR);
 
	String mmString = request.getParameter("month");
	if (mmString == null) {
		mm = c.get(Calendar.MONTH);
	} else {
		for (int i=0; i<months.length; i++)
			if (months[i].equals(mmString)) {
				mm = i;
				break;
			}
	}
 %>
 
<form method=post action="CalendarPage.jsp">
	Month: <select name=month>
	<% for (int i=0; i<months.length; i++) {
		if (i==mm)
			out.print("<option selected>");
		else
			out.print("<option>");
		out.print(months[i]);
		out.println("</option>");
	}
	%>
	</select>
	Year (4-digit): 
		<input type="text" size="5" name="year"
			value="<%= yy %>"></input>
	<input type=submit value="Display">
</form>
<%!
	/** The names of the months */
	String[] months = {
		"January", "February", "March", "April",
		"May", "June", "July", "August",
		"September", "October", "November", "December"
	};
 
	/** The days in each month. */
	int dom[] = {
			31, 28, 31, 30,	/* jan feb mar apr */
			31, 30, 31, 31, /* may jun jul aug */
			30, 31, 30, 31	/* sep oct nov dec */
	};
%>
 
<%
	/** The number of days to leave blank at the start of this month */
	int leadGap = 0;
%>
<table border=1>
<tr><th colspan=7><%= months[mm] %>  <%= yy %></tr>
 
<%		GregorianCalendar calendar = new GregorianCalendar(yy, mm, 1); %>
 
<tr><td>Su<td>Mo<td>Tu<td>We<td>Th<td>Fr<td>Sa</tr>
 
<%
		// Compute how much to leave before the first.
		// getDay(  ) returns 0 for Sunday, which is just right.
		leadGap = calendar.get(Calendar.DAY_OF_WEEK)-1;
 
		int daysInMonth = dom[mm];
		if (calendar.isLeapYear(calendar.get(Calendar.YEAR)) && mm == 1)
			++daysInMonth;
 
		out.print("<tr>");
 
		// Blank out the labels before 1st day of month
		for (int i = 0; i < leadGap; i++) {
			out.print("<td>&nbsp;");
		}
 
		// Fill in numbers for the day of month.
		for (int i = 1; i <= daysInMonth; i++) {
 
			out.print("<td>");
			out.print(i);
			out.print("</td>");
 
			if ((leadGap + i) % 7 == 0) {		// wrap if end of line.
				out.println("</tr>");
				out.print("<tr>");
			}
		}
%>
</tr>
</table>

For another example, Example 18-13 shows the list of terms and definitions from First Servlet: Generating an HTML Page done as a JSP.

Example 18-13: terms.jsp

<HTML>
<HEAD>
	<TITLE>Ian Darwin's Computer Terms and Acronyms</TITLE>
	<%@ page import="java.io.*" %>
</HEAD>
<BODY BGCOLOR=white>
<H1>Ian Darwin's Computer Terms and Acronyms</h2>
<TABLE BORDER=2>
<TR><TH>Term<TH>Meaning</TR>
	<%
	// This part of the Servlet generates a list of lines like
	//	<TR> <TD>JSP <TD>Java Server Pages, a neat tool for ...
 
	// Filenames like this must NOT be read as parameters, since that
	// would allow any script kiddie to read any file on your system!!
	// In production code they would be read from a Properties file.
	String TERMSFILE = "/var/www/htdocs/hs/terms.txt";
 
	TermsAccessor tax = new TermsAccessor(TERMSFILE);
	Iterator it = tax.iterator(  );
	while (it.hasNext(  )) {
		Term t = it.next(  );
		out.print("<TR><TD>");
		out.print(t.term);
		out.print("</TD><TD>");
		out.print(t.definition);
		out.println("</TD></TR>");
	}
	%>
</TABLE>
<HR></HR>
<A HREF="/servlet/TermsServletPDF">Printer-friendly (Acrobat PDF) version</A>
<HR></HR>
<A HREF="mailto:compquest@darwinsys.com?subject=Question">Ask about another term</A>
<HR></HR>
<A HREF="index.html">Back to HS</A> <A HREF="../">Back to DarwinSys</A>
<HR></HR>

JSP Include/Forward

Problem

You want to write a "page-composite" JSP that includes other pages or passes control to another page.

Solution

Use <jsp:include> or <jsp:forward>.

Discussion

Suppose you have some common HTML code that you want to appear on every page, such as a navigator or header. You could copy it into each HTML and JSP file, but if it changed, you'd have to find all the files that used it and update each of them. It would be much easier to have one copy and include it everywhere you need it. Most webs servers feature such a mechanism already (e.g., server-side includes). However, using JSP's mechanism has some advantages, such as the ability to attach objects to a request, a topic I'll explore in JavaServer Pages Using a Servlet.

The basic mechanism is simply to have <jsp:include> with a PAGE attribute naming the page to be included, and end with </jsp:include>. For convenience, you can put the / at the end of the opening tag and omit the closing tag. Much of this syntax is taken from XML namespaces (see Chapter 21). The FLUSH attribute is also required, and it must have the value TRUE; this is to remind you that, once you do an include, the contents of the output are actually written. Therefore, you can no longer do anything that involves sending HTTP headers, such as changing content type or transferring control using an HTTP redirect request. So a full JSP include might look like this:

<H2>News of the day</H2>
<jsp:include page="./news.jsp" flush="true" />

The jsp:forward request is similar to a jsp:include, but you don't get control back afterwards. The attribute flush="true" is required on some JSP engines (including the release of Tomcat at the time this book went to press) to remind you that once you do this include, you have committed your output (prior to the include, the output might be all in a buffer). Therefore, as I just stated, you can no longer do anything that might generate headers, including setContentType(), sendRedirect( ), and so on.

An alternate include mechanism is <%@include file="filename"%>. This mechanism is a bit more efficient (the inclusion is done at the time the JSP is being compiled), but is limited to including text files (the file is read, rather than being processed as an HTTP URL; so if you include, say, a CGI script, the contents of your CGI script are revealed in the JSP output: not useful!). The <jsp:include> can include a URL of any type (HTML, servlet, JSP, CGI, even PHP or ASP).

JavaServer Pages Using a Servlet

Problem

It may seem that servlets and JSPs are mutually exclusive, but in fact they work well together. You can reduce the amount of Java coding in your JSP by passing control from a servlet to a JSP.

Solution

Use the Model-View-Controller paradigm, and implement it using ServletDispatcher().forward( ).

Discussion

Model-View-Controller is a paradigm for building programs that interact well with the user. The Model is an object or collection that represents your data; the View is what the user sees; and the Controller responds to user request. Think of a slide-show (presentation) program: you probably have a text view, a slide view, and a sorter view. Yet when you change the data in any view, all the other views are updated immediately. This is because MVC allows a single model to have multiple views attached to it. MVC provides the basis for most well-designed GUI applications.

Using the Model-View-Controller paradigm, a servlet can be the controller and the JSP can be the view. A servlet, for example, could receive the initial request from the form, interrogate a database based upon the query, construct a collection of objects matching the user's query, and forward it to a JSP to be displayed (the servlet can attach data it found to the request). A good example of this is a search page, which might have only a few (or even one) form parameters, so using a JSP with a bean to receive the results would be overkill. A better design is to have a servlet retrieve the form parameter and contact the search API or database. From there, it would retrieve a list of pages matching the query. It could package these into a Vector or ArrayList, attach this to the request, and forward it to a JSP for formatting.

The basic syntax of this is:

ArrayList searchResultsList = // get from the query
RequestDispatcher disp;
disp = getServletContext(  ).getRequestDispatcher("searchresults.jsp");
request.setAttribute("my.search.results", searchResultsList);
disp.forward(request, response);

This causes the servlet to pass the search results to the JSP. The JSP can retrieve the result set using this code:

ArrayList myList = (ArrayList) request.getAttribute("my.search.results");

You can then use a for loop to print the contents of the search request. Note that the URL in the getRequestDispatcher( ) call must be a call to the same web server, not to a server on a different port or machine.

Simplifying Your JSP with a JavaBean

Problem

You want to reduce the amount of Java coding in your JSP using a JavaBean component.

Solution

Use <jsp:useBean> with the name of your bean.

Discussion

JavaBeans is Java's component technology, analogous to COM components on MS-Windows. Recipes and contain a formula for packaging certain Java classes as JavaBeans. While JavaBeans were originally introduced as client-side, GUI-builder-friendly components, there is nothing in the JavaBeans specification that limits their use to the client-side or GUI. In fact, it's fairly common to use JavaBean components with a JSP. It's also easy and useful, so let's see how to do it.

At the bare minimum, a JavaBean is an object that has a public no-argument constructor and follows the set/get paradigm. This means that there is regularity in the get and set methods. Consider a class, each instance of which represents one user account on a login-based web site. For the name, for example, the methods:

public void setName(String name);
public String getName(  );

allow other classes full control over the "name" field in the class but with some degree of encapsulation; that is, the program doesn't have to know the actual name of the field (which might be name, or myName, or anything else suitable). Other programs can even get a list of your get/set methods using introspection. Example 18-14 is the full class file; as you can see, it is mostly concerned with these set and get methods.

Example 18-14: User.java, a class usable as a bean

/** Represents one logged in user 
 */
public class User {
 
	protected String name;
	protected String passwd;
	protected String fullName;
	protected String email;
	protected String city;
	protected String prov;
	protected String country;
 
	protected boolean editPrivs = false;
	protected boolean adminPrivs = false;
 
	/** Construct a user with no data -- must be a no-argument
	 * constructor for use in jsp:useBean.
	 */
	public User(  ) {
	}
 
	/** Construct a user with just the name */
	public User(String n) {
		name = n;
	}
 
	/** Return the nickname. */
	public String getName(  ) {

	}
 
	public void setName(String nick) {
		name = nick;
	}
 
	// The password is not public - no getPassword.
 
	/** Validate a given password against the user's. */
	public boolean checkPassword(String userInput) {
		return passwd.equals(userInput);
	}
 
	/** Set password */
	public void setPassword(String passwd) {
		this.passwd = passwd;
	}
 
	/** Get email */
	public String getEmail(  ) {
		return email;
	}
 
	/** Set email */
	public void setEmail(String email) {
		this.email = email;
	}
 
	// MANY SIMILAR STRING-BASED SET/GET METHODS OMITTED
 
	/** Get adminPrivs */
	public boolean isAdminPrivileged(  ) {
		return adminPrivs;
	}
 
	/** Set adminPrivs */
	public void setAdminPrivileged(boolean adminPrivs) {
		this.adminPrivs = adminPrivs;
	}
 
	/** Return a String representation. */
	public String toString(  ) {
		return new StringBuffer("User[").append(name)
			.append(',').append(fullName).append(']').toString(  );
	}
 
	/** Check if all required fields have been set */
	public boolean isComplete(  ) {
		if (name == null || name.length(  )==0 ||
		    email == null || email.length(  )==0 ||
		    fullName == null || fullName.length(  )==0 )
			return false;
		return true;
	}
}

The only methods that do anything other than set/get are the normal toString( ) and isComplete( ) (the latter returns true if all required fields have been set in the bean). If you guessed that this has something to do with validating required fields in an HTML form, give yourself a gold star.

We can use this bean in a JSP-based web page just by saying:

<jsp:useBean id="myUserBean" scope="request" class="User">

This creates an instance of the class called myUserBean. However, at present it is blank; no fields have been set. To fill in the fields, we can either refer to the bean directly within scriptlets, or, more conveniently, we can use <jsp:setProperty> to pass a value from the HTML form directly into the bean! This can save us a great deal of coding.

Further, if all the names match up, such as an HTML parameter "name" in the form and a setName(String) method in the bean, the entire contents of the HTML form can be passed into a bean using property="*"!

<jsp:setProperty name="myUserBean" property="*"/>
</jsp:useBean>

Now that the bean has been populated, we can check that it is complete by calling its isComplete( ) method. If it's complete, we print a response, but if not, we direct the user to go back and fill out all the required fields:

<% // Now see if they already filled in the form or not...
	if (!myUserBean.isComplete(  )) {
 %>
		<TITLE>Welcome New User - Please fill in this form.</TITLE>
		<BODY BGCOLOR=White>
		<H1>Welcome New User - Please fill in this form.</h2>
			<FORM ACTION="name_of_this_page.jsp" METHOD=post>
			// Here we would output the form again, for them to try again.			
			</FORM>
		<%		
		} else {
		String nick = newUserBean.getName(  );
		String fullname = newUserBean.getFullName(  );
// etc...
		// Give the user a welcome
		out.println("Welcome " + fullname);

You'll see the full version of this JSP in Program: JabaDot Web News Portal.

See Also

You can extract even more Java out of the JSP, making it look almost like pure HTML, by using Java custom tags. Custom tags (also called custom actions) are a new mechanism for reducing the amount of Java code that must be maintained in a JSP. They have the further advantage of looking syntactically just like elements brought in from an XML namespace, making them more palatable both to HTML editor software and to HTML editor personware. Their disadvantage is that to write them requires a greater investment of time than, say, servlets or JSP. However, you don't have to write them to use them; there are several good libraries of custom tags available, one from Tomcat (http://jakarta.apache.org) and another from JRun (http://jrun.allaire.com). Sun is also working on a standard for a generic tag library. JSP tags are compiled classes, like applets or servlets, so any tag library from any vendor can be used with any conforming JSP engine. There are a couple of JSP custom tags in the source directory for the JabaDot program in Program: JabaDot Web News Portal.

JSP Syntax Summary

Problem

You can't remember all this post-HTML syntax.

Solution

Use the Table.

Discussion

Table 18-1 summarizes the syntax of JavaServer Pages. As the title implies, it contains only the basics; a more complete syntax can be downloaded from http://java.sun.com/products/jsp/.

Table 18-1: Basic JSP Syntax

Item

Syntax

Example

Scriptlet

<% code;%>
<% mountain.setHeight(1000); %>

Expression (to print)

<%= expr %>
<%= mountain.getHeight(  ) %>

Declaration

<%! decls; %>
<%! int height = 0; %>

Include

<jsp:include page="URL" flush=true />
<jsp:include page="./mountain-list.html" flush=true />

Forward

<jsp:forward page="url"/>
<jsp:forward page="./last-resort.html"/>

Use bean

<jsp:useBean .../>
<jsp:useBean class="x.ClimbBean" id="myClimbBean" scope="page"/>

Set property

<jsp:setProperty ... />
<jsp:setProperty name="myClimbBean" property="*" />

Page directive

<%@ page ... %>
<%@ page import="java.io.*" errorPage="catcher.jsp" %>

Comment

<!-- comment -->
<%!-- This comment appears in HTML -->

Hidden comment

<%-- comment --%>
<%-- This comment is local to JSP --%>

Program: CookieCutter

CookieCutter is a little program I wrote that allows you to display, modify, and even delete cookies. Since the banner-ad-tracking firm DoubleClick probably keeps a lot of information on your browsing habits, you want to befuddle them. After all, they are using a tiny bit of storage on your hard disk to rack up per-click profits, giving you nothing in return (directly, at least; obviously, ad sponsorship keeps some web sites on the air). In Figure 18-10, I am editing the cookie to, umm, "update" the personal identity cookie to an invalid number (a lot of 9's, and too many digits). A few lines above that, you can see the prefs.bgcolor cookie that I set to "green."

Figure 18-10. The CookieCutter display

 

I won't show the CookieCutter source code here as it doesn't really relate to web techniques (it's a client-side application), but it's included in the source archive for the book. CookieCutter also assumes your cookies are stored in the Netscape format; for the Microsoft Explorer format, you'll have to change the file-reading and file-writing code.

Program: JabaDot Web News Portal

Here is perhaps the most ambitious program developed in this book. It's the beginnings of a complete "news portal" web site, similar to http://www.slashdot.org, http://www.deadly.org, or http://daily.daemonnews.org. However (and as you should expect!), the entire site is written in Java. Or perhaps should I say "written in or by Java," since the JSP mechanism--which is written entirely in Java--turns the JSP pages into Java servlets that get run on this site. The web site is shown in Figure 18-11.

Figure 18-11. JabaDot welcome page

 

Like most portal sites, JabaDot allows some services (such as the current news items and of course the ubiquitous banner ads) without logging in, but requires a login for others. In this figure I am logged in as myself, so I have a list of all available services. The page that supports this view is index.jsp (Example 18-15), which contains a hodgepodge of HTML and Java code.

Example 18-15: index.jsp

<%@page errorPage="oops.jsp"%>
<HTML>
<TITLE>JabaDot - Java News For Ever(yone)</TITLE>
<P ALIGN=CENTER><jsp:include page="/servlet/AdRotator" flush="true"/></P>
<BODY BGCOLOR="#f0f0f0">
<%	HttpSession sess = request.getSession(true);
	User user = (User)sess.getValue("jabadot.login");
 %>
<TABLE>
<TD WIDTH=75% ALIGN="TOP">
	<!-- Most of page, at left -->
	<IMG SRC="logo.gif" ALIGN="LEFT"></IMG>
	<BR CLEAR="ALL">
	<jsp:include page="./news.jsp" flush="true"/>
</TD>
<TD WIDTH=25% BGCOLOR="#00cc00" ALIGN="TOP">
	<!-- Rest of page, at right -->
	<FONT COLOR=WHITE NAME="Helvetica,Arial">
	<% if (user == null) { %>
	<FORM ACTION=login.jsp METHOD=POST>
		Name: <INPUT TYPE=TEXT SIZE=8 NAME=nick>
		<br>
		Password: <INPUT TYPE=PASSWORD SIZE=8 NAME=pass>
		<br>
		<INPUT TYPE=SUBMIT VALUE="Login" ALIGN=CENTER>
	</FORM>
	<jsp:include page="public_services.html" flush="true"/>
	<% } else { %>
	Logged in as <%= user.getName(  ) %>
	<jsp:include page="./logged_in_services.html" flush="true"/>
	<li><a href="logout.jsp">Log out</a>
	<% } %>
	</FONT>
</TD>
</TABLE>
</BODY>
</HTML>

As you can see, this code actually starts with a "page" tag (%@page) to specify an error handling page (the error page just prints the stack trace neatly, along with an apology). Then the output of the AdRotator servlet is included; the program just randomly selects a banner advertisement and outputs it as an HTML anchor around an IMG tag. Then I get the HttpSession object and, from that, the current User object, which is null if there is not a currently logged-in user. The User class was discussed when we talked about JavaBeans in JSPs (see Simplifying Your JSP with a JavaBean); it's used as an ordinary object in most of these JSPs, but as a bean in the newuser.jsp page, when the user has entered all the fields on the "Create an Account" page.

Then there's an HTML table, which basically divides the rest of the page into two large columns. The left side of the page is fairly wide and contains the news stories, their headlines, the submitter's name, the time, optionally a URL, and the text of the news article. A future version will allow the user to send comments on the stories; as Slashdot has demonstrated, this is an important part of "community building," part of the art of keeping people coming back to your web site so you can show them more banner ads. :-)

The navigator part is displayed differently depending on whether you are logged in or not. If you're not, it begins with a login form, then lists the few services that are publicly available as HTML anchors, with the unavailable services in italic text. If you are logged in, there is a full list of links and a logout page at the end.

Before you log in, you must create an account. The trick here is that we require the user to give a valid email address, which we'll use for various authentication purposes and, just possibly, to send them a monthly newsletter by email. To ensure that the user gives a valid email address, we email to them the URL from which they must download the password. Figure 18-12 shows the entry page for this. This form is processed by newuser.jsp.

Figure 18-12. newuser.jsp in action

 

Example 18-16 is the source for newuser.jsp. As mentioned previously, this gets a User object as a JavaBean (see Simplifying Your JSP with a JavaBean).

Example 18-16: newuser.jsp

<%@page errorPage="oops.jsp" import="jabadot.*, java.io.*" %>
<%! java.util.Random r = new java.util.Random(  ); %>
<jsp:useBean id="newUserBean" scope="request" class="jabadot.User">
	<jsp:setProperty name="newUserBean" property="*"/>
</jsp:useBean>
<jsp:useBean id="mailBean" scope="request" class="jabadot.Mailer"/>
<html>
<%@include file="header.html" %>
<%
	User user = (User)session.getAttribute("jabadot.login");
	if (user != null) {
 %>
<TITLE>You're already logged on!</TITLE>
<H1>You are logged on!</h2>
<P>Please <A href="logout.jsp">log out</A> before
trying to create a new account.  Thank you!
/code>
	}
 %>
<% // Now see if they already filled in the form or not...
	if (!newUserBean.isComplete(  )) {
		// out.println("<!-- in new -->");
 %>
		<TITLE>Welcome New User - Please fill in this form.</TITLE>
		<BODY BGCOLOR=White>
		<H1>Welcome New User - Please fill in this form.</h2>
		<TABLE>
		<TD>
		<FORM ACTION="newuser.jsp" METHOD=post>
			<table><!-- inner table so fields line up -->
			<tr><td>Nickname:</td>
				<td><INPUT TYPE=TEXT SIZE=10 NAME="name"> (required)</td></tr>
			<tr><td>Full name:</td>
				<td><INPUT TYPE=TEXT SIZE=10 NAME="fullName"> (required)</td></tr>
			<tr><td>E-mail:</td>
				<td><INPUT TYPE=TEXT SIZE=10 NAME="email"> (required)</td></tr>
			<tr><td>City:</td>
				<td><INPUT TYPE=TEXT SIZE=10 NAME="city"></td></tr>
			<tr><td>Province/State:</td>
				<td><INPUT TYPE=TEXT SIZE=10 NAME="prov"></td></tr>
			<tr><td>Country</td>
				<td><select name="location">
				<jsp:include page="country_select.html" flush="true"/>
				</select>
				</td></tr>
			<tr><td colspan=2 align="center">
				<INPUT TYPE=SUBMIT VALUE="Create My JabaDot!"></tr>
			</table>
		</FORM>
		<TD>
		<P>If you've done one of these before, you may be wondering where
		the "Password" field is. It's not there. Believing somewhat in
		security, we'll make up a fairly good password for you.
		We won't email it to you, but will email to you the location
		from which you can learn it, so watch your email closely
		after you complete the form. Thank you!
		</TABLE>
<%		return;
		}
 
		// out.println("<!-- in get -->");
		String nick = newUserBean.getName(  );
		if (UserDB.getInstance(  ).getUser(nick) != null) {
 %>
			<P>It seems that that user name is already in use!
			Please go back and pick another name.
			<% return;
			} %>
<%
		String fullname = newUserBean.getFullName(  );
		String email = newUserBean.getEmail(  );
 %>
		<!-- Give the user a welcome -->
		Welcome <%= fullname %>.
		We will mail you (at <%= email %>) with a URL
		from which you can download your initial password.
 
		<jsp:setProperty name="newUserBean" 
			property="editPrivileged" value="false"/>
		<jsp:setProperty name="newUserBean" 
			property="adminPrivileged" value="false"/>
<%
		// Generate initial random password and store it in the User
		String newPass = Password.getNext().toString(  );
		newUserBean.setPassword(newPass);
 
		// NOW add the user to the persistent database.
		UserDB.getInstance(  ).addUser(newUserBean);
 
		// Create a temporary HTML file containing the full name
		// and the new password, and mail the URL for it to the user.
		// This will confirm that the user gave us a working email.
		// NEVER show the nickname and the password together!
		String tempDir = JDConstants.getProperty("jabadot.tmp_links_dir");
		File tempLink = File.createTempFile(
			r.nextInt(  )+"$PW", ".html", new File(tempDir));
		PrintWriter pw = new PrintWriter(new FileWriter(tempLink));
		pw.print("<HTML><BODY>");
		pw.print("Greetings ");
		pw.print(newUserBean.getFullName(  ));
		pw.print(". Your new password for accessing JabaDot is <B>");
		pw.print(newPass);
		pw.print("</B>. Please remember this, or better yet, ");
		pw.print("<a href=\"/jabadot/index.jsp\">");
		pw.print("login</a> now!");
		pw.print("You may want to visit \"My Jabadot\"");
		pw.print("and change this password after you log in.");
		pw.println("</HTML>");
		pw.close(  );
 
		// Now we have to mail the URL to the user.
		mailBean.setFrom(JDConstants.getProperty("jabadot.mail_from"));
		mailBean.setSubject("Welcome to JabaDot!");
		mailBean.addTo(email);
		mailBean.setServer(JDConstants.getProperty("jabadot.mail.server.smtp"));
 
		// Get our URL, strip off "newuser.jsp", append "/tmp/"+tmpname
		StringBuffer getPW_URL = HttpUtils.getRequestURL(request);
		int end = getPW_URL.length(  );
		int start = end - "newuser.jsp".length(  );
		getPW_URL.delete(start,end).append("tmp/").append(tempLink.getName(  ));
		mailBean.setBody("To receive your JabaDot password,\n" +
			"please visit the URL " + getPW_URL);
 
		// Now send the mail.
		mailBean.doSend(  );
 
		// AVOID the temptation to sess.setAttribute(  ) here, since
		// the user has not yet verified their password!
 %>

Once you create an account and read the email containing the link for the password, you can return to the site and log in normally. The login form is handled by login.jsp, shown in Example 18-17.

Example 18-17: login.jsp

<%@page errorPage="oops.jsp" import="jabadot.*" %>
<HTML>
<%
	User user = (User)session.getAttribute("jabadot.login");
	if (user != null) {
		session.setAttribute("jabadot.message", 
			"<H1>You're already logged on!</h2>"+
			"(as user " + user.getName(  ) + "). Please" +
			"<a href=\"logout.jsp\">" +
			"logout</a> if you wish to log in as a different user.");
		response.sendRedirect("/jabadot/");
	}
	String nick = request.getParameter("nick");
	String pass = request.getParameter("pass");
	if (nick == null || nick.length(  ) == 0 ||
		pass == null || pass.length(  ) == 0) {
 %>
		<!-- Must use jsp include not @ include here since
		 ** tomcat complains if it sees the @ include twice.
		 ** Can't just include it once at beginning, since we
		 ** do a redirect at the end of this jsp if successful.
		 -->
		<jsp:include page="./header.html" flush="true" />
		<TITLE>Missing name/password!</TITLE>
		<BODY BGCOLOR=WHITE>
		<H1>Missing name/password!</h2>
		<P>Please enter both a name and a password in the form.
<%		return;
	}
	
	User u = UserDB.getInstance(  ).getUser(nick); 
	if (u == null || !u.checkPassword(pass)) {	
%>
		<jsp:include page="./header.html" flush="true" />
		<TITLE>Invalid name/password</TITLE>
		<BODY BGCOLOR=WHITE>
		<H1>Invalid name/password</h2>
		<P>We could not find that name and password combination.
		Please try again if you have an account, else go create one.
<%		return;
	}
 
	// Hallelujeah! WE FINALLY GOT THIS ONE LOGGED IN.
 
	session.setAttribute("jabadot.login", u); // login flag
	//session.setAttribute("jabadot.ads", new AdServlet(  ));
	session.setAttribute("jabadot.message", 
		"<H1>Welcome back, " + u.getFullName(  ) + "</h2>");
 
	// For non-admin logins, provide a 3-hour timeout
	if (!u.isAdminPrivileged(  )) {
		session.setMaxInactiveInterval(3600*3);
	}
 
	// Send Redirect back to top, so user sees just this in URL textfield.
	response.sendRedirect("/jabadot/");
%>

After ensuring that you're not already logged in, this page gets the username and password from the HTML form, checks that both are present, looks up the name in the password database and, if found, validates the password. If either the name or the password is wrong, I report a generic error (this is deliberate security policy to avoid giving malicious users any more information than they already have[2]). If you log in, I put the User object representing you into the HttpSession, set a little greeting, and pass control to the main page via a redirect.

Whether logged in or not, you can send a general comment to the system's administrators via the submit.jsp page. This simply generates the HTML form shown in Figure 18-13.

Figure 18-13. Input form for comments.jsp

 

This form is processed by comments.jsp, shown in Example 18-18, when you press the "Submit Article" button.

Example 18-18: comments.jsp

<%@page errorPage="oops.jsp" %>
<%@page import="jabadot.*, javax.mail.*" %>
<jsp:useBean id="mailBean" scope="request" class="jabadot.Mailer">
	<jsp:setProperty name="mailBean" property="*"/>
</jsp:useBean>
<%
	User user = (User)session.getAttribute("jabadot.login");
 
	mailBean.setFrom(JDConstants.getProperty("jabadot.mail_from"));
	mailBean.setSubject("Comment from jabadot site");
	mailBean.addTo(JDConstants.getProperty("jabadot.mail_comments_to"));
	mailBean.setServer(JDConstants.getProperty("jabadot.mail.server.smtp"));
 
	String message = request.getParameter("message");
	if (message != null)
		mailBean.setBody(message);
 
	// See if they already filled in the form or not...
	if (mailBean.isComplete(  )) {
		try {
			mailBean.doSend(  );
 
			// Now attach a thank you note and send them to the index page
			session.setAttribute("jabadot.message", 
				"<H1>Mail sent</h2><p>Your commentary has been sent to our chief" + 
				" pickle.<b>Thank you.</b></p>");
			response.sendRedirect("/jabadot/");
			// No return from sendRedirect
		} catch (MessagingException ex) {
			throw new IllegalArgumentException(ex.toString(  ));
		}
	}
	// ELSE - mailbean is NOT complete, put up form.
 %>
	<%@include file="header.html" %>
	<P ALIGN=CENTER><jsp:include page="/servlet/AdServlet" flush="true"/></P>
	<TITLE>Send Comments</TITLE>
	<BODY BGCOLOR="white">
 
	<DIV ALIGN="CENTER">
	<BLOCKQUOTE>
 
	<FORM METHOD="POST" ACTION="comments.jsp">
 
	<P>Please send us your feedback on this site.
<%	if (user != null) { %>
		<P>Since you are logged in, you can use
		<A href="mailto:<%=JDConstants.getProperty(
			// This subject= without quotes WORKS but doesn't feel good :-)
			"jabadot.mail_comments_to")%>?subject=Comments about JabaDot">
		this <I>mailto</I> link</A>.</P>
<%	} %>
	<P>Name: <INPUT TYPE="TEXT" NAME="name" SIZE="15" 
	VALUE="<%= user==null?"":user.getFullName(  )%>">&nbsp; 
		Email:<INPUT TYPE="TEXT" NAME="from" SIZE="15" 
		VALUE="<%= user==null?"":user.getEmail(  )%>"></P>
	<P>City:&nbsp;&nbsp;&nbsp;&nbsp; <INPUT TYPE="TEXT" NAME="City" SIZE="15"
	VALUE="<%= user==null?"":user.getCity(  ) %>">&nbsp;
		State: <INPUT TYPE="text" NAME="State" SIZE="15" 
		VALUE="<%= user==null?"":user.getProv(  ) %>"></P>
	<P>Country:&nbsp;<INPUT TYPE="text" NAME="country" size="15" 
	VALUE="<%= user==null?"": user.getCountry(  ) %>"></P>
	<TEXTAREA NAME="message" ROWS="8" COLS="50" WRAP="physical">
<%= message %>
	</TEXTAREA>
	<P><INPUT TYPE="submit" VALUE="Send Comments"></P>
	</FORM>
 
	</BLOCKQUOTE>
	</DIV>
	</BODY>

This page starts off like the first one. I particularly like the code that displays a mailto: URL only if the user is logged in. SPAM perpetrators (see Chapter 19) are notorious for automatically loading entire web sites just to look for mailto: URLs. This is a good way to fence these rodents out, since they normally won't go to the trouble of signing up for a service and providing a real (working) email address just to get one mailto: URL from your site. There are easier ways to find mailto:'s on other sites; hopefully the SPAM perps will go there. For extra fun, make up a unique email address for each user to send mail to, so if you do get spammed, you have an idea who might have done it.

See Also

There is more to servlets and JSPs than I've got room to tell you about. These technologies offer an interesting partitioning of code and functionality. The JSP can be concerned primarily with getting the input and displaying the results. A JSP can forward to a servlet, or can include or jump to any other local web resource, like an audio file. Servlets and JSP are primary parts of the Java Enterprise Edition, and are becoming very important web server technologies.

For an opposing view (that JSPs are the wrong solution to the wrong problem), surf on over to http://www.servlets.com. For more information on servlets and JSPs, refer to the O'Reilly books Java Servlet Programming and JavaServer Pages.


1. It has been said of Sun that when they copy something, they both improve upon it and give credit where credit's due in the name. Consider Microsoft ODBC and Java JDBC; Microsoft ASP and Java JSP. The same cannot be said of most large companies.

2. This ancient advice comes from the early days of Unix; you'd be surprised how many sites still don't get it.

Back to: Java Cookbook


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com