Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

The Mail4ME Project


Jun02: The Mail4ME Project

Jörg is a research assistant and Ph.D. candidate in the computer science department at the University of Dortmund, Germany. He can be contacted at [email protected].


Apart from the World Wide Web, e-mail is the most popular Internet service. According to a recent study, the number of e-mails sent on an average day in 2001 will have passed the 10 billion mark. With communication-centric, wireless devices such as two-way pagers, cell phones, and PDAs becoming more prevalent, it would be nice if they supported real e-mail too, rather than the 150 characters possible with SMS.

The Java 2 Micro Edition (J2ME) platform and Mobile Information Device Profile (MIDP; http://java.sun.com/products/midp) used in the emerging generation of cell phones and PDAs should be able to meet this need, allowing the development of portable, Java-based e-mail applications for these devices. Yet, neither the core J2ME nor MIDP provide libraries suitable for Java-based e-mail access. The JavaMail API (http://java.sun.com/products/javamail/) uses features not available in J2ME. While there are some devices that have built-in native e-mail applications, there's no standardized interface allowing Java applications to utilize this functionality; for example, by sending e-mail from inside a MIDlet.

The Mail4ME project (http://mail4me.enhydra.org/) provides a solution to this problem. Mail4ME is a lightweight implementation of the popular POP3 and SMTP protocols — including MIME support and with IMAP support to come — allowing wireless J2ME/MIDP devices to access e-mail services at any time and from any place (as long as a suitable antenna is in reach and the device's battery is not empty, of course). Mail4ME belongs to the EnhydraME family of projects (http://me.enhydra.org/). The goal of EnhydraME is to provide application server-like functionality for J2ME-enabled devices. Most EnhydraME projects — including Mail4ME — are available as open source under the Enhydra Public License, which is similar to the BSD license.

Since the project targets the J2ME environment, the implementation must respect the limited resources inherent in that platform. In particular, this implies that it's not possible to represent every piece of information an e-mail contains (or may contain) by a specialized Java class. So the level of abstraction is somewhat lower than that used by JavaMail. In terms of memory constraints, it should be possible to deploy only those parts of the library to target devices that are actually needed by an application. The latter means that the different classes must not be mutually dependent.

As Figure 1 shows, the basic classes of the Mail4ME package and their most important relationships include:

  • Messages, represented by a line-oriented data structure following the rules specified by the Internet Message Standard (RFC822; http://www.nic.mil/ftp/rfc/rfc822.txt). This representation is easy to implement, easy to use, and requires little memory. Since it's also close to the data format used during actual transmission, not much transformation is needed, making it reasonably fast even on low-horsepower J2ME devices.
  • Headers and body lines can be accessed and manipulated by their index, with a number of additional methods allowing for comfortable access to the various field names and values of a message's header.

  • Protocols are encapsulated by one class each, so there's a Pop3Client class (RFC1939; http://www.nic.mil/ftp/rfc/rfc1939.txt), SmtpClient class (RFC821; http://www.nic.mil/ftp/rfc/rfc821.txt), and two specialized exception classes that report errors during POP3 and SMTP sessions. Messages are retrieved and sent by calling a method on one of the protocol clients. For the special case that a message has to be resent or forwarded without changing the original message's header, an additional Envelope class is provided, giving some control over the addressing stage of an SMTP session.

  • A special MimeDecoder class allows access to the tree-like hierarchy of body parts in a MIME-encoded message (RFC2057: http://www.nic.mil/ftp/rfc/rfc2057.txt). It can extract plaintext and binary data that uses "base64" encoding.

  • Since the implementation uses a helper class that provides abstraction from J2ME's Generic Connection Framework as well as from J2SE's Socket class, the package can be used in both environments, thus providing a lightweight and easy-to-use alternative to JavaMail, even for the desktop. The mechanism used here is akin to that used in the Generic Connection Framework itself: The actual connection class is chosen at run time depending on the value of the "microedition.configuration" property.

Figure 2 shows the dependencies between the Mail4ME classes. The Message class is at the heart of the package and is the only class that is mandatory. All other classes build upon Message but are optional, meaning that only those parts of Mail4ME actually needed by an application have to be deployed to a J2ME MIDP device. If, for example, an application deals with nothing but receiving plaintext messages from a POP3 mailbox and is not involved in sending messages or MIME decoding at all, the SMTP and MIME classes could be left out of the final JAR file to save memory on the device.

A Sample Application

For the purpose of illustration, assume an imaginary customer is in need of mailing-list processor software to run on J2ME-enabled hardware. For simplicity, the list of requirements consists of only three items:

  1. The list processor reads mail from a POP3 account and forwards it to a fixed list of users using the SMTP service.

  2. Only subscribed users are allowed to use the list. Postings by users not subscribed to the list are rejected.

  3. In favor of saving bandwidth, the list allows posting of plaintext messages only. As a result, messages containing binary or other nontextual attachments are also rejected.

How can these requirements be addressed? First of all, a class skeleton (Listing One) containing some constant definitions for the POP3 and SMTP server settings is needed. This skeleton also declares a string array containing mailing-list subscribers.

Additionally, a main() method that contains the basic control flow is declared. This method provides an entry point for a plain J2ME implementation, such as those available for Windows and Linux. For an MIDP implementation, main() could be replaced by the startApp() lifecycle method used for MIDlets. Whatever its name, the method opens a POP3 session, processes all waiting messages, and closes the session. In Listing Two, downloading the messages from the server is done by calling the getMessageCount() and getMessage() methods of the Pop3Client class. For each of the messages, the code first checks whether it is valid in terms of requirement #2. If valid, the message is forwarded to the list; otherwise, an error message is sent to the originator. Also, the messages are removed from the server after being processed.

The next step is to implement the methods isValidUser(), goodMessage(), and badMessage(), which are used by main().

The isValidUser() method (Listing Three) checks whether the originator of a given message actually subscribes to the list. This is easily implemented by first getting the value of the message's "From" header field using the getHeaderValue() method. This method could also query other interesting fields contained in the header; for example, "To," "Subject," or "Date." For now I'm only interested in knowing the sender. The getMachineAddress() method then extracts the machine-readable part of the address (the user@domain part), since you don't want to deal with real-life names or other commentary stuff that may be part of the address. The result is converted to lowercase and compared to each of the entries of the users[] array. If it is found, true is returned; otherwise, the invalidity of the message is denoted by returning false.

The second method to implement is badMessage(), which takes two parameters — the "bad" message and an additional string that is to be included in the response sent to users. The method queries the originator of the message. It then uses this value in conjunction with the list owner's address and the string "Error" to create a new message. The creation process makes use of a "convenience constructor" that automatically sets the "From," "To," and "Subject" fields to the given values, as well as "Date" and "Message-ID" to reasonable ones (the current date/time and a unique ID). It is also possible, of course, to use the default constructor and set the different fields manually. Afterwards, several body lines are added to the message. The last four lines of the method are interesting because they show how easy it is to send a message using Mail4ME: An SmtpClient instance is created (Listing Four) and a new SMTP session is opened. The message can then be sent by calling the sendMessage() method before the session is closed to free the underlying socket resources. It is possible, of course, to send more than one message inside the same SMTP session.

The third method is goodMessage() (Listing Five), which forwards a given message to all subscribers of the list. The method does its duty by creating an instance of the Envelope class, in which the original message in enclosed. The envelope's sender is then set to the list owner, and all the subscribers are added to the envelope's list of recipients. This method also opens an SMTP session, but does not send the message directly; instead it sends the envelope in which the message is contained. As a result, the original message is forwarded to the subscribers without changing any header fields. This behavior, which is considered good style in the world of mail-processing software, is known as the "principal of minimal mangling."

In its current form, the list fulfills requirements #1 and #2, but not the third one: The list should only allow plaintext postings and reject all attempts to post binary content. To address this requirement, you need to make use of the MIME capabilities of the Mail4ME package, namely the MimeDecoder class. Remember that a MIME message is constructed of a tree-like hierarchy of MIME parts, the leaves of which bear the actual content of the message. An instance of MimeDecoder represents exactly one of these parts, giving access to the part's type, and either its content or its list of subordinate parts.

The isTextOnly() method (Listing Six) is able to check whether a MIME part and all of its subordinate parts are text only. The method first checks the result of the getPartCount() method to find out whether it deals with an inner node in the hierarchy of parts or with a leaf. In the former case, it calls itself recursively to check all the subordinate parts; in the latter case, it checks the type of the given part using the getType() method. If this type is not "text/plain," the message is considered bad.

As usual with recursive methods, the solution is short and elegant. The question left is how and when the first invocation of the method takes place. The answer is simple: The MimeDecoder constructor expects to be passed an existing Message instance, resulting in a top-level MIME view of the message. With that in mind, the isTextOnly() method is integrated with the existing code of the main() method; see Listing Seven.

This mailing-list processor is by no means perfect, or even capable of running a real mailing list. There are a lot of missing features — the ability to change list subscriptions by mail, for instance. Also, user data should be saved to disk, and a list archive could be created for good messages. Some error handling needs to be added to the program; otherwise, subscribers whose e-mail accounts don't exist any more (or simply an invalid subscriber address) result in delivery-failed messages bouncing merrily between the list processor and the nonexistent address. In short, work needs to be done before the list is applicable in real life. Still, building on the basic concepts presented here should be a manageable task.

MIDP-Specific Problems

The Mail4ME project has a problem that many MIDP applications deal with: The MIDP specification demands only an implementation of the HTTP protocol, meaning that an MIDP-enabled wireless device is not required to provide plain sockets. Unfortunately, these plain sockets are needed for POP3 and SMTP connections. Luckily, the manufacturers of many MIDP-enabled devices implemented the plain socket protocol in addition to the MIDP-specified requirements. Consequently, everything already works fine on these devices.

But for devices that do not provide plain socket access, a specialized proxy service on a desktop or server machine is needed to allow tunneling POP3 and SMTP through HTTP. This proxy, which is currently under development, might be based on Locumi (http://locumi.enhydra.org/), another EnhydraME project. The tunneling approach would also work for other Internet protocols; for example, if you want to implement Telnet or FTP clients for MIDP. An alternative approach might be to use XML/SOAP-based mail services where possible. Again, an EnhydraME project might provide the foundation, namely KSOAP (http://ksoap.enhydra.org/).

J2ME implementations other than MIDP (for example, the upcoming PDA profile) are not expected to exhibit these problems because they're targeted at bigger devices and likely to encompass plain sockets. The same is hopefully true for updated versions of MIDP, now under development by the Sun Community Process.

Present and Future

There is currently one demo application that shows the potential of Mail4ME on top of MIDP — an e-mail client MIDlet (included in the project's source code) that lets you browse through your POP3 inbox, read individual messages, and compose/send new ones. Since it uses the package's MIME capability, it can display messages composed of plaintext and PNG images.

We've tested the application and underlying Mail4ME library in the following environments:

While the Mail4ME package already works well, there is room for enhancements. The top two items on the list are support for the IMAP protocol and a proxy service for MIDP devices that don't support plain sockets. Also, the demo MIDlet is merely a proof of concept and also more-or-less of a client-side feature. Another application that shows Mail4ME's potential inside application server environments is currently under development: A full-featured mailing list processor — somewhat akin to the one shown in the example section — that can be run on any J2ME-enabled device.

Acknowledgment

Thanks to the people at Lutris, especially the Enhydra folks, for providing a platform for this project.

DDJ

Listing One

public class MailingList {
  static String pop3Host = "pop3.yourisp.com";
  static String pop3User = "user0815";
  static String pop3Pass = "secret";
  static String smtpHost = "smtp.yourisp.com";
  static String listMachine = "localhost";
  static String listOwner = "[email protected]";
  static String[] users = {"[email protected]", "[email protected]"};
}

Back to Article

Listing Two

  public static void main(String[] args) throws Exception {
    Pop3Client pop3 = new Pop3Client();
    pop3.open(pop3Host, pop3User, pop3Pass);
    for (int i = 0; i < pop3.getMessageCount(); i++) {
      Message message = pop3.getMessage(i);
      if (!isValidUser(message)) {
        badMessage(message, "you are not subscribed to the list.");
      }
      else {
        goodMessage(message);
      }
      pop3.removeMessage(i);
    }
    pop3.close();
  }

Back to Article

Listing Three

static boolean isValidUser(Message message) throws Exception {
  String sender = message.getHeaderValue("From");
  sender = Message.getMachineAddress(sender).toLowerCase();
  for (int i = 0; i < users.length; i++) {
    if (users[i].equals(sender)) return true;
  }
  return false;
}

Back to Article

Listing Four

static void badMessage(Message message, String reason) throws Exception {
  String sender = message.getHeaderValue("From");

  Message reply = new Message(listOwner, sender, "Error");
  reply.addBodyLine("Sorry, " + Message.getDisplayAddress(sender));
  reply.addBodyLine("");
  reply.addBodyLine("your message could not be delivered because");
  reply.addBodyLine(reason);
  reply.addBodyLine("");
  reply.addBodyLine("Regards, " + owner);

  SmtpClient smtp = new SmtpClient(listMachine);
  smtp.open(smtpHost);
  smtp.sendMessage(reply);
  smtp.close();
}

Back to Article

Listing Five

static void goodMessage(Message message) throws Exception {
  Envelope envelope = new Envelope(message, false);
  envelope.setSender(listOwner);
  for (int i = 0; i < users.length; i++) {
    envelope.addRecipient(users[i]);
  }
  SmtpClient smtp = new SmtpClient(listMachine);
  smtp.open(smtpHost);
  smtp.sendMessage(envelope);
  smtp.close();
}

Back to Article

Listing Six

static boolean isTextOnly(MimeDecoder mime) {
  int count = mime.getPartCount();
  if (int i == 0) {
    for (int i = 0; i < count; i++) {
      if (!isTextOnly(mime.getPart(i))) return false;
    }
  }
  else {
    if (!mime.getType().toLowerCase().equals("text/plain")) return false;
  }
  return true;
}

Back to Article

Listing Seven

if (!isValidUser(message)) {
  badMessage(message, "you are not subscribed to the list.");
}
else if (!isTextOnly(new MimeDecoder(message))) {
  badMessage(message, "the list allows plain text messages only.");
}
else {
  goodMessage(message);
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.