HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Have you ever wanted to have your own Web e-mail system, rather than relying on free Web e-mail services? In this article I'll show you how to build a scalable Web e-mail system based on Java servlets and JavaMail, two members of the Java 2 Enterprise Edition (J2EE) platform. The system provides a thin, HTML-based e-mail client that will enable you to access your own e-mail account from any Web browser, anywhere in the world.

Java Servlets
Servlets have become a popular choice for building interactive Web applications. A servlet can be thought of as a faceless applet that runs on the server side. Numerous third-party servlet engines and servlet-enabled application servers are on the market today. Servlets provide a rich, platform-independent set of APIs for building Web-based applications without the performance limitations of CGI programs. The central point of the Servlets API is the Servlet interface, which needs to be implemented by the servlet host engine. This interface specifies two very important methods, init() and service(). The init() method is called only once by the servlet engine - when the servlet instance is created - while the service() method is executed every time a request to a servlet is made. This means that, depending on the servlet engine, multiple threads can execute the service() method at the same time, which is important if you have servlet member variables that need to be thread-safe. The Servlet API provides simple implementations of the Servlet interface in the form of the GenericServlet and HttpServlet classes. The e-mail servlet described here extends the HttpServlet class and overrides its init(), doPost() and doGet() methods.

JavaMail
The JavaMail API provides a platform-independent and protocol-independent framework to build mail and messaging applications. The JavaMail API is a part of the J2EE. The e-mail servlet described here has been developed and tested with JavaMail version 1.1.3. The JavaMail API includes implementations of the IMAP and SMTP service providers. The POP3 service provider is available for download as a separate set of APIs. Almost 100 interfaces and classes are provided with the JavaMail API. The main ones to note are the Session, Store, Folder, Address, Message and BodyPart classes.

Three-Tier Thin Client Web Architecture
Multitier Web architectures have been widely adopted by today's enterprises. The Web e-mail solution described here is a three-tier application with a presentation tier, a business logic tier and a persistent storage tier (see Figure 1). The presentation tier is a Java servlet that processes user requests and generates HTML pages. The business logic tier uses JavaMail to validate users; track sessions; retrieve, compose and delete messages; and handle attachments. The persistency tier can be any e-mail server that supports SMTP and POP3 or IMAP.

Figure 1
Figure 1:

Initialization
The Servlet interface specifies that each implementation must have the init() method. The host servlet engine calls the init() method just once and must complete successfully before the servlet can receive any requests. In this case the init() method retrieves configuration parameters and initializes the default mail session. The parameters I used to develop and test the servlet are in Listing 1. The Java source for the init() method is in Listing 2. To successfully send and receive e-mail, the servlet needs the IP addresses of the incoming and outgoing mail servers. In most cases these addresses are the same, but there are cases when the servers might be separated. Next, the incoming mail protocol parameter needs to be assigned to the class variable, thus avoiding the parameter lookup every time a user logs on. JavaMail supports IMAP by default. The POP3 protocol is also supported, but has to be downloaded from JavaSoft as a separate product.

The next parameter to be retrieved is the Internet domain served by the e-mail servlet. This value is used to compose the reply e-mail address in the outgoing e-mail messages.

Next, you obtain the default e-mail session that will be shared among all servlet users. This e-mail session is used in the login process to retrieve the e-mail store for each user. The last parameter in the initialization process is optional. It's useful to turn the debug feature on and off. There are two different debug levels - one at the Mail Servlet level, the other at the JavaMail level.

Login
The first page presented to the user is the login page, generated by the e-mail servlet when called without parameters. This page is very simple, with only two edit fields (username and password) and two buttons (submit and reset). It also contains a hidden field called command that is the key to navigating the e-mail servlet. It tells the servlet about the next operation to be performed. The value of this field in the login page - "login" - signals the servlet to call its doLogin() method.

