.
.
developers.sun.com java.sun.com
   View this issue as simple text April 26, 2004    

In this Issue

Welcome to the Enterprise Java Technologies Tech Tips for April 26, 2004. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE).

This issue covers:

.Using the JavaMail API to Send HTML Email
.Experimenting with JDBC

These tips were developed using Java 2, Enterprise Edition, v 1.4 SDK. You can download the SDK at http://java.sun.com/j2ee/1.4/download-dr.html.

This issue of the Tech Tips is written by Mark Johnson, president of elucify technical communications, and co-author of Designing Enterprise Applications with the J2EE Platform, 2nd Edition. Mark Johnson runs an open forum for discussion of the tips.

You can download the sample archive for these tips. The context root for the application is ttapr2004, and the index.html welcome file indicates how to use the sample code. Any use of this code and/or information below is subject to the license terms.

For more Java technology content, visit these sites:

java.sun.com - The latest Java platform releases, tutorials, and newsletters.

java.net - A web forum for collaborating and building solutions together.

java.com - The marketplace for Java technology, applications and services.

.

USING THE JAVAMAIL API TO SEND HTML EMAIL

In the old days of the Internet, when it was called Arpanet, you could send and receive any kind of email you liked -- as long as it was plain text. Today people have enormously more computing power at their disposal than in the days of Arpanet, yet for the most part, the primary type of email is still text.

Text email is excellent for interpersonal communication, but many organizations can benefit from being able to email other types of content. For example, a magazine publisher might want to email the electronic content of a magazine in HTML. However there are problems sending email content in HTML. Some Web-based email systems show email in text-only mode, and for the large number of recipients who still read email using text-only clients, receiving HTML email can be annoying.

The first tip in this Tech Tips issue explains how to use the JavaMail API to send email that can be displayed as text or as HTML, depending on the recipient's email client. The tip does not discuss the many, valuable, and important arguments for and against HTML email. Instead, it assumes you've decided to use HTML email, and want also to support text email readers.

MIME and the Multipart Content Type

The old Arpanet email standard, RFC 822, was designed specifically for text messages. That's because in those days, text email was the only type of email available. By the early 1990's, the state of the art had advanced to the point where RFC 822's limitations were becoming apparent. There was a growing need to support non-textual and multi-part email. To provide this and other types of support, Internet engineers created MIME RFC 1341, the Multipurpose Internet Mail Extensions mechanism. Among other things it covers, MIME defines how to structure an email message body to deliver more than one item in a single message. Interestingly, much of how Web technology handles media types is based on MIME, even though MIME was originally designed for email. The original RFC 1341 MIME specification has itself been supplanted by RFCs 2045-2049.

MIME distinguishes emails that contain more than one item by indicating the email's Content-type as multipart. This tells the email client that the message body contains multiple parts, and describes how to split those parts. Each part also has its own Content-type.

The Multipart Alternative Content Type

Multipart emails let you send both an HTML version and a text version in the same email. In other words, multipart supports both HTML and text (and even audio) email users. But who wants to have to deal with several versions of email? Fortunately, the authors of MIME foresaw this problem, so they created the MIME Content-type multipart/alternative.

A multipart/alternative content type tells the user's email client that the parts of the message are alternative versions of the same thing. This lets the email client decide which version to display, based on its capabilities or on user preferences.

Is it expensive and slow to send multiple versions of everything, when only one is ever seen? The answer to that question depends on several factors that are specific to your particular environment. It's certainly important to keep in mind factors such as system load, message size, and user connection speed when designing an email communication system. But remember, this tip assumes that you already considered those things, and have decided to use HTML email.

You have at least two choices if you want to use MIME to send emails. First, you can read RFC 1341, and implement the protocols yourself. Or, you can use the JavaMail APIs, and allow the Java platform to do all of that work for you.

Using JavaMail to Send Multipart Emails

The sample application for this tip sends a multipart/alternative email message to a specified email address. The application transmits email using configuration information that is defined in a java.util.Properties file. The JavaMail interface is so feature-rich, that putting every option directly in the interfaces would result in a huge API. Also, setting up emails by calling a lot of set methods results in a great deal of tedious coding. The resulting program would have to be recompiled every time you wanted to change something. So the JavaMail designers let you configure JavaMail using a Properties object, which can be read from a file (as is done here), or created in any other way.

The section "Running the Sample Code" for instructions on how to get and unpack the JAR for the sample (ttapr2004.jar). The information that follows concentrates on explaining the code.

First, let's examine the contents of the Properties file (apr2004.props), which structures the input to the program. The comments are self-explanatory.

    # The hostname or IP address of your SMTP server
    mail.smtp.host=smtp.duke.java.sun.com

    # You get lots of debugging information if
    # this is turned on. Comment this line to turn
    # it off.
    mail.debug=true

    # The To email address
    ttapr2004.to=duke@duke.java.sun.com

    # The From email address
    ttapr2004.from=duke@j2ee_fanclub.java.sun.com

    # The subject of the email
    ttapr2004.subject=Declaration of Independence

    # The name of the TEXT file to send
    # This file is included in the distribution
    ttapr2004.txtfile=declaration.txt

    # The name of the HTML file to send
    # This file is included in the distribution
    ttapr2004.htmlfile=declaration.html

Change the hostname and IP address of the SMTP server in the Properties file as appropriate for your environment. Also change the debug and to lines as needed.

To run the program, be sure that the sample code JAR file is in the CLASSPATH, as well as the libraries mail.jar (JavaMail) and activation.jar (the Java Activation Framework). You can download these JAR files from http://java.sun.com/products/javamail/ and http://java.sun.com/javase/technologies/desktop/javabeans/glasgow/jaf.html, respectively. Then, execute the class TwoKindsOfEmail, with the props file as its argument:

    $ # Set classpath...
    $ java TwoKindsOfEmail apr2004.props

The program creates an email containing both files, and sends it to the "to" address. Check that email with one reader that supports HTML, and you'll see a formatted message. Check with a text-only email client, and you'll see text.

Here, for example, is the message presented by an email client that supports HTML (only part of the message is shown).

Here is how the sample code works. In class TwoKindsOfEmail, method sendmail() does virtually all of the work. The first part of the code loads the properties file, and creates and initializes a new MimeMessage:

     Properties props = new Properties();
     props.load(new FileInputStream(propfile));

     Session session = Session.getInstance(props, null);
     MimeMessage msg = new MimeMessage(session);

     InternetAddress from =
        new InternetAddress((String)props.get
                 ("ttapr2004.from"));

      InternetAddress to =
         new InternetAddress((String)props.get
                 ("ttapr2004.to"));

      msg.setFrom(from);
      msg.addRecipient(Message.RecipientType.TO, to);
      msg.setSubject((String)props.get
              ("ttapr2004.subject"));

The next line creates a multipart message. Note that the multipart message is explicitly marked as "alternative". Without this string, you would see both the HTML and text emails in your email browser:

    // Create an "Alternative" Multipart message
    Multipart mp = new MimeMultipart("alternative");

