Enterprise Java Technologies Tech Tips
Tips, Techniques, and Sample Code
Welcome to the Enterprise Java Technologies Tech Tips for
June 25, 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:
* Advanced HTML Email
* Domain-neutral Messaging with JMS 1.1
These tips were developed using the 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 (http://www.elucify.com/),
and co-author of Designing Enterprise Applications with the J2EE
Platform, 2nd Edition (http://java.sun.com/blueprints/guidelines/
designing_enterprise_applications_2e/). Mark Johnson runs an open
forum for discussion of the tips at
http://groups.yahoo.com/group/techtipsarchive/.
You can view this issue of the Tech Tips on the Web at
http://java.sun.com/developer/EJTechTips/2004/tt0625.html.
See the Subscribe/Unsubscribe note at the end of this newsletter
to subscribe to Tech Tips that focus on technologies and products
in other Java platforms.
You can download the sample archive for these tips at
http://java.sun.com/developer/EJTechTips/download/ttjun2004.jar.
Any use of this code and/or information below is subject to the
license terms at
http://developers.sun.com/dispatcher.jsp?uid=6910008.
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.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ADVANCED HTML EMAIL
The April 26, 2004 Tech Tip titled "Using the JavaMail API to
Send HTML Email"
(http://java.sun.com/developer/EJTechTips/2004/tt0426.html#1),
showed how to use the JavaMail API to send HTML email that is
also readable by text browsers. But the email from that tip was
text only -- it didn't include images. In the following tip,
you'll see how to use the JavaMail API to send HTML email that
contains embedded images.
Email Image Links Considered Harmful
You might sometimes receive HTML email that includes images.
Sometimes the message's HTML code links to the image on the
sender's server using an
tag, and a src attribute that
identifies the image's URL, like this:
The browser accesses these images just as if it were displaying
an image in a Web page. Unfortunately, unscrupulous spammers
have used this mechanism as a sneaky way to record who visits
their site. They do this by including a unique id in the image
src URL, and then saving the ID along with your email address.
Such an image URL might look like this:
When you visit the site, the server looks up your email address
by the ID, and singles you out for special treatment.
Unknowingly, you've just told a spammer that you're considering
their product, so they may choose to market to you more
aggressively. Worse, you've told them that your email address is
"live", so it can be sold at a higher price to yet other
spammers.
To protect your privacy, many Web-based (and other) email
clients don't display images in HTML emails. While blocking
image URLs in emails protects your privacy as a user, it can
complicate your life as an application developer. Fortunately,
you have another option: embedded images.
Embedding Images in HTML Emails
An alternative to placing absolute URLs to images in your HTML
is to include the images as attachments to the email. You then
use relative URLs to those images in your HTML code. The email
format for attachments is defined by MIME (Multipurpose Internet
Email Extensions), the standard protocol specifications for
Internet email.
MIME has a mechanism that can be used to send an email
containing HTML, plus attachments that contain the images
the HTML references. A specification called RFC 2387
(http://www.ietf.org/rfc/rfc2387.txt) defines the MIME content
type "multipart/related". A message in multipart/related format
contains a collection of objects that are meant to be used
together. For the purposes of this tip, that means an HTML file
and the images it uses.
Each attachment in a multipart/related message has a unique
identifier called its content-id. The content-id is defined by
the application that sends the email, and is assigned when the
message is constructed. The HTML can reference the image in an
attachment by using the protocol prefix cid: plus the content-id
of the attachment. For example, if an attachment has content-id
image.part.1@x.org, then an HTML file in the same message could
use the image inline with the following tag:
A mail reading program (either a standalone program, or a
web-based email interface) would render the tag above using the
image with the given content-id.
Providing a Text Alternative
As described in the April 2004 tip, you can use a
multipart/alternative content type to include alternative
versions of the same object. In doing this, you allow the
receiving program (the mail reader) to choose the most
appropriate content type. The "most appropriate" content type
may be defined by the capabilities of the mail reader or by user
preferences.
The sample code that accompanies this tip includes both a text
version of the transmitted file and an HTML version with
embedded images. The content-type of the message itself is
multipart/alternative. The message's first body part is the text
alternative. The second body part is the HTML alternative.
Images are included in the second part (the HTML) by defining
the second part's content-type as multipart/related. The first
related body part is the HTML code (text/html), and subsequent
body parts are attachments containing any images (image/jpg)
used by the first (HTML) related body part.
JavaMail Sample Code
The class that sends the email, HtmlEmailer.java, uses the
JavaMail API to send an email with the structure defined above.
The program takes a single argument: the name of a properties
file that configures the program's behavior. The first section
of the code loads the properties file and uses its values to
create and initialize 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
("ttjun2004.from"));
InternetAddress to =
new InternetAddress((String)props.get
("ttjun2004.to"));
msg.setFrom(from);
msg.addRecipient(Message.RecipientType.TO, to);
msg.setSubject((String)props.get("ttjun2004.subject"));
The next section of the code creates the multipart/alternative
section of the message by constructing a MimeMultipart object.
It also creates the first alternative body part from the text
file name supplied in the props file.
// Create a "related" Multipart message
Multipart mp = new MimeMultipart("alternative");
// Read text file, load it into a BodyPart,
// and add it to the message.
String textfile = (String)props.get("ttjun2004.txtfile");
BodyPart alt_bp1 = getFileBodyPart(textfile);
mp.addBodyPart(alt_bp1);
Method getFileBodyPart is simply a convenience method for
creating body parts to use as file attachments:
public BodyPart getFileBodyPart(String filename)
throws javax.mail.MessagingException {
BodyPart bp = new MimeBodyPart();
bp.setDataHandler(new DataHandler
(new FileDataSource(filename)));
return bp;
}
The DataHandler loads the file. The JavaMail classes identify
and correctly set the file's Content-type header.
Returning to the class's sendmail method, the next several lines
of code create a multipart/related Multipart container, and load
it with its first body part: the HTML file contents.
// Include an HTML version with images. The HTML file
// is a Multipart of type "related". Inside it are
// two BodyParts: the HTML file and an image
String htmlfile = (String)props.get("ttjun2004.htmlfile");
Multipart html_mp = new MimeMultipart("related");
// Get the HTML file
BodyPart rel_bph = getFileBodyPart(htmlfile);
html_mp.addBodyPart(rel_bph);
The next task is to get the image file. The simple example
program allows only a single image file, but more files would be
easy to add. The properties file contains the name of the image
file to attach, and also defines the content-id. The content-id
must match the content-id used by the HTML file. The HTML file
can use the same content-id multiple times, but the content-id
should be globally unique: no two content-ids should ever be the
same.
The code below creates a new MimeBodyPart for the image, and
loads it with a FileDataSource that acquires the image file
contents. The code then initializes several of the image
properties. It then adds the image as the second related part of
the HTML alternative.
// Get the image file
String imagefile = (String)props.get("ttjun2004.imagefile");
String cid = (String)props.get("ttjun2004.cid");
MimeBodyPart rel_bpi = new MimeBodyPart();
FileDataSource ifds = new FileDataSource(imagefile);
// Initialize and add the image file to the html body part
rel_bpi.setFileName(ifds.getName());
rel_bpi.setText("Image 1");
rel_bpi.setDataHandler(new DataHandler(ifds));
rel_bpi.setHeader("Content-ID", "<" + cid + ">");
rel_bpi.setDisposition("inline");
html_mp.addBodyPart(rel_bpi);
Two of the initializations above merit special attention. First,
if the Content-ID header is not set, the image will not be
identifiable by the HTML file. In this case, the image will not
appear. By convention, the Content-ID is enclosed in
angle-brackets. Also, a MIME message's content-id must be
URL-encoded (using the same encoding as an HTTP GET query
string). Second, the call to setDisposition("inline") configures
the image to appear inline in the HTML, rather than separately
at the bottom of the message. This is necessary because some
mail readers show images inline only when explicitly told to do
so.
The final few lines of the sendmail method create a new
MimeBodyPart to wrap the MimeMultipart that forms the second
alternative in the message. This code adds the second
alternative to the main message body, adds the main message body
to the Message object, and then uses the JavaMail Transport
class to send the message.
// Create the second BodyPart of the multipart/alternative,
// set its content to the html multipart, and add the
// second bodypart to the main multipart.
BodyPart alt_bp2 = new MimeBodyPart();
alt_bp2.setContent(html_mp);
mp.addBodyPart(alt_bp2);
// Set the content for the message and transmit
msg.setContent(mp);
Transport.send(msg);
The default content (text file, html file, and image) simulates
a newsletter that provides a "Space Image of the Day". In this
case, the Space Image of the Day" is a picture of the planet
Saturn, accompanied by some text describing the picture.
Experiment with this class by defining your own HTML content,
and linking and attaching images. Don't forget to change the
parameters in the properties file to reflect your changes.
See the section "Running the Sample Code" for instructions on
how to run the sample program.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DOMAIN-NEUTRAL MESSAGING WITH JMS 1.1
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Two previous Enterprise Java Technologies Tech Tips discussed
the two "domains" of JMS messaging: point-to-point
messaging with Queues, and publish/subscribe messaging with
Topics. (See "Using JMS Queues"
(http://java.sun.com/developer/EJTechTips/2003/tt0311.html#2)
and "Publish/Subscribe Messaging With JMS Topics"
(http://java.sun.com/developer/EJTechTips/2003/tt0415.html#1).)
JMS 1.1 unifies these two domains, allowing you to write code
that works for either or both messaging domains. You can still
use the older 1.0 interfaces, but writing code to the newer
JMS 1.1 API makes your messaging code more flexible. This tip
shows how to write JMS messaging code that works without
modification in either domain.
Introduction
JMS 1.1 still offers both messaging domains that were offered in
JMS 1.0: point-to-point and publish/subscribe. In JMS 1.1, these
domains still use Queues and Topics, respectively. The semantics
are also no different than they were in JMS 1.0. As before,
point-to-point messaging is still reliable, and is always a
1-to-1 communication. Publish/subscribe is still 1-to-many. The
difference between JMS 1.1 and JMS 1.0 is that you can use the
same code written with the JMS 1.1 APIs for point-to-point
communication with Queues, or publish/subscribe communication
with Topics. Before examining the new API, let's review JMS 1.0
messaging interfaces.
Point-to-Point Messaging with Queues
Point-to-point messaging always involves exactly one sender and
one receiver for any delivered message. A sender can send
messages to many receivers, and a receiver can receive messages
from multiple senders. However, there is only one sender and
one receiver associated with an individual message. The class
that serves as the rendezvous point for the message delivery is
called a Queue. A Queue ensures that each message is delivered
exactly once (or zero times, if it expires) to only one
receiver. Only one receiver can connect to a Queue at any one
time. The JMS provider (the server that implements the JMS
interfaces) blocks attempts by receivers to connect to a busy
Queue.
Typically, here are the steps taken by a sender to send a
message to a Queue:
1. Look up a QueueConnectionFactory by name using the JNDI API.
2. Look up the Queue by name using the JNDI API.
3. Use the QueueConnectionFactory to create a QueueConnection.
4. Use the Connection to create a QueueSession.
5. Get a QueueSender and a Message object from the QueueSession.
6. Initialize Message with data to send.
7. Use the QueueSender to send the Message object.
Typically, here are the steps that the JMS 1.0 queue receiver
takes:
1. Look up a QueueConnectionFactory by name using the JNDI API.
2. Look up the Queue by name using the JNDI API.
3. Use the QueueConnectionFactory to create a QueueConnection.
4. Use the Connection to create a QueueSession.
5. Get a QueueReceiver from the session.
6. Call QueueReceiver.receive, which returns a Message.
7. Use the Message data.
Publish/Subscribe Messaging with Topics
Publish/subscribe messaging delivers each message from a single
publisher to several subscribers. The rendezvous point for
publishers and subscribers is called a Topic. A Topic ensures
that each message is delivered exactly one (or zero times, if it
expires) to every receiver that was listening when the Message
was published to the Topic. A feature called "durable"
subscriptions even allow publishers to send Messages to
subscribers that are not running when the message is sent. The
next time such a subscriber connects, the messages are
delivered.
Here are the typical steps taken by a publisher for
publish/subscribe message delivery:
1. Look up a TopicConnectionFactory by name using the JNDI API.
2. Look up the Topic by name using the JNDI API.
3. Use the TopicConnectionFactory to create a TopicConnection .
4. Use the Connection to create a TopicSession.
5. Get a TopicPublisher and a Message object from the
TopicSession.
6. Initialize the Message object with data to send.
7. Use the TopicPublisher to publish the Message object.
Note how similar the steps are to the steps for sending a
message to a Queue.
To receive a message from a Topic, a subscriber typically takes
the following actions:
1. Look up a TopicConnectionFactory by name using the JNDI API.
2. Look up the Topic by name using the JNDI API.
3. Use the TopicConnectionFactory to create a TopicConnection.
4. Use the Connection to create a TopicSession.
5. Get a TopicSubscriber from the session.
6. Call TopicSubscriber.receive, which returns a Message.
7. Use the Message data.
If you look at the sequence of steps for the all of these tasks,
you'll notice that the steps to send messages on Queues and
Topics are almost identical. Similarly, the steps for receiving
messages from Queues and Topics are also almost identical.
Sample Code
The sample code for this tip consists of message sending class,
JMSSender, and a receiving class, JMSReceiver. These classes can
be used to send message using either Queues (point-to-point) or
Topics (pub/sub). The JMS 1.1 APIs make this unification
possible by using the superclasses of each of the
domain-specific classes. The superclasses do not refer to the
domain. Notice in sample code excerpts below how the steps in
the send or receive process match the steps listed in the
previous discussion of "Point-to-Point Messaging with Queues"
and "Publish/Subscribe Messaging with Topics."
The sequence of steps taken by the JMSSender class looks
something like this:
1. Look up the ConnectionFactory by name. The ConnectionFactory
can be either a TopicConnectionFactory or a
QueueConnectionFactory. A ConnectionFactory is a named object
on the server that can create a connection to a JMS server.
The application server administrator creates a
ConnectionFactory using the server vendor's administration
tools. A program that uses JMS can look up a ConnectionFactory
by name, and use it to create a JMS server connection.
ConnectionFactory connectionFactory =
(ConnectionFactory)jndiContext.lookup
(connectionFactoryName);
2. Look up the Destination by name. The Destination can be
either a Queue or a Topic.
Destination dest =
(Destination)jndiContext.lookup(destName);
3. Use a ConnectionFactory to create a Connection. The
connection can be either a TopicConnection or a
QueueConnection. A Destination is a named object on the
server to which messages can be sent, using a connection to
that server.
Connection connection =
connectionFactory.createConnection();
4. Use the Connection to create a Session. The first argument
indicates that the session is not transactional. The second
argument makes message delivery acknowledgement automatic.
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
5. Get a Producer and a Message object from the Session.
TextMessage message = session.createTextMessage();
Producer producer = session.createProducer(dest);
6. Initialize Message with data to send
// within a loop...
message.setText(line);
7. Use the Producer to publish the Message object
producer.send(message);
The message is sent out to the Queue or published on the Topic,
assuming that the appropriate resource names are supplied on the
program's command line. (See "Running the Sample Code" below for
further details about these resource names.)
The receiver class, JMSReceiver, also works across domains, as
shown in the following excerpt from the sample code class
JMSReceiver.java. The sequence of steps taken by the JMSReceiver
class looks like this:
Steps 1-4: Identical to those for the JMSSender class.
5. Get a MessageConsumer from the Session. Depending on the
Session, the Session will return either a QueueReceiver or a
TopicSubscriber,
consumer = session.createConsumer(dest);
6. Call MessageConsumer.receive, which returns a Message. The
argument "1000" means to wait 1000 ms for a response. (This
code is in a loop.)
Message m = consumer.receive(1000);
7. Use the Message data
String txt = message.getText();
Sample Program Experiments
To experiment with the sample programs, you need to first do
some setup work. See the section "Running the Sample Code"
before trying the experiments below.
The sample programs both take two arguments: the name of a
ConnectionFactory, and the name of a Destination. You use vendor
tools to create ConnectionFactories and Destinations manually.
Configure them to work with either Queues or Topics, depending
on your application--both will work.
When you run either the JMSSender or JMSReceiver, you need to
provide as command-line arguments the names of the
ConnectionFactory and the Destination. For example, to send with
topics, execute the following command:
java JMSSender jms/TopicConnectionFactory jms/Topic
This assumes that you have configured the factory and
destination with those names.
To receive with topics, execute the following command:
java JMSReceive jms/TopicConnectionFactory jms/Topic
After you enter the appropriate command, type lines of text into
the standard input of the JMSSender. It will send them through
the Destination to the JMSReceiver. If you run the programs with
the names of a TopicConnectionFactory and a Topic, you can run
multiple receivers. Try creating a QueueConnectionFactory and a
Queue and verify that messages are delivered. You'll find that
you get an exception if you try to start a second JMSReceiver
for that Queue,
For further information about JMS 1.1, see the JMS page
(http://java.sun.com/products/jms/index.jsp).
If you're attending the 2004 JavaOne Conference
(http://java.sun.com/javaone/sf) here are two Birds-of-a-Feather
(BOF) sessions that cover JMS-related topics:
BOF-2130 Effectively Using Java Message Service API for
Asynchronous Business Processing
BOF-2280 Reliable Messaging Cookbook: How to Make Sure Your
Java Message Service (JMS) Messages Absolutely,
Positively, Get There No Matter What
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RUNNING THE SAMPLE CODE
Download the sample archive for these tips (from
http://java.sun.com/jdc/EJTechTips/download/ttjun2004.jar).
The downloaded JAR file also contains the complete source code
for the sample.
1. Extract all files from the sample archive:
jar xvf ttjun2004.jar
This will create a new directory, ttjun2004
2. Change to the extracted directory:
cd ttjun2004
To run Tip 1:
From the command line:
1. Edit the properties file jun2004 to customize to your
environment.
2. Run the program, including ".", mail.jar and activation.jar
in the classpath (enter the following command on one line):
java -classpath ".":$J2EE_HOME/lib/activation.jar:
$J2EE_HOME/lib/mail.jar" HtmlEmailer jun2004.props
The target destination identified in the properties file
should receive an email that displays a picture of the planet
Saturn, accompanied by some text describing the picture.
To run tip 2:
1. Use your JMS provider's tools to create a
QueueConnectionFactory and a Queue, naming them jms/QCF and
jms/Queue, respectively.
If you are using the J2EE 1.4 reference implementation, you
can create the server objects in steps 1 and 2 by using the
Admin Console as described in the section "Creating JMS
Administered Objects"
(http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JMS5.html#wp80290)
in the J2EE 1.4 Tutorial.
2. Create a TopicConnectionFactory and a Topic, naming them
jms/TCF and jms/Topic, respectively.
3. Package the JMSSender and JMSReceiver classes as application
clients. If you're using the J2EE 1.4 reference
implementation, you can use the deploytool to do the
packaging, as described in the section "Packaging the Clients"
(http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JMS5.html#wp92094).
in the J2EE 1.4 Tutorial. When you do the packaging, make sure
that the file name you specify for JMSSender is JMSSender.jar
and the AppClientName is JMSSender. For JMSReceiver, the file
name is JMSReceiver.jar and the AppClientName is JMSReceiver.
4. The JMSSender and JMSReceiver clients take the name of a
connection factory and the name of a Queue or Topic as their
two arguments. For the sender:
appclient -client JMSSender.jar jms/TCF jms/Topic
Use the same arguments for the receiver:
appclient -client JMSReceiver.jar jms/TCF jms/Topic
The sender will prompt you to enter one or more messages.
You'll see that each message you send will be received by
the receiver.
. . . . . . . . . . . . . . . . . . . . . . .
Please read our Terms of Use and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://developers.sun.com/dispatcher.jsp?uid=6910008
PRIVACY STATEMENT:
Sun respects your online time and privacy (http://sun.com/privacy).
You have received this based on your e-mail preferences. If you
would prefer not to receive this information, please follow the
steps at the bottom of this message to unsubscribe.
* FEEDBACK
Comments? Send your feedback on the Enterprise Java Technologies
Tech Tips to:
http://developers.sun.com/contact/feedback.jsp?category=sdn
* SUBSCRIBE/UNSUBSCRIBE
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,
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions),
choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions page,
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions),
uncheck the appropriate checkbox, and click "Update".
- To use our one-click unsubscribe facility, see the link at
the end of this email:
- ARCHIVES
You'll find the Enterprise Java Technologies Tech Tips archives at:
http://java.sun.com/developer/EJTechTips/index.html
- COPYRIGHT
Copyright 2004 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html
Enterprise Java Technologies Tech Tips
June 25, 2004
Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks
or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries.