This is a toy application that has, we believe, the potential to get grown-ups interested. Suppose you have a group of people working together, and you want to build a spirit of community among them. One way to do that is to make sure that if today is someone's birthday then you let everyone know and send congratulations via email. So, you put together a table in your database that has everyone's name, birthday and email address. Every morning a friendly demon submits a form, and if there is a birthday on that day, everybody gets an email. (As you well know, it's very easy to overdo these things, and get everybody extremely annoyed at you for too much friendly email.)
The entry point to the application is birthdays.htm. It sets up the initial parameters, two of them in hidden input elements: bbcmd is set to login and dbOperation to BIRTHDAYLIST. Visible inputs can be used to customize the application in much the same way an initialization file is used in the QShell application we developed in the first chapters of the book. This is what birthdays.htm looks like:
|
|
After the initial form gets submitted, control is passed to the main JSP page, birthdays.jsp. It creates the BirthdayBean and sets its properties. One of those properties is bbcmd. That property determines what the bean will do. If the command is sendMail, the bean will send email, using the parameters set by the user in the form on the client. If the command is dbOperation, the bean will create a DBHandler object (unless it has already been created) and run the dbOperation. (Do you still remember DBHandler? That's the guy that handles all the interactions with the database using internal Query objects. It's still with us, after all these chapters, and is as useable out of a bean as it is out of a servlet.)
Suppose the user has submitted a database query asking for December 8 birthdays. The screen shot below shows the results returned by the database, and the email parameters set by the user:
|
|
If the user now selects and submits the sendMail command, the following email will be sent:
X-POP3-Rcpt: sasha@cs
Return-Path: <tomm@tommyers.colgate.edu>
Date: Thu, 28 Oct 99 09:05:43 -0400
From: tomm@tommyers.colgate.edu
To: sasha@cs.colgate.edu
Subject: December 8 Birthdays
Eli Whitney 1765-12-08 00:00:00 tom.myers@worldnet.att.net
Jan Sibelius 1865-12-08 00:00:00 tom.myers@worldnet.att.net
The bean also sets its jspcmd property, and the rest of the application operates in a circle described in the preceding section. The jspcmd property determines the page returned to the user. If it is not an error or logout, then the page returned to the user contains a form; the user can specify the command to perform and submit the form, and so on. Our task now is to understand how it all works together. We will start with the client, move on to the JSP page, visit the bean, and follow its database and email connections. As we meander through the application, the following bean-centric diagram, showing the bean's inputs and outputs, may be useful in keeping track of where we are in the overall scheme of things:
|
|
The entry point to the system is an HTML file that consists entirely of a single form.
<html><head><title>birthday.htm</title></head><body bgcolor="lightblue">
<h1>The Birthday Lister</h1>
<form name=theForm type=POST action="birthday.jsp">
Within that form, there are the following divisions:
q two hidden inputs for bbcmd and dbOperation
q two inputs for the database: username and password
q a simple piece of JavaScript code to generate an input for Date with the value of the current date
q two inputs for sending mail: return address and SMTP host
q a textarea element with initialization data, in the same format as a QShell initialization file
The first of these divisions have already been mentioned; the second is completely trivial:
<input type=hidden name="bbcmd" value="login">
<input type=hidden name="dbOperation"
value="BIRTHDAYLIST">
DB username, password:
<input type=text name="dbUser" value="name"><br>
<input type=password name="dbPwd" value="pwd">
The JavaScript code is a sequence of document.write() statements, to make it cross-browser. It creates a Date object that holds the current date and converts it to String in the yyyy-mm-dd format that Java likes:
<script>
function dw(x){document.write(x);}
var today=new Date();
var todayStr=today.getFullYear()+"-"+
(1+today.getMonth())+"-"+
today.getDate();
dw(todayStr);
dw("<br>Compare birthdays with: ");
dw("<input type=text name=when value=\"");
dw(todayStr);
dw("\">");
</script>
The inputs having to do with e-mail are, in a sense, self-explanatory; the way they work in JavaMail code will be discussed later. These two inputs create a great opportunity for mischief: imagine an immature and mean-spirited hacker writing a little program that submits the form 100 times using the e-mail of somebody they hate and 20 different SMTP servers. There are, and probably there will always be people like that among us, so in any kind of serious application, a JSP page that receives such a form should be password-protected.
Finally, the initial content of the textarea element contains initialization data in the familiar format. One new entry there is dateFormat so users can specify their own input date format, if necessary. (The output format will not be affected: it will always be JDBC "escape format", as defined in java.text.SimpleTextFormat.)
<br>EMail Return Address: <input type=text name=retaddr value="">
<br>SMTP host: <input type=text name=smtphost value="cs.colgate.edu">
<br>
<textarea name="initdefs" rows="5" cols="40">
dbDriver
sun.jdbc.odbc.JdbcOdbcDriver
dbName
jdbc:odbc:BIRTHDAYS
dateFormat
yyyy-MM-dd
dbQueries
LOOKUPALL,BIRTHDAYLIST,LOOKUPFROMWHEN,LOOKUPFROMWHO,ADD,DELETE,CREATE
...
CREATE
CREATE TABLE Birthdays(Who VARCHAR, When DATE, Addr VARCHAR);
</textarea>
<input TYPE=submit name="submit" value="submit">
</form></body></html>
The main thing to remember about all these input elements is that our BirthdayBean has properties with identical names, and at some point the main JSP page will say:
<jsp:setProperty name="bbean" property="*" />
As we discussed above, if you want all request parameters copied to bean properties of the same name, you code it as property="*".
You have already seen the beginning of birthday.jsp:
<html>
<head>
<title>Birthday JSP/Bean/DBHandler/Mail</title>
</head>
<body>
<%@ page import="java.util.*, MyNa.utils.*" errorPage="errorpage.jsp" %>
<jsp:useBean id="bbean" scope="session" class="birthday.BirthdayBean" />
<jsp:setProperty name="bbean" property="*" />
Since birthday.jsp is our main servlet for the application, we unload all processing to the bean, in order to keep the structure of the servlet clear. In particular:
q we process the request in the bean, by calling its public processRequest() method
q we have the bean process the current command, on the basis of the docmd property set in the user input
q we produce output based on the value of the jspcmd property set by the bean. If the required output is more than a couple of lines long, we put it into an include file
Since much of birthday.jsp code has already been discussed, we show the rest of it in one piece:
<% bbean.processRequest(request); %>
<%
bbean.doCommand(); // usually, this is all we need after setting properties
String msgtext=""; // accumulate messages here, if needed.
String
select=bbean.getJspcmd();
if("logout".equals(select))
{
%>
Goodbye, come again soon.
<%
}
else if("error".equals(select))
{
%>
Something went wrong: <%= bbean.getErrorString() %>
Click your back button and try again, or tell us.
<%
}
else if("change".equals(select))
{
%>
We changed <%= bbean.getNumberAffected() %> rows.
<%@ include file="continue.jsp" %>
<%
}
else if("msgsent".equals(select))
{
%>
Congratulations, you sent a message.
<%@ include file="continue.jsp" %>
<%
}
else if("list".equals(select)){ %>
<%@ include file="listall.jsp" %>
<%
}
else if("birthdaylist".equals(select))
{
%>
<%@ include file="birthdaylist.jsp" %>
<%
}
else
{
%>
Unimplemented command [<%= select %>]; how did this happen?
<%
}
%>
</body>
</html>
The possible values of select (i.e. of jspcmd) are: logout, error, change, msgsent, listall and birthdaylist. The difference between the last two is that listall lists the entire contents of the birthdays table in the database, while birthdaylist lists only the records matching the specified date. The corresponding output files both include the file continue.jsp. Let's take a look at the output.
Apart from error.jsp, there are three output files, birthdaylist.jsp, listall.jsp and continue.jsp. The first two are very similar: they show a typical JSP loop to output a table. Here is birthdaylist.jsp:
this is the file birthdaylist.jsp,
<%
String[][]rows=bbean.getResultTable();
if(rows.length==0){
%>
Sorry, nobody has a birthday coinciding with <%= bbean.getWhen() %>.
<%
}
else
{
%>
Birthday Announcements!<br>
<table border="1">
<%
for(int i=0;i<rows.length;i++)
{
%>
<tr>
<%
String[] row=rows[i];
for(int j=0;j<row.length;j++)
{
msgtext+=row[j]+"\t";
%>
<td><%= row[j] %></td>
<%
}
%>
</tr>
<% msgtext+="\n"; } %>
</table>
<%
}
%>
<%@ include file="continue.jsp" %>
This file outputs a form whose ACTION is birthday.jsp, to complete the circle. It's almost completely HTML; the only JSP element is an expression, <%= msgtext %>, that we place in the textarea as a possibly helpful hint to the user. Otherwise, it's just an HTML form with two SELECT elements and several text inputs; the SELECT elements are for bbcmd and dbOperation. We precede the form with a simple JavaScript function to validate its input values before sending them to the server.
In outline, then, continue.jsp consists of two large sections:
<script>
<!-- two Javascript functions, including onsub() -->
</script>
<form name="theForm" action="birthday.jsp"
method="post" onsubmit="return onsub()">
<!-- the form as described -->
</form>
We present the two sections in order.
The onsub() method checks two conditions: that the toaddr and subject inputs are filled, and that database queries have the right number of parameters. For each condition, there is a supporting function that checks it. In order to check the right number of arguments, we set up a JavaScript object argCount, which associates a query name with the number of arguments for that query. (As you may know, Javascript objects can be used as associative arrays. For more details, see our JavaScript Objects, Wrox Press 1998, ISBN: 1861001894.)
<script>
var argCount={LOOKUPFROMWHEN:1,LOOKUPFROMWHO:1,ADD:3,DELETE:1};
function onsub()
{
var theForm=window.document.theForm;// get reference to the form
var theCmd=theForm.bbcmd.value; // get the value of bbcmd
if(theCmd=="logout")return true; // if logout, submit the form
if(theCmd=="send")
return sendOk(theForm);// check sendOk condition
return doDBOk(theForm); // it's a db query:check doDBOk condition
}
function sendOk(theForm)
{
if(""==theForm.toaddr.value)
{
alert("must fill in toaddress");
return false;
}
if(""==theForm.subject.value)
{
alert("must fill in subject");
return false;
}
return true;
}
function doDBOk()
{
var theOp=theForm.dbOperation.value;
var N=argCount[theOp]; // retrieve number of arguments
if(!N)N=0; // if null, set it to 0
theForm.ParameterMax.value=N;
for(var
i=1;i<=N;i++)
{
if(""==theForm["Parameter"+i].value)
{
alert("must fill in Parameter"+i);
return false;
}
}
if(theOp=="BIRTHDAYLIST") // param passed as "when"
theForm.when.value=theForm.Parameter1.value;
return true;
}
</script>
And here is the form; its onsubmit attribute is a call on onSub(). As we said, the only JSP element in it (and the entire page) is an expression inside the text area:
<form name="theForm" action="birthday.jsp"
method="post" onsubmit="return onsub()">
<input type=hidden name=ParameterMax value="0">
<input type=hidden name=when>
Select a BirthdayBean Command:
<select name=bbcmd size=1>
<option value="dodb" selected>doDB Op</option>
<option value="send">send message</option>
<option value="logout">logout</option>
</select>
<br>
Select a Database Op:
<select name=dbOperation size=1>
<option value="LOOKUPALL" selected>show the table</option>
<option value="BIRTHDAYLIST">birthday list for yyyy-MM-dd date</option>
<option value="LOOKUPFROMWHEN">look up a specific date</option>
<option value="LOOKUPFROMWHO">look up a specific person</option>
<option value="ADD">add a (who,when,addr) entry in dbase</option>
<option value="DELETE">delete a person (by name)</option>
</select>
<br>
<input type=submit>
<br>
Parameters for DB Queries:
<br><input type=text name=Parameter1 size=10 value="">
<br><input type=text name=Parameter2 size=10 value="">
<br><input type=text name=Parameter3 size=10 value="">
<br>
Or you can send mail. <br>
Send to: <input type=text name=toaddr value="" size=20><br.
about: <input type=text name=subject value="" size=20>
<br>
<textarea name=msgtext rows=10 cols=50>
<%= msgtext %>
</textarea></form>
This concludes continue.jsp. We have now seen all the inputs and outputs, and the entry point and the main JSP "servlet page". What we have not seen is the Java code that makes it all work. Time to open up the bean.
The bean works with the familiar components, Env and DBHandler, to do database access, and with new email components that are tucked away in MyNa.utils.MiscMail.java. For now, let's concentrate on the overall structure. We have, as usual:
q imports and declarations
q a null constructor, getXXX() and setXXX() methods
q processRequest() and doCommand() methods called by the JSP page
q "command methods": doLogout(), sendMessage() and doDB() (with pruneResults())
We take them up in order.
Imports are self-explanatory, but remember that MyNa.utils.* now includes MiscMail:
package birthday;
import javax.servlet.http.*;
import java.util.Vector;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.Enumeration;
import javax.mail.MessagingException;
import MyNa.utils.*;
public class BirthdayBean
{
String bbcmd = null; //BirthdayBeanCommand from JSP specifies action
String jspcmd = null; // JSPCommand from BBean specifies display
// variables for databases: the fields of the table are WHO, WHEN and RETADDR
String who = null;
String when = null;
String retaddr = null;
// variables for mail connection
String toaddr = null;
String smtphost = null;
String mailuser = null;
String subject = null;
String msgtext = null;
// variables for Env and DBHandler; initdefs comes from client
String initdefs = null;
Env env = null;
DBHandler dbh = null;
// outputs
String errorString = null;
String[][] resultTable = null;
String numberAffected = null; // if query returns a number
The first two declarations, bbcmd and jspcmd, have been explained earlier.
Most of this is fairly trivial code, but some of it sets jspcmd:
public BirthdayBean()
{
}
public void setBbcmd(String S)
{
bbcmd = S;
}
public void setWhen(String S)
{
if(null != S)
{
when = S;
}
}
public void setRetaddr(String S)
{
retaddr = S;
}
public void setToaddr(String S)
{
toaddr = S;
}
public void setSmtphost(String S)
{
smtphost = S;
}
public void setSubject(String S)
{
subject = S;
}
public void setMsgtext(String S)
{
msgtext = S;
}
public void setInitdefs(String S)
{
initdefs = S;
}
// set methods that also set jspcmd
public void setNumberAffected(String S)
{
jspcmd = "change";
numberAffected = S;
}
public void setResultTable(String[][] S)
{
jspcmd = "list";
resultTable = S;
}
public void setErrorString(String S)
{
errorString = S;
if(null != S && S.length() > 0)
{
jspcmd = "error";
}
}
// get methods
public String getErrorString()
{
return errorString;
}
public String getNumberAffected()
{
return numberAffected;
}
public String getWhen()
{
return when; // to a new jsp page
}
public String[][] getResultTable()
{
return resultTable;
}
public String getJspcmd()
{
return jspcmd;
}
public Env getEnv()
{
return env;
}
public boolean shouldDoDB()
{
return "login".equalsIgnoreCase(bbcmd) || "dodb".equalsIgnoreCase(bbcmd);
}
What processRequest() does depends on the value of bbcmd. The possible values are:
q login (sent from the entry HTML page)
q dodb
q sendmsg
q and logout (sent from the continue.jsp page)
If the value of bbcmd is sendmsg or logout then the processRequest() method does nothing. With either of the first two options, the bean will have to do database access, either to establish a connection and a login (for "login"), or to run a query (for "dodb"). You will see from the second last line of the code above that shouldDoDB() returns exactly the boolean value of the disjunction (in English) "bbcmd equals 'login' or bbcmd equals 'dodb'".
So, in either of those two cases we have to process a new Request, which means re-initializing the Env that holds request information. This is what is done in the first part of the method: a new Env is created from Request, and information from initdefs is added, if it is not null. (It will be non-null only if we are processing the initial HTML file that has an initdefs textarea element.)
public void processRequest(HttpServletRequest request)
{
try
{
if(shouldDoDB())
{
// set up env with Request and initdefs info
try
{
env = new Env(request);
}
catch(java.lang.NullPointerException e)
{
setErrorString("null in Env init");
return;
}
// end create Env from Request
if(null == env)
{
setErrorString("can't initialize env from request");
}
else
{
if(null != initdefs)
{
// add initdefs to Env
StringReader sr = new StringReader(initdefs);
BufferedReader brin = new BufferedReader(sr);
env.addBufferedReader(brin);
}
// end add initdefs to Env
}
} // end shouldDoDB
If we are, indeed, doing the "login" command then we also have to establish a database connection and initialize queries, all of which is wrapped into a DBHandler object, created from the Env:
try
{
if("login".equalsIgnoreCase(bbcmd))
{
// login to database
dbh = new DBHandler(env);
}
}
catch(java.lang.NullPointerException e)
{
setErrorString("null in DBHandler init");
return;
} // end create DBHandler
} // end try
catch(ParseSubstException e)
{
setErrorString("bad initdefs" + e);
}
catch(Exception e)
{
<