Send trouble ticket emails

In web applications, sending email is a common task.

Example

Here's an example of a utility class which sends simple emails. It can also validate an email address. It uses an entry in web.xml for an SMTP host name.

import javax.servlet.ServletConfig;
import java.util.Properties;

import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.AddressException;
import javax.mail.internet.MimeMessage;

/**
* Send simple email from the webmaster to a receiver.
*
* <P>The emails sent by this class include sender, receiver, subject, and 
* body. No features such as attachments are supported by this class. If more 
* flexibity is needed, please use {@link javax.mail} directly.
*/
public final class Mailer {

  /**
  * Called upon startup, to allow this class to extract mail server 
  * config information from <code>web.xml</code>.
  */
  public static void init(ServletConfig aConfig){
    fConfig = aConfig;
  }
  
  /**
  * Validate the form of an email address.
  *
  * <P>Return <code>true</code> only if 
  *<ul> 
  * <li> <code>aEmailAddress</code> can successfully construct an 
  * {@link javax.mail.internet.InternetAddress} 
  * <li> when parsed with a "@" delimiter, <code>aEmailAddress</code> contains 
  * two tokens which have content.
  *</ul>
  *
  *<P> The second condition arises since local email addresses, simply of the form
  * "<tt>albert</tt>", for example, are valid but almost always undesired.
  */
  public static boolean isValidEmailAddress(String aEmailAddress){
    if (aEmailAddress == null) return false;
    
    boolean result = true;
    try {
      InternetAddress emailAddr = new InternetAddress(aEmailAddress);
      if (! hasNameAndDomain(aEmailAddress)) {
        result = false;
      }
    }
    catch (AddressException ex){
      result = false;
    }
    return result;
  }
  
  /**
  * Send an email to a single recipient, using a hard-coded Webmaster email address.
  *
  * @param aToAddress satisfies {@link #isValidEmailAddress}.
  * @param aSubject has content.
  * @param aBody has content.
  */
  public void send(String aToAddress, String aSubject, String aBody){
    if (!textHasContent(aSubject) || !textHasContent(aBody)) {
      throw new IllegalArgumentException("Must have content.");
    }

    if (isMailDisabled()){
      log("Mailing is disabled.");
      return;
    }
    
    Session session = Session.getDefaultInstance( getMailServerConfig(), null );
    MimeMessage message = new MimeMessage( session );
    try {
      message.setFrom(new InternetAddress("admin@blah.com"));
      message.addRecipient(Message.RecipientType.TO, new InternetAddress(aToAddress));
      message.setSubject(aSubject);
      message.setText(aBody);
      Transport.send(message); //thread-safe?
    }
    catch (MessagingException ex){
      log("CANNOT SEND EMAIL." + ex);
    }
    log("Mail is sent.");
  }
  
  // PRIVATE
  
  private static ServletConfig fConfig;
  
  /**
  * Identifies the item in web.xml which contains the configured 
  * mail server.
  */
  private static final String fMAIL_SERVER = "MailServer";
  
  /**
  * Identifies the lack of any configured mail server in web.xml.
  * If fMAIL_SERVER takes this value, then mailing is disabled, and 
  * this class will abort all attempts to send email.
  */
  private static final String fNONE = "NONE";
  
  private String getMailServer(){
    return fConfig.getServletContext().getInitParameter(fMAIL_SERVER);
  }
  
  private boolean isMailDisabled(){
    return getMailServer().equalsIgnoreCase(fNONE);
  }

  /**
  * Return the configured mail server in the form of a Properties object.
  */
  private Properties getMailServerConfig(){
    Properties result = new Properties();
    result.put("mail.host", getMailServer());
    return result;
  }
  
  private static boolean hasNameAndDomain(String aEmailAddress){
    String[] tokens = aEmailAddress.split("@");
    return 
      tokens.length == 2 &&
      textHasContent(tokens[0]) && 
      textHasContent(tokens[1])
    ;
  }

  private static void log(Object aObject){
    System.out.println(String.valueOf(aObject));
  }

  private static boolean textHasContent(String aText){
    return aText != null && aText.trim().length()>0;
  }
}
  

A common type of email is a "trouble ticket", sent to the webmaster when a problem occurs in a deployed application. Such an email provides a detailed listing of information which may help to resolve the problem. A TroubleTicket class would gather together such information, and a class similar to the above Mailer would be used to send it to the webmaster.

Example

Here, a Controller creates and sends a TroubleTicket if