The first step is to extract the values of the submitted username and password fields. If any of these two fields are missing, the login page with the error will be sent back to the Web browser. The next step is to construct the URLName to be used for the user's authentication and e-mail store retrieval. The URLName needs the protocol, e-mail server address, port number, folder name, username and password parameters. In this case -1 is specified for the port number, which means the default port for the requested protocol will be used. If there's no provider for the specified protocol, the exception will be thrown and the error with the login page will be sent back to the Web browser.

After the user's store has been obtained, the user's inbox folder is retrieved from the store. This is actually the place where the user will be authenticated. If the authentication fails or something else goes wrong, the exception will be thrown and the error with the login page will be sent to the Web browser.

Client Sessions Tracking
The Java Servlets API provides an interface for user sessions handling. Which way it will implement this interface is up to the servlet host engine. Some support only simple session tracking by using cookies, which is a problem if a user disables cookies in his or her browser. Most of the commercial servlet engines implement more sophisticated user session tracking mechanisms without the need for cookies to be enabled in the Web browser.

The servlet engine should also give you a mechanism to configure user session parameters such as inactivity and timeout. You'll need to take all these things into consideration for your servlet engine selection. A user session can be obtained from the request object by calling its getSession() method.

HttpSession httpSession = req.getSession(true);
if (httpSession == null)
{
// The servlet engine doesn't support user sessions or cookies have been disabled by the user.
... display error page here
}

The getSession() method accepts a boolean that indicates if a session needs to be created or the current user session is to be used. The doLogin() method is the only place in the servlet where "true" is passed, so a new session is created. All other operations assume that the user has already logged in and his or her session has already been created, so "false" is passed to the getSession() call.

After the user session has been created, the username, the password and the folder need to be stored into the session. These values are stored for later use by other servlet operations. The HttpSession interface provides putValue() and removeValue() methods to store and remove parameters.

Message List
After the user has been authenticated, the message list page is sent to the browser. This page is also generated when the Mail Server receives the "msglist" command.

doMessageList() is the method that does this (see Listing 3). This method's first step is to open the inbox mail folder stored in the user's session and fetch a list of messages from the folder. To do this you need to create a fetch profile to tell the folder how to fetch messages. In this case only a list of user messages is displayed so the fetch profile is set to fetch message envelopes only, i.e., only message headers without their bodies will be fetched.

Folder folder = null;
Message[] msgs = null;
try
{
folder = (Folder)httpSession.getValue("folder");
if (!folder.isOpen())
folder.open(Folder.READ_WRITE);

msgs = folder.getMessages();
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
folder.fetch(msgs, fp);
}
catch ....

The next step is to loop through the array of fetched messages and generate the HTML table for the message details. Each row in the table shows the message "from," "date" and "subject" fields (see Figure 2). If you wish, you can add more fields to be displayed, such as the "cc" or "reply to."

Figure 2
Figure 2:

The "from" field in the table is created as a hyperlink, where the username and the message number are set as the parameters to be sent by the browser when the hyperlink is selected. At the bottom of the page the standard operation hyperlinks are displayed.

Message Retrieval
As mentioned above, each message in the displayed message link has a hyperlink that, when selected, sends the retrieve command and the message number to the e-mail servlet. This triggers the doRetrieve() method call, which opens the user's folder and retrieves the requested message number from the folder. If the message exists, the displayMessage() method is called to generate the HTML page for the message header and body. First, the message header elements subject, date sent, sender and recipients will be retrieved to be embedded in the HTML page. The next task is to determine the message content type. If the content type is "text/plain" the content is sent as is to the browser. If the content type is "multipart/*" the message content will be cast to the Multipart object type, which will contain message parts. Each message part is retrieved as the Java BodyPart object type, which is done by going through a loop and retrieving body part objects from the multipart object. For each body part the displayPartDetails() method is called to generate the HTML code if the part is a plain or HTML text. If the part is of any other type, such as a gif or a zip file, only the file name will be displayed as a hyperlink. The user can then click on this hyperlink to retrieve the part. In this case the body part is sent as a raw array of bytes to the browser, which will either be able to handle it or prompt the user for the part to be saved.

