Refactor large Controllers
A Web App Framework WEB4J
Launch other applications
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; } }
TroubleTicket
class would gather together such information, and
a class similar to the above Mailer
would be used to send it to
the webmaster.
Here, a Controller
creates and sends a TroubleTicket
if
Exception
is thrown (a bug occurs)
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(); } }