This implementation uses a setting in web.xml for throttling down on excessive numbers of similar trouble tickets.
/**
* Servlet Controller.
* Sends email to webmaster when problem occurs, or when response time 
* exceeds a configured value.
*/
public class Controller extends HttpServlet {
  
  @Override public final void init(ServletConfig aConfig) throws ServletException {
    //..elided
    TroubleTicket.init(aConfig, appInfo);
  }

  /** Call {@link #processRequest}.  */
  @Override public final void doGet(
    HttpServletRequest aRequest, HttpServletResponse aResponse
  ) throws ServletException, IOException {
    processRequest(aRequest, aResponse);
  }

  /** Call {@link #processRequest}.  */
  @Override public final void doPost(
    HttpServletRequest aRequest, HttpServletResponse aResponse
  ) throws ServletException, IOException {
    processRequest(aRequest, aResponse);
  }

  /**
  * Handle all HTTP <tt>GET</tt> and <tt>POST</tt> requests.
  * 
  * <P>Operations include :
  * <ul>
  * <li>if an unexpected problem occurs, create a {@link TroubleTicket}, log it, and
  * email it to the webmaster email address configured in <tt>web.xml</tt>
  * <li>if the response time exceeds a configured threshold, build a 
  * {@link TroubleTicket}, log it, and email it to the webmaster address configured 
  * in <tt>web.xml</tt>
  * </ul>
  */
  protected void processRequest(
    HttpServletRequest aRequest, HttpServletResponse aResponse
  ) throws ServletException, IOException {
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.start();
   
    RequestParser requestParser = RequestParser.getInstance(aRequest, aResponse);
    try {
      Action action = requestParser.getWebAction();
      ResponsePage responsePage = action.execute();
      //..elided
    }
    catch (BadRequestException ex){
      //..elided 
    }
    catch (Throwable ex) {
      //Bugs OR rare conditions, for example datastore failure
      logAndEmailSeriousProblem(ex, aRequest);
      aResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
    }
   
    stopwatch.stop();
    if (stopwatch.toValue() >= fPOOR_PERFORMANCE_THRESHOLD) {
      logAndEmailPerformanceProblem(stopwatch.toValue(), aRequest);
    }
  }
  
  /**
  * Inform the webmaster of an unexpected problem with the deployed application.
  * 
  * <P>Typically called when an unexpected <tt>Exception</tt> occurs in 
  * {@link #processRequest}. Uses {@link TroubleTicket#mailToWebmaster}.
  * 
  *  <P>Also, stores the trouble ticket in application scope, for possible 
  *  later examination. 
  */
  protected final void logAndEmailSeriousProblem (
    Throwable ex, HttpServletRequest aRequest
  ) throws AppException {
    TroubleTicket troubleTicket = new TroubleTicket(ex, aRequest);
    fLogger.severe("TOP LEVEL CATCHING Throwable");
    fLogger.severe( troubleTicket.toString() ); 
    log("SERIOUS PROBLEM OCCURRED.");
    log( troubleTicket.toString() );
    aRequest.getSession().getServletContext().setAttribute(
      MOST_RECENT_TROUBLE_TICKET, troubleTicket
    );
    troubleTicket.mailToWebmaster();
  }

  /**
  * Inform the webmaster of a performance problem.
  * 
  * <P>Called only when the response time of a request is above the threshold 
  * value configured in <tt>web.xml</tt>.
  * 
  * <P>Builds a <tt>Throwable</tt> with a description of the problem, then creates and 
  * emails a {@link TroubleTicket} to the webmaster.
  * 
  * @param aMilliseconds response time of a request in milliseconds
  */
  protected final void logAndEmailPerformanceProblem(
    long aMilliseconds, HttpServletRequest aRequest
  ) throws AppException {
    String message = 
      "Response time of web application exceeds configured performance threshold." 
      + NEW_LINE + 
      "Time : " + aMilliseconds + " milliseconds."
    ;
    Throwable ex = new Throwable(message);
    TroubleTicket troubleTicket = new TroubleTicket(ex, aRequest);
    fLogger.severe("Poor response time : " + aMilliseconds + " milliseconds");
    fLogger.severe( troubleTicket.toString() ); 
    log("Poor response time : " + aMilliseconds + " milliseconds");
    log( troubleTicket.toString() );
    troubleTicket.mailToWebmaster();
  }
  
}
 

See Also :
Refactor large Controllers
A Web App Framework WEB4J
Launch other applications