classes/hirondelle/web4j/action/ActionTemplateListAndEdit.java
author Tomas Zeman <tzeman@volny.cz>
Wed, 04 Dec 2013 17:00:31 +0100
changeset 0 3060119b1292
permissions -rw-r--r--
Imported web4j 4.10.0

package hirondelle.web4j.action;

import hirondelle.web4j.model.AppException;
import hirondelle.web4j.request.RequestParameter;
import hirondelle.web4j.request.RequestParser;
import hirondelle.web4j.database.DAOException;

/** 
 <b>Template</b> for "all-in-one" {@link hirondelle.web4j.action.Action}s, which perform
  common operations on a Model Object. 
 
 <P>Typically a single JSP is used, for displaying both a listing of Model Objects, 
 and an accompanying form for editing these Model Objects one at a time. This style 
 is practical when : 
 <ul>
  <li>the number of items in the listing is not excessively large.
  <li>the Model Objects can be rendered reasonably well in a "one-per-line" style. 
  (If the  Model Object itself has a large number of items, it may be difficult to 
  render them well in such a listing.)
 </ul>
 
 <P>The {@link #SupportedOperation}s for this template are a subset of the members of the 
 {@link Operation} enumeration. If other operations are desired, then this template class cannot be used.
  
 <P>This class interacts a bit with its JSP - the form changes from "Add" mode to "Change" mode 
 according to the value of the {@link Operation}.
 
 <P>If an operation is not appropriate in a given case, then simply provide an empty implementation of 
 its corresponding abstract method (or an implementation that throws an 
 {@link java.lang.UnsupportedOperationException}).
 
 <P><span class="highlight">To communicate messages to the end user, the implementation 
 must use the various <tt>addMessage</tt> and <tt>addError</tt> methods</span>.
*/
public abstract class ActionTemplateListAndEdit extends ActionImpl {

  /**
   Constructor.
   
   @param aForward used for {@link Operation#List} and {@link Operation#FetchForChange} 
   operations, and also for <em>failed</em> {@link Operation#Add}, {@link Operation#Change},
   and {@link Operation#Delete} operations. This is the default response.
   @param aRedirect used for <em>successful</em> {@link Operation#Add}, 
   {@link Operation#Change}, and {@link Operation#Delete} operations. 
   @param aRequestParser passed to the superclass constructor.
  */
  protected ActionTemplateListAndEdit (
    ResponsePage aForward, ResponsePage aRedirect, RequestParser aRequestParser
  ){
    super(aForward, aRequestParser);
    fRedirect = aRedirect;
  }

  /**
   The operations supported by this template.
   
   <P>This action supports :
   <ul>
   <li> {@link Operation#List}
   <li> {@link Operation#Add}
   <li> {@link Operation#FetchForChange}
   <li> {@link Operation#Change}
   <li> {@link Operation#Delete}
   </ul>
   
   The source of the <tt>Operation</tt> is described by {@link ActionImpl#getOperation()}.
  */
  public static final RequestParameter SupportedOperation = RequestParameter.withRegexCheck(
    "Operation", "(" + 
      Operation.List + "|" + Operation.Add + "|" + Operation.FetchForChange + "|" + 
      Operation.Change + "|" + Operation.Delete + 
    ")"
  );
   
  /**
   <b>Template</b> method.
   
   <P>In order to clearly understand the operation of this method, here is the 
   core of its implementation, with all abstract methods in <em>italics</em> :
   <PRE>
    if (Operation.List == getOperation() ){
      <em>doList();</em>
    }
    else if (Operation.FetchForChange == getOperation()){
      <em>attemptFetchForChange();</em>
    }
    else if (Operation.Add == getOperation()) {
      <em>validateUserInput();</em>
      if ( ! hasErrors() ){
        <em>attemptAdd();</em>
        ifNoErrorsRedirectToListing();
      }
    }
    else if (Operation.Change == getOperation()) {
      <em>validateUserInput();</em>
      if ( ! hasErrors() ){
        <em>attemptChange();</em>
        ifNoErrorsRedirectToListing();
      }
    }
    else if(Operation.Delete == getOperation()) {
      <em>attemptDelete();</em>
      ifNoErrorsRedirectToListing();
    }
    //Fresh listing WITHOUT a redirect is required if there is an error, 
    //and for successful FetchForChange operations.
    if( hasErrors() || Operation.FetchForChange == getOperation() ){
      <em>doList();</em>
    }
   </PRE>
  */
  @Override public final ResponsePage execute() throws AppException {
    //the default operation is a forward to the nominal page
    if (Operation.List == getOperation()){
      doList();
    }
    else if (Operation.FetchForChange == getOperation()){
      attemptFetchForChange();
    }
    else if (Operation.Add == getOperation()) {
      validateUserInput();
      if ( ! hasErrors() ){
        attemptAdd();
        ifNoErrorsRedirectToListing();
      }
    }
    else if (Operation.Change == getOperation()) {
      validateUserInput();
      if ( ! hasErrors() ){
        attemptChange();
        ifNoErrorsRedirectToListing();
      }
    }
    else if(Operation.Delete == getOperation()) {
      attemptDelete();
      ifNoErrorsRedirectToListing();
    }
    else {
      throw new AssertionError("Unexpected kind of Operation for this Action template : " + getOperation());
    }
    
    //A fresh listing WITHOUT a redirect is required if there is an error, and for 
    //successful FetchForChange operations.
    if( hasErrors() || Operation.FetchForChange == getOperation() ){
      doList();
    }
    
    return getResponsePage();
  }

  /**
   Validate items input by the user into a form.
   
   <P>Applied to {@link Operation#Add} and {@link Operation#Change}. If an error occurs, then 
   <tt>addError</tt> must be called.
   
   <P>Example of a typical implementation :
   <PRE>
  protected void validateUserInput() {
    try {
      ModelFromRequest builder = new ModelFromRequest(getRequestParser());
      fResto = builder.build(Resto.class, RESTO_ID, NAME, LOCATION, PRICE, COMMENT);
    }
    catch (ModelCtorException ex){
      addError(ex);
    }    
  }
   </PRE>
   
   <P>Note that the Model Object constructed in this example (<tt>fResto</tt>) is retained 
   as a field, for later use when applying an edit to the database. This is the recommended style.
  */
  protected abstract void validateUserInput();
  
  /**
   Retrieve a listing of Model Objects from the database (<tt>SELECT</tt> operation).  
  */
  protected abstract void doList() throws DAOException;
  
  /**
   Attempt an <tt>INSERT</tt> operation on the database. The data will first be validated using 
   {@link #validateUserInput}.
  */
  protected abstract void attemptAdd() throws DAOException;
  
  /**
   Attempt to fetch a single Model Object from the database, in preparation for 
   editing it (<tt>SELECT</tt> operation). 
  */
  protected abstract void attemptFetchForChange() throws DAOException;
  
  /**
   Attempt an <tt>UPDATE</tt> operation on the database. The data will first be validated 
   using {@link #validateUserInput()}. 
  */
  protected abstract void attemptChange() throws DAOException;
  
  /**
   Attempt a <tt>DELETE</tt> operation on the database. 
  */
  protected abstract void attemptDelete() throws DAOException;
  
  /**
   Add a dynamic query parameter to the redirect {@link ResponsePage}.
   
   <P>This method will URL-encode the name and value.
  */
  protected void addDynamicParameterToRedirectPage(String aParamName, String aParamValue){
    fRedirect = fRedirect.appendQueryParam(aParamName, aParamValue); //ResponsePage is immutable
  }
  
  // PRIVATE //
  private ResponsePage fRedirect;
  
  private void ifNoErrorsRedirectToListing(){
    if ( ! hasErrors() ) {
      setResponsePage(fRedirect);
    }
  }
}