Java 1.4 Logging API

   

Building an E-Mail Horizontal Service

Now we are going to build a horizontal service that supports e-mail messages to be sent by clients and eventually sent out to a Simple Mail Transport Protocol (SMTP) server somewhere on the network. It's possible that we might want to store the e-mail message before being sent in case the system crashes after a client sends it, but before it can make its way to the SMTP server.

Although it's not currently a requirement for the e- mails to be sent to anywhere else but the SMTP server, it's possible it will be a requirement somewhere down the road and we would like to plan for it in our design. The following list summarizes the requirements for the e-mail service for the example auction site:

  • Support e-mail messages to be sent to an SMTP server.

  • Support external configuration of the e-mail destination.

  • Future support of attachmentsthey are not currently needed.

  • Client should have a simple interface and a wrapper class for e-mail information.

Developing the E-Mail Service Architecture

Based on the small set of requirements that we have listed, we have arrived at the e-mail service architecture in Figure 21.3.

Figure 21.3. The e-mail component architecture for the example auction site must be configurable.

graphics/21fig03.gif

The main component of the e-mail service architecture is a JMS Queue . Any component, including the Web tier , could generate an e-mail message and send it to the JMS Queue . When a JMS message arrives at the e-mail Queue , a message-driven bean will handle the sending of the e-mail to the SMTP server. Using message-driven beans to handle the work allows the clients to asynchronously send e-mails and at the same time support a large number of clients in a very thread-safe manner.

Building the E-Mail APIs

As before with the logging service, we will first build the e-mail APIs that the clients will use to interact with the e-mail service. Listing 21.16 shows the EmailService class, which is used by the clients to initiate an e-mail message.

