Removed Spring MVC command/form controller class hierarchy

This commit is contained in:
Juergen Hoeller
2013-03-19 16:06:37 +01:00
parent 74021b9e4a
commit f19f55a59b
22 changed files with 14 additions and 9123 deletions

View File

@@ -1,213 +0,0 @@
/*
* 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 javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.validation.BindException;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.util.PortletUtils;
/**
* <p>Abstract base class for custom command controllers. Autopopulates a
* command bean from the request. For command validation, a validator
* (property inherited from BaseCommandController) can be used.</p>
*
* <p>This command controller should preferrable not be used to handle form
* submission, because functionality for forms is more offered in more
* detail by the {@link org.springframework.web.portlet.mvc.AbstractFormController
* AbstractFormController} and its corresponding implementations.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
* <i>none</i> (so only those available in superclass).</p>
*
* <p><b><a name="workflow">Workflow
* (<a name="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
* @see #setCommandClass
* @see #setCommandName
* @see #setValidator
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class AbstractCommandController extends BaseCommandController {
/**
* This render parameter is used to indicate forward to the render phase
* that a valid command (and errors) object is in the session.
*/
private static final String COMMAND_IN_SESSION_PARAMETER = "command-in-session";
private static final String TRUE = Boolean.TRUE.toString();
/**
* Create a new AbstractCommandController.
*/
public AbstractCommandController() {
}
/**
* Create a new AbstractCommandController.
* @param commandClass class of the command bean
*/
public AbstractCommandController(Class commandClass) {
setCommandClass(commandClass);
}
/**
* Create a new AbstractCommandController.
* @param commandClass class of the command bean
* @param commandName name of the command bean
*/
public AbstractCommandController(Class commandClass, String commandName) {
setCommandClass(commandClass);
setCommandName(commandName);
}
@Override
protected final void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception {
// Create the command object.
Object command = getCommand(request);
// Compute the errors object.
PortletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
// Actually handle the action.
handleAction(request, response, command, errors);
// Pass the command and errors forward to the render phase.
setRenderCommandAndErrors(request, command, errors);
setCommandInSession(response);
}
@Override
protected final ModelAndView handleRenderRequestInternal(
RenderRequest request, RenderResponse response) throws Exception {
Object command = null;
BindException errors = null;
// Get the command and errors objects from the session, if they exist.
if (isCommandInSession(request)) {
logger.debug("Render phase obtaining command and errors objects from session");
command = getRenderCommand(request);
errors = getRenderErrors(request);
}
else {
logger.debug("Render phase creating new command and errors objects");
command = getCommand(request);
PortletRequestDataBinder binder = bindAndValidate(request, command);
errors = new BindException(binder.getBindingResult());
}
return handleRender(request, response, command, errors);
}
/**
* Template method for request handling, providing a populated and validated instance
* of the command class, and an Errors object containing binding and validation errors.
* <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 action request
* @param response current action response
* @param command the populated command object
* @param errors validation errors holder
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract void handleAction(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception;
/**
* Template method for render request handling, providing a populated and validated instance
* of the command class, and an Errors object containing binding and validation errors.
* <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 render request
* @param response current render response
* @param command the populated command object
* @param errors validation errors holder
* @return a ModelAndView to render, or null if handled directly
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract ModelAndView handleRender(
RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception;
/**
* Return the name of the render parameter that indicates there
* is a valid command (and errors) object in the session.
* @return the name of the render parameter
* @see javax.portlet.RenderRequest#getParameter
*/
protected String getCommandInSessionParameterName() {
return COMMAND_IN_SESSION_PARAMETER;
}
/**
* Set the action response parameter that indicates there is a
* command (and errors) object in the session for the render phase.
* @param response the current action response
* @see #getCommandInSessionParameterName
* @see #isCommandInSession
*/
protected final void setCommandInSession(ActionResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Setting render parameter [" + getCommandInSessionParameterName() +
"] to indicate a valid command (and errors) object are in the session");
}
try {
response.setRenderParameter(getCommandInSessionParameterName(), TRUE);
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Determine if there is a valid command (and errors) object in the
* session for this render request.
* @param request current render request
* @return if there is a valid command object in the session
* @see #getCommandInSessionParameterName
* @see #setCommandInSession
*/
protected boolean isCommandInSession(RenderRequest request) {
return (TRUE.equals(request.getParameter(getCommandInSessionParameterName())) &&
PortletUtils.getSessionAttribute(request, getRenderCommandSessionAttributeName()) != null);
}
}

View File