Compose, Reply, Forward
The e-mail composition page is generated by the doCompose() method called by the servlet when a new, reply or forward message is needed. The page is simple, with input fields for the recipient addresses (to, cc and bcc), the subject and the message body. While the message subject and body are empty when a new message is being composed, for the reply and forward commands the original message fields must be assigned. This e-mail servlet doesn't handle user folders, so e-mails sent aren't saved - but users can be given the option to bcc themselves, as in the example shown in Figure 3.

Figure 3
Figure 3:

When a message is composed and the user clicks on the send button, the servlet will call its doSend() method. This method is quite straightforward and its Java source is in Listing 4. After creating a MimeMessage object, it extracts the values for the recipients, message subject and the body from the message composition page. These values are placed into the created message object along with the sent date and the mailer values. The last step is to call a static method send() of the JavaMail Transport class with the message as the parameter. From this point onward the JavaMail and the SMTP server will handle message delivery. If one of them can't deliver the message, the MessagingException will be thrown and the error page will be sent to the browser.

Attachments
Well, this is where you'll have to roll your own! Java Servlet API doesn't handle multipart/form-data content types such as when the browser uploads a file to the Web server. First, you'll need to set the form encotype to multipart/form-data in the doCompose() method, as follows:

toClient.println( "<form encotype=multipart/form-data action=" +
HttpUtils.getRequestURL(req) + " method=post>" );

You'll also include a field in the compose form to select a file to be attached:

<input type=file size=20 name=Attach>

Since there is another content type to handle now, you'll have to check it in the doProcess() before doing any processing.

String contentType = req.getHeader("Content-Type");
if (contentType != null &&
contentType.toLowerCase().startsWith("multipart/form-data"))
{
MultipartServletRequest msr = new MultipartServletRequest(req);

doSend(msr, res);
}
else // do normal processing

The MultipartServletRequest is the class you need to create to extract the request parameters and the uploaded file. As the file upload is a frequently requested functionality for Web sites and intranets, I don't know why a similar class isn't included in the Servlets API. It's an important class, but unfortunately I can't include it here due to limits of space.

Security
When you implement this solution security will be a major issue, so you might consider an encryption technique such as the Secure Sockets Layer (SSL) to protect the content of the data exchanged between the servlet and the browser. This should be used for the login page - or maybe for all servlet operations if the content of your e-mail is sensitive.

The other security concern is denial-of-service attacks in which huge amounts of data are sent to the Web server with the aim of bringing it down. You can prevent this by checking the length of content received in the servlet before processing the request. Currently, the maximum content length in the e-mail servlet is 1MB, but you'll probably need to increase this limit if you're intending to send big attachments via the servlet.

Enhancements
The e-mail servlet described is based on the Java servlets technology, used when the presentation layer doesn't change often. If you frequently change the look of your Web pages, you should consider converting this e-mail servlet into a JavaServer Page (JSP). You could characterize servlets, briefly, as "HTML code in Java code," while JSPs are "Java code contained in HTML code."

Another architectural modification to this solution may be considered. If you need to distribute its processing to achieve better scalability, you would extract the business logic from the servlet and wrap it into Enterprise JavaBean(s). The bean(s) would run on a separate box and would be created and used by the servlet.

Summary
The solution described in this article is just a starting point for creating a Web e-mail system. With a little imagination you can extend the functionality of the system I have described and integrate it into your Web site. The purpose of this article is to show you how powerful Java servlets are when combined with other J2EE technologies such as JavaMail. Moreover, Java's support for open standards enables this solution to run on any platform and to support any e-mail server.

Author Bio
Davor Bisko, an architect specialist at Unisys in Melbourne, Australia, is focused on multitier Web applications. He holds a master's in IT and is a Sun-certified Java programmer, developer and architect with nine years of object-oriented application design and development experience in C++ and Java. [email protected]

	

Listing 1: 