The next section of code loads the text file into memory. It creates a java.mail.BodyPart object with the text file content, and a Content-type of "text/plain". (It uses method getFileBodyPart(), which simply reads a file's contents, constructs a BodyPart object, and sets its content and content-type.)

    // Read text file, load it into a BodyPart, and add it to the
    // message.
    String textfile = (String)props.get
            ("ttapr2004.txtfile");
    BodyPart bp1 = getFileBodyPart
            (textfile, "text/plain");
    mp.addBodyPart(bp1);

The following code does the same thing for the HTML body part:

    // Do the same with the HTML part
    String htmlfile = (String)props.get
            ("ttapr2004.htmlfile");
    BodyPart bp2 = getFileBodyPart
            (htmlfile, "text/html");
    mp.addBodyPart(bp2);

Finally, the method sets the message content to the multipart object, and tells the Transport object to send it:

    // Set the content for the message and transmit
    msg.setContent(mp);
    Transport.send(msg);

Note that the text part is set first. The MIME definition for multipart/alternative recommends ordering the alternatives from the least sophisticated to the most sophisticated. This convention allows each type of client to look through the list of alternative message types, and to display the most sophisticated one it understands. Many text email readers simply show the first part of a multipart/alternative message and hope for the best. Putting simpler content first makes it more likely that the email recipient gets something appropriate.

.
.

EXPERIMENTING WITH JDBC

As most people know, JDBC is a mechanism for portable access to relational databases from Java programs. You can use JDBC in your J2EE applications to access relational databases. This tip presents a sample application that uses JDBC. The tip also recommends some good JDBC practices for portability and flexibility.

JDBC Introduction

The standard procedure for accessing data with JDBC is simple:

  1. Load a driver, usually by classname

  2. Get a Connection to the database. You can get a Connection by asking JNDI for a DataSource by name, and then getting a connection from the DataSource. This procedure will be demonstrated presently in the sample servlet method getConnection.

  3. Get a Statement object from the Connection. Each Statement represents a single SQL command. A Statement is bound to the Connection it came from: it can't be reused for some other Connection.

  4. Perform business functions using Statement methods such as updateQuery (which updates data but does not return results) or executeQuery (which returns a ResultSet of selected data).

  5. If you get a ResultSet, iterate through the results of the query to perform other business functions.

The sample code for this tip is a servlet named jdbcservlet. The servlet connects to a database, allows you to execute arbitrary SQL statements, and presents as a table any results that are returned. The servlet also allows you to store named queries in a table called SAVEDQUERIES.

Creating A Database

Before continuing, note that the data loading script bundled in this application drops and creates tables in the database to which it connects. Be absolutely certain that you are not connecting to a database that contains data you need.

The best way to be sure you are connecting to a safe database is to create a new one that can be discarded when you're through with it. This tip uses PointBase, the Java technology relational database management system (RDBMS) that ships with the standard J2EE 1.4 distribution from Sun. The distribution comes with an integrated PointBase driver, so it's the easiest one to use. If you want, you can use provider-specific tools to create your own database (in that case, you'll only need to change some of the information at deploy time.) To create the database, follow the instructions under "Tip 2: Creating the database" in the section "Running the Sample Code."

Running Queries

On the main page displayed by the sample application, you should see a text box with an SQL query.

Click the Execute SQL button to execute this query.

Only part of the Query Results table is shown.

The result form has three areas:

  • On the top left, a query box.
  • On the top right, a list of stored queries.
  • Underneath, a table of results -- that is, when there are results from executing a query. (Some SQL statements, such as deletes and DDL statements.)

The data in the database come from public-domain sources. It presents some basic statistics about countries in the world, and the people who live there. Have fun coming up with complex questions that you can answer with even more complex SQL queries.

You can save queries you find interesting by typing a name in the Name box and clicking the Save Query button. The query will appear in the Saved queries box on the right. Execute a saved query by clicking its link.

Sample Code

Although this application looks as if it does a lot, it's really quite simple. Here is are some of the key things the code does:

1. Load a driver

The code that loads the driver is in the init method for the servlet:

    public void init(ServletConfig config)
       throws ServletException {
       _config = config;

       // Load driver. If this fails, the rest won't work.
       try {
          String driver =
                   _config.getInitParameter("driver");
          Class.forName(driver).newInstance();
       } catch (Exception e) {
          throw new ServletException(e);
       }
    }

Notice that the driver class name comes from a servlet initialization parameter. This parameter is set in the Web application deployment descriptor, web.xml:

   <servlet>
     <description>Sample servlet</description>
     <display-name>Apr2004Servlet</display-name>
     <servlet-name>Apr2004Servlet</servlet-name>
     <servlet-class>
       com.elucify.tips.apr2004.Apr2004Servlet
     </servlet-class>
     <init-param>
       <description>
         This is the name of the JDBC driver
         used to connect to the database.
       </description>
       <param-name>driver</param-name>
       <param-value>
         com.pointbase.jdbc.jdbcUniversalDriver
       </param-value>
     </init-param>
     ...

Putting the driver class name in the deployment information is good practice. If you need to change the driver class (for example, because you're using another database), you can do so in a deployment tool, instead of having to change the code and recompile it.

The same is true for the database name, user, and password -- and for the same reasons.

2. Connect to a database

The following method is used to get a Connection to the database:

    protected Connection getConnection() throws Exception {
       String dbname = _config.getInitParameter("dbname");
       String user = _config.getInitParameter("user");
       String pass = _config.getInitParameter("pass");

       InitialContext ic = new InitialContext();
       DataSource ds = (DataSource)ic.lookup(dbname);
       Connection conn = ds.getConnection(user, pass);

       return conn;
    }

Defining and Specifying Init Parameters

The deployment descriptor settings for these init parameters look like this:

     <init-param>
       <description>
         This is the JDBC name of the database.
       </description>
       <param-name>dbname</param-name>
       <param-value>jdbc/PointBase</param-value>
     </init-param>

    <init-param>
      <description>
        The name of the database user.
      </description>
      <param-name>user</param-name>
      <param-value>PBPUBLIC</param-value>
    </init-param>

    <init-param>
      <description>
        The password for the user named above.
      </description>
      <param-name>pass</param-name>
      <param-value>PBPUBLIC</param-value>
    </init-param>

Again, placing this information in a deployment descriptor allows you to change the information without recompiling.

In production, you typically wouldn't store a password in a deployment descriptor file. It's usually better to consult with your organization's security personnel to determine how best to manage authentication. The string "jdbc/PointBase" above is the database name. This JDBC resource exists preconfigured in the Sun One application server. If you are using a different application server, see your server documentation to find out the name of the database. (You may have to configure one yourself.)

3. Load the database

Method dataload reads sql statements from a file (data/data.sql) in the WAR archive, and executes them one by one until the file is processed. Much of the code in this method is just string processing -- such as escaping quotes and joining lines. The result of that string work is a String variable called sqlcmd, which is built from a StringBuffer. The method creates a Connection, gets a Statement from it, builds the sqlcmd query string, and then passes that string to sqlcmd.executeUpdate when it has a complete command. The interesting bits of the code follow:

   StringBuffer sbcmd = new StringBuffer();
   String line = "";
   Connection conn = null;

   try {
      // Get connection and statement
      conn = getConnection();
      Statement stmt = conn.createStatement();

      while ((line = in.readLine()) != null) {
         linenumber++;

         // ... line splitting, cleaning, joining, etc.
         // ... result is in sbcmd

          String sqlcmd = new String(sbcmd);
         
          // ... more housekeeping

          stmt.executeUpdate(sqlcmd);

   } catch (Exception e) {
      // ... blah blah
   } finally {
      if (conn != null) {
         try {
            conn.close();
         } catch (SQLException exc) {
            // Not much we can do about it here except...
            System.err.println
              ("Failed closing connection in dataload");
         }
      }
   }

The method dataload code builds SQL statements read from a file, and passes them to Statement.executeUpdate to be executed. The method uses executeUpdate instead of executeQuery because executeUpdate doesn't return results, and the data load script must not contain SELECT statements.

Notice the try/finally block in the method dataload code. This code block follows an important practice: connections should always be closed when you have finished using them. Failing to do so can produce a nasty resource leak. In that case, all of the connections in your server's connection pool are taken up by unclosed connections. Always enclose usage of a Connection object in a try/finally block, and close the connection as needed.

Another important practice for server-side connection management is the following: don't try to implement your connection pool unless you know your server doesn't already have one. Doing your own connection pools is often a waste of development time if the server already pools connections. Also, contention between the server connection pool and the application-level pool can worsen your application's performance. Connection pooling is a job for a server programmer, not an application programmer.

In Web applications, if your server has a connection pool (as most do), you should always release the connection before returning an HTML response. If the server is already managing the shared connection efficiently, there's no reason to hold onto the shared connection to try to save time. You can slow down your application if your servlets are unnecessarily holding on to database connections.

4. Execute SQL statements

SQL statements received from the browser can be SELECT statements that return data, or other statements that do not return data. Method evalprint uses Statement.executeQuery if the incoming SQL is a SELECT statement. It uses executeUpdate otherwise. The code that executes user SQL received from the input form appears below:

   try {
      // Get connection and statement
      conn = getConnection();
      Statement stmt = conn.createStatement();
      ResultSet rs;

      // Execute posted sqlcmd
      if (sqlcmd.trim().toUpperCase().indexOf("SELECT ") == 0) {
         rs = stmt.executeQuery(sqlcmd);
         pw.println("<div class=\"results\">");
         pw.println("<h2>Query Results</h2>");
         printResultSet(pw, rs);
         pw.println("</div>");
      } else {
         stmt.executeUpdate(sqlcmd);
      }

5. Print results

Method printResultSet prints the ResultSet object as an HTML table. The data access interface for ResultSet objects make SQL results easy to handle:

   protected void printResultSet(PrintWriter pw, 
           ResultSet rs)
      throws SQLException {
      ResultSetMetaData rsmd = rs.getMetaData();
      int cols = rsmd.getColumnCount();
      int nrows = 0;

      pw.println("<TABLE class=\"tbl\">");
      pw.println("<TR class=\"hdrtr\">");
      for (int n = 1; n <= cols; n++) {
         pw.print
           ("<TD class=\"hdrcol hdrcol" + n + "\">");
         pw.print(rsmd.getColumnLabel(n));
         pw.println("</TD>");
      }
      pw.println("</TR>");

      while (rs.next()) {
         ++nrows;
         pw.println
           ("<TR class=\"datatr datatr" + nrows + "\">");
         for (int i = 1; i <= cols; i++) {
            pw.print("<TD class=\"datacol datacol" + 
                     i + "\">");
            pw.print(rs.getObject(i).toString());
            pw.println("</TD>");
         }
         pw.println("</TR>");
      }
      pw.println("</TABLE>");

      pw.println("<div class=\"rowcount\">");
      pw.println(nrows + " rows returned.");
      pw.println("</div>");
   }

For more information about JDBC see the "JDBC Basics" lesson in the Java Tutorial. Also see the book "JDBC API Tutorial and Reference, Third Edition" by Fisher, Ellis, and Bruce.

RUNNING THE SAMPLE CODE

Note: For Tip 2, you must start the PointBase database server before you start your J2EE server.

Download the sample archive for these tips. The application's context root is ttapr2004. The downloaded ear file also contains the complete source code for the sample.

You can deploy the application archive (ttapr2004.ear) on the J2EE 1.4 Application Server using the deploytool program or the admin console. You can also deploy it by issuing the asadmin command as follows:

   asadmin deploy install_dir/ttapr2004.ear

Replace install_dir with the directory in which you installed the war file.

You can access the application at http://localhost:8000/ttapr2004.

For a J2EE 1.4-compatible implementation other than the J2EE 1.4 Application Server, use your J2EE product's deployment tools to deploy the application on your platform.

See the index.jsp welcome file for instructions on running the application.

Tip 1: Getting and unpacking the JAR

The sample code for Tip 1 is in the application archive in a JAR file called apr2004-tip1.jar. The easiest way to get the apr2004-tip1.jar file is to deploy the application EAR file as described above, visit the main page, and click on the download link in the first section of the Web page.

The other way to get the JAR file is to use the JAR utility to first extract apr2004.war from ttapr2004.ear, and then extract apr2004-tip1.jar from apr2004.war.

After you have the jar file, extract all of its contents using:

   $ jar xvf apr2004.jar

The result is a directory called "apr2004", which contains the source file with the code, the compiled class, and supporting data files.

Tip 2: Creating the database

The first time you run the program, you must load the data into the database. The database load script is inside the application archive. Simply click the appropriate link on the page to load the data. The database contains over 6000 rows, so it may take a few minutes.

After the data is loaded, you can type SQL statements into the text area and execute them using the Execute SQL button.

You will see data from this tip. You will probably also see sample data from other Sun sample code.

If you click on a saved query in the list, you should see the SQL statement for the query, and the result of running the query.

For example, if you click on the saved query CountryBorders, you should see the following:

.
.
Reader Feedback
Excellent   Good   Fair   Poor  

If you have other comments or ideas for future technical tips, please type them here:

Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.

Have a question about Java programming? Use Java Online Support.

.
.

IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html

Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.

Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

- Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://java.sun.com/developer/EJTechTips/index.html


Copyright 1994-2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by Copyright 1994-2004 Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.