@@ -1,974 +0,0 @@
/*
* 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.Arrays;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
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.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
/**
* <p>Form controller that auto-populates a form bean from the request.
* This, either using a new bean instance per request, or using the same bean
* when the {@code sessionForm} property has been set to
* {@code true}.</p>
*
* <p>This class is the base class for both framework subclasses such as
* {@link SimpleFormController} and {@link AbstractWizardFormController}
* and custom form controllers that you may provide yourself.</p>
*
* <p>A form-input view and an after-submission view have to be provided
* programmatically. To provide those views using configuration properties,
* use the {@link SimpleFormController}.</p>
*
* <p>Subclasses need to override {@code showForm} to prepare the form view,
* {@code processFormSubmission} to handle submit requests, and
* {@code renderFormSubmission} to display the results of the submit.
* For the latter two methods, binding errors like type mismatches will be
* reported via the given "errors" holder. For additional custom form validation,
* a validator (property inherited from BaseCommandController) can be used,
* reporting via the same "errors" instance.</p>
*
* <p>Comparing this Controller to the Struts notion of the {@code Action}
* shows us that with Spring, you can use any ordinary JavaBeans or database-
* backed JavaBeans without having to implement a framework-specific class
* (like Struts' {@code ActionForm}). More complex properties of JavaBeans
* (Dates, Locales, but also your own application-specific or compound types)
* can be represented and submitted to the controller, by using the notion of
* a {@code java.beans.PropertyEditors}. For more information on that
* subject, see the workflow of this controller and the explanation of the
* {@link BaseCommandController BaseCommandController}.</p>
*
* <p>This controller is different from it's servlet counterpart in that it must take
* into account the two phases of a portlet request: the action phase and the render
* phase. See the JSR-168 spec for more details on these two phases.
* Be especially aware that the action phase is called only once, but that the
* render phase will be called repeatedly by the portal; it does this every time
* the page containing the portlet is updated, even if the activity is in some other
* portlet. (This is not quite true, the portal can also be told to cache the results of
* the render for a period of time, but assume it is true for programming purposes.)</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
* <ol>
* <li><b>The controller receives a request for a new form (typically a
* Render Request only).</b> The render phase will proceed to display
* the form as follows.</li>
* <li>Call to {@link #formBackingObject formBackingObject()} which by
* default, returns an instance of the commandClass that has been
* configured (see the properties the superclass exposes), but can also be
* overridden to e.g. retrieve an object from the database (that needs to
* be modified using the form).</li>
* <li>Call to {@link #initBinder initBinder()} which allows you to
* register custom editors for certain fields (often properties of non-
* primitive or non-String types) of the command class. This will render
* appropriate Strings for those property values, e.g. locale-specific
* date strings. </li>
* <li>The {@link PortletRequestDataBinder PortletRequestDataBinder}
* gets applied to populate the new form object with initial request parameters and the
* {@link #onBindOnNewForm(RenderRequest, Object, BindException)} callback method is invoked.
* (<i>only if {@code bindOnNewForm} is set to {@code true}</i>)
* Make sure that the initial parameters do not include the parameter that indicates a
* form submission has occurred.</li>
* <li>Call to {@link #showForm(RenderRequest, RenderResponse,
* BindException) showForm} to return a View that should be rendered
* (typically the view that renders the form). This method has to be
* implemented in subclasses. </li>
* <li>The showForm() implementation will call {@link #referenceData referenceData},
* which you can implement to provide any relevant reference data you might need
* when editing a form (e.g. a List of Locale objects you're going to let the
* user select one from).</li>
* <li>Model gets exposed and view gets rendered, to let the user fill in
* the form.</li>
* <li><b>The controller receives a form submission (typically an Action
* Request).</b> To use a different way of detecting a form submission,
* override the {@link #isFormSubmission isFormSubmission} method.
* The action phase will proceed to process the form submission as follows.</li>
* <li>If {@code sessionForm} is not set, {@link #formBackingObject
* formBackingObject} is called to retrieve a form object. Otherwise,
* the controller tries to find the command object which is already bound
* in the session. If it cannot find the object, the action phase does a
* call to {@link #handleInvalidSubmit handleInvalidSubmit} which - by default -
* tries to create a new form object and resubmit the form. It then sets
* a render parameter that will indicate to the render phase that this was
* an invalid submit.</li>
* <li>Still in the action phase of a valid submit, the {@link
* PortletRequestDataBinder PortletRequestDataBinder} gets applied to populate
* the form object with current request parameters.</li>
* <li>Call to {@link #onBind onBind(PortletRequest, Object, Errors)}
* which allows you to do custom processing after binding but before
* validation (e.g. to manually bind request parameters to bean
* properties, to be seen by the Validator).</li>
* <li>If {@code validateOnBinding} is set, a registered Validator
* will be invoked. The Validator will check the form object properties,
* and register corresponding errors via the given {@link Errors Errors}
* object.</li>
* <li>Call to {@link #onBindAndValidate onBindAndValidate} which allows
* you to do custom processing after binding and validation (e.g. to
* manually bind request parameters, and to validate them outside a
* Validator).</li>
* <li>Call to {@link #processFormSubmission processFormSubmission}
* to process the submission, with or without binding errors.
* This method has to be implemented in subclasses and will be called
* only once per form submission.</li>
* <li>The portal will then call the render phase of processing the form
* submission. This phase will be called repeatedly by the portal every
* time the page is refreshed. All processing here should take this into
* account. Any one-time-only actions (such as modifying a database) must
* be done in the action phase.</li>
* <li>If the action phase indicated this is an invalid submit, the render
* phase calls {@link #renderInvalidSubmit renderInvalidSubmit} which &ndash;
* also by default &ndash; will render the results of the resubmitted
* form. Be sure to override both {@code handleInvalidSubmit} and
* {@code renderInvalidSubmit} if you want to change this overall
* behavior.</li>
* <li>Finally, call {@link #renderFormSubmission renderFormSubmission} to
* render the results of the submission, with or without binding errors.
* This method has to be implemented in subclasses and will be called
* repeatedly by the portal.</li>
* </ol>
* </p>
*
* <p>In session form mode, a submission without an existing form object in the
* session is considered invalid, like in the case of a resubmit/reload by the browser.
* The {@link #handleInvalidSubmit handleInvalidSubmit} /
* {@link #renderInvalidSubmit renderInvalidSubmit} methods are invoked then,
* by default trying to resubmit. This can be overridden in subclasses to show
* corresponding messages or to redirect to a new form, in order to avoid duplicate
* submissions. The form object in the session can be considered a transaction token
* in that case.</p>
*
* <p>Make sure that any URLs that take you to your form controller are Render URLs,
* so that it will not try to treat the initial call as a form submission.
* If you use action URLs to link to your controller, you will need to override the
* {@link #isFormSubmission isFormSubmission} method to use a different mechanism for
* determining whether a form has been submitted. Make sure this method will work for
* both the ActionRequest and the RenderRequest objects.</p>
*
* <p>Note that views should never retrieve form beans from the session but always
* from the request, as prepared by the form controller. Remember that some view
* technologies like Velocity cannot even access a HTTP session.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>bindOnNewForm</td>
* <td>false</td>
* <td>Indicates whether to bind portlet request parameters when
* creating a new form. Otherwise, the parameters will only be
* bound on form submission attempts.</td>
* </tr>
* <tr>
* <td>sessionForm</td>
* <td>false</td>
* <td>Indicates whether the form object should be kept in the session
* when a user asks for a new form. This allows you e.g. to retrieve
* an object from the database, let the user edit it, and then persist
* it again. Otherwise, a new command object will be created for each
* request (even when showing the form again after validation errors).</td>
* </tr>
* <tr>
* <td>redirectAction</td>
* <td>false</td>
* <td>Specifies whether {@code processFormSubmission} is expected to call
* {@link ActionResponse#sendRedirect ActionResponse.sendRedirect}.
* This is important because some methods may not be called before
* {@link ActionResponse#sendRedirect ActionResponse.sendRedirect} (e.g.
* {@link ActionResponse#setRenderParameter ActionResponse.setRenderParameter}).
* Setting this flag will prevent AbstractFormController from setting render
* parameters that it normally needs for the render phase.
* If this is set true and {@code sendRedirect} is not called, then
* {@code processFormSubmission} must call
* {@link #setFormSubmit setFormSubmit}.
* Otherwise, the render phase will not realize the form was submitted
* and will simply display a new blank form.</td>
* </tr>
* <tr>
* <td>renderParameters</td>
* <td>null</td>
* <td>An array of parameters that will be passed forward from the action
* phase to the render phase if the form needs to be displayed
* again. These can also be passed forward explicitly by calling
* the {@code passRenderParameters} method from any action
* phase method. Abstract descendants of this controller should follow
* similar behavior. If there are parameters you need in
* {@code renderFormSubmission}, then you need to pass those
* forward from {@code processFormSubmission}. If you override the
* default behavior of invalid submits and you set sessionForm to true,
* then you probably will not need to set this because your parameters
* are only going to be needed on the first request.</td>
* </tr>
* </table>
* </p>
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @author Alef Arendsen
* @author Rob Harrop
* @since 2.0
* @see #showForm(RenderRequest, RenderResponse, BindException)
* @see SimpleFormController
* @see AbstractWizardFormController
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class AbstractFormController extends BaseCommandController {
/**
* These render parameters are used to indicate forward to the render phase
* if the form was submitted and if the submission was invalid.
*/
private static final String FORM_SUBMISSION_PARAMETER = "form-submit";
private static final String INVALID_SUBMISSION_PARAMETER = "invalid-submit";
private static final String TRUE = Boolean.TRUE.toString();
private boolean bindOnNewForm = false;
private boolean sessionForm = false;
private boolean redirectAction = false;
private String[] renderParameters = null;
/**
* Create a new AbstractFormController.
* <p>Subclasses should set the following properties, either in the constructor
* or via a BeanFactory: commandName, commandClass, bindOnNewForm, sessionForm.
* Note that commandClass doesn't need to be set when overriding
* {@code formBackingObject}, as the latter determines the class anyway.
* <p>"cacheSeconds" is by default set to 0 (-> no caching for all form controllers).
* @see #setCommandName
* @see #setCommandClass
* @see #setBindOnNewForm
* @see #setSessionForm
* @see #formBackingObject
*/
public AbstractFormController() {
setCacheSeconds(0);
}
/**
* Set if request parameters should be bound to the form object
* in case of a non-submitting request, i.e. a new form.
*/
public final void setBindOnNewForm(boolean bindOnNewForm) {
this.bindOnNewForm = bindOnNewForm;
}
/**
* Return if request parameters should be bound in case of a new form.
*/
public final boolean isBindOnNewForm() {
return this.bindOnNewForm;
}
/**
* Activate/deactivate session form mode. In session form mode,
* the form is stored in the session to keep the form object instance
* between requests, instead of creating a new one on each request.
* <p>This is necessary for either wizard-style controllers that populate a
* single form object from multiple pages, or forms that populate a persistent
* object that needs to be identical to allow for tracking changes.
*/
public final void setSessionForm(boolean sessionForm) {
this.sessionForm = sessionForm;
}
/**
* Return if session form mode is activated.
*/
public final boolean isSessionForm() {
return this.sessionForm;
}
/**
* Specify whether the action phase is expected to call
* {@link ActionResponse#sendRedirect}.
* This information is important because some methods may not be called
* before {@link ActionResponse#sendRedirect}, e.g.
* {@link ActionResponse#setRenderParameter} and
* {@link ActionResponse#setRenderParameters}.
* <p><b>NOTE:</b> Call this at initialization time of your controller:
* either in the constructor or in the bean definition for your controller.
* @see ActionResponse#sendRedirect
*/
public void setRedirectAction(boolean redirectAction) {
this.redirectAction = redirectAction;
}
/**
* Return if {@link ActionResponse#sendRedirect} is
* expected to be called in the action phase.
*/
public boolean isRedirectAction() {
return this.redirectAction;
}
/**
* Specify the list of parameters that should be passed forward
* from the action phase to the render phase whenever the form is
* re-rendered or when {@link #passRenderParameters} is called.
* @see #passRenderParameters
*/
public void setRenderParameters(String[] parameters) {
this.renderParameters = parameters;
}
/**
* Returns the list of parameters that will be passed forward
* from the action phase to the render phase whenever the form is
* rerendered or when {@link #passRenderParameters} is called.
* @return the list of parameters
* @see #passRenderParameters
*/
public String[] getRenderParameters() {
return this.renderParameters;
}
/**
* Handles action phase of two cases: form submissions and showing a new form.
* Delegates the decision between the two to {@code isFormSubmission},
* always treating requests without existing form session attribute
* as new form when using session form mode.
* @see #isFormSubmission
* @see #processFormSubmission
* @see #handleRenderRequestInternal
*/
@Override
protected void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception {
// Form submission or new form to show?
if (isFormSubmission(request)) {
// Fetch form object, bind, validate, process submission.
try {
Object command = getCommand(request);
if (logger.isDebugEnabled()) {
logger.debug("Processing valid submit (redirectAction = " + isRedirectAction() + ")");
}
if (!isRedirectAction()) {
setFormSubmit(response);
}
PortletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
processFormSubmission(request, response, command, errors);
setRenderCommandAndErrors(request, command, errors);
return;
}
catch (PortletSessionRequiredException ex) {
// Cannot submit a session form if no form object is in the session.
if (logger.isDebugEnabled()) {
logger.debug("Invalid submit detected: " + ex.getMessage());
}
setFormSubmit(response);
setInvalidSubmit(response);
handleInvalidSubmit(request, response);
return;
}
}
else {
logger.debug("Not a form submit - passing parameters to render phase");
passRenderParameters(request, response);
return;
}
}
/**
* Handles render phase of two cases: form submissions and showing a new form.
* Delegates the decision between the two to {@code isFormSubmission},
* always treating requests without existing form session attribute
* as new form when using session form mode.
* @see #isFormSubmission
* @see #showNewForm
* @see #processFormSubmission
* @see #handleActionRequestInternal
*/
@Override
protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
throws Exception {
// Form submission or new form to show?
if (isFormSubmission(request)) {
// If it is an invalid submit then handle it.
if (isInvalidSubmission(request)) {
logger.debug("Invalid submit - calling renderInvalidSubmit");
return renderInvalidSubmit(request, response);
}
// Valid submit -> render.
logger.debug("Valid submit - calling renderFormSubmission");
return renderFormSubmission(request, response, getRenderCommand(request), getRenderErrors(request));
}
else {
// New form to show: render form view.
return showNewForm(request, response);
}
}
/**
* Determine if the given request represents a form submission.
* <p>The default implementation checks to see if this is an ActionRequest
* and treats all action requests as form submission. During the action
* phase it will pass forward a render parameter to indicate to the render
* phase that this is a form submission. This method can check both
* kinds of requests and indicate if this is a form submission.
* <p>Subclasses can override this to use a custom strategy, e.g. a specific
* request parameter (assumably a hidden field or submit button name). Make
* sure that the override can handle both ActionRequest and RenderRequest
* objects properly.
* @param request current request
* @return if the request represents a form submission
*/
protected boolean isFormSubmission(PortletRequest request) {
return (request instanceof ActionRequest || TRUE.equals(request.getParameter(getFormSubmitParameterName())));
}
/**
* Determine if the given request represents an invalid form submission.
*/
protected boolean isInvalidSubmission(PortletRequest request) {
return TRUE.equals(request.getParameter(getInvalidSubmitParameterName()));
}
/**
* Return the name of the render parameter that indicates this
* was a form submission.
* @return the name of the render parameter
* @see javax.portlet.RenderRequest#getParameter
*/
protected String getFormSubmitParameterName() {
return FORM_SUBMISSION_PARAMETER;
}
/**
* Return the name of the render parameter that indicates this
* was an invalid form submission.
* @return the name of the render parameter
* @see javax.portlet.RenderRequest#getParameter
*/
protected String getInvalidSubmitParameterName() {
return INVALID_SUBMISSION_PARAMETER;
}
/**
* Set the action response parameter that indicates this in a form submission.
* @param response the current action response
* @see #getFormSubmitParameterName()
*/
protected final void setFormSubmit(ActionResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Setting render parameter [" + getFormSubmitParameterName() +
"] to indicate this is a form submission");
}
try {
response.setRenderParameter(getFormSubmitParameterName(), TRUE);
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Set the action response parameter that indicates this in an invalid submission.
* @param response the current action response
* @see #getInvalidSubmitParameterName()
*/
protected final void setInvalidSubmit(ActionResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Setting render parameter [" + getInvalidSubmitParameterName() +
"] to indicate this is an invalid submission");
}
try {
response.setRenderParameter(getInvalidSubmitParameterName(), TRUE);
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Return the name of the PortletSession attribute that holds the form object
* for this form controller.
* <p>The default implementation delegates to the
* {@code getFormSessionAttributeName} version without arguments.
* @param request current HTTP request
* @return the name of the form session attribute,
* or {@code null} if not in session form mode
* @see #getFormSessionAttributeName()
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getFormSessionAttributeName(PortletRequest request) {
return getFormSessionAttributeName();
}
/**
* Return the name of the PortletSession attribute that holds the form object
* for this 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 form session attribute
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getFormSessionAttributeName() {
return getClass().getName() + ".FORM." + getCommandName();
}
/**
* Pass the specified list of action request parameters to the render phase
* by putting them into the action response object. This may not be called
* when the action will call will call
* {@link ActionResponse#sendRedirect sendRedirect}.
* @param request the current action request
* @param response the current action response
* @see ActionResponse#setRenderParameter
*/
protected void passRenderParameters(ActionRequest request, ActionResponse response) {
if (this.renderParameters == null) {
return;
}
try {
for (int i = 0; i < this.renderParameters.length; i++) {
String paramName = this.renderParameters[i];
String paramValues[] = request.getParameterValues(paramName);
if (paramValues != null) {
if (logger.isDebugEnabled()) {
logger.debug("Passing parameter to render phase '" + paramName + "' = " +
(paramValues == null ? "NULL" : Arrays.asList(paramValues).toString()));
}
response.setRenderParameter(paramName, paramValues);
}
}
}
catch (IllegalStateException ex) {
// Ignore in case sendRedirect was already set.
}
}
/**
* Show a new form. Prepares a backing object for the current form
* and the given request, including checking its validity.
* @param request current render request
* @param response current render response
* @return the prepared form view
* @throws Exception in case of an invalid new form object
* @see #getErrorsForNewForm
*/
protected final ModelAndView showNewForm(RenderRequest request, RenderResponse response)
throws Exception {
logger.debug("Displaying new form");
return showForm(request, response, getErrorsForNewForm(request));
}
/**
* Create a BindException instance for a new form.
* Called by {@code showNewForm}.
* <p>Can be used directly when intending to show a new form but with
* special errors registered on it (for example, on invalid submit).
* Usually, the resulting BindException will be passed to
* {@code showForm}, after registering the errors on it.
* @param request current render request
* @return the BindException instance
* @throws Exception in case of an invalid new form object
*/
protected final BindException getErrorsForNewForm(RenderRequest request) throws Exception {
// Create form-backing object for new form
Object command = formBackingObject(request);
if (command == null) {
throw new PortletException("Form object returned by formBackingObject() must not be null");
}
if (!checkCommand(command)) {
throw new PortletException("Form object returned by formBackingObject() must match commandClass");
}
// Bind without validation, to allow for prepopulating a form, and for
// convenient error evaluation in views (on both first attempt and resubmit).
PortletRequestDataBinder binder = createBinder(request, command);
BindException errors = new BindException(binder.getBindingResult());
if (isBindOnNewForm()) {
if (logger.isDebugEnabled()) {
logger.debug("Binding to new form");
}
binder.bind(request);
onBindOnNewForm(request, command, errors);
}
// Return BindException object that resulted from binding.
return errors;
}
/**
* Callback for custom post-processing in terms of binding for a new form.
* Called when preparing a new form if {@code bindOnNewForm} is {@code true}.
* <p>Default implementation delegates to {@code onBindOnNewForm(request, command)}.
* @param request current render request
* @param command the command object to perform further binding on
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @throws Exception in case of invalid state or arguments
* @see #onBindOnNewForm(RenderRequest, Object)
* @see #setBindOnNewForm
*/
protected void onBindOnNewForm(RenderRequest request, Object command, BindException errors)
throws Exception {
onBindOnNewForm(request, command);
}
/**
* Callback for custom post-processing in terms of binding for a new form.
* Called by the default implementation of the {@code onBindOnNewForm} version
* with all parameters, after standard binding when displaying the form view.
* Only called if {@code bindOnNewForm} is set to {@code true}.
* <p>The default implementation is empty.
* @param request current render request
* @param command the command object to perform further binding on
* @throws Exception in case of invalid state or arguments
* @see #onBindOnNewForm(RenderRequest, Object, BindException)
* @see #setBindOnNewForm(boolean)
*/
protected void onBindOnNewForm(RenderRequest request, Object command) throws Exception {
}
/**
* Return the form object for the given request.
* <p>Calls {@code formBackingObject} if the object is not in the session
* @param request current request
* @return object form to bind onto
* @see #formBackingObject
*/
@Override
protected final Object getCommand(PortletRequest request) throws Exception {
// If not in session-form mode, create a new form-backing object.
if (!isSessionForm()) {
return formBackingObject(request);
}
// Session-form mode: retrieve form object from portlet session attribute.
PortletSession session = request.getPortletSession(false);
if (session == null) {
throw new PortletSessionRequiredException("Must have session when trying to bind (in session-form mode)");
}
String formAttrName = getFormSessionAttributeName(request);
Object sessionFormObject = session.getAttribute(formAttrName);
if (sessionFormObject == null) {
throw new PortletSessionRequiredException("Form object not found in session (in session-form mode)");
}
// Remove form object from porlet session: we might finish the form workflow
// in this request. If it turns out that we need to show the form view again,
// we'll re-bind the form object to the portlet session.
if (logger.isDebugEnabled()) {
logger.debug("Removing form session attribute [" + formAttrName + "]");
}
session.removeAttribute(formAttrName);
// Check the command object to make sure its valid
if (!checkCommand(sessionFormObject)) {
throw new PortletSessionRequiredException("Object found in session does not match commandClass");
}
return sessionFormObject;
}
/**
* Retrieve a backing object for the current form from the given request.
* <p>The properties of the form object will correspond to the form field values
* in your form view. This object will be exposed in the model under the specified
* command name, to be accessed under that name in the view: for example, with
* a "spring:bind" tag. The default command name is "command".
* <p>Note that you need to activate session form mode to reuse the form-backing
* object across the entire form workflow. Else, a new instance of the command
* class will be created for each submission attempt, just using this backing
* object as template for the initial form.
* <p>The default implementation calls {@code BaseCommandController.createCommand},
* creating a new empty instance of the command class.
* Subclasses can override this to provide a preinitialized backing object.
* @param request current portlet request
* @return the backing object
* @throws Exception in case of invalid state or arguments
* @see #setCommandName
* @see #setCommandClass
* @see #createCommand
*/
protected Object formBackingObject(PortletRequest request) throws Exception {
return createCommand();
}
/**
* Prepare the form model and view, including reference and error data.
* Can show a configured form page, or generate a form view programmatically.
* <p>A typical implementation will call
* {@code showForm(request, errors, "myView")}
* to prepare the form view for a specific view name, returning the
* ModelAndView provided there.
* <p>For building a custom ModelAndView, 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.
* You also need to include the model returned by {@code referenceData}.
* <p>Note: If you decide to have a "formView" property specifying the
* view name, consider using SimpleFormController.
* @param request current render request
* @param response current render response
* @param errors validation errors holder
* @return the prepared form view, or null if handled directly
* @throws Exception in case of invalid state or arguments
* @see #showForm(RenderRequest, BindException, String)
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
* @see #referenceData(PortletRequest, Object, Errors)
* @see SimpleFormController#setFormView
*/
protected abstract ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors) throws Exception;
/**
* Prepare model and view for the given form, including reference and errors.
* <p>In session form mode: Re-puts the form object in the session when
* returning to the form, as it has been removed by getCommand.
* <p>Can be used in subclasses to redirect back to a specific form page.
* @param request current render request
* @param errors validation errors holder
* @param viewName name of the form view
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #showForm(RenderRequest, BindException, String, Map)
* @see #showForm(RenderRequest, RenderResponse, BindException)
*/
protected final ModelAndView showForm(RenderRequest request, BindException errors, String viewName)
throws Exception {
return showForm(request, errors, viewName, null);
}
/**
* Prepare model and view for the given form, including reference and errors,
* adding a controller-specific control model.
* <p>In session form mode: Re-puts the form object in the session when returning
* to the form, as it has been removed by getCommand.
* <p>Can be used in subclasses to redirect back to a specific form page.
* @param request current render request
* @param errors validation errors holder
* @param viewName name of the form view
* @param controlModel model map containing controller-specific control data
* (e.g. current page in wizard-style controllers or special error message)
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #showForm(RenderRequest, BindException, String)
* @see #showForm(RenderRequest, RenderResponse, BindException)
*/
protected final ModelAndView showForm(RenderRequest request, BindException errors, String viewName, Map controlModel)
throws Exception {
// In session form mode, re-expose form object as portlet session attribute.
// Re-binding is necessary for proper state handling in a cluster,
// to notify other nodes of changes in the form object.
if (isSessionForm()) {
String formAttrName = getFormSessionAttributeName(request);
if (logger.isDebugEnabled()) {
logger.debug("Setting form session attribute [" + formAttrName + "] to: " + errors.getTarget());
}
request.getPortletSession().setAttribute(formAttrName, errors.getTarget());
}
// Fetch errors model as starting point, containing form object under
// "commandName", and corresponding Errors instance under internal key.
Map model = errors.getModel();
// Merge reference data into model, if any.
Map referenceData = referenceData(request, errors.getTarget(), errors);
if (referenceData != null) {
model.putAll(referenceData);
}
// Merge control attributes into model, if any.
if (controlModel != null) {
model.putAll(controlModel);
}
// Trigger rendering of the specified view, using the final model.
return new ModelAndView(viewName, model);
}
/**
* 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 render request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
protected Map referenceData(PortletRequest request, Object command, Errors errors) throws Exception {
return null;
}
/**
* Process render phase of form submission request. Called by {@code handleRequestInternal}
* in case of a form submission, with or without binding errors. Implementations
* need to proceed properly, typically showing a form view in case of binding
* errors or rendering the result of a submit action else.
* <p>For a success view, 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. For a form view,
* simply return the ModelAndView object privded by {@code showForm}.
* @param request current render request
* @param response current render response
* @param command form object with request parameters bound onto it
* @param errors errors holder
* @return the prepared model and view, or null
* @throws Exception in case of errors
* @see #handleRenderRequestInternal
* @see #processFormSubmission
* @see #isFormSubmission
* @see #showForm(RenderRequest, RenderResponse, BindException)
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception;
/**
* Process action phase of form submission request. Called by {@code handleRequestInternal}
* in case of a form submission, with or without binding errors. Implementations
* need to proceed properly, typically performing a submit action if there are no binding errors.
* <p>Subclasses can implement this to provide custom submission handling
* like triggering a custom action. They can also provide custom validation
* or proceed with the submission accordingly.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @param errors errors holder (subclass can add errors if it wants to)
* @throws Exception in case of errors
* @see #handleActionRequestInternal
* @see #renderFormSubmission
* @see #isFormSubmission
* @see org.springframework.validation.Errors
*/
protected abstract void processFormSubmission(ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception;
/**
* 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 simply tries to resubmit the form with a new form object.
* This should also work if the user hit the back button, changed some form data,
* and resubmitted the form.
* <p>Note: To avoid duplicate submissions, you need to override this method.
* Either show some "invalid submit" message, or call {@code showNewForm} for
* resetting the form (prepopulating it with the current values if "bindOnNewForm"
* is true). In this case, the form object in the session serves as transaction token.
* <pre class="code">
* protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
* return showNewForm(request, response);
* }</pre>
* You can also show a new form but with special errors registered on it:
* <pre class="code">
* protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
* BindException errors = getErrorsForNewForm(request);
* errors.reject("duplicateFormSubmission", "Duplicate form submission");
* return showForm(request, response, errors);
* }</pre>
* <p><b>WARNING:</b> If you override this method, be sure to also override the action
* phase version of this method so that it will not attempt to perform the resubmit
* action by default.
* @param request current render request
* @param response current render response
* @return a prepared view, or null if handled directly
* @throws Exception in case of errors
* @see #handleInvalidSubmit
*/
protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response)
throws Exception {
return renderFormSubmission(request, response, getRenderCommand(request), getRenderErrors(request));
}
/**
* 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 simply tries to resubmit the form with a new form object.
* This should also work if the user hit the back button, changed some form data,
* and resubmitted the form.
* <p>Note: To avoid duplicate submissions, you need to override this method.
* Most likely you will simply want it to do nothing here in the action phase
* and diplay an appropriate error and a new form in the render phase.
* <pre class="code">
* protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
* }</pre>
* <p>If you override this method but you do need a command object and bind errors
* in the render phase, be sure to call {@link #setRenderCommandAndErrors setRenderCommandAndErrors}
* from here.
* @param request current action request
* @param response current action response
* @throws Exception in case of errors
* @see #renderInvalidSubmit
* @see #setRenderCommandAndErrors
*/
protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
passRenderParameters(request, response);
Object command = formBackingObject(request);
if (command == null) {
throw new PortletException("Form object returned by formBackingObject() must not be null");
}
if (!checkCommand(command)) {
throw new PortletException("Form object returned by formBackingObject() must match commandClass");
}
PortletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
processFormSubmission(request, response, command, errors);
setRenderCommandAndErrors(request, command, errors);
}
}

