Birthday Announcements with JSP and JavaMail
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:
Entry Point: birthday.htm
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:
two
hidden inputs for bbcmd
and dbOperation
two
inputs for the database: username
and password
a
simple piece of JavaScript code to generate an input for Date with the
value of the current date
two
inputs for sending mail: return address and SMTP host
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="*".
The Main JSP page: birthday.jsp
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:
we process the request in the bean, by calling its
public processRequest()
method
we have the bean process the current command, on the
basis of the docmd
property set in the user input
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.
Output Template Files
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" %>
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.
Two Javascript Functions
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>
The Form
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.
Inside the Bean: BirthdayBean.java
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:
imports and declarations
a null constructor, getXXX() and setXXX() methods
processRequest()
and doCommand()
methods called by the JSP page
"command methods": doLogout(), sendMessage() and doDB() (with pruneResults())
We take them up in order.
Imports and declarations
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.
Constructor, Getters and Setters
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);
}
processRequest() and doCommand()
What processRequest()
does depends on the value of bbcmd. The
possible values are:
login
(sent from the entry HTML page)
dodb
sendmsg
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)
{
setErrorString("dbhandler failure: " + e);
}
}
The doCommand() method
does not really do any commands itself, just dispatches the action to the right
method:
public void doCommand()
{
if("logout".equalsIgnoreCase(bbcmd))
{
doLogout();
}
else
{