Java Tip 36: Share Java objects using e-mail

The Serializable interface, new with JDK 1.1, simplifies object persistence. Here's how to transfer an object to another user via SMTP e-mail

1 2 Page 2
Page 2 of 2

We're making good progress. So far we have serialized the object into a memory-resident object and converted the serialization data to Base64-encoding so that it is ready to be e-mailed to its destination user(s). To summarize what we have accomplished so far, here is a code fragment that creates an instance of the BugReport object, serializes it into memory, and Base64-encodes it:

 
 1   import java.io.*;
 2   import Codecs.base64Encode;
     :
 3   BugReport bug = new BugReport(1.0, "Crashes when spell checker invoked", 2);
 4   ByteArrayOutputStream os = new ByteArrayOutputStream();
 5   bug.save(os);
 6   String strSerializedBug = os.toString();
 7   strSerializedBug = Codecs.base64Encode(strSerializedBug);

Connecting to an SMTP Server

To send the message to our recipient(s), we have to walk through five easy steps:

  1. Obtain the DNS name for an SMTP server.

  2. Establish a TCP/IP session with the server.

  3. "Log in" to the server.

  4. Address the envelope.

  5. Stuff the envelope.

Each of these steps is discussed below.

Obtain the DNS name for an SMTP server

Just as when you send snail mail, the first thing you have to do is locate a post office that will handle the delivery process for you. For most residences and businesses, this is a simple task because they have a mailbox right outside their front door. It's that easy for Internet mail too.

You probably already have an Internet e-mail account. If so, your mail reader, either the one bundled with your Web browser or a standalone package such as Eudora, required you to enter some information regarding an SMTP server. That information is the DNS name of the SMTP server you can use. Here are some SMTP server names for various ISPs:

ISPSMTP Server
AT&Tmailhost.worldnet.att.com
Netcomsmtp.ix.netcom.com
Interaccesssmtp.interaccess.com

Your ISP will probably use a DNS name for its SMTP server that is similar to these examples.

Establish a TCP/IP session with the server

Generally, SMTP servers listen for connections on port 25. Therefore, establishing a connection to an SMTP host is simply a matter of creating a TCP/IP socket connected to port 25 of the SMTP host. Here is some Java code to connect to a hypothetical server with a DNS name of "smtp.tjd.com":

1 import java.net.*; 2 import java.io.*; 3 : 4 Socket socketSmtpServer = null; 5 DataOutputStream dos = null; 6 DataInputStream dis = null;

7 try { 8 socketSmtpServer = new Socket("smtp.tjd.com", 25); 9 dos = new DataOutputStream(socketSmtpServer.getOutputStream()); 10 dis = new DataInputStream (socketSmtpServer.getInputStream()); 11 } 12 catch (UnknownHostException e) {throw (e);} 13 catch (IOException e) {throw (e);}

Notice that this code creates a DataOutputStream object and a DataInputStream object as soon as the TCP/IP connection is made. We will use these later for sending and receiving data from the SMTP server.

"Log in" to the server

You don't really log in to an SMTP server the way you log in to a Unix system or a database. Since there is no authentication/authorization process, it's not really a log in at all. You simply identify yourself so the server can try to verify the sender. This step is not really needed, but it is rude to omit it.

When you first connect to the server, it will send you two lines of data that identify it and the version of SMTP that it speaks. For our example, we are not concerned with these data so we will just read them in and discard them. After we have read them in, the server will put us in the driver's seat and wait for and respond to commands. Here is one way to perform this login process from within a Java program:

1 String strBuf;

2 String strMyName = "tomdaley"; 3 strBuf = dis.readLine(); 4 strBuf = dis.readLine(); 5 dos.writeBytes("HELO " + strMyName + "\n"); 6 strBuf = dis.readLine(); 7 dos.writeBytes("RSET\n"); 8 strBuf = dis.readLine();

The HELO command identifies you to the SMTP server. The RSET command resets the state of the SMTP server. If everything always works well, the RSET command is not necessary. But since things don't always work perfectly and RSET is a "cheap" command to send and execute, it's a good idea to go ahead and send it in.

Note the readLine() after each writeBytes(). SMTP servers send a status message back for each command that you try to send. The status message begins with a 3-byte number that can be used to determine the success or failure of a command. RFC 821 explains this thoroughly.

Address the envelope