View File

@@ -1,982 +0,0 @@
/*
* 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");
}
}

View File

@@ -1,666 +0,0 @@
/*
* 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 javax.portlet.ActionRequest;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.context.PortletWebRequest;
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
/**
* <p>Controller implementation which creates an object (the command object) on
* receipt of a request and attempts to populate this object with request parameters.</p>
*
* <p>This controller is the base for all controllers wishing to populate
* JavaBeans based on request parameters, validate the content of such
* JavaBeans using {@link Validator Validators} and use custom editors (in the form of
* {@link java.beans.PropertyEditor PropertyEditors}) to transform
* objects into strings and vice versa, for example. Three notions are mentioned here:</p>
*
* <p><b>Command class:</b><br>
* An instance of the command class will be created for each request and populated
* with request parameters. A command class can basically be any Java class; the only
* requirement is a no-arg constructor. The command class should preferably be a
* JavaBean in order to be able to populate bean properties with request parameters.</p>
*
* <p><b>Populating using request parameters and PropertyEditors:</b><br>
* Upon receiving a request, any BaseCommandController will attempt to fill the
* command object using the request parameters. This is done using the typical
* and well-known JavaBeans property notation. When a request parameter named
* {@code 'firstName'} exists, the framework will attempt to call
* {@code setFirstName([value])} passing the value of the parameter. Nested properties
* are of course supported. For instance a parameter named {@code 'address.city'}
* will result in a {@code getAddress().setCity([value])} call on the
* command class.</p>
*
* <p>It's important to realize that you are not limited to String arguments in
* your JavaBeans. Using the PropertyEditor-notion as supplied by the
* java.beans package, you will be able to transform Strings to Objects and
* the other way around. For instance {@code setLocale(Locale loc)} is
* perfectly possible for a request parameter named {@code locale} having
* a value of {@code en}, as long as you register the appropriate
* PropertyEditor in the Controller (see {@link #initBinder initBinder()}
* for more information on that matter).</p>
*
* <p><b>Validators:</b>
* After the controller has successfully populated the command object with
* parameters from the request, it will use any configured validators to
* validate the object. Validation results will be put in a
* {@link org.springframework.validation.Errors Errors} object which can be
* used in a View to render any input problems.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
* Since this class is an abstract base class for more specific implementation,
* it does not override the {@code handleRequestInternal()} methods and also has no
* actual workflow. Implementing classes like
* {@link AbstractFormController AbstractFormController},
* {@link AbstractCommandController AbstractCommandController},
* {@link SimpleFormController SimpleFormController} and
* {@link AbstractWizardFormController AbstractWizardFormController}
* provide actual functionality and workflow.
* More information on workflow performed by superclasses can be found
* <a href="AbstractController.html#workflow">here</a>.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></th>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>commandName</td>
* <td>command</td>
* <td>the name to use when binding the instantiated command class
* to the request</td>
* </tr>
* <tr>
* <td>commandClass</td>
* <td><i>null</i></td>
* <td>the class to use upon receiving a request and which to fill
* using the request parameters. What object is used and whether
* or not it should be created is defined by extending classes
* and their configuration properties and methods.</td>
* </tr>
* <tr>
* <td>validators</td>
* <td><i>null</i></td>
* <td>Array of Validator beans. The validator will be called at appropriate
* places in the workflow of subclasses (have a look at those for more info)
* to validate the command object.</td>
* </tr>
* <tr>
* <td>validator</td>
* <td><i>null</i></td>
* <td>Short-form property for setting only one Validator bean (usually passed in
* using a &lt;ref bean="beanId"/&gt; property.</td>
* </tr>
* <tr>
* <td>validateOnBinding</td>
* <td>true</td>
* <td>Indicates whether or not to validate the command object after the
* object has been populated with request parameters.</td>
* </tr>
* </table>
* </p>
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author Juergen Hoeller
* @author John A. Lewis
* @since 2.0
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class BaseCommandController extends AbstractController {
/**
* Unlike the servlet version of these classes, we have to deal with the
* two-phase nature of the portlet request. To do this, we need to pass
* forward the command object and the bind/validation errors that occured
* on the command object from the action phase to the render phase.
* The only direct way to pass things forward and preserve them for each
* render request is through render parameters, but these are limited to
* String objects and we need to pass more complicated objects. The only
* other way to do this is in the session. The bad thing about using the
* session is that we have no way of knowing when we are done re-rendering
* the request and so we don't know when we can remove the objects from
* the session. So we will end up polluting the session with old objects
* when we finally leave the render of this controller and move on to
* somthing else. To minimize the pollution, we will use a static string
* value as the session attribute name. At least this way we are only ever
* leaving one orphaned set behind. The methods that return these names
* can be overridden if you want to use a different method, but be aware
* of the session pollution that may occur.
*/
private static final String RENDER_COMMAND_SESSION_ATTRIBUTE =
"org.springframework.web.portlet.mvc.RenderCommand";
private static final String RENDER_ERRORS_SESSION_ATTRIBUTE =
"org.springframework.web.portlet.mvc.RenderErrors";
public static final String DEFAULT_COMMAND_NAME = "command";
private String commandName = DEFAULT_COMMAND_NAME;
private Class commandClass;
private Validator[] validators;
private boolean validateOnBinding = true;
private MessageCodesResolver messageCodesResolver;
private BindingErrorProcessor bindingErrorProcessor;
private PropertyEditorRegistrar[] propertyEditorRegistrars;
private WebBindingInitializer webBindingInitializer;
/**
* Set the name of the command in the model.
* The command object will be included in the model under this name.
*/
public final void setCommandName(String commandName) {
this.commandName = commandName;
}
/**
* Return the name of the command in the model.
*/
public final String getCommandName() {
return this.commandName;
}
/**
* Set the command class for this controller.
* An instance of this class gets populated and validated on each request.
*/
public final void setCommandClass(Class commandClass) {
this.commandClass = commandClass;
}
/**
* Return the command class for this controller.
*/
public final Class getCommandClass() {
return this.commandClass;
}
/**
* Set the primary Validator for this controller. The Validator
* must support the specified command class. If there are one
* or more existing validators set already when this method is
* called, only the specified validator will be kept. Use
* {@link #setValidators(Validator[])} to set multiple validators.
*/
public final void setValidator(Validator validator) {
this.validators = new Validator[] {validator};
}
/**
* @return the primary Validator for this controller.
*/
public final Validator getValidator() {
return (this.validators != null && this.validators.length > 0 ? this.validators[0] : null);
}
/**
* Set the Validators for this controller.
* The Validator must support the specified command class.
*/
public final void setValidators(Validator[] validators) {
this.validators = validators;
}
/**
* Return the Validators for this controller.
*/
public final Validator[] getValidators() {
return this.validators;
}
/**
* Set if the Validator should get applied when binding.
*/
public final void setValidateOnBinding(boolean validateOnBinding) {
this.validateOnBinding = validateOnBinding;
}
/**
* Return if the Validator should get applied when binding.
*/
public final boolean isValidateOnBinding() {
return this.validateOnBinding;
}
/**
* Set the strategy to use for resolving errors into message codes.
* Applies the given strategy to all data binders used by this controller.
* <p>Default is {@code null}, i.e. using the default strategy of the data binder.
* @see #createBinder
* @see org.springframework.validation.DataBinder#setMessageCodesResolver
*/
public final void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
this.messageCodesResolver = messageCodesResolver;
}
/**
* Return the strategy to use for resolving errors into message codes (if any).
*/
public final MessageCodesResolver getMessageCodesResolver() {
return this.messageCodesResolver;
}
/**
* Set the strategy to use for processing binding errors, that is,
* required field errors and {@code PropertyAccessException}s.
* <p>Default is {@code null}, i.e. using the default strategy of
* the data binder.
* @see #createBinder
* @see org.springframework.validation.DataBinder#setBindingErrorProcessor
*/
public final void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
this.bindingErrorProcessor = bindingErrorProcessor;
}
/**
* Return the strategy to use for processing binding errors (if any).
*/
public final BindingErrorProcessor getBindingErrorProcessor() {
return this.bindingErrorProcessor;
}
/**
* Specify a single PropertyEditorRegistrar to be applied
* to every DataBinder that this controller uses.
* <p>Allows for factoring out the registration of PropertyEditors
* to separate objects, as an alternative to {@code initBinder}.
* @see #initBinder
*/
public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
}
/**
* Specify one or more PropertyEditorRegistrars to be applied
* to every DataBinder that this controller uses.
* <p>Allows for factoring out the registration of PropertyEditors
* to separate objects, as alternative to {@code initBinder}.
* @see #initBinder
*/
public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}
/**
* Return the PropertyEditorRegistrars (if any) to be applied
* to every DataBinder that this controller uses.
*/
public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
return this.propertyEditorRegistrars;
}
/**
* Specify a WebBindingInitializer which will apply pre-configured
* configuration to every DataBinder that this controller uses.
* <p>Allows for factoring out the entire binder configuration
* to separate objects, as an alternative to {@link #initBinder}.
*/
public final void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Return the WebBindingInitializer (if any) which will apply pre-configured
* configuration to every DataBinder that this controller uses.
*/
public final WebBindingInitializer getWebBindingInitializer() {
return this.webBindingInitializer;
}
@Override
protected void initApplicationContext() {
if (this.validators != null) {
for (int i = 0; i < this.validators.length; i++) {
if (this.commandClass != null && !this.validators[i].supports(this.commandClass))
throw new IllegalArgumentException("Validator [" + this.validators[i] +
"] does not support command class [" +
this.commandClass.getName() + "]");
}
}
}
/**
* Retrieve a command object for the given request.
* <p>The default implementation calls {@link #createCommand()}.
* Subclasses can override this.
* @param request current portlet request
* @return object command to bind onto
* @see #createCommand
*/
protected Object getCommand(PortletRequest request) throws Exception {
return createCommand();
}
/**
* Create a new command instance for the command class of this controller.
* <p>This implementation uses {@code BeanUtils.instantiateClass},
* so the command needs to have a no-arg constructor (supposed to be
* public, but not required to).
* @return the new command instance
* @throws Exception if the command object could not be instantiated
* @see org.springframework.beans.BeanUtils#instantiateClass(Class)
*/
protected final Object createCommand() throws Exception {
if (this.commandClass == null) {
throw new IllegalStateException("Cannot create command without commandClass being set - " +
"either set commandClass or (in a form controller) override formBackingObject");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating new command of class [" + this.commandClass.getName() + "]");
}
return BeanUtils.instantiateClass(this.commandClass);
}
/**
* Check if the given command object is a valid for this controller,
* i.e. its command class.
* @param command the command object to check
* @return if the command object is valid for this controller
*/
protected final boolean checkCommand(Object command) {
return (this.commandClass == null || this.commandClass.isInstance(command));
}
/**
* Bind the parameters of the given request to the given command object.
* @param request current portlet request
* @param command the command to bind onto
* @return the PortletRequestDataBinder instance for additional custom validation
* @throws Exception in case of invalid state or arguments
*/
protected final PortletRequestDataBinder bindAndValidate(PortletRequest request, Object command)
throws Exception {
PortletRequestDataBinder binder = createBinder(request, command);
if (!suppressBinding(request)) {
binder.bind(request);
BindException errors = new BindException(binder.getBindingResult());
onBind(request, command, errors);
if (this.validators != null && isValidateOnBinding() && !suppressValidation(request)) {
for (int i = 0; i < this.validators.length; i++) {
ValidationUtils.invokeValidator(this.validators[i], command, errors);
}
}
onBindAndValidate(request, command, errors);
}
return binder;
}
/**
* Return whether to suppress binding for the given request.
* <p>The default implementation always returns {@code false}.
* Can be overridden in subclasses to suppress validation:
* for example, if a special request parameter is set.
* @param request current portlet request
* @return whether to suppress binding for the given request
* @see #suppressValidation
*/
protected boolean suppressBinding(PortletRequest request) {
return false;
}
/**
* Create a new binder instance for the given command and request.
* <p>Called by {@code bindAndValidate}. Can be overridden to plug in
* custom PortletRequestDataBinder instances.
* <p>The default implementation creates a standard PortletRequestDataBinder and
* invokes {@code prepareBinder} and {@code initBinder}.
* <p>Note that neither {@code prepareBinder} nor {@code initBinder}
* will be invoked automatically if you override this method! Call those methods
* at appropriate points of your overridden method.
* @param request current portlet request
* @param command the command to bind onto
* @return the new binder instance
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #prepareBinder
* @see #initBinder
*/
protected PortletRequestDataBinder createBinder(PortletRequest request, Object command)
throws Exception {
PortletRequestDataBinder binder = new PortletRequestDataBinder(command, getCommandName());
prepareBinder(binder);
initBinder(request, binder);
return binder;
}
/**
* Prepare the given binder, applying the specified MessageCodesResolver,
* BindingErrorProcessor and PropertyEditorRegistrars (if any).
* Called by {@code createBinder}.
* @param binder the new binder instance
* @see #createBinder
* @see #setMessageCodesResolver
* @see #setBindingErrorProcessor
*/
protected final void prepareBinder(PortletRequestDataBinder binder) {
if (useDirectFieldAccess()) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.propertyEditorRegistrars != null) {
for (int i = 0; i < this.propertyEditorRegistrars.length; i++) {
this.propertyEditorRegistrars[i].registerCustomEditors(binder);
}
}
}
/**
* Determine whether to use direct field access instead of bean property access.
* Applied by {@code prepareBinder}.
* <p>The default is {@code false}. Can be overridden in subclasses.
* @see #prepareBinder
* @see org.springframework.validation.DataBinder#initDirectFieldAccess()
*/
protected boolean useDirectFieldAccess() {
return false;
}
/**
* Initialize the given binder instance, for example with custom editors.
* Called by {@code createBinder}.
* <p>This method allows you to register custom editors for certain fields of your
* command class. For instance, you will be able to transform Date objects into a
* String pattern and back, in order to allow your JavaBeans to have Date properties
* and still be able to set and display them in an HTML interface.
* <p>The default implementation is empty.
* @param request current portlet request
* @param binder new binder instance
* @throws Exception in case of invalid state or arguments
* @see #createBinder
* @see org.springframework.validation.DataBinder#registerCustomEditor
* @see org.springframework.beans.propertyeditors.CustomDateEditor
*/
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) throws Exception {
if (this.webBindingInitializer != null) {
this.webBindingInitializer.initBinder(binder, new PortletWebRequest(request));
}
}
/**
* Callback for custom post-processing in terms of binding.
* Called on each submit, after standard binding but before validation.
* <p>The default implementation delegates to {@code onBind(request, command)}.
* @param request current portlet request
* @param command the command object to perform further binding on
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #onBind(PortletRequest, Object)
*/
protected void onBind(PortletRequest request, Object command, BindException errors) throws Exception {
onBind(request, command);
}
/**
* Callback for custom post-processing in terms of binding.
* Called by the default implementation of the {@code onBind} version with
* all parameters, after standard binding but before validation.
* <p>The default implementation is empty.
* @param request current portlet request
* @param command the command object to perform further binding on
* @throws Exception in case of invalid state or arguments
* @see #onBind(PortletRequest, Object, BindException)
*/
protected void onBind(PortletRequest request, Object command) throws Exception {
}
/**
* Return whether to suppress validation for the given request.
* <p>The default implementation always returns {@code false}.
* Can be overridden in subclasses to suppress validation:
* for example, if a special request parameter is set.
* @param request current portlet request
* @return whether to suppress validation for the given request
*/
protected boolean suppressValidation(PortletRequest request) {
return false;
}
/**
* Callback for custom post-processing in terms of binding and validation.
* Called on each submit, after standard binding and validation,
* but before error evaluation.
* <p>The default implementation is empty.
* @param request current portlet request
* @param command the command object, still allowing for further binding
* @param errors validation errors holder, allowing for additional
* custom validation
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see org.springframework.validation.Errors
*/
protected void onBindAndValidate(PortletRequest request, Object command, BindException errors)
throws Exception {
}
/**
* Return the name of the session attribute that holds
* the render phase command object for this form controller.
* @return the name of the render phase command object session attribute
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getRenderCommandSessionAttributeName() {
return RENDER_COMMAND_SESSION_ATTRIBUTE;
}
/**
* Return the name of the session attribute that holds
* the render phase command object for this form controller.
* @return the name of the render phase command object session attribute
* @see javax.portlet.PortletSession#getAttribute
*/
protected String getRenderErrorsSessionAttributeName() {
return RENDER_ERRORS_SESSION_ATTRIBUTE;
}
/**
* Get the command object cached for the render phase.
* @see #getRenderErrors
* @see #getRenderCommandSessionAttributeName
* @see #setRenderCommandAndErrors
*/
protected final Object getRenderCommand(RenderRequest request) throws PortletException {
PortletSession session = request.getPortletSession(false);
if (session == null) {
throw new PortletSessionRequiredException("Could not obtain portlet session");
}
Object command = session.getAttribute(getRenderCommandSessionAttributeName());
if (command == null) {
throw new PortletSessionRequiredException("Could not obtain command object from portlet session");
}
return command;
}
/**
* Get the bind and validation errors cached for the render phase.
* @see #getRenderCommand
* @see #getRenderErrorsSessionAttributeName
* @see #setRenderCommandAndErrors
*/
protected final BindException getRenderErrors(RenderRequest request) throws PortletException {
PortletSession session = request.getPortletSession(false);
if (session == null) {
throw new PortletSessionRequiredException("Could not obtain portlet session");
}
BindException errors = (BindException) session.getAttribute(getRenderErrorsSessionAttributeName());
if (errors == null) {
throw new PortletSessionRequiredException("Could not obtain errors object from portlet session");
}
return errors;
}
/**
* Set the command object and errors object for the render phase.
* @param request the current action request
* @param command the command object to preserve for the render phase
* @param errors the errors from binding and validation to preserve for the render phase
* @see #getRenderCommand
* @see #getRenderErrors
* @see #getRenderCommandSessionAttributeName
* @see #getRenderErrorsSessionAttributeName
*/
protected final void setRenderCommandAndErrors(
ActionRequest request, Object command, BindException errors) throws Exception {
logger.debug("Storing command and error objects in session for render phase");
PortletSession session = request.getPortletSession();
session.setAttribute(getRenderCommandSessionAttributeName(), command);
session.setAttribute(getRenderErrorsSessionAttributeName(), errors);
}
}