Listing 21.16 The EmailService Class used by Clients to Send E-Mail Messages
 /**   * Title:        EmailService<p>   * Description:  This class represents the horizontal email service   *               Component. It contains static methods for generating email   *               messages.<p>   */  package com.que.ejb20.services.email;  import com.que.ejb20.services.email.jms.JMSEmailDelegate;  public class EmailService {   private static EmailDelegate delegate = null;    // The method for all clients to use when they need to send an email. The    // method is synchronized because it lazily initializes the delegate.    public synchronized static void sendEmail( Emailable email )      throws EmailException {     // If the delegate has not been created yet, then do it now.      if ( delegate == null ){       delegate = new JMSEmailDelegate();      }      delegate.sendEmail( email );    }  } 

The EmailService class contains a static method called sendEmail that clients can call and pass an instance of an object that implements the Emailable interface. The EmailService class uses a delegate to handle the implementation. By doing this, the implementations can be switched without affecting the clients or the EmailService class.

Any class that contains information that can be sent as an e-mail message may implement the Emailable interface. The Emailable interface appears in Listing 21.17.

Listing 21.17 A Java Interface Defining Methods That Any Class Wishing to Represent an E-Mail Must Implement
 /**  * Title:        Emailable<p>  * Description:  This interface defines methods that an object that wishses to  *               represent an email message must implement.<p>  */  package com.que.ejb20.services.email;  public interface Emailable extends java.io.Serializable {   public String getToAddress();    public void setToAddress(String newToAddress);    public void setFromAddress(String newFromAddress);    public String getFromAddress();    public void setSubject(String newSubject);    public String getSubject();    public void setBody(String newBody);    public String getBody();  } 

The Emailable interface contains the necessary behavior to send an e-mail message to a SMTP server. When the client needs to send an e-mail message, it can use any class that implements the Emailable interface. If the client doesn't already have an instance of class that implements that interface, it can construct an instance of an EmailMessage . This class implements the Emailable interface and may be sent to the static sendEmail method in the EmailService class. Listing 21.18 shows the EmailMessage class.

Listing 21.18 The Java Class Used to Represent an E-Mail Message
 /**   * Title:         EmailMessage<p>   * Description:   This class encapsulates the data that must be sent in a   *                Email message. This class does not support attachments.   *                This class implements the java.io.Serializable   *                interface so that this object can be marshalled over   *                the network.<p>   */  package com.que.ejb20.services.email;  public class EmailMessage implements Emailable, java.io.Serializable{   // Default Constructor    public EmailMessage() {     super();    }    // Private instance references    private String toAddress;    private String fromAddress;    private String subject;    private String body;    // Public Accessors and Mutators    public String getToAddress() {     return toAddress;    }    public void setToAddress(String newToAddress) {     toAddress = newToAddress;    }    public void setFromAddress(String newFromAddress) {     fromAddress = newFromAddress;    }    public String getFromAddress() {     return fromAddress;    }    public void setSubject(String newSubject) {     subject = newSubject;    }    public String getSubject() {     return subject;    }    public void setBody(String newBody) {     body = newBody;    }    public String getBody() {     return body;    }    // Override the default toString method    public String toString() {     StringBuffer buf = new StringBuffer();      buf.append( "To: " + getToAddress() );      buf.append( "\n" );      buf.append( "From: " + getFromAddress() );      buf.append( "\n" );      buf.append( "Subject: " + getSubject() );      buf.append( "\n" );      buf.append( "Body: " + getBody() );      buf.append( "\n" );      return buf.toString();    }  } 

The EmailMessage class contains the necessary attributes to send an e-mail message to the SMTP server. It also overrides the default toString method so that it can be displayed in a user -friendly format.

When there is a problem during the process of sending an e-mail message, an EmailException may be thrown by the sendEmail method in the EmailService class from Listing 21.16. A client must use a try/catch block around the call to the sendEmail method. The EmailException class is shown in Listing 21.19.

Listing 21.19 An Exception That Is Thrown When a Problem Occurs Sending an
  E-Mail Message  /**   * Title:        EmailException<p>   * Description:  The exception that is thrown when there's a problem with   *               sending emails.   */  package com.que.ejb20.services.email;  public class EmailException extends Exception {   public EmailException( String msg ) {     super( msg );    }  } 

For the e-mail service, we will also use the concept of a delegate as we did with the logging service. With this approach, we can substitute in different delegates in case the requirements change. Listing 21.20 shows the EmailDelegate interface that all e-mail delegates must implement.

Listing 21.20 The EmailDelegate Interface That All Implementations of E-Mail Service Must Implement
 /**   * Title:        EmailDelegate<p>   * Description:  Methods that all implementations of the email service that   *               is responsible for sending emails must implement.   */  package com.que.ejb20.services.email;  public interface EmailDelegate {   public void sendEmail( Emailable emailMessage ) throws EmailException;  } 

Listing 21.21 shows the JMSEmailDelegate that we will be using as the default delegate for our example. This delegate uses JMS and publishes the EmailMessage to a Queue . It's possible that you might want to use a JMS Topic rather than a Queue . If that's the case, you could substitute in a different delegate class or modify this one. For the auction example, we are going to be using a Queue to keep it simple.

Listing 21.21 The Implementation of the EmailDelegate Interface that Uses JMS to Send E-Mail Messages to a Queue
 /**   * Title:        JMSEmailDelegate<p>   * Description:  An Email service delegate that uses JMS to send an Emailable   *               object to it so that it can be sent out to a smtp host.<p>   */  package com.que.ejb20.services.email.jms;  import javax.jms.*;  import javax.naming.*;  import com.que.ejb20.services.email.Emailable;  import com.que.ejb20.services.email.EmailException;  import com.que.ejb20.services.email.EmailDelegate;  public class JMSEmailDelegate implements EmailDelegate {   // JMS Administrative objects    private Queue emailDestination = null;    private QueueSession emailSession = null;    private QueueSender emailSender = null;    // Default Constructor    public JMSEmailDelegate() throws EmailException {     super();      // The Queue Name and Connection Factory names. In production,      // these values belong in a resource file.      String destinationName = "com.que.ejb20book.EmailQueue";     String connectionFactoryName = "com.que.ejb20book.AuctionConnectionFactory";      QueueConnectionFactory connFact = null;      QueueConnection queueConn = null;      Context ctx = null;      try{       // Look up the JMS objects and create a QueueSender        ctx = new InitialContext();        connFact = (QueueConnectionFactory)ctx.lookup( connectionFactoryName );        emailDestination = (Queue)ctx.lookup( destinationName );        queueConn = connFact.createQueueConnection();        emailSession =            queueConn.createQueueSession( false, Session.AUTO_ACKNOWLEDGE );        emailSender = emailSession.createSender( emailDestination );      }catch( NamingException ex ){       ex.printStackTrace();      }catch( JMSException ex ){       ex.printStackTrace();      }    }    // The method that is required by the EmailDelegate interface.    // This is the method that actually sends the email message    public void sendEmail( Emailable emailMessage ) throws EmailException {     try{       ObjectMessage msg = emailSession.createObjectMessage( emailMessage );        emailSender.send( msg );      }catch( Exception ex ){       ex.printStackTrace();        throw new EmailException( "Problem sending email " + emailMessage );      }    }  } 

As with the JMSLoggerDelegate in Listing 21.13, most of the code in the JMSEmailDelegate class is spent connecting to the necessary JMS service. Once a QueueSender is created, the e-mail message is sent. The EmailService class holds onto the delegate in a static reference, so it is created only once and is used by all clients. That's why we added the synchronized keyword to the sendEmail method, so that it can handle multiple threads in a safe manner.

Building the E-Mail Service Message-Driven Bean

For this example, we will be using a message-driven bean to handle the messages that arrive at the e-mail queue. As you learned in Chapter 11, "Message-Driven Beans," the container will manage the number of instances necessary based on the load. Listing 21.22 shows the message-driven bean for the e-mail service example.

Listing 21.22 The Message-Driven Bean That Receives E-Mail Messages from the JMS Queue
 /**   * Title:        EmailServiceBean<p>   * Description:  A Message-Driven bean that listens on a javax.jms.Queue for   *               messages and gets the email message out and sends it off to   *               a smtp host.<p>   */  package com.que.ejb20.services.email.impl;  import java.util.Date;  import java.util.Properties;  import javax.ejb.*;  import javax.jms.*;  import javax.naming.*;  import javax.mail.*;  import javax.mail.internet.*;  import javax.activation.*;  import com.que.ejb20.services.email.Emailable;  public class EmailServiceBean    implements javax.ejb.MessageDrivenBean, MessageListener {   // Instance ref for the beans context    MessageDrivenContext context = null;    // Default Constructor    public EmailServiceBean() {     super();    }    // The required onMessage method from the MessageListener interface    // The onMessage method is not allowed to throw exceptions, so    // we will catch every checked exception and just print out a    // stack trace.    public void onMessage( javax.jms.Message message ){     // Local reference to the javax.mail.Session      javax.mail.Session mailSession = null;      try{   //Make sure it's a type ObjectMessage    if (!(message instanceof ObjectMessage)){     return;    }    // Make sure it's an EmailMessage    Object obj = ((ObjectMessage)message).getObject();    if (!(obj instanceof Emailable)){     return;    }    Emailable email = (Emailable)obj;    Context ctx = new InitialContext();    // Get the properties for this bean from the environment. The    // properties were specified in the env-entry tags in the deployment    // descriptor for this bean    Context myEnv = (Context)ctx.lookup( "java:comp/env" );    String smtpHost = (String)myEnv.lookup( "smtpHost" );    Properties props = new Properties();    props.put( "mail.smtp.host", smtpHost );    // Get a mail session. You would normally get this from    // JNDI, but some servers have a problem with this.    // Each Message Driven bean instance is responsible for    //getting its own unshared javax.mail.Session.    mailSession = javax.mail.Session.getDefaultInstance( props, null );    javax.mail.Message msg = new MimeMessage( mailSession );    // Set the mail properties    msg.setFrom(     new javax.mail.internet.InternetAddress( email.getFromAddress() ) );    InternetAddress[] addresses =       { new InternetAddress(email.getToAddress()) };    msg.setRecipients( javax.mail.Message.RecipientType.TO, addresses );    msg.setSubject( email.getSubject() );    msg.setSentDate( new Date() );    // Create the body text    Multipart parts = new MimeMultipart();    MimeBodyPart mainBody = new MimeBodyPart();    mainBody.setText( email.getBody() );    parts.addBodyPart( mainBody );    // Could also have supported attachments, but not for this version    // it's commented it out.    /*      MimeBodyPart attachmentBody = new MimeBodyPart();      attachmentBody.setText( "This is text in the attachment" );      attachmentBody.addBodyPart( p2 );    */    // Set some header fields    msg.setHeader( "X-Priority", "High" );    msg.setHeader( "Sensitivity", "Company-Confidential" );    msg.setContent( parts );        System.out.println( "Sending mail to " + email.getToAddress() );        Transport.send( msg );      }catch( Exception ex ){       // The onMessage method should not throw any kind of exceptions        // according to the EJB 2.0 specification.        ex.printStackTrace();      }      finally{       mailSession = null;      }    }    public void setMessageDrivenContext( MessageDrivenContext ctx ){     context = ctx;    }    public void ejbRemove(){     // Not used for this bean    }    public void ejbCreate(){     // Not used for this bean    }  } 

Obviously from looking at the EmailServiceBean from Listing 21.22, all the major work is done in the onMessage method. This is the callback method from the container when a message arrives at the JMS destination. Most of what goes on in the onMessage method has to do with using the JavaMail API. We won't go into depth about it in this book, but you can find more information on the API at the following URL:

http://java.sun.com/products/javamail

Note

Some EJB servers allow a client to locate a javax.mail.Session instance from JNDI by performing a lookup in the java:comp/env/mail subcontext, but not all of them handle this correctly. A workaround is to use the approach shown previously and create a nonshared session for a specific client.


Let's summarize what the onMessage is doing in the following steps:

  1. Be sure the message sent to the queue is of type javax.jms.ObjectMessage and that the object within this message implements the Emailable interface. If either condition is false, the message is ignored and the method returns.

  2. Get the beans environment properties using JNDI. These are the properties that are specified in the bean's deployment descriptor and are only accessible by that particular bean. This is a nice way of specifying properties for enterprise bean components .

  3. Create a javax.mail.Session that will be used to create the message.

  4. Create a javax.mail.internet.MimeMessage and its parts. Notice for this implementation we are not supporting attachments, but we could very easily by just adding additional MimeBodyPart objects.

  5. Fill in the MimeMessage with the correct information from the JMS message.

  6. Finally, send the e-mail off to the SMTP server. The SMTP server host was determined by the properties read in from the bean's environment. If the e-mail service needed to be changed, you only have to modify the deployment information and redeploy the bean. Although this might sound like too much to have to do, it really is better than having the values hard-coded in the bean.

Configuring E-Mail Required Properties

As previously mentioned, the bean obtains property information from the deployment descriptor. The descriptor also tells the container which type of JMS destination the message-driven bean is listening on. Listing 21.23 shows the bean's deployment information.

Listing 21.23 The EmailServiceBean 's Deployment Descriptor
 <!DOCTYPE ejb-jar PUBLIC      "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"      "http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">  <ejb-jar>   <enterprise-beans>      <message-driven>        <ejb-name>EmailServiceBean</ejb-name>        <ejb-class>com.que.ejb20.services.email.impl.EmailServiceBean</ejb-class>        <transaction-type>Container</transaction-type>        <message-driven-destination>          <jms-destination-type>javax.jms.Queue</jms-destination-type>        </message-driven-destination>        <env-entry>          <description>some description</description>          <env-entry-name>smtpHost</env-entry-name>          <env-entry-type>java.lang.String</env-entry-type>          <env-entry-value>192.18.97.137</env-entry-value>        </env-entry>          <security-identity>        </security-identity>      </message-driven>   </enterprise-beans>  </ejb-jar> 

Note

You must use a valid host or IP in the smtpHost value in the deployment descriptor. The one in Listing 21.23 should not be used when you run this example.


When it comes time to deploy your message-driven bean, you must use the EJB container's tools to generate specific deployment information for that container. For WebLogic, Listing 21.24 shows what that deployment information would look like for our EmailServiceBean .

Listing 21.24 The EmailServiceBean 's Deployment Descriptor
 <?xml version="1.0"?>  <!DOCTYPE weblogic-ejb-jar PUBLIC      "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"  <!-- Sample MessageDriven bean Weblogic deployment descriptor -->  <weblogic-ejb-jar>    <weblogic-enterprise-bean>      <ejb-name>EmailServiceBean</ejb-name>      <message-driven-descriptor>        <pool>          <max-beans-in-free-pool>200</max-beans-in-free-pool>          <initial-beans-in-free-pool>20</initial-beans-in-free-pool>        </pool>        <destination-jndi-name>              com.que.ejb20book.EmailQueue        </destination-jndi-name>      </message-driven-descriptor>      <jndi-name>EmailServiceBean</jndi-name>    </weblogic-enterprise-bean>  </weblogic-ejb-jar> 

This proprietary deployment descriptor for WebLogic allows the deployer to set the initial number of instances of this bean in the pool, the maximum number, and a few other configuration values like the name of the JMS destination for the bean to listen on.

The final step in the e-mail horizontal service is to build a client test program to test the e-mail service. Assuming that you have all the JMS administration set up correctly and you have configured and deployed the EmailServiceBean , you can run the EmailTest class that's shown in Listing 21.25 and you should get an e-mail sent to the address that you specify on the command line. To run the program, you should type this on the command line:

 java EmailTest bob@testemail.com sue@testemail.com 

Of course, you probably want to use your e-mail address instead of mine when running the example.

Caution

Although some e-mail servers will forward e-mails even if you are not an authorized user, be careful about sending fake e-mails to people you don't know. This is sometimes referred to as e-mail spoofing and you can wind up in tons of trouble with the e-mail police. Make sure that you are sending e-mails only to your friends . In fact, you're probably better off if you only test sending e-mails to yourself. In this way you can make sure you get them and you won't be incarcerated.


Listing 21.25 shows the e-mail test program.

Listing 21.25 A Test Program to Test the E-Mail Horizontal Service
 import com.que.ejb20.services.email.*;  public class EmailTest {   public EmailTest() {     super();    }    public void sendEmail( Emailable msg ) throws EmailException {     EmailService.sendEmail( msg );    }    // The main method for running this class    // Usage: java EmailTest <toAddress> <fromAddress>    public static void main(String[] args) {     if ( args.length < 2 ){       System.out.println( "Usage: java EmailTest <to> <from>" );        System.exit( 0 );      }      EmailTest test = new EmailTest();      Emailable msg = new EmailMessage();      msg.setToAddress( args[0] );      msg.setFromAddress( args[1] );      msg.setSubject( "This is a Test Email" );      msg.setBody( "This is a test email sent by the horizontal service" );      try{       test.sendEmail( msg );      }catch( EmailException ex ){       ex.printStackTrace();      }    }  } 

Each e-mail server is different in how long it takes to forward the e-mail. Normally, it should show up at the destination address within seconds. However, depending on the load, it can take several minutes. Keep checking and after more than a couple of minutes, it probably means that the e-mail server is having trouble or it was never sent. You can turn on a debug option with JavaMail by setting the setDebug flag to true. With this on, it will print out debug information as the JavaMail API talks with the e-mail server.



Special Edition Using Enterprise JavaBeans 2.0
Special Edition Using Enterprise JavaBeans 2.0
ISBN: 0789725673
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net