Refactor large Controllers

Implementations of HttpServlet are sometimes informally called Controller classes. They form the entry point of client HTTP requests into a web app. Controller classes should be fairly compact, and their code should in general be at a high level of abstraction. Controller classes do a lot of work, but most of their work should be performed by delegating to other classes. A well designed application will have a Controller which is not excessively lengthy. If you use a framework, the Controller class is often defined as part of the framework, and not by your application.

Examples of tasks which can be delegated by the Controller to another class include:

Example

Here is part of the Controller from the WEB4J framework. It delegates most of its work to:

/**
* Controller that delegates to various framework classes.
*/
public class Controller extends HttpServlet {

  //..elided
  
  /** 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>This method can be overridden, if desired. Most applications will not need 
  * to override this method. 
  * 
  * <P>Operations include :
  * <ul>
  * <li>set the request character encoding to the value configured in <tt>web.xml</tt>
  * <li>get an instance of {@link RequestParser}
  * <li>get its {@link Action}, and execute it
  * <li>perform either a forward or a redirect to its  
  * {@link hirondelle.web4j.action.ResponsePage}
  * <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();
      ApplicationFirewall appFirewall = BuildImpl.forApplicationFirewall();
      appFirewall.doHardValidation(action, requestParser);
      ResponsePage responsePage = action.execute();
      if ( responsePage.getIsRedirect() ) {
        redirect(responsePage, aResponse);
      }
      else {
        forward(responsePage, aRequest, aResponse);
      }
    }
    catch (BadRequestException ex){
      if( Util.textHasContent(ex.getErrorMessage()) ){
        aResponse.sendError(ex.getStatusCode(), ex.getErrorMessage());      
      }
      else {
        aResponse.sendError(ex.getStatusCode());      
      }
    }
    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();
  }

  //..elided
}
 



See Also :
Use Model-View-Controller framework
Parse parameters into domain objects
Command objects
Send trouble-ticket emails
A Web App Framework - WEB4J
Would you use this technique?
Yes   No   Undecided   
© 2014 Hirondelle Systems | Source Code | Contact | License | RSS
Individual code snippets can be used under this BSD license - Last updated on September 21, 2013.
Over 2,000,000 unique IPs last year - Built with WEB4J.
- In Memoriam : Bill Dirani -