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);
}
}
}