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 }