email servlet 
email.code=au.com.esoft.servlet.MailServlet 
email.initparams=\ 
sendserver=smtp.esoft.com.au,\ 
recvserver=pop3.esoft.com.au,\ 
protocol=pop3,\ 
domain=esoft.com.au,\ 
debug=true 
 
Listing 2: 

public void init(ServletConfig config) 
throws ServletException 
{ 
super.init(config); 
sendServer = getInitParameter("sendserver"); 
if (sendServer == null) 
throw new ServletException("Send mail server parameter 
missing!"); 
recvServer = getInitParameter("recvserver"); 
if (recvServer == null) 
throw new ServletException("Incoming mail server parame- 
ter missing!"); 
protocol = getInitParameter("protocol"); 
if (protocol == null) 
protocol = "pop3"; 
domain = getInitParameter("domain"); 
if (domain == null) 
throw new ServletException("Domain parameter missing!"); 
Properties props = System.getProperties(); 
props.put("mail.smtp.host", sendServer); 
mailSession = Session.getDefaultInstance(props, null); 
String debugParam = getInitParameter("debug"); 
if ("true".equals(debugParam)) 
{ 
debug = true; 
mailSession.setDebug(true); 
} 
else 
{ 
debug = false; 
mailSession.setDebug(false); 
} 
} 
 
Listing 3: 

public void doMessageList(HttpServletRequest req, 
HttpServletResponse res) 
throws ServletException, IOException 
{ 
PrintWriter toClient = res.getWriter(); 
// Get the session for this user. 
HttpSession httpSession = req.getSession(false); 
sendPageStart(toClient); 
Folder folder = null; 
Message[] msgs = null; 
try 
{ 
folder = (Folder)httpSession.getValue("folder"); 
if (!folder.isOpen()) 
folder.open(Folder.READ_WRITE); 
msgs = folder.getMessages(); 
FetchProfile fp = new FetchProfile(); 
fp.add(FetchProfile.Item.ENVELOPE); 
folder.fetch(msgs, fp); 
} 
catch (MessagingException me) 
toClient.println("<tr><td>" + me.toString() + 
"</td></tr>"); 
if (msgs.length > 0 ) 
{ 
sendReplyHeader(toClient, "Message List"); 
sendTableHeader(toClient); 
} 
else 
sendReplyHeader(toClient, 
"There are no messages in the mailbox!"); 
// For each message, show its header 
for (int i = msgs.length; i > 0; i--) 
{ 
try 
{ 
Message m = msgs[i-1]; 
if (m.isSet(Flags.Flag.DELETED)) 
continue; 
// Generate HTML code for the message header details. 
toClient.println("<trbgcolor=#eeeecc><td><a href=" + 
HttpUtils.getRequestURL(req) + 
?command=retrieve&username=" + 
username + "&msg=" + i + 
">" + 
m.getFrom()[0].toString() + 
"</a></td><td>" + 
m.getSentDate() + 
"</td><td>" + 
m.getSubject() + 
" </td></tr>"); 
} 
catch (MessagingException me) 
toClient.println("<tr><td>" + 
me.toString() + 
"</td></tr>"); 
} 
if (msgs.length > 0 ) 
sendTableFooter(toClient); 
// Send the mail options. 
toClient.println("<center>" ); 
toClient.println("<nobr> " + 
"<font face=Arial, Helvetica " + 
"size=2 color=#FFFFFF " + 
"style=color:#FFFFFF; " + 
"text-decoration:none><a href=" + 
HttpUtils.getRequestURL(req) + 
"?command=logout&username=" + 
username + 
">Logout</a></font> </nobr> "); 
toClient.println("<nobr> " + 
"<font face=Arial, Helvetica " + 
"size=2 color=#FFFFFF " + 
"style=color:#FFFFFF; " + 
"text-decoration:none><a href=" + 
HttpUtils.getRequestURL(req) + 
"?command=msglist&username=" + 
username + 
">Refresh</a></font> </nobr> "); 
toClient.println("<nobr> " + 
"<font face=Arial, Helvetica " + 
"size=2 color=#FFFFFF " + 
"style=color:#FFFFFF; " + 
"text-decoration:none><a href=" + 
HttpUtils.getRequestURL(req) + 
"?command=compose&username=" + 
username + 
">Compose</a></font> </nobr> "); 
toClient.println("</center>" ); 
try 
folder.close(true); 
catch (MessagingException me) 
me.printStackTrace(); 
sendPageEnd(toClient); 
} 
 
Listing 4: 

public void doSend(HttpServletRequest req, 
HttpServletResponse res) 
throws ServletException, 
IOException 
{ 
PrintWriter toClient = res.getWriter(); 
// Get the session for this user. 
HttpSession httpSession = req.getSession(false); 
sendPageStart(toClient); 
try 
{ 
MimeMessage msg = new MimeMessage(mailSession); 
InternetAddress sender = 
new InternetAddress(username + '@' + domain); 
msg.setFrom(sender); 
String recipients = req.getParameter("to"); 
// JavaMail follows RFC822 standard which allows 
// only comma separated addresses, so we have to 
// replace semicolons with commas, if any 
recipients = recipients.replace(';', ','); 
Address[] address = 
(Address[])InternetAddress.parse(recipients, false); 
msg.addRecipients(Message.RecipientType.TO, address); 
// Parse the 'cc' recipients and add them to the message. 
recipients = req.getParameter("cc"); 
recipients = recipients.replace(';', ','); 
address = (Address[])InternetAddress.parse(recipients, false); 
msg.addRecipients(Message.RecipientType.CC, address); 
// Parse the 'bcc' recipients and add them to the message. 
recipients = req.getParameter("bcc"); 
recipients = recipients.replace(';', ','); 
address = (Address[])InternetAddress.parse(recipients, false); 
msg.addRecipients(Message.RecipientType.BCC, address); 
// Check if we have to send the message to the sender too. 
String bccMyself = req.getParameter("bccmyself"); 
debugMsg("Bcc myself = " + bccMyself); 
if (bccMyself != null) 
msg.addRecipient(Message.RecipientType.BCC, sender); 
// Set the message subject. 
String subject = req.getParameter("subject"); 
msg.setSubject(subject); 
// Put the message body in the message. 
String body = req.getParameter("body"); 
msg.setText(body); 
// Set other message parameters. 
msg.setHeader("X-Mailer", productName); 
msg.setSentDate(new Date()); 
// Finally send the message. 
Transport.send(msg); 
sendReplyHeader(toClient, "Message sent."); 
} 
catch (AddressException ae) 
sendReplyHeader(toClient, ae.getMessage()); 
catch (MessagingException me) 
sendReplyHeader(toClient, me.getMessage()); 
toClient.println("<br><br>" ); 
toClient.println("<center>" ); 
toClient.println("<nobr> " + 
"<font face=Arial, Helvetica " + 
"size=2 color=#FFFFFF " + 
"style=color:#FFFFFF; " + 
"text-decoration:none>" + 
"<a href=" + 
HttpUtils.getRequestURL(req) + 
"?command=logout&username=" + 
username + 
">Logout</a></font> </nobr> "); 
toClient.println("<nobr> " + 
"<font face=Arial, Helvetica " + 
"size=2 color=#FFFFFF " + 
"style=color:#FFFFFF; " + 
"text-decoration:none>" + 
"<a href=" + 
HttpUtils.getRequestURL(req) + 
"?command=msglist&username=" + 
username + 
">Message 
List</a></font> </nobr> "); 
toClient.println("<nobr> " + 
"<font face=Arial, Helvetica " + 
"size=2 color=#FFFFFF " + 
"style=color:#FFFFFF; " + 
"text-decoration:none>" + 
"<a href=" + 
HttpUtils.getRequestURL(req) + 
"?command=compose&username=" + 
username + 
">Compose</a></font> </nobr> "); 
toClient.println("</center>" ); 
sendPageEnd(toClient); 
} 
  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.