/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.portlet.mvc;

import java.util.HashMap;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.util.PortletUtils;

/**
 * Form controller for typical wizard-style workflows.
 *
 * <p>In contrast to classic forms, wizards have more than one form view page.
 * Therefore, there are various actions instead of one single submit action:
 * <ul>
 * <li>finish: trying to leave the wizard successfully, i.e. performing its
 * final action, and thus needing a valid state;
 * <li>cancel: leaving the wizard without performing its final action, and
 * thus without regard to the validity of its current state;
 * <li>page change: showing another wizard page, e.g. the next or previous
 * one, with regard to "dirty back" and "dirty forward".
 * </ul>
 *
 * <p>Finish and cancel actions can be triggered by request parameters, named
 * PARAM_FINISH ("_finish") and PARAM_CANCEL ("_cancel"), ignoring parameter
 * values to allow for HTML buttons. The target page for page changes can be
 * specified by PARAM_TARGET, appending the page number to the parameter name
 * (e.g. "_target1"). The action parameters are recognized when triggered by
 * image buttons too (via "_finish.x", "_abort.x", or "_target1.x").
 *
 * <p>The current page number will be stored in the session. It can also be
 * specified as request parameter PARAM_PAGE ("_page") in order to properly handle
 * usage of the back button in a browser: In this case, a submission will always
 * contain the correct page number, even if the user submitted from an old view.
 *
 * <p>The page can only be changed if it validates correctly, except if a
 * "dirty back" or "dirty forward" is allowed. At finish, all pages get
 * validated again to guarantee a consistent state.
 *
 * <p>Note that a validator's default validate method is not executed when using
 * this class! Rather, the {@code validatePage} implementation should call
 * special {@code validateXXX} methods that the validator needs to provide,
 * validating certain pieces of the object. These can be combined to validate
 * the elements of individual pages.
 *
 * <p>Note: Page numbering starts with 0, to be able to pass an array
 * consisting of the corresponding view names to the "pages" bean property.
 *
 * <p>Parameters indicated with {@code setPassRenderParameters} will be present
 * for each page.  If there are render parameters you need in {@code renderFinish}
 * or {@code renderCancel}, then you need to pass those forward from the
 * {@code processFinish} or {@code processCancel} methods, respectively.

 * @author Juergen Hoeller
 * @author John A. Lewis
 * @since 2.0
 * @see #setPages
 * @see #validatePage
 * @see #processFinish
 * @see #processCancel
 * @deprecated as of Spring 3.0, in favor of annotated controllers
 */
@Deprecated
public abstract class AbstractWizardFormController extends AbstractFormController {

	/**
	 * Parameter triggering the finish action.
	 * Can be called from any wizard page!
	 */
	public static final String PARAM_FINISH = "_finish";

	/**
	 * Parameter triggering the cancel action.
	 * Can be called from any wizard page!
	 */
	public static final String PARAM_CANCEL = "_cancel";

	/**
	 * Parameter specifying the target page,
	 * appending the page number to the name.
	 */
	public static final String PARAM_TARGET = "_target";

	/**
	 * Parameter specifying the current page as value. Not necessary on
	 * form pages, but allows to properly handle usage of the back button.
	 * @see #setPageAttribute
	 */
	public static final String PARAM_PAGE = "_page";


	private String[] pages;

	private String pageAttribute;

	private boolean allowDirtyBack = true;

	private boolean allowDirtyForward = false;


	/**
	 * Create a new AbstractWizardFormController.
	 * <p>"sessionForm" is automatically turned on, "validateOnBinding"
	 * turned off, and "cacheSeconds" set to 0 by the base class
	 * (-> no caching for all form controllers).
	 */
	public AbstractWizardFormController() {
		// AbstractFormController sets default cache seconds to 0.
		super();

		// Always needs session to keep data from all pages.
		setSessionForm(true);

		// Never validate everything on binding ->
		// wizards validate individual pages.
		setValidateOnBinding(false);
	}

