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 }