To continue with a snail-mail analogy, we now are ready to address the envelope. Like all polite correspondence, we will provide a return address as well as the recipient(s)' address(es) to the mail delivery agent. The next code fragment shows how to perform this in Java.

 1   dos.writeBytes("MAIL FROM:<tomdaley@ix.netcom.com>\n");
 2   dis.readLine();
 3   dos.writeBytes("RCPT TO:<tdaley@sei-it.com>\n");
 4   dos.readLine();

Stuff the envelope

Now we're ready to construct the interesting part of the message, the DATA section. The DATA section will be composed of two sections:

  1. Headers for mail clients to read

  2. MIME-encoded text and data

The headers part of the DATA section is not required but it makes your message look nicer when viewed through the recipient's mail client. The headers give the message a more professional polish and, depending on the capabilities of the recipient's mail client, can make the message easier to manage.

The headers are analogous to the top of any interoffice memorandum and can be sent thus:

 
�?1   dos.writeBytes ("DATA\N");
 2   strBuf = dis.readLine();
 3   dos.writeBytes ("To: Tom Daley <tdaley@sei-it.com>\n");
 4   dos.writeBytes ("From: Tom Daley <tomdaley@ix.netcom.com>\n");
 5   dos.writeBytes ("Subject: Bug Report\n");

Notice that we read one line after we sent the DATA command and then did not read any more. When you send the DATA command, the server responds with a message like this: 354 Enter mail, end with "." on a line by itself". That means that the SMTP server will not send any more data across the socket until it sees the end of the message.

In order to mail our serialized, encoded object, we will encapsulate it within a multipart MIME message. The first part of the message will be plain text, which, in MIME parlance, is "Content-Type: text/plain." In the text portion we will send some instructions to accompany the object and let the recipient know a little about it. Here's how to set up the MIME document and send the recipient some instructions:

 1   String strBoundary = "SimpleBoundary";
 2   String strInstructions = "Save the attached file and read it with BugNews.class.";
 2   dos.writeBytes("Mime-Version 1.0\n");
 3   dos.writeBytes("Content-Type: multipart/mixed; boundary=\"" + strBoundary + "\"\n");
 4   dos.writeBytes("--" + strBoundary + "\n");
 5   dos.writeBytes("Content-Type: text/plain; charset=\"us-ascii\"\n\n");
 6   dos.writeBytes(strInstructions + "\n");

Now we are ready to do what we've been working toward for lo these many pages: Attach the serialized Java object to our SMTP mail message. Remember that the message lines cannot exceed 1,000 bytes. For multipart MIME, there is the additional constraint that there can be no more than 74 bytes of encoded binary data per line. This means that we have to take the String object that contains our serialized, Base64-encoded object and write it out to the SMTP socket 74 bytes at a time. The following code fragment will send the MIME boundary marker, indicate that the data that follow are application data, write out the object, and wrap up this SMTP session with the server.

1 dos.writeBytes("--" + strBoundary + "\n"); 2 dos.writeBytes("Content-Type: application/octet-stream; name=\"BugReport.bug\"\n"); 3 dos.writeBytes("Content-Transfer-Encoding: base64\n"); 4 dos.writeBytes("Content-Disposition: attachment; filename=\"BugReport.bug\"\n"); 5 dos.writeBytes("Content-Description: Bug Report from a customer\n\n");

6 int iLines = strObject.length() / 74; 7 for (i = 0; i < iLines; i++) 8 dos.writeBytes(strSerializedBug.substring(i*74, i*74+74)+"\n");

9 if (iLines*74 < strSerializedBug.length()) 10 dos.writeBytes(strSerializedBug.substring(i*74, strSerializedBug.length()) + "\n");

11 dos.writeBytes("--" + strBoundary + "--\n\n");

12 dos.writeBytes("\n.\n"); 13 strBuf = dis.readLine(); 14 dos.writeBytes("QUIT\n"); 15 strBuf = dis.readLine(); 16 dos.close(); 17 dis.close(); 18 socketSmtpServer.close();

We did it!

By now our bug report is winding its way through the global SMTP mail distribution system and will soon be on someone's desk. I bet they won't be as happy to receive the bug report as you were when you figured out how to send it!

Tom Daley is director of SEI Information Technology's Financial Services Practice (http://www.sei-it.com), where he provides technical guidance and management advice to companies in the financial services industry. He has worked extensively with Java and supporting technologies in a critical line of business systems ever since he figured out that Java was not just another Starbucks wannabe. Other areas of interest include CORBA, asynchronous middleware, and real-time, high volume data distribution over the Internet.
Related:
1 2 Page 2
Page 2 of 2