	/**
	 * Set the wizard pages, i.e. the view names for the pages.
	 * The array index is interpreted as page number.
	 * @param pages view names for the pages
	 */
	public final void setPages(String[] pages) {
		if (pages == null || pages.length == 0) {
			throw new IllegalArgumentException("No wizard pages defined");
		}
		this.pages = pages;
	}

	/**
	 * Return the wizard pages, i.e. the view names for the pages.
	 * The array index corresponds to the page number.
	 * <p>Note that a concrete wizard form controller might override
	 * {@code getViewName(PortletRequest, Object, int)} to
	 * determine the view name for each page dynamically.
	 * @see #getViewName(PortletRequest, Object, int)
	 */
	public final String[] getPages() {
		return this.pages;
	}

	/**
	 * Return the number of wizard pages.
	 * Useful to check whether the last page has been reached.
	 * <p>Note that a concrete wizard form controller might override
	 * {@code getPageCount(PortletRequest, Object)} to determine
	 * the page count dynamically.
	 * @see #getPageCount(PortletRequest, Object)
	 */
	protected final int getPageCount() {
		return this.pages.length;
	}

	/**
	 * Set the name of the page attribute in the model, containing
	 * an Integer with the current page number.
	 * <p>This will be necessary for single views rendering multiple view pages.
	 * It also allows for specifying the optional "_page" parameter.
	 * @param pageAttribute name of the page attribute
	 * @see #PARAM_PAGE
	 */
	public final void setPageAttribute(String pageAttribute) {
		this.pageAttribute = pageAttribute;
	}

	/**
	 * Return the name of the page attribute in the model.
	 */
	public final String getPageAttribute() {
		return this.pageAttribute;
	}

	/**
	 * Set if "dirty back" is allowed, i.e. if moving to a former wizard
	 * page is allowed in case of validation errors for the current page.
	 * @param allowDirtyBack if "dirty back" is allowed
	 */
	public final void setAllowDirtyBack(boolean allowDirtyBack) {
		this.allowDirtyBack = allowDirtyBack;
	}

	/**
	 * Return whether "dirty back" is allowed.
	 */
	public final boolean isAllowDirtyBack() {
		return this.allowDirtyBack;
	}

	/**
	 * Set if "dirty forward" is allowed, i.e. if moving to a later wizard
	 * page is allowed in case of validation errors for the current page.
	 * @param allowDirtyForward if "dirty forward" is allowed
	 */
	public final void setAllowDirtyForward(boolean allowDirtyForward) {
		this.allowDirtyForward = allowDirtyForward;
	}

	/**
	 * Return whether "dirty forward" is allowed.
	 */
	public final boolean isAllowDirtyForward() {
		return this.allowDirtyForward;
	}


	/**
	 * Calls page-specific onBindAndValidate method.
	 */
	@Override
	protected final void onBindAndValidate(PortletRequest request, Object command, BindException errors)
			throws Exception {

		onBindAndValidate(request, command, errors, getCurrentPage(request));
	}

	/**
	 * Callback for custom post-processing in terms of binding and validation.
	 * Called on each submit, after standard binding but before page-specific
	 * validation of this wizard form controller.
	 * <p>Note: AbstractWizardFormController does not perform standard
	 * validation on binding but rather applies page-specific validation
	 * on processing the form submission.
	 * @param request current portlet request
	 * @param command bound command
	 * @param errors Errors instance for additional custom validation
	 * @param page current wizard page
	 * @throws Exception in case of invalid state or arguments
	 * @see #bindAndValidate
	 * @see #processFormSubmission
	 * @see org.springframework.validation.Errors
	 */
	protected void onBindAndValidate(PortletRequest request, Object command, BindException errors, int page)
			throws Exception {
	}

	/**
	 * Consider an explicit finish or cancel request as a form submission too.
	 * @see #isFinishRequest(PortletRequest)
	 * @see #isCancelRequest(PortletRequest)
	 */
	@Override
	protected boolean isFormSubmission(PortletRequest request) {
		return super.isFormSubmission(request) || isFinishRequest(request) || isCancelRequest(request);
	}

	/**
	 * Calls page-specific referenceData method.
	 */
	@Override
	protected final Map referenceData(PortletRequest request, Object command, Errors errors)
			throws Exception {

		return referenceData(request, command, errors, getCurrentPage(request));
	}