View File

@@ -1,562 +0,0 @@
/*
* 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.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;
/**
* <p>Concrete FormController implementation that provides configurable
* form and success views, and an onSubmit chain for convenient overriding.
* Automatically resubmits to the form view in case of validation errors,
* and renders the success view in case of a valid submission.</p>
*
* <p>The workflow of this Controller does not differ much from the one described
* in the {@link AbstractFormController AbstractFormController}. The difference
* is that you do not need to implement {@link #showForm showForm},
* {@link #processFormSubmission processFormSubmission}, and
* {@link #renderFormSubmission renderFormSubmission}: A form view and a
* success view can be configured declaratively.</p>
*
* <p>This controller is different from it's servlet counterpart in that it must take
* into account the two phases of a portlet request: the action phase and the render
* phase. See the JSR-168 spec for more details on these two phases.
* Be especially aware that the action phase is called only once, but that the
* render phase will be called repeatedly by the portal -- it does this every time
* the page containing the portlet is updated, even if the activity is in some other
* portlet. The main difference in the methods in this class is that the
* {@code onSubmit} methods have all been split into {@code onSubmitAction}
* and {@code onSubmitRender} to account for the two phases.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractFormController.html#workflow">in addition to the superclass</a>):</b><br>
* <ol>
* <li>Call to {@link #processFormSubmission processFormSubmission} which inspects
* the {@link org.springframework.validation.Errors Errors} object to see if
* any errors have occurred during binding and validation.</li>
* <li>If errors occured, the controller will return the configured formView,
* showing the form again (possibly rendering according error messages).</li>
* <li>If {@link #isFormChangeRequest isFormChangeRequest} is overridden and returns
* true for the given request, the controller will return the formView too.
* In that case, the controller will also suppress validation. Before returning the formView,
* the controller will invoke {@link #onFormChange}, giving sub-classes a chance
* to make modification to the command object.
* This is intended for requests that change the structure of the form,
* which should not cause validation and show the form in any case.</li>
* <li>If no errors occurred, the controller will call
* {@link #onSubmitAction(ActionRequest, ActionResponse, Object, BindException) onSubmitAction}
* during the action phase and then {@link #onSubmitRender(RenderRequest, RenderResponse,
* Object, BindException) onSubmitRender} during the render phase, which in case of the
* default implementation delegate to {@link #onSubmitAction(Object, BindException)
* onSubmitAction} and {@link #onSubmitRender(Object, BindException) onSubmitRender}
* with just the command object.
* The default implementation of the latter method will return the configured
* {@code successView}. Consider just implementing {@link #doSubmitAction doSubmitAction}
* for simply performing a submit action during the action phase and then rendering
* the success view during the render phase.</li>
* </ol>
* </p>
*
* <p>The submit behavior can be customized by overriding one of the
* {@link #onSubmitAction onSubmitAction} or {@link #onSubmitRender onSubmitRender}
* methods. Submit actions can also perform custom validation if necessary
* (typically database-driven checks), calling {@link #showForm(RenderRequest,
* RenderResponse, BindException) showForm} in case of validation errors to show
* the form view again. You do not have to override both the {@code onSubmitAction} and
* {@code onSubmitRender} methods at a given level unless you truly have custom logic to
* perform in both.<p>
*
* <p><b>WARNING:</b> Make sure that any one-time system updates (such as database
* updates or file writes) are performed in either an {@link #onSubmitAction onSubmitAction}
* method or the {@link #doSubmitAction doSubmitAction} method. Logic in the
* {@link #onSubmitRender onSubmitRender} methods may be executed repeatedly by
* the portal whenever the page containing the portlet is updated.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractFormController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>formView</td>
* <td><i>null</i></td>
* <td>Indicates what view to use when the user asks for a new form
* or when validation errors have occurred on form submission.</td>
* </tr>
* <tr>
* <td>successView</td>
* <td><i>null</i></td>
* <td>Indicates what view to use when successful form submissions have
* occurred. Such a success view could e.g. display a submission summary.
* More sophisticated actions can be implemented by overriding one of
* the {@link #onSubmitRender(Object) onSubmitRender()} methods.</td>
* </tr>
* <table>
* </p>
*
* <p>Parameters indicated with {@code setPassRenderParameters} will be
* preserved if the form has errors or if a form change request occurs.
* If there are render parameters you need in {@code onSubmitRender},
* then you need to pass those forward from {@code onSubmitAction}.
*
* <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
*
* @author John A. Lewis
* @author Juergen Hoeller
* @author Rob Harrop
* @since 2.0
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public class SimpleFormController extends AbstractFormController {
private String formView;
private String successView;
/**
* Create a new SimpleFormController.
* <p>Subclasses should set the following properties, either in the constructor
* or via a BeanFactory: commandName, commandClass, sessionForm, formView,
* successView. Note that commandClass doesn't need to be set when overriding
* {@code formBackingObject}, as this determines the class anyway.
* @see #setCommandClass(Class)
* @see #setCommandName(String)
* @see #setSessionForm(boolean)
* @see #setFormView
* @see #setSuccessView
* @see #formBackingObject(PortletRequest)
*/
public SimpleFormController() {
// AbstractFormController sets default cache seconds to 0.
super();
}
/**
* Set the name of the view that should be used for form display.
*/
public final void setFormView(String formView) {
this.formView = formView;
}
/**
* Return the name of the view that should be used for form display.
*/
public final String getFormView() {
return this.formView;
}
/**
* Set the name of the view that should be shown on successful submit.
*/
public final void setSuccessView(String successView) {
this.successView = successView;
}
/**
* Return the name of the view that should be shown on successful submit.
*/
public final String getSuccessView() {
return this.successView;
}
/**
* This implementation shows the configured form view, delegating to the
* analogous showForm version with a controlModel argument.
* <p>Can be called within onSubmit implementations, to redirect back to the form
* in case of custom validation errors (i.e. not determined by the validator).
* <p>Can be overridden in subclasses to show a custom view, writing directly
* to the response or preparing the response before rendering a view.
* <p>If calling showForm with a custom control model in subclasses, it's preferable
* to override the analogous showForm version with a controlModel argument
* (which will handle both standard form showing and custom form showing then).
* @see #setFormView
* @see #showForm(RenderRequest, RenderResponse, BindException, Map)
*/
@Override
protected ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors)
throws Exception {
return showForm(request, response, errors, null);
}
/**
* This implementation shows the configured form view.
* <p>Can be called within onSubmit implementations, to redirect back to the form
* in case of custom validation errors (i.e. not determined by the validator).
* <p>Can be overridden in subclasses to show a custom view, writing directly
* to the response or preparing the response before rendering a view.
* @param request current render request
* @param errors validation errors holder
* @param controlModel model map containing controller-specific control data
* (e.g. current page in wizard-style controllers or special error message)
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #setFormView
*/
protected ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors, Map controlModel)
throws Exception {
return showForm(request, errors, getFormView(), controlModel);
}
/**
* Create a reference data map for the given request and command,
* consisting of bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation delegates to {@link #referenceData(PortletRequest)}.
* 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
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
@Override
protected Map referenceData(PortletRequest request, Object command, Errors errors) throws Exception {
return referenceData(request);
}
/**
* Create a reference data map for the given request.
* Called by referenceData version with all parameters.
* <p>The default implementation returns {@code null}.
* Subclasses can override this to set reference data used in the view.
* @param request current portlet request
* @return a Map with reference data entries, or null if none
* @throws Exception in case of invalid state or arguments
* @see #referenceData(PortletRequest, Object, Errors)
* @see ModelAndView
*/
protected Map referenceData(PortletRequest request) throws Exception {
return null;
}
/**
* This implementation calls {@code showForm} in case of errors,
* and delegates to {@code onSubmitRender}'s full version else.
* <p>This can only be overridden to check for an action that should be executed
* without respect to binding errors, like a cancel action. To just handle successful
* submissions without binding errors, override one of the {@code onSubmitRender}
* methods.
* @see #showForm(RenderRequest, RenderResponse, BindException)
* @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
* @see #onSubmitRender(Object, BindException)
* @see #onSubmitRender(Object)
* @see #processFormSubmission(ActionRequest, ActionResponse, Object, BindException)
*/
@Override
protected ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
if (errors.hasErrors() || isFormChangeRequest(request)) {
return showForm(request, response, errors);
}
else {
return onSubmitRender(request, response, command, errors);
}
}
/**
* This implementation does nothing in case of errors,
* and delegates to {@code onSubmitAction}'s full version else.
* <p>This can only be overridden to check for an action that should be executed
* without respect to binding errors, like a cancel action. To just handle successful
* submissions without binding errors, override one of the {@code onSubmitAction}
* methods or {@code doSubmitAction}.
* @see #showForm
* @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
* @see #onSubmitAction(Object, BindException)
* @see #onSubmitAction(Object)
* @see #doSubmitAction(Object)
* @see #renderFormSubmission(RenderRequest, RenderResponse, Object, BindException)
*/
@Override
protected void processFormSubmission(
ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
if (errors.hasErrors()) {
if (logger.isDebugEnabled()) {
logger.debug("Data binding errors: " + errors.getErrorCount());
}
if (isRedirectAction()) {
setFormSubmit(response);
}
passRenderParameters(request, response);
}
else if (isFormChangeRequest(request)) {
logger.debug("Detected form change request -> routing request to onFormChange");
if (isRedirectAction()) {
setFormSubmit(response);
}
passRenderParameters(request, response);
onFormChange(request, response, command, errors);
}
else {
logger.debug("No errors - processing submit");
onSubmitAction(request, response, command, errors);
}
}
/**
* This implementation delegates to {@link #isFormChangeRequest}:
* A form change request changes the appearance of the form
* and should not get validated but just show the new form.
* @see #isFormChangeRequest
*/
@Override
protected boolean suppressValidation(PortletRequest request) {
return isFormChangeRequest(request);
}
/**
* Determine whether the given request is a form change request.
* A form change request changes the appearance of the form
* and should always show the new form, without validation.
* <p>Gets called by {@link #suppressValidation} and {@link #processFormSubmission}.
* Consequently, this single method determines to suppress validation
* <i>and</i> to show the form view in any case.
* <p>The default implementation returns {@code false}.
* @param request current portlet request
* @return whether the given request is a form change request
* @see #suppressValidation
* @see #processFormSubmission
*/
protected boolean isFormChangeRequest(PortletRequest request) {
return false;
}
/**
* Called during form submission if {@link #isFormChangeRequest(PortletRequest)}
* returns {@code true}. Allows subclasses to implement custom logic
* to modify the command object to directly modify data in the form.
* <p>The default implementation delegates to
* {@code onFormChange(request, response, command)}.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @param errors validation errors holder, allowing for additional
* custom validation
* @throws Exception in case of errors
* @see #isFormChangeRequest(PortletRequest)
* @see #onFormChange(ActionRequest, ActionResponse, Object)
*/
protected void onFormChange(ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
onFormChange(request, response, command);
}
/**
* Simpler {@code onFormChange} variant, called by the full version
* {@code onFormChange(request, response, command, errors)}.
* <p>The default implementation is empty.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onFormChange(ActionRequest, ActionResponse, Object, BindException)
*/
protected void onFormChange(ActionRequest request, ActionResponse response, Object command)
throws Exception {
}
/**
* Submit render phase callback with all parameters. Called in case of submit without errors
* reported by the registered validator, or on every submit if no validator.
* <p>The default implementation delegates to {@link #onSubmitRender(Object, BindException)}.
* For simply performing a submit action and rendering the specified success view,
* do not implement an {@code onSubmitRender} at all.
* <p>Subclasses can override this to provide custom rendering to display results of
* the action phase. Implementations can also call {@code showForm} to return to the form
* if the {@code onSubmitAction} failed custom validation. Do <i>not</i> implement multiple
* {@code onSubmitRender} methods: In that case,
* just this method will be called by the controller.
* <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 render request
* @param response current render response
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors (subclass can add errors if it wants to)
* @return the prepared model and view
* @throws Exception in case of errors
* @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
* @see #onSubmitRender(Object, BindException)
* @see #doSubmitAction
* @see #showForm
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView onSubmitRender(RenderRequest request, RenderResponse response, Object command, BindException errors)
throws Exception {
return onSubmitRender(command, errors);
}
/**
* Submit action phase callback with all parameters. Called in case of submit without errors
* reported by the registered validator respectively on every submit if no validator.
* <p>The default implementation delegates to {@link #onSubmitAction(Object, BindException)}.
* For simply performing a submit action consider implementing {@code doSubmitAction}
* rather than an {@code onSubmitAction} version.
* <p>Subclasses can override this to provide custom submission handling like storing
* the object to the database. Implementations can also perform custom validation and
* signal the render phase to call {@code showForm} to return to the form. Do <i>not</i>
* implement multiple {@code onSubmitAction} methods: In that case,
* just this method will be called by the controller.
* @param request current action request
* @param response current action response
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors (subclass can add errors if it wants to)
* @throws Exception in case of errors
* @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
* @see #onSubmitAction(Object, BindException)
* @see #doSubmitAction
* @see org.springframework.validation.Errors
*/
protected void onSubmitAction(ActionRequest request, ActionResponse response, Object command, BindException errors)
throws Exception {
onSubmitAction(command, errors);
}
/**
* Simpler {@code onSubmitRender} version. Called by the default implementation
* of the {@code onSubmitRender} version with all parameters.
* <p>The default implementation calls {@link #onSubmitRender(Object)}, using the
* returned ModelAndView if actually implemented in a subclass. Else, the
* default behavior will apply: rendering the success view with the command
* and Errors instance as model.
* <p>Subclasses can override this to provide custom submission handling that
* does not need request and response.
* <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 command form object with request parameters bound onto it
* @param errors Errors instance without errors
* @return the prepared model and view, or null
* @throws Exception in case of errors
* @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
* @see #onSubmitRender(Object)
* @see #onSubmitAction(Object, BindException)
* @see #setSuccessView
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView onSubmitRender(Object command, BindException errors) throws Exception {
ModelAndView mv = onSubmitRender(command);
if (mv != null) {
// simplest onSubmit version implemented in custom subclass
return mv;
}
else {
// default behavior: render success view
if (getSuccessView() == null) {
throw new PortletException("successView isn't set");
}
return new ModelAndView(getSuccessView(), errors.getModel());
}
}
/**
* Simpler {@code onSubmitAction} version. Called by the default implementation
* of the {@code onSubmitAction} version with all parameters.
* <p>The default implementation calls {@link #onSubmitAction(Object)}.
* <p>Subclasses can override this to provide custom submission handling that
* does not need request and response.
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors
* @throws Exception in case of errors
* @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
* @see #onSubmitAction(Object)
* @see #onSubmitRender(Object, BindException)
* @see org.springframework.validation.Errors
*/
protected void onSubmitAction(Object command, BindException errors) throws Exception {
onSubmitAction(command);
}
/**
* Simplest {@code onSubmitRender} version. Called by the default implementation
* of the {@code onSubmitRender} version with command and BindException parameters.
* <p>This implementation returns null as ModelAndView, making the calling
* {@code onSubmitRender} method perform its default rendering of the success view.
* <p>Subclasses can override this to provide custom submission handling
* that just depends on the command object.
* @param command form object with request parameters bound onto it
* @return the prepared model and view, or null for default (i.e. successView)
* @throws Exception in case of errors
* @see #onSubmitRender(Object, BindException)
* @see #onSubmitAction(Object)
* @see #doSubmitAction
* @see #setSuccessView
*/
protected ModelAndView onSubmitRender(Object command) throws Exception {
return null;
}
/**
* Simplest {@code onSubmitAction} version. Called by the default implementation
* of the {@code onSubmitAction} version with command and BindException parameters.
* <p>This implementation calls {@code doSubmitAction}.
* <p>Subclasses can override this to provide custom submission handling
* that just depends on the command object.
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onSubmitAction(Object, BindException)
* @see #onSubmitRender(Object)
* @see #doSubmitAction
*/
protected void onSubmitAction(Object command) throws Exception {
doSubmitAction(command);
}
/**
* Template method for submit actions. Called by the default implementation
* of the simplest {@code onSubmitAction} version.
* <p><b>This is the preferred submit callback to implement if you want to
* perform an action (like storing changes to the database) and then render
* the success view with the command and Errors instance as model.</b>
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onSubmitAction(Object)
* @see #onSubmitRender(Object)
* @see #setSuccessView
*/
protected void doSubmitAction(Object command) throws Exception {
}
}