Thẻ
JavaMail is one of the common services of the J2EE architecture that is responsible for reading, composing, and sending e-mail messages from enterprise applications. JavaMail enables you to implement a Mail User Agent (MUA), similar to the familiar Microsoft Outlook, Pine, and Eudora. Today you’ll explore the benefits and features that the JavaMail API offers, and how you can use them in a simple EJB: the Emailer EJB. First, you’ll be exposed to the main concepts (classes and interfaces) of the JavaMail API. JavaMail basically provides a set of classes that model a mail system. The API provides a platform-and protocol-independent framework to build Java-technology-based mail and messaging applications. At the end of the day, you’ll apply this to some of the scenarios we use in our university registration system. JavaMail is implemented as a Java platform optional package and is also available as part of the J2EE.
Today you’ll explore the JavaMail API and use it to develop a sample full J2EE application that includes components in the EJB tier, Web tier, and client tier.
Throughout today’s activities, you’ll learn
-
What JavaMail is, and the different protocols it can handle
-
What the JavaMail API is, and the concepts it implements
-
How to compose, send, and read e-mail messages with different scenarios (forward; reply; nontext messages; multilingual messages; messages with attachments) that are similar to a real e-mail system
-
How to develop and deploy a full portable J2EE enterprise application based on the MVC pattern to demonstrate the use of the JavaMail API
-
How to deploy the J2EE application on both the WebLogic Server and the JBoss server environments, and configure each server’s environment to run the portable application
Understanding JavaMail
JavaMail is a unified service for abstracting an electronic mail (e-mail) system. It has pre-built implementations of some of the most popular protocols for mail transfer and provides an easy way to use them. The JavaMail API is designed to provide protocol-independent access for sending and receiving messages. However, it does not include any facilities for adding, removing, or changing user accounts. There are no standards to accomplish these tasks, and every e-mail system handles them differently.
You probably already know the most common e-mail agents (clients), such as Eudora, Outlook, Netscape, and other modern e-mail clients, which let you send both text and HTML e-mails. E-mail messages were originally limited to plain text and they did not support bold, italics, or hyperlinks. Modern e-mail agents recognize HTML, so you can now send either plain text messages or rich-content documents languages that aren’t Latin based, such as Japanese and Chinese.
The JavaMail API provides a set of abstract classes and interfaces that comprise an electronic mail system. The abstract classes and interfaces support many different implementations of message stores, formats, and transports. Many simple applications will need to interact with the messaging system only through these base classes and interfaces.
Note
JavaMail helps create an e-mail agent (or mail client), and does not provide any mail server functionality. So, you must have access to a mail server before you try using JavaMail.
The abstract classes in the JavaMail API are expandable, and they can be subclassed to provide new protocols and add new functionality when necessary. In addition, JavaMail API includes concrete subclasses that implement the most widely used Internet mail protocols, such as Internet Message Access Protocol (IMAP), Post Office Protocol (POP), and Simple Mail Transfer Protocol (SMTP). They are ready to be used in application development. Developers can subclass JavaMail classes to provide the implementations of particular messaging systems, other than these protocols.
JavaMail Architecture
JavaMail is designed to standardized access to a variety of e-mail services. The JavaMail architecture provides a protocol-independent access for sending and receiving messages. This abstract mechanism is similar to other J2EE APIs, such as JDBC, JNDI, and JMS. Similarly, the JavaMail API is divided into two main parts: an application-independent part, and a service-dependent part. Your applications are written in a standard way to use the application-independent part of the JavaMail API, which transparently calls the underlying protocol or e-mail service. A JavaMail-compliant service must implement part of the JavaMail API. Here is a brief description of the two parts that comprise the JavaMail architecture:
-
An application-programming interface (API): This API is used by the application components to send and receive mail messages, independent of the underlying provider or protocol used.
-
A service provider interface (SPI): This part of the API speaks the protocol-specific languages, such as SMTP, POP, IMAP, and Network News Transfer Protocol (NNTP). It is used to plug in a provider of an e-mail service to the J2EE platform.
Figure 20.1 illustrates the JavaMail architecture and the interaction of the two main APIs.
Figure 20.1. The JavaMail architecture.
As you learned from Day 15, “Understanding J2EE Architecture,” JavaMail can be used from within components of either the Web tier or the EJB tier. It can’t be used from J2EE client applications or applets.
Comparing JavaMail and JMS
JavaMail and JMS have something in common: both Both are used for delivering asynchronous messaging in J2EE applications. However, there are many differences between the two APIs, including the purpose and the implementations.
Java JMS is used as a unified API to access a MOM (Message-Oriented Middleware) provider, whereas JavaMail is used to access an e-mail system provider. JMS is designed to produce and consume messages by applications, and not by users, as is the case in JavaMail.
Both JMS and JavaMail are used to design loosely coupled applications, which is different from those tightly coupled applications built using RMI/IIOP. In asynchronous communication, users or components send messages, and they do not have to wait for an immediate response. You’ve covered JMS in detail on Day 13, “Understanding JMS and Message-Driven Beans.”
JavaBeans Activation Framework
The JavaBeans Activation Framework (JAF) API integrates support for MIME (Multipurpose Internet Mail Extensions) data types into the Java platform. This means that you are not limited to using only plain text e-mail messages. You can use many different content types, such as HTML, images, sound, and video. JavaBeans components can be specified for particular MIME data operations, such as viewing or editing the data. The JAF API also provides a mechanism to map filename extensions to MIME types, which is useful in sending messages with attachments.
The JAF API is used by the JavaMail API to handle the data included in e-mail messages. Typical applications will not need to use the JAF API directly, although applications making sophisticated use of e-mail might need it. Later today, you’ll learn how to use JAF in conjunction with JavaMail to process HTML e-mail messages and messages with attachments.
Reviewing Basic Protocols
The JavaMail API is designed to use the most common protocols for exchanging e-mail messages. Each of the following subsections briefly discusses one of these protocols. A good understanding of the basics of these protocols will help you grasp how to use the JavaMail API. Although the API is designed to be protocol-independent, you can’t overcome the limitations of the underlying protocols. Certain protocols support more capabilities than others.
Simple Mail Transfer Protocol
SMTP is the mechanism for delivery of e-mail. This protocol is used for sending outgoing messages from your applications. Your JavaMail-compliant client will communicate with the SMTP server of your company or a particular ISP to deliver e-mail messages to the specified destinations. JavaMail uses the term transport for the service of sending an e-mail message. The SMTP server relays the message to the SMTP server at the destination, and the recipients retrieve the messages through the use of POP, IMAP, or other protocols, as described in the next two sections.
Post Office Protocol
Also known as POP3, this protocol is used to store and retrieve mail to and from an SMTP server. Similar in concept to the U.S Post Office, it defines support for a single mailbox for each user. JavaMail applications connect to a mailbox to retrieve, read, and delete e-mail messages using POP. Other functionality, such as counting unread messages, can also be accomplished from the application. With POP3, the server provides a folder that stores messages as they arrive. When a client connects to a POP3 server, it specifies the folder from which it retrieves the messages and transfers them to a message store on the client.
Internet Message Access Protocol
IMAP (currently in version 4 and called IMAP4) is a more advanced protocol than POP for storing and retrieving e-mail messages. It can access messages from more than one computer, which has become extremely important as reliance on e-mail messaging and use of multiple computers have increased. Additionally, JavaMail applications can take advantage of the fact that users can have multiple folders on the server and multiple users can share those folders. With IMAP, message folders are stored on the mail server, including folders that contain incoming messages and folders that contain archived messages.
Multipurpose Internet Mail Extensions
MIME is not a mail transfer protocol, per se. Instead, it defines the content of the messages to be handled, such as the format and the attachments. As a user of the JavaMail API, you usually don’t need to worry about these formats. However, these formats do exist and are used by your programs. To handle non-plain text mail content, the JavaBeans Activation Framework is required, as we discussed earlier today.
NNTP and Others
The JavaMail architecture provides support for new mechanisms and protocols to be added to the existing family of protocols. This is due to the separation of the JavaMail API into application-specific and provider-specific APIs. Some of the newly added protocols are the S/MIME (Secure Multipurpose Internet Mail Extensions) protocol, NNTP (which is used for newsgroups), and more.
Learning the JavaMail API
The JavaMail API consists of some interfaces and classes used to send, read, and delete e-mail messages. The javax.mail and javax.mail.internet packages contain all the JavaMail core classes. The javax.mail.activation package represents the JavaBean Activation Framework.
Exploring the Core Classes
The JavaMail core classes, which belong to the javax.mail package, are Session, Message, Address, Authenticator, Transport, Store, and Folder. Table 20.1 gives a brief description of each core class.
In the following sections, you’ll learn these core classes in more detail. After that, you’ll learn how to use those classes to perform e-mail system agent functionality, such as sending, reading, and deleting messages.
Session
The Session class is the primary class of the JavaMail API. It defines a basic mail session. The Session object acts as the connection factory for the JavaMail API, which handles both configuration setting and authentication. It is through the Session object that everything else works. To create a Session object, you look up the administered object stored in the JNDI service, as mentioned on Day 4, “Using JNDI for Naming Services and Components.”
InitialContext ctx = new InitialContext(); Session session = (Session) ctx.lookup("ursMailSession");
In the preceding snippet, ursMailSession is the JNDI name object used as the administered object for the Session object. ursMailSession can be created and configured with the required parameters as name/value pairs, including information such as the mail server hostname, the user account sending the mail, and the protocols supported by the Session object. Using this declarative approach as the default method when creating a Session object helps makes your application portable by not hardcoding any parameters inside your applications. Later today, we’ll explain how to configure this administered Session object, in the WebLogic Server and JBoss server environments.
Note
As you learned on Day 4, the J2EE specification recommends that all resource manager connection factory references be organized in the subcontexts of the application component’s environment, using a different subcontext for each resource manager type. According to Table 4.2, connection factories of JavaMail should be declared in the java:comp/env/mail subcontext.
The other method of creating the Session object is based on the programmatic approach in which you can use a java.util.Properties object to override some of the default information, such as the mail server name, username, password, and other information that can be shared across your entire application. As you learned, J2EE promotes portability, and always recommends the declarative approach over the programmatic approach to avoid hardcoding parameters.
Because the constructors for the Session class are private, you can get a single default Session that can be shared with other components using the getDefaultInstance() method:
Properties props = new Properties(); // Override props with your customized data props.put("mail.transport.protocol", "smtp"); props.put("mail.host", "acme"); Session session = Session.getDefaultInstance(props, null);
Similarly, to create a unique Session object (not shared), you use the getInstance() method:
Session session = Session.getInstance(props, null);
In both cases, the null argument is an Authenticator object. More information about the Authenticator object will be given shortly.
In most cases, it’s sufficient (also efficient) to use a shared Session, even if you’re working with mail sessions for multiple user mailboxes. You can add the username and password combination at a later step in the communication process and keep everything separate.
Message
The Message object is the container for all parts of the e-mail message. After you’ve created a Session object, you can start composing messages to send. This is accomplished by using a concrete subclass of Message. Because Message is an abstract class, you must work with a subclass; in most cases, you’ll use a MimeMessage (in javax.mail.internet). A MimeMessage is an e-mail message that understands MIME types and headers. Message headers are restricted to ASCII characters only, although non-ASCII characters can be encoded in certain header fields. MimeMessage acts as a container to hold all the parts of the message.
To create a Message, you pass the Session object as an argument to the MimeMessage constructor:
MimeMessage msg = new MimeMessage(session);
After you’ve created a new MimeMessage, you can start filling its parts. The Message class implements the Part interface (with MimeMessage implementing MimePart). To set the content, you use the setContent() method with arguments for the content and the MIME type. Here’s an example of setting the message body with a plain text message:
msg.setContent("Hello World", "text/plain");
The special method setText() is used to set the text content (with MIME type of text/plain):
msg.setText("Hello World");
The setContent() method is used to set other kinds of MIME types, such as HTML e-mail messages.
The method setSubject() is used to set the subject of the message:
message.setSubject("Just Say Hello");
Other methods used to set various message properties will be discussed in the next sections.
Address
Address is an abstract class that represents an e-mail address, such as john.doe@acme.com. This represents any sender address (from) or recipient address (to). To create an Address object, you pass the e-mail address as a parameter to the InternetAddress subclass constructor:
Address address = new InternetAddress("john.doe@acme.com");
If you want the name to appear next to the e-mail address, you can pass that name along to the constructor:
Address address = new InternetAddress("john.doe@acme.com", "John Doe");
This method is used to create a valid e-mail address, whether you need to create address objects for the message’s from field or the to field.
Note
You can send a message that appears to be from anyone, unless the mail server prevents you from doing so for security reasons.
You use the setFrom() method to set the sender address, and the setReplyTo() method to set the address to which the reply should be directed (where from and to are of type Address):
msg.setFrom(from); msg.setReplyTo(to);
Caution
Not all message types allow the setReplyTo() method to be specified separately from the sender of the message.
You can send a message from multiple senders simply by creating the following:
Address address[0] = address1; Address address[1] = address1; ... msg.addFrom(address);
To set the message recipients, you use the addRecipient() method. This method requires a Message.RecipientType in addition to the address.
message.addRecipient(type, address)
The three predefined address types are objects with one of these values:
-
Message.RecipientType.TO for a primary recipient
-
Message.RecipientType.CC for a carbon copy recipient
-
Message.RecipientType.BCC for a blind carbon copy recipient
For example, to send a message to our friend John Doe and carbon copy support@acme.com, the following would be appropriate:
Address toAddress = new InternetAddress("john.doe@acme.com"); Address ccAddress = new InternetAddress("support@acme.com"); message.addRecipient(Message.RecipientType.TO, toAddress); message.addRecipient(Message.RecipientType.CC, ccAddress);
The JavaMail API provides no mechanism to check for the validity of an e-mail address. This is left to the e-mail server provider.
Authenticator
The JavaMail API can use an Authenticator object to access the e-mail server by using a username and password. Because it’s an abstract class, you create a subclass PasswordAuthentication, passing a username and password to its constructor. You must register the Authenticator with the session when you create it, by replacing the null in the example shown in the “Session” section earlier today. Here’s an example with the user jDoe and his pswrd:
Properties props = new Properties(); // Override props with any customized data PasswordAuthentication auth = new PasswordAuthentication("jDoe", "pswrd") Session session = Session.getDefaultInstance(props, auth);
Transport
The last task in sending an e-mail message is to use the Transport class. This class normally uses the SMTP protocol to send a message. An easy way to send a message is to use the default version of the class by calling the static send() method:
Transport.send(msg);
Here the static send() method makes a separate connection to the e-mail server for each method call. To enhance your application performance, we recommended keeping your connection to the mail server alive when sending multiple messages. To do so, you need to create a specific instance of the Transport object and not the default object. You pass the protocol along with the hostname, username, and password, send the messages, and then close the connection, as shown here:
MimeMessage msg1, msg2; // ... Create and populate messages ... Transport trans = session.getTransport("smtp"); trans.connect("mail.acme.com", "jDore", "pswrd"); trans.sendMessage(msg1, msg1.getAllRecipients()); trans.sendMessage(msg2, msg2.getAllRecipients()); trans.close();
To monitor your mail server while sending messages, you turn on your debug flag by using the setDebug() method of the Transport class:
session.setDebug(true);
The default parameter value is false, which turns off debugging.
Store and Folder
When you store and retrieve messages from your e-mail server, you must connect to a Store. A Store holds messages in different Folders on your server. After you create a Session object, you connect to a Store, and identify the e-mail server host, user ID, and password. Both the Transport and Store classes extend the Service class, which defines the connect() and close() methods. You need to tell the Store what protocol to use:
Store store = session.getStore("pop3"); store.connect(host, userid, password);
After you connect to a Store, you can get a Folder, open it, and start reading messages. The following is an example of how to read messages from the (reserved name) INBOX folder:
Folder folder = store.getFolder("INBOX"); folder.open(Folder.READ_ONLY); Message message[] = folder.getMessages();
Note
The only folder available in POP3 is INBOX. If you’re using IMAP, you can have other folders available.
The folder name is case sensitive. You open a folder in a read only or read/write mode. The latter mode enables you to delete messages. To create a new folder under the current folder, use the create() method. The delete() and the exists() methods delete and check whether a folder exists, respectively.
To read the contents of a message, use the getContent() method (without the message’s header). The writeTo() method writes the message, including the message header, to a stream.
System.out.println(((MimeMessage)message).getContent());
After you’ve finished reading your e-mail, close the connection to the folder and store:
folder.close(true); store.close();
In the preceding snippet, the true parameter value of the close() method indicates the removal of all deleted messages. Expunge is another term used to describe the deletion of messages.
The JavaBean Activation Framework
The JAF API is used by the JavaMail API to manage MIME data. The DataSource interface provides the JAF with an abstraction of some arbitrary collection of data. The FileDataSource class implements a simple DataSource object that encapsulates a file. This helps you include attachments of different file types in your messages. The following section demonstrates the use of these interfaces and classes to send messages with attachments.
Using the JavaMail API
The previous section summarizes all the main classes of the JavaMail API, and in this section, you’ll learn how to connect all the pieces together to perform the regular operations of e-mail services. You’ll explore how to send, reply, and forward messages, in addition to finding out how to read or retrieve and delete them.
Sending Messages with JavaMail
Sending an e-mail message can vary between sending a plain text message and a rich-content message. The next sections shed light on how to send messages in languages that aren’t Latin based. Forwarding and replying to messages are also implemented by sending messages. You’ll learn how to send messages with attachments.
Sending a Text Message
This is the most common method of sending e-mail messages. Here are the steps to send a message with JavaMail from within you application components in the Web tier or the EJB tier:
-
Import the JNDI (naming), JavaBean activation, and JavaMail packages. You’ll also need to import java.util.Properties:
import java.util.*; import javax.activation.*; import javax.mail.*; import javax.mail.internet.*; import javax.naming.*;
-
Look up the mail session in the JNDI service by using the default context:
InitialContext ic = new InitialContext(); Session session = (Session) ic.lookup("ursMailSession");
-
Use the properties to override the default Session by creating a Properties object and adding the properties you want to override. Then call getInstance() to get a new Session object with the new properties.
-
Construct a MimeMessage with the recipient’s address, subject, and body parts of the message. Create an Address for each recipient of the message:
String body = "Welcome to the new world of JavaMail"; Message msg = new MimeMessage(session); Address to = new InternetAddress("john.doe@acme.com"); msg.setFrom(); meg.addRecipient(Message.RecipientType.TO, to); msg.setSubject("Hello"); msg.setSentDate(new Date()); msg.setText(body);
In the setFrom() method, the value of the attribute is obtained implicitly from the mail.user property, which is set during the configuration of the mail Session. If this property is absent, the system property user.name is used.
-
Send the message.
Transport.send(msg);
If the JNDI lookup fails, the NamingException will be thrown; if JavaMail transport fails, the MessagingException will be thrown. You must put your code in a try block and catch these exceptions.
Replying to Messages
To reply to a message, you must create a new message from the original by using the reply() method. You also need to set the new Message with the proper recipient and subject, and add the "Re: " if it isn’t already there. The reply() method copies the from or reply-to header to the new recipient. It doesn’t add any content to the original message, so you need to set the body of the new message. The method also takes a Boolean parameter indicating whether to reply to only the sender (false) or reply to all the recipients (true). The following is an example of replying only to the sender:
MimeMessage reply = (MimeMessage) msg.reply(false); reply.setFrom(new InternetAddress("john.doe@acme.com")); reply.setText("Have Fun!"); Transport.send(reply);
To set the reply-to address when sending a message, use the setReplyTo() method.
Forwarding Messages
To forward a message to a new recipient, you must construct a new message and then populate its parts. An e-mail message can be made up of multiple parts. Each part is a MimeBodyPart, and the different body parts are combined into a container called a MimeMultipart. To forward a message, you create one part for the text of your message and a second part with the message to forward, and then combine the two into a multipart message. Then you add the multipart message to a properly addressed message and send it. Here’s an example of forwarding a message:
Address to = new InternetAddress("john.doe@acme.com"); Message forward = new MimeMessage(session); forward.setSubject("Fwd: " + msg.getSubject()); forward.setFrom(new InternetAddress(msg.getFrom())); forward.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Construct the message part of the text BodyPart mbp1 = new MimeBodyPart(); mbp.setText("fyi"); // Create a multi-part to combine the parts Multipart mp = new MimeMultipart(); mp.addBodyPart(mbp1); // Construct the message part of the forwarded content BodyPart mbp2 = new MimeBodyPart(); mbp2.setDataHandler(msg.getDataHandler()); mp.addBodyPart(mbp2); // Associate the multipart with the message forward.setContent(mp); // Send the message Transport.send(forward);
Sending a Message with Attachments
Resources can be attached to and detached from messages using the JavaMail API. Attachments are resources that are associated with a mail message, and are usually kept outside of that message; for example, a text file, a Word document, or an image. A message with attachments is represented as a MIME multipart message in which the first part is the main body of the message and the other parts are the attachments. Sending a message with an attachment is similar to forwarding a message; here’s an example:
Message msg = new MimeMessage(session); String filename="c:\myfiles\notes.doc"; Multipart mp = new MimeMultipart(); // Construct the message text part mp.addBodyPart("Attached find my document."); // Construct the attachment part MimeBodyPart mbp = new MimeBodyPart(); DataSource source = new FileDataSource(filename); mbp.setDataHandler(new DataHandler(source)); mbp.setFileName(filename); mp.addBodyPart(mbp); msg.setContent(mp); Transport.send(msg);
The FileDataSource is part of the Java Activation Framework, and its constructor accepts a String that indicates the full filename on the disk.
Sending Messages as HTML and Images
New e-mail systems support HTML and image content. To send an HTML file, you use the more generic setContent() method, and set the MIME type to text/html, as follows:
String html = "<HTML><H1> Welcome to the new world of JavaMail</H1></HTML>"; Message msg = new MimeMessage(session); msg.setContent(html, "text/html");
On the other hand, if you want your HTML content message to be complete with embedded images included as part of the message, you must treat those images as attachments and reference each one with a special content ID (CID) URL, where the CID is a reference to the Content-ID header of the image attachment:
Message msg = new MimeMessage(session); BodyPart mbp = new MimeBodyPart(); String html = "<H1>Welcome to the world of JavaMail</H1>" + "<img src=\"cid:img1\">"; mbp.setContent(html, "text/html"); // Create a related multi-part to combine the parts MimeMultipart mp = new MimeMultipart("related"); mp.addBodyPart(mbp); // Create part for the image mbp = new MimeBodyPart(); // Fetch the image and associate to part DataSource img = new FileDataSource("images/logo.gif"); mbp.setDataHandler(new DataHandler(img)); mbp.setHeader("Content-ID","img1"); // Add part to multi-part mp.addBodyPart(mbp); // Associate multi-part with message msg.setContent(mp);
Sending Message Content Based on Locale
Situations sometimes arise in which you need to send a message in a language other than a Latin-based script (char set). This is common in internationalization (i18n) efforts of enterprise applications. To accomplish this, you must set the message to the right locale for the language. The following example is set to send an e-mail message based on the Japanese locale (ISO-2022-JP):
Message msg = new MimeMessage(session); msg.setSubject(subject, "ISO-2022-JP"); msg.setText(body, "iso-2022-jp");
Note
Some Web servers are familiar only with the ISO-8859-1 (Western European) char set, and they can’t decode messages written in other char sets. You might need to check the Web server documentation before sending messages using different char sets.
Reading Messages with JavaMail
Reading mail messages involves fetching the messages from the message store. The JavaMail API enables you to connect to a message store, which could be an IMAP server or POP3 server. The JavaMail API provides several options for reading messages, such as reading a specified message number or range of message numbers, or prefetching specific parts of messages into the folder’s cache. Here’s an example of reading incoming messages on a POP3 server:
Store store = session.getStore("POP3"); store.connect(host, userid, password); Folder mbox = store.getFolder("INBOX"); mbox.open(Folder.READ_ONLY); Message msg[] = mbox.getMessages(); //...process each message according to requirements... mbox.close(false); store.close();
Each message in the preceding snippet is processed according to your business requirements.
Note
Reading messages from an IMAP server is similar to reading messages from a POP3 server. With IMAP, however, the JavaMail API provides methods to create and manipulate folders and transfer messages between them. If you use an IMAP server, you can implement a full-featured, Web-based mail client with much less code than if you use a POP3 server.
Deleting Messages and Flags
To delete a message, you must connect to the message store and open the message folder using READ_WRITE mode. You must flag the message with the Flags.Flag.DELETED flag (too many flags!). When you’ve processed all the messages, close the folder, and pass in a true value to expunge the deleted messages. The following code snippet demonstrates how to delete a message:
mbox.open(Folder.READ_WRITE); Message msg = mbox.getMessage(1); msg.setFlag(Flags.Flag.DELETED, true); mbox.close(true);
The setFlag() method does not delete the message, it only marks the message for deletion. The close() method with the true parameter value is where messages are deleted or expunged.
Developing JavaMail ApplicationsToday you’ll develop the Emailer EJB, which is responsible in our university registration system for sending e-mails to students when they submit courses at enrollment time, and for notifying them again when they are approved for registration. Our example today uses the Model-View-Controller (MVC) architecture pattern to design a JavaMail application. The servlet component in the Web tier represents the controller, which acts as a client to the EJB tier. The Emailer EJB in the EJB tier represents the model, which handles business logic (primarily sending an e-mail message). The view is represented by an HTML form (which could be a JSP) that interacts with the servlet. Figure 20.2 illustrates the use of the MVC pattern in designing our JavaMail application. Figure 20.2. Using the MVC pattern in the JavaMail application.The example also partitions the application into a Web tier module (packaged into a WAR file) as well as an EJB module (packaged into a JAR file). You’ll learn how to create and deploy an EAR file, which combines both the JAR file and the WAR file into an enterprise application. Figure 20.3 summarizes the sequence diagram of the sample application in sending an e-mail message. The student interacts with the EmailerServlet using the emailer.html HTML form. The EmailerServlet, on behalf of the client, relays the request to the Emailer EJB, which sends the e-mail to the recipient through the e-mail server. Figure 20.3. The sequence diagram for sending an e-mail message.Here’s a summary of the steps required to develop your JavaMail application of today’s example:
The following sections give you more details about performing each of the steps mentioned in this list. As usual, the full application code listing, and scripts for compiling and deploying into the WebLogic Server and JBoss server environments can be downloaded from our Web site. Developing the EJB Tier ComponentsThe EJB tier contains only the Emailer EJB, and we selected a stateless session bean to model its activities. The main business method is the sendMail() method, which sends e-mail messages to students. To optimize the Emailer EJB, you can cache the mail Session object so that you don’t have to look it up every time you send a message. As you learned on previous days, we need to develop the remote interface, home interface, and the bean class. The following listings provide the code for these components. Listing 20.1 is for the remote interface Emailer, which lists all the business methods used in our example. Listing 20.1 The Remote Interface Emailer.javapackage day20; import java.rmi.RemoteException; import javax.ejb.EJBObject; // Provides a method to send mail messages public interface Mailer extends EJBObject { public void sendMail(String to, String body) throws RemoteException, URSMailerException; } Listing 20.2 shows the home interface EmailerHome, which lists all the life cycle methods used to manage the bean. Listing 20.2 The Home Interface EmailerHome.javapackage day20; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; // The Home interface for MailerEJB public interface MailerHome extends EJBHome { public Mailer create() throws RemoteException, CreateException; } Listing 20.3 is the bean class EmailerEJB, which implements all the business methods listed in the remote interface and the callback methods to manage the bean’s life cycle by the container. Listing 20.3 The Bean Class EmailerEJB.javapackage day20; import java.util.Date; import java.util.Locale; import java.util.Properties; import javax.naming.InitialContext; import javax.activation.DataHandler; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Transport; import javax.mail.Session; import javax.mail.Multipart; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.InternetAddress; import java.rmi.RemoteException; import javax.ejb.EJBException; import javax.ejb.FinderException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; // Session Bean implementation of MailerEJB. // Used to send a mail message confirmation such as an email // to a student after a registration into courses is completed. public class MailerEJB implements SessionBean { private Session mailSession = null; public void sendMail(String to, String body)throws URSMailerException { try { MimeMessage msg = new MimeMessage(mailSession); msg.setFrom(); InternetAddress dest = new InternetAddress(to); msg.setSubject("Testing STYEJB JavaMail"); msg.setRecipient(Message.RecipientType.TO, dest); msg.setSentDate(new Date()); msg.setHeader("X-Mailer", "JavaMailer"); msg.setContent(body, "text/plain"); Transport.send(msg); } catch (Exception e) { e.printStackTrace(); throw new URSMailerException("Failure while sending email"); } } public void ejbCreate() { try { InitialContext ctx = new InitialContext(); mailSession = (Session) ctx.lookup("java:comp/env/mail/Mail"); } catch (javax.naming.NamingException e) { e.printStackTrace(); } } public void ejbPostCreate() {} public void ejbActivate() {} public void ejbPassivate() {} public void ejbRemove() {} public void setSessionContext(javax.ejb.SessionContext ec) {} } Notice that we’ve created the Session object in the ejbCreate() method, and cached it into the session instance variable. This will enhance the application’s performance, and can handle a larger number of students. Packaging the Emailer EJB into an EJB ModuleFor large applications, all the components of the EJB tiers are packaged into one JAR file, along with its deployment descriptors. For our example today, you need to package only one component. To build the example for the appropriate application server, you need to be in the Day20 directory, and then run the build script provided for this server. This will create a build subdirectory that contains all the compiled code. The script will then package the EJBs with the deployment descriptor (see Listing 20.4) into an EJB module (JAR file). Listing 20.4 The Deployment Descriptor ejb-jar.xml<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <ejb-jar> <enterprise-beans> <session> <ejb-name>MailerEJB</ejb-name> <home>day20.MailerHome</home> <remote>day20.Mailer</remote> <ejb-class>day20.MailerEJB</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <resource-env-ref> <resource-env-ref-name>mail/Mail</resource-env-ref-name> <resource-env-ref-type>javax.mail.Session</resource-env-ref-type> </resource-env-ref> </session> </enterprise-beans> </ejb-jar> You must provide a server-specific deployment descriptor for each application server; they are included in your download (jboss.xml for the JBoss server and weblogic-ejb-jar.xml for the WebLogic Server). For the sake of making your application portable, you must specify the <resource-env-ref> element in your ejb-jar.xml deployment descriptor, as you learned on Day 4. Developing the Web Tier ComponentsThis example includes a servlet component in the Web tier to act as a client to the Emailer EJB in the EJB tier. Students first interact with the servlet (through the HTML form discussed in the next section), and requests are relayed back to the EJB tier to execute the required business logic. Listing 20.5 is the EmailerServlet. Listing 20.5 The Servlet EmailerServlet.javapackage web; import java.io.IOException; import java.io.PrintWriter; import java.util.Hashtable; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import day20.*; // This Servlet will call the MailerEJB with the email address. public class MailerServlet extends HttpServlet { private MailerHome mailerHome = null; // Looks up the MailerHome interface and saves it for use in doGet(). public void init() throws ServletException{ try{ InitialContext jndiCtx = new InitialContext(); Object ref = jndiCtx.lookup("day20/Mailer"); mailerHome = (MailerHome) PortableRemoteObject.narrow(ref, MailerHome.class); } catch(Exception e){ throw new ServletException("Failed to lookup Mailer", e); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ String title = "Servlet client to Mailer EJB"; String toAddress = request.getParameter("toAddress"); String mailMsg = request.getParameter("message"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<HTML><HEAD><TITLE>"); out.println(title); out.println("</TITLE></HEAD><BODY>"); out.println("<H1>" + title + "</H1>"); out.println("<H2>Calling EJB...</H2>"); try{ Mailer bean = mailerHome.create(); bean.sendMail(toAddress, mailMsg); out.println("Mail Message is sent...."); } catch(Exception e){ out.println(e.toString()); } finally{ out.println("</BODY></HTML>"); out.close(); } } } Packaging the EmailerServlet into a WAR File ModuleAll components in the Web tier (such as JSP, servlets, and TagLibs), along with a web.xml deployment descriptor, should be packaged into a Web module (WAR file). You must also provide a server-specific Web deployment descriptor. The jboss-web.xml file is included for the JBoss server, and weblogic.xml is provided for WebLogic Server. The script provided for each server will create a web subdirectory that contains all the compiled code. It will then package the EmailerServlet component and the deployment descriptor web.xml into a Web module (WAR file). Listing 20.6 provides the web.xml deployment descriptor. Listing 20.6 The Web Deployment Descriptor web.xml<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>MailerServlet</servlet-name> <servlet-class>MailerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MailerServlet</servlet-name> <url-pattern>/MailerServlet</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> Building the EAR FileThe modules created in the previous sections—one for the Web tier and one for the EJB tier—can be combined into an application file (EAR file). For the sake of convenience, both scripts are also combined into one script to package the application into an EAR file. Listing 20.7 shows the application.xml deployment descriptor that will combine both WAR and JAR files into an enterprise application. Listing 20.7 The EAR Deployment Descriptor application.xml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN' 'http://java.sun.com/dtd/application_1_3.dtd'> <application> <display-name>STYEJB JavaMail Application</display-name> <module> <web> <web-uri>mailer.war</web-uri> <context-root>day20</context-root> </web> </module> <module> <ejb>mailer.jar</ejb> </module> </application> Listing 20.8 provides the build script for the WebLogic Server application server. The script for JBoss is included in the downloaded files. Listing 20.8 The Build Script for WebLogic Server buildWebLogic.bat@echo Compiling EJB tier files... rem "Cleaning previous build (if any)" rmdir /s/q build build1 rem "Creating a staging area for the build" mkdir build build\META-INF rem "Copying deployment files to META-INF directory" copy %STYEJB_HOME%\day20\ejb-jar.xml build\META-INF copy %STYEJB_HOME%\day20\weblogic-ejb-jar.xml build\META-INF rem "Compiling EJB classes and interfaces" javac -g -d build URSMailerException.java \ Mailer.java MailerHome.java MailerEJB.java rem "Creating the EJB's deployment Jar file" cd build jar cv0f tmp_mailer.jar META-INF day20 cd .. rem "Compiling the container classes" java weblogic.ejbc -keepgenerated -g \ -deprecation build\tmp_mailer.jar build\mailer.jar @echo Compiling Web tier files... rem "Cleaning previous build (if any)" rmdir /s/q web mkdir web web\WEB-INF web\WEB-INF\classes copy GUI\MailerServlet.java web\. copy GUI\*.html web\. javac -g -d web\WEB-INF\classes -classpath %CLASSPATH%;build \ URSMailerException.java Mailer.java MailerHome.java MailerEJB.java javac -g -d web\WEB-INF\classes \ -classpath %CLASSPATH%;web\WEB-INF\classes;build web\*.java rem Copying Web Deployment Descriptor... copy web.xml web\WEB-INF copy weblogic.xml web\WEB-INF jar cf mailer.war -C web . mkdir build1 build1\META-INF move mailer.war build1 @echo Creating Enterprise Archive (EAR) file ... copy application.xml build1\META-INF copy build\mailer.jar build1\ jar cf mailer.ear -C build1 . @echo Moving Enterprise archive file in to build directory... move mailer.ear build1 rem "Deploying the war file" copy build1\mailer.ear %APPLICATIONS% Implementing the ClientThe Web client for the EmailerServlet is an HTML form (see Listing 20.9) for testing our JavaMail application. The HTML form (see Figure 20.4) makes a request with the recipient’s e-mail address and the body of the message to be sent. Listing 20.9 The HTML Form index.html<html> <head> <title>JavaMailer EJB Form</title> </head> <body> <h1>Send Message Using JavaMail Form</h1> <form method=”POST” action=”MailerServlet”> <table cellspacing=”2″ cellpadding=”2″ border=”0″> <tr><td>To:</td> <td><input type=”text” name=”toAddress” size=”40″></td> </tr> <tr><td>Message:</td> <td> <textarea name=”message” cols=”50″ rows=”10″></textarea> </td> </tr> <tr><td><input type=”submit” name=”Send” value=”Send”></td> <td><input type=”Reset”></td> </tr> </table> </form> </body> </html> Figure 20.4. WebLogic Server Administration Console.Configuring the Mail Session in WebLogicBefore running the JavaMail application, you must create and configure a JavaMail Session into a JNDI service in the WebLogic Server Administration Console (http://localhost:7001/console). This allows server-side components and applications to access JavaMail services with JNDI. In our example, you create a mail Session by specifying the default mail user, mail host, transport, and store protocols in the Administration Console so that components that use JavaMail do not have to set these properties (see Figure 20.4). Applications that are heavy e-mail users benefit because WebLogic Server creates a single Session object and makes it available via JNDI to any component that needs it.
Note You can override any properties set in the mail Session in your code by creating a Properties object that contains the properties you want to override. Then, after you look up the mail Session object in JNDI, call the Session.getInstance() method with your Properties to get a customized Session. To configure the JBoss environment, you must customize the entries in the mail-service.xml file in the <JBOSS_HOME>/server/default/deploy directory. The README.TXT file provides more information about how to configure these parameters. Running the JavaMail ApplicationTo run the JavaMail application, you must deploy the emailer.ear file into the appropriate application server. The following steps show you how to deploy the EAR file into WebLogic Server: c:\styejb>setEnvWebLogic.bat c:\styejb>cd day20 c:\styejb\day20> runWebLogic.bat Similarly, you can deploy the application into the JBoss server using the runJboss.bat script. After you deploy the application on WebLogic Server, you can run the client from your Web browser by putting the following URL in the address field: http://localhost:7001/day20/index.html. On the JBoss server, you need to use the URL http://localhost:8080/day20/index.html. As shown in Figure 20.5, fill the e-mail address with your e-mail address, and a message of your choice, and then click on the Send button. Check your e-mail for the delivered message. Figure 20.5. HTML form to send e-mail messages.Best PracticesReading messages from an IMAP server is similar to reading messages from a POP3 server. With IMAP, however, the JavaMail API provides methods to create and manipulate folders and transfer messages between them. If you use an IMAP server, you can implement a full-featured, Web-based mail client with much less code than if you use a POP3 server. With POP3, you must provide code to manage a message store through your application server, possibly using a database or file system to represent folders. One of J2EE’s objectives is portability, and it always recommends a declarative approach over a programmatic approach to avoid hardcoding parameters. Avoid the programmatic approach as much as possible. Create and configure the Session object in a JNDI service as an administered object, so that you can tune it or change its parameters without changing any code. It’s always more efficient to use a shared Session object among all your components, even if you’re working with mail sessions for multiple user mailboxes. SummaryToday you explored the JavaMail API classes and their use. You started by learning the protocol used in e-mail systems, and the difference between sending an e-mail message and a JMS message. You also learned how to send, read, and delete e-mail messages, using the JavaMail and JAF APIs. Through a working example, you explored how to develop a simple JavaMail application. You also found out how to develop a complete enterprise application by portioning the application into a Web module (WAR file) and a EJB module (JAR file), and then combining them into an EAR file. You learned how to test your application from your browser through the use of an HTML form. |