	/**
	 * Create a reference data map for the given request, consisting of
	 * bean name/bean instance pairs as expected by ModelAndView.
	 * <p>The default implementation delegates to {@link #referenceData(PortletRequest, int)}.
	 * Subclasses can override this to set reference data used in the view.
	 * @param request current portlet request
	 * @param command form object with request parameters bound onto it
	 * @param errors validation errors holder
	 * @param page current wizard page
	 * @return a Map with reference data entries, or null if none
	 * @throws Exception in case of invalid state or arguments
	 * @see #referenceData(PortletRequest, int)
	 * @see org.springframework.web.portlet.ModelAndView
	 */
	protected Map referenceData(PortletRequest request, Object command, Errors errors, int page)
			throws Exception {

		return referenceData(request, page);
	}

	/**
	 * Create a reference data map for the given request, consisting of
	 * bean name/bean instance pairs as expected by ModelAndView.
	 * <p>The default implementation returns {@code null}.
	 * Subclasses can override this to set reference data used in the view.
	 * @param request current portlet request
	 * @param page current wizard page
	 * @return a Map with reference data entries, or null if none
	 * @throws Exception in case of invalid state or arguments
	 * @see org.springframework.web.portlet.ModelAndView
	 */
	protected Map referenceData(PortletRequest request, int page) throws Exception {
		return null;
	}


	/**
	 * Show the first page as form view.
	 * <p>This can be overridden in subclasses, e.g. to prepare wizard-specific
	 * error views in case of an Exception.
	 */
	@Override
	protected ModelAndView showForm(
			RenderRequest request, RenderResponse response, BindException errors) throws Exception {

		return showPage(request, errors, getInitialPage(request, errors.getTarget()));
	}

	/**
	 * Prepare the form model and view, including reference and error data,
	 * for the given page. Can be used in {@code processFinish} implementations,
	 * to show the corresponding page in case of validation errors.
	 * @param request current portlet render request
	 * @param errors validation errors holder
	 * @param page number of page to show
	 * @return the prepared form view
	 * @throws Exception in case of invalid state or arguments
	 */
	protected final ModelAndView showPage(RenderRequest request, BindException errors, int page)
			throws Exception {

		if (page >= 0 && page < getPageCount(request, errors.getTarget())) {
			if (logger.isDebugEnabled()) {
				logger.debug("Showing wizard page " + page + " for form bean '" + getCommandName() + "'");
			}

			// Set page session attribute, expose overriding request attribute.
			Integer pageInteger = new Integer(page);
			String pageAttrName = getPageSessionAttributeName(request);
			if (isSessionForm()) {
				if (logger.isDebugEnabled()) {
					logger.debug("Setting page session attribute [" + pageAttrName + "] to: " + pageInteger);
				}
				request.getPortletSession().setAttribute(pageAttrName, pageInteger);
			}
			request.setAttribute(pageAttrName, pageInteger);

			// Set page request attribute for evaluation by views.
			Map controlModel = new HashMap();
			if (this.pageAttribute != null) {
				controlModel.put(this.pageAttribute, new Integer(page));
			}
			String viewName = getViewName(request, errors.getTarget(), page);
			return showForm(request, errors, viewName, controlModel);
		}

		else {
			throw new PortletException("Invalid wizard page number: " + page);
		}
	}

	/**
	 * Return the page count for this wizard form controller.
	 * <p>The default implementation delegates to {@link #getPageCount()}.
	 * Can be overridden to dynamically adapt the page count.
	 * @param request current portlet request
	 * @param command the command object as returned by formBackingObject
	 * @return the current page count
	 * @see #getPageCount
	 */
	protected int getPageCount(PortletRequest request, Object command) {
		return getPageCount();
	}

	/**
	 * Return the name of the view for the specified page of this wizard form controller.
	 * <p>The default implementation takes the view name from the {@link #getPages()} array.
	 * Can be overridden to dynamically switch the page view or to return view names
	 * for dynamically defined pages.
	 * @param request current portlet request
	 * @param command the command object as returned by {@code formBackingObject}
	 * @return the current page count
	 * @see #getPageCount
	 */
	protected String getViewName(PortletRequest request, Object command, int page) {
		return getPages()[page];
	}

