001    package hirondelle.web4j.action;
002    
003    import hirondelle.web4j.model.AppException;
004    import hirondelle.web4j.request.RequestParameter;
005    import hirondelle.web4j.request.RequestParser;
006    import hirondelle.web4j.database.DAOException;
007    
008    /** 
009     <b>Template</b> for "all-in-one" {@link hirondelle.web4j.action.Action}s, which perform
010      common operations on a Model Object. 
011     
012     <P>Typically a single JSP is used, for displaying both a listing of Model Objects, 
013     and an accompanying form for editing these Model Objects one at a time. This style 
014     is practical when : 
015     <ul>
016      <li>the number of items in the listing is not excessively large.
017      <li>the Model Objects can be rendered reasonably well in a "one-per-line" style. 
018      (If the  Model Object itself has a large number of items, it may be difficult to 
019      render them well in such a listing.)
020     </ul>
021     
022     <P>The {@link #SupportedOperation}s for this template are a subset of the members of the 
023     {@link Operation} enumeration. If other operations are desired, then this template class cannot be used.
024      
025     <P>This class interacts a bit with its JSP - the form changes from "Add" mode to "Change" mode 
026     according to the value of the {@link Operation}.
027     
028     <P>If an operation is not appropriate in a given case, then simply provide an empty implementation of 
029     its corresponding abstract method (or an implementation that throws an 
030     {@link java.lang.UnsupportedOperationException}).
031     
032     <P><span class="highlight">To communicate messages to the end user, the implementation 
033     must use the various <tt>addMessage</tt> and <tt>addError</tt> methods</span>.
034    */
035    public abstract class ActionTemplateListAndEdit extends ActionImpl {
036    
037      /**
038       Constructor.
039       
040       @param aForward used for {@link Operation#List} and {@link Operation#FetchForChange} 
041       operations, and also for <em>failed</em> {@link Operation#Add}, {@link Operation#Change},
042       and {@link Operation#Delete} operations. This is the default response.
043       @param aRedirect used for <em>successful</em> {@link Operation#Add}, 
044       {@link Operation#Change}, and {@link Operation#Delete} operations. 
045       @param aRequestParser passed to the superclass constructor.
046      */
047      protected ActionTemplateListAndEdit (
048        ResponsePage aForward, ResponsePage aRedirect, RequestParser aRequestParser
049      ){
050        super(aForward, aRequestParser);
051        fRedirect = aRedirect;
052      }
053    
054      /**
055       The operations supported by this template.
056       
057       <P>This action supports :
058       <ul>
059       <li> {@link Operation#List}
060       <li> {@link Operation#Add}
061       <li> {@link Operation#FetchForChange}
062       <li> {@link Operation#Change}
063       <li> {@link Operation#Delete}
064       </ul>
065       
066       The source of the <tt>Operation</tt> is described by {@link ActionImpl#getOperation()}.
067      */
068      public static final RequestParameter SupportedOperation = RequestParameter.withRegexCheck(
069        "Operation", "(" + 
070          Operation.List + "|" + Operation.Add + "|" + Operation.FetchForChange + "|" + 
071          Operation.Change + "|" + Operation.Delete + 
072        ")"
073      );
074       
075      /**
076       <b>Template</b> method.
077       
078       <P>In order to clearly understand the operation of this method, here is the 
079       core of its implementation, with all abstract methods in <em>italics</em> :
080       <PRE>
081        if (Operation.List == getOperation() ){
082          <em>doList();</em>
083        }
084        else if (Operation.FetchForChange == getOperation()){
085          <em>attemptFetchForChange();</em>
086        }
087        else if (Operation.Add == getOperation()) {
088          <em>validateUserInput();</em>
089          if ( ! hasErrors() ){
090            <em>attemptAdd();</em>
091            ifNoErrorsRedirectToListing();
092          }
093        }
094        else if (Operation.Change == getOperation()) {
095          <em>validateUserInput();</em>
096          if ( ! hasErrors() ){
097            <em>attemptChange();</em>
098            ifNoErrorsRedirectToListing();
099          }
100        }
101        else if(Operation.Delete == getOperation()) {
102          <em>attemptDelete();</em>
103          ifNoErrorsRedirectToListing();
104        }
105        //Fresh listing WITHOUT a redirect is required if there is an error, 
106        //and for successful FetchForChange operations.
107        if( hasErrors() || Operation.FetchForChange == getOperation() ){
108          <em>doList();</em>
109        }
110       </PRE>
111      */
112      @Override public final ResponsePage execute() throws AppException {
113        //the default operation is a forward to the nominal page
114        if (Operation.List == getOperation()){
115          doList();
116        }
117        else if (Operation.FetchForChange == getOperation()){
118          attemptFetchForChange();
119        }
120        else if (Operation.Add == getOperation()) {
121          validateUserInput();
122          if ( ! hasErrors() ){
123            attemptAdd();
124            ifNoErrorsRedirectToListing();
125          }
126        }
127        else if (Operation.Change == getOperation()) {
128          validateUserInput();
129          if ( ! hasErrors() ){
130            attemptChange();
131            ifNoErrorsRedirectToListing();
132          }
133        }
134        else if(Operation.Delete == getOperation()) {
135          attemptDelete();
136          ifNoErrorsRedirectToListing();
137        }
138        else {
139          throw new AssertionError("Unexpected kind of Operation for this Action template : " + getOperation());
140        }
141        
142        //A fresh listing WITHOUT a redirect is required if there is an error, and for 
143        //successful FetchForChange operations.
144        if( hasErrors() || Operation.FetchForChange == getOperation() ){
145          doList();
146        }
147        
148        return getResponsePage();
149      }
150    
151      /**
152       Validate items input by the user into a form.
153       
154       <P>Applied to {@link Operation#Add} and {@link Operation#Change}. If an error occurs, then 
155       <tt>addError</tt> must be called.
156       
157       <P>Example of a typical implementation :
158       <PRE>
159      protected void validateUserInput() {
160        try {
161          ModelFromRequest builder = new ModelFromRequest(getRequestParser());
162          fResto = builder.build(Resto.class, RESTO_ID, NAME, LOCATION, PRICE, COMMENT);
163        }
164        catch (ModelCtorException ex){
165          addError(ex);
166        }    
167      }
168       </PRE>
169       
170       <P>Note that the Model Object constructed in this example (<tt>fResto</tt>) is retained 
171       as a field, for later use when applying an edit to the database. This is the recommended style.
172      */
173      protected abstract void validateUserInput();
174      
175      /**
176       Retrieve a listing of Model Objects from the database (<tt>SELECT</tt> operation).  
177      */
178      protected abstract void doList() throws DAOException;
179      
180      /**
181       Attempt an <tt>INSERT</tt> operation on the database. The data will first be validated using 
182       {@link #validateUserInput}.
183      */
184      protected abstract void attemptAdd() throws DAOException;
185      
186      /**
187       Attempt to fetch a single Model Object from the database, in preparation for 
188       editing it (<tt>SELECT</tt> operation). 
189      */
190      protected abstract void attemptFetchForChange() throws DAOException;
191      
192      /**
193       Attempt an <tt>UPDATE</tt> operation on the database. The data will first be validated 
194       using {@link #validateUserInput()}. 
195      */
196      protected abstract void attemptChange() throws DAOException;
197      
198      /**
199       Attempt a <tt>DELETE</tt> operation on the database. 
200      */
201      protected abstract void attemptDelete() throws DAOException;
202      
203      /**
204       Add a dynamic query parameter to the redirect {@link ResponsePage}.
205       
206       <P>This method will URL-encode the name and value.
207      */
208      protected void addDynamicParameterToRedirectPage(String aParamName, String aParamValue){
209        fRedirect = fRedirect.appendQueryParam(aParamName, aParamValue); //ResponsePage is immutable
210      }
211      
212      // PRIVATE //
213      private ResponsePage fRedirect;
214      
215      private void ifNoErrorsRedirectToListing(){
216        if ( ! hasErrors() ) {
217          setResponsePage(fRedirect);
218        }
219      }
220    }