	/**
	 * Return the initial page of the wizard, i.e. the page shown at wizard startup.
	 * <p>The default implementation delegates to {@link #getInitialPage(PortletRequest)}.
	 * @param request current portlet request
	 * @param command the command object as returned by {@code formBackingObject}
	 * @return the initial page number
	 * @see #getInitialPage(PortletRequest)
	 * @see #formBackingObject
	 */
	protected int getInitialPage(PortletRequest request, Object command) {
		return getInitialPage(request);
	}

	/**
	 * Return the initial page of the wizard, i.e. the page shown at wizard startup.
	 * <p>The default implementation returns 0 for first page.
	 * @param request current portlet request
	 * @return the initial page number
	 */
	protected int getInitialPage(PortletRequest request) {
		return 0;
	}

	/**
	 * Return the name of the PortletSession attribute that holds the page object
	 * for this wizard form controller.
	 * <p>The default implementation delegates to the {@code getPageSessionAttributeName}
	 * version without arguments.
	 * @param request current portlet request
	 * @return the name of the form session attribute, or null if not in session form mode
	 * @see #getPageSessionAttributeName
	 * @see #getFormSessionAttributeName
	 * @see javax.portlet.PortletSession#getAttribute
	 */
	protected String getPageSessionAttributeName(PortletRequest request) {
		return getPageSessionAttributeName();
	}

	/**
	 * Return the name of the PortletSession attribute that holds the page object
	 * for this wizard form controller.
	 * <p>Default is an internal name, of no relevance to applications, as the form
	 * session attribute is not usually accessed directly. Can be overridden to use
	 * an application-specific attribute name, which allows other code to access
	 * the session attribute directly.
	 * @return the name of the page session attribute
	 * @see #getFormSessionAttributeName
	 * @see javax.portlet.PortletSession#getAttribute
	 */
	protected String getPageSessionAttributeName() {
		return getClass().getName() + ".PAGE." + getCommandName();
	}

	/**
	 * Pass the page number to the render phase by setting a render parameter.
	 * This method may not be called when the action calls
	 * {@link javax.portlet.ActionResponse#sendRedirect(String)}.
	 * @param response the current action response
	 * @param page the page number
	 * @see ActionResponse#setRenderParameter
	 */
	protected void setPageRenderParameter(ActionResponse response, int page) {
		if (logger.isDebugEnabled())
			logger.debug("Setting page number render parameter [" + PARAM_PAGE + "] to [" + page + "]");
		try {
			response.setRenderParameter(PARAM_PAGE, new Integer(page).toString());
		}
		catch (IllegalStateException ex) {
			// ignore in case sendRedirect was already set
		}
	}

	/**
	 * Pass the the parameter that indicates the target page of the request
	 * forward to the render phase. If the {@code getTargetPage} method
	 * was overridden, this may need to be overriden as well.
	 * @param request the current action request
	 * @param response the current action response
	 * @see #PARAM_TARGET
	 * @see #getTargetPage(PortletRequest, int)
	 * @see #getTargetPage(PortletRequest, Object, Errors, int)
	 * @see ActionResponse#setRenderParameter
	 */
	protected void setTargetRenderParameter(ActionRequest request, ActionResponse response) {
		try {
			Map<String, Object> params = PortletUtils.getParametersStartingWith(request, PARAM_TARGET);
			for (Map.Entry<String, Object> entry : params.entrySet()) {
				String param = PARAM_TARGET + entry.getKey();
				Object value = entry.getValue();
				if (logger.isDebugEnabled()) {
					logger.debug("Setting target render parameter [" + param + "]");
				}
				if (value instanceof String) {
					response.setRenderParameter(param, (String) value);
				}
				else if (value instanceof String[]) {
					response.setRenderParameter(param, (String[]) value);
				}
			}
		}
		catch (IllegalStateException ex) {
			// ignore in case sendRedirect was already set
		}
	}

	/**
	 * Pass the the parameter that indicates a finish request forward to the
	 * render phase. If the {@code isFinishRequest} method
	 * was overridden, this may need to be overriden as well.
	 * @param request the current action request
	 * @param response the current action response
	 * @see #PARAM_FINISH
	 * @see #isFinishRequest
	 * @see ActionResponse#setRenderParameter
	 */
	protected void setFinishRenderParameter(ActionRequest request, ActionResponse response) {
		if (logger.isDebugEnabled())
			logger.debug("Setting cancel render parameter [" + PARAM_FINISH + "]");
		try {
			String name = PortletUtils.getSubmitParameter(request, PARAM_FINISH);
			if (name != null)
				response.setRenderParameter(name, request.getParameter(name));
		}
		catch (IllegalStateException ex) {
			// ignore in case sendRedirect was already set
		}
	}

	/**
	 * Pass the the parameter that indicates a cancel request forward to the
	 * render phase. If the {@code isCancelRequest} method
	 * was overridden, this may need to be overriden as well.
	 * @param request the current action request
	 * @param response the current action response
	 * @see #PARAM_CANCEL
	 * @see #isCancelRequest
	 * @see ActionResponse#setRenderParameter
	 */
	protected void setCancelRenderParameter(ActionRequest request, ActionResponse response) {
		if (logger.isDebugEnabled())
			logger.debug("Setting cancel render parameter [" + PARAM_CANCEL + "]");
		try {
			String name = PortletUtils.getSubmitParameter(request, PARAM_CANCEL);
			if (name != null)
				response.setRenderParameter(name, request.getParameter(name));
		}
		catch (IllegalStateException ex) {
			// ignore in case sendRedirect was already set
		}
	}

	/**
	 * Handle an invalid submit request, e.g. when in session form mode but no form object
	 * was found in the session (like in case of an invalid resubmit by the browser).
	 * <p>The default implementation for wizard form controllers simply shows the initial page
	 * of a new wizard form. If you want to show some "invalid submit" message, you need
	 * to override this method.
	 * @param request current portlet render request
	 * @param response current portlet render response
	 * @return a prepared view, or null if handled directly
	 * @throws Exception in case of errors
	 * @see #showNewForm
	 * @see #setBindOnNewForm
	 * @see #handleInvalidSubmit
	 */
	@Override
	protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response)
			throws Exception {

		return showNewForm(request, response);
	}

	/**
	 * Handle an invalid submit request, e.g. when in session form mode but no form object
	 * was found in the session (like in case of an invalid resubmit by the browser).
	 * <p>The default implementation for wizard form controllers simply shows the initial page
	 * of a new wizard form, so here in the action phase this method does nothing. If you
	 * want to take some action on an invalid submit, you need to override this method.
	 * @param request current portlet action request
	 * @param response current portlet action response
	 * @throws Exception in case of errors
	 * @see #renderInvalidSubmit
	 */
	@Override
	protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
	}

	/**
	 * Apply wizard workflow: finish, cancel, page change.
	 * @see #processFormSubmission
	 */
	@Override
	protected final ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object command, BindException errors)
			throws Exception {

		int currentPage = getCurrentPage(request);
		String pageAttrName = getPageSessionAttributeName(request);
		request.setAttribute(pageAttrName, new Integer(currentPage));

		// cancel?
		if (isCancelRequest(request)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
			}
			return renderCancel(request, response, command, errors);
		}

		// finish?
		if (isFinishRequest(request)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
			}
			return renderValidatePagesAndFinish(request, response, command, errors, currentPage);
		}

		// Normal submit: show specified target page.
		int targetPage = getTargetPage(request, command, errors, currentPage);
		if (logger.isDebugEnabled()) {
			logger.debug("Target page " + targetPage + " requested");
		}
		if (targetPage != currentPage) {
			if (!errors.hasErrors() || (this.allowDirtyBack && targetPage < currentPage) ||
					(this.allowDirtyForward && targetPage > currentPage)) {
				// Allowed to go to target page.
				return showPage(request, errors, targetPage);
			}
		}

		// Show current page again
		return showPage(request, errors, currentPage);
	}


	/**
	 * Apply wizard workflow: finish, cancel, page change.
	 * @see #renderFormSubmission
	 */
	@Override
	protected final void processFormSubmission(
			ActionRequest request, ActionResponse response, Object command, BindException errors)
			throws Exception {

		int currentPage = getCurrentPage(request);
		// Remove page session attribute, provide copy as request attribute.
		String pageAttrName = getPageSessionAttributeName(request);
		if (isSessionForm()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Removing page session attribute [" + pageAttrName + "]");
			}
			request.getPortletSession().removeAttribute(pageAttrName);
		}
		request.setAttribute(pageAttrName, new Integer(currentPage));

		// cancel?
		if (isCancelRequest(request)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
			}
			setPageRenderParameter(response, currentPage);
			setCancelRenderParameter(request, response);
			processCancel(request, response, command, errors);
			return;
		}

		// finish?
		if (isFinishRequest(request)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
			}
			if (!isRedirectAction()) {
				setPageRenderParameter(response, currentPage);
				setFinishRenderParameter(request, response);
			}
			validatePagesAndFinish(request, response, command, errors, currentPage);
			return;
		}

		// Normal submit: validate current page
		if (!suppressValidation(request)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Validating wizard page " + currentPage + " for form bean '" + getCommandName() + "'");
			}
			validatePage(command, errors, currentPage, false);
		}

		setPageRenderParameter(response, currentPage);
		setTargetRenderParameter(request, response);
		passRenderParameters(request, response);

		// Give subclasses a change to perform custom post-procession
		// of the current page and its command object.
		postProcessPage(request, command, errors, currentPage);

	}

	/**
	 * Return the current page number. Used by {@link #processFormSubmission}.
	 * <p>The default implementation checks the page session attribute.
	 * Subclasses can override this for customized page determination.
	 * @param request current portlet request
	 * @return the current page number
	 * @see #getPageSessionAttributeName()
	 */
	protected int getCurrentPage(PortletRequest request) {
		// Check for overriding attribute in request.
		String pageAttrName = getPageSessionAttributeName(request);
		Integer pageAttr = (Integer) request.getAttribute(pageAttrName);
		if (pageAttr != null) {
			return pageAttr.intValue();
		}
		// Check for explicit request parameter.
		String pageParam = request.getParameter(PARAM_PAGE);
		if (pageParam != null) {
			return Integer.parseInt(pageParam);
		}
		// Check for original attribute in session.
		if (isSessionForm()) {
			pageAttr = (Integer) request.getPortletSession().getAttribute(pageAttrName);
			if (pageAttr != null) {
				return pageAttr.intValue();
			}
		}
		throw new IllegalStateException("Page attribute [" + pageAttrName + "] neither found in session nor in request");
	}

	/**
	 * Determine whether the incoming request is a request to finish the
	 * processing of the current form.
	 * <p>By default, this method returns {@code true} if a parameter
	 * matching the "_finish" key is present in the request, otherwise it
	 * returns {@code false}. Subclasses may override this method
	 * to provide custom logic to detect a finish request.
	 * <p>The parameter is recognized both when sent as a plain parameter
	 * ("_finish") or when triggered by an image button ("_finish.x").
	 * @param request current portlet request
	 * @return whether the request indicates to finish form processing
	 * @see #PARAM_FINISH
	 */
	protected boolean isFinishRequest(PortletRequest request) {
		return PortletUtils.hasSubmitParameter(request, PARAM_FINISH);
	}

	/**
	 * Determine whether the incoming request is a request to cancel the
	 * processing of the current form.
	 * <p>By default, this method returns {@code true} if a parameter
	 * matching the "_cancel" key is present in the request, otherwise it
	 * returns {@code false}. Subclasses may override this method
	 * to provide custom logic to detect a cancel request.
	 * <p>The parameter is recognized both when sent as a plain parameter
	 * ("_cancel") or when triggered by an image button ("_cancel.x").
	 * @param request current portlet request
	 * @return whether the request indicates to cancel form processing
	 * @see #PARAM_CANCEL
	 */
	protected boolean isCancelRequest(PortletRequest request) {
		return PortletUtils.hasSubmitParameter(request, PARAM_CANCEL);
	}

	/**
	 * Return the target page specified in the request.
	 * <p>The default implementation delegates to {@link #getTargetPage(PortletRequest, int)}.
	 * Subclasses can override this for customized target page determination.
	 * @param request current portlet request
	 * @param command form object with request parameters bound onto it
	 * @param errors validation errors holder
	 * @param currentPage the current page, to be returned as fallback
	 * if no target page specified
	 * @return the page specified in the request, or current page if not found
	 * @see #getTargetPage(PortletRequest, int)
	 */
	protected int getTargetPage(PortletRequest request, Object command, Errors errors, int currentPage) {
		return getTargetPage(request, currentPage);
	}

	/**
	 * Return the target page specified in the request.
	 * <p>The default implementation examines "_target" parameter (e.g. "_target1").
	 * Subclasses can override this for customized target page determination.
	 * @param request current portlet request
	 * @param currentPage the current page, to be returned as fallback
	 * if no target page specified
	 * @return the page specified in the request, or current page if not found
	 * @see #PARAM_TARGET
	 */
	protected int getTargetPage(PortletRequest request, int currentPage) {
		return PortletUtils.getTargetPage(request, PARAM_TARGET, currentPage);
	}

	/**
	 * Validate all pages and process finish.
	 * If there are page validation errors, show the corresponding view page.
	 * @see #validatePagesAndFinish
	 */
	private ModelAndView renderValidatePagesAndFinish(
			RenderRequest request, RenderResponse response, Object command, BindException errors, int currentPage)
			throws Exception {

		// In case of any errors -> show current page.
		if (errors.hasErrors())
			return showPage(request, errors, currentPage);

		// No remaining errors -> proceed with finish.
		return renderFinish(request, response, command, errors);
	}

	/**
	 * Validate all pages and process finish.
	 * If there are page validation errors, show the corresponding view page.
	 * @see #renderValidatePagesAndFinish
	 */
	private void validatePagesAndFinish(
			ActionRequest request, ActionResponse response, Object command, BindException errors, int currentPage)
			throws Exception {

		// In case of binding errors -> show current page.
		if (errors.hasErrors()) {
			setPageRenderParameter(response, currentPage);
			passRenderParameters(request, response);
			return;
		}

		if (!suppressValidation(request)) {
			// In case of remaining errors on a page -> show the page.
			for (int page = 0; page < getPageCount(request, command); page++) {
				validatePage(command, errors, page, true);
				if (errors.hasErrors()) {
					setPageRenderParameter(response, currentPage);
					passRenderParameters(request, response);
					return;
				}
			}
		}

		// No remaining errors -> proceed with finish.
		if (!isRedirectAction())
			setPageRenderParameter(response, currentPage);
		processFinish(request, response, command, errors);

	}

	/**
	 * Template method for custom validation logic for individual pages.
	 * The default implementation calls {@code validatePage(command, errors, page)}.
	 * <p>Implementations will typically call fine-granular {@code validateXXX}
	 * methods of this instance's Validator, combining them to validation of the
	 * corresponding pages. The Validator's default {@code validate} method
	 * will not be called by a wizard form controller!
	 * @param command form object with the current wizard state
	 * @param errors validation errors holder
	 * @param page number of page to validate
	 * @param finish whether this method is called during final revalidation on finish
	 * (else, it is called for validating the current page)
	 * @see #validatePage(Object, Errors, int)
	 * @see org.springframework.validation.Validator#validate
	 */
	protected void validatePage(Object command, Errors errors, int page, boolean finish) {
		validatePage(command, errors, page);
	}

	/**
	 * Template method for custom validation logic for individual pages.
	 * The default implementation is empty.
	 * <p>Implementations will typically call fine-granular validateXXX methods of this
	 * instance's validator, combining them to validation of the corresponding pages.
	 * The validator's default {@code validate} method will not be called by a
	 * wizard form controller!
	 * @param command form object with the current wizard state
	 * @param errors validation errors holder
	 * @param page number of page to validate
	 * @see org.springframework.validation.Validator#validate
	 */
	protected void validatePage(Object command, Errors errors, int page) {
	}

	/**
	 * Post-process the given page after binding and validation, potentially
	 * updating its command object. The passed-in request might contain special
	 * parameters sent by the page.
	 * <p>Only invoked when displaying another page or the same page again,
	 * not when finishing or cancelling.
	 * @param request current action request
	 * @param command form object with request parameters bound onto it
	 * @param errors validation errors holder
	 * @param page number of page to post-process
	 * @throws Exception in case of invalid state or arguments
	 */
	protected void postProcessPage(ActionRequest request, Object command, Errors errors, int page)
			throws Exception {
	}

	/**
	 * Template method for the render phase of the finish action of this wizard.
	 * <p>The default implementation throws a PortletException, saying that a finish
	 * render request is not supported by this controller. Thus, you do not need to
	 * implement this template method if you do not need to render after a finish.
	 * <p>Call {@code errors.getModel()} to populate the ModelAndView model
	 * with the command and the Errors instance, under the specified command name,
	 * as expected by the "spring:bind" tag.
	 * @param request current portlet render request
	 * @param response current portlet render response
	 * @param command form object with the current wizard state
	 * @param errors validation errors holder
	 * @return the finish view
	 * @throws Exception in case of invalid state or arguments
	 * @see #processFinish
	 * @see org.springframework.validation.Errors
	 * @see org.springframework.validation.BindException#getModel
	 */
	protected ModelAndView renderFinish(
			RenderRequest request, RenderResponse response, Object command, BindException errors)
			throws Exception {

		throw new PortletException("Wizard form controller class [" + getClass().getName() + "] does not support a finish render request");
	}

	/**
	 * Template method for the action phase of the finish action of this wizard.
	 * <p>The default implementation throws a PortletException, saying that a finish
	 * action request is not supported by this controller. You will almost certainly
	 * need to override this method.
	 * @param request current portlet action request
	 * @param response current portlet action response
	 * @param command form object with the current wizard state
	 * @param errors validation errors holder
	 * @throws Exception in case of invalid state or arguments
	 * @see #renderFinish
	 * @see org.springframework.validation.Errors
	 */
	protected void processFinish(
			ActionRequest request, ActionResponse response, Object command, BindException errors)
			throws Exception {

		throw new PortletException(
				"Wizard form controller class [" + getClass().getName() + "] does not support a finish action request");
	}

	/**
	 * Template method for the render phase of the cancel action of this wizard.
	 * <p>The default implementation throws a PortletException, saying that a cancel
	 * render request is not supported by this controller. Thus, you do not need to
	 * implement this template method if you do not support a cancel operation.
	 * <p>Call {@code errors.getModel()} to populate the ModelAndView model
	 * with the command and the Errors instance, under the specified command name,
	 * as expected by the "spring:bind" tag.
	 * @param request current portlet render request
	 * @param response current portlet render response
	 * @param command form object with the current wizard state
	 * @param errors Errors instance containing errors
	 * @return the cancellation view
	 * @throws Exception in case of invalid state or arguments
	 * @see #processCancel
	 * @see org.springframework.validation.Errors
	 * @see org.springframework.validation.BindException#getModel
	 */
	protected ModelAndView renderCancel(
			RenderRequest request, RenderResponse response, Object command, BindException errors)
			throws Exception {

		throw new PortletException(
				"Wizard form controller class [" + getClass().getName() + "] does not support a cancel render request");
	}

	/**
	 * Template method for the action phase of the cancel action of this wizard.
	 * <p>The default implementation throws a PortletException, saying that a cancel
	 * action request is not supported by this controller. Thus, you do not need to
	 * implement this template method if you do not support a cancel operation.
	 * @param request current portlet action request
	 * @param response current portlet action response
	 * @param command form object with the current wizard state
	 * @param errors Errors instance containing errors
	 * @throws Exception in case of invalid state or arguments
	 * @see #renderCancel
	 * @see org.springframework.validation.Errors
	 */
	protected void processCancel(
			ActionRequest request, ActionResponse response, Object command, BindException errors)
			throws Exception {

		throw new PortletException(
				"Wizard form controller class [" + getClass().getName() + "] does not support a cancel action request");
	}

}
