📄 abstractformcontroller.java
字号:
/*
* Copyright 2002-2007 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</code> property has been set to
* <code>true</code>.</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</code> to prepare the form view,
* <code>processFormSubmission</code> to handle submit requests, and
* <code>renderFormSubmission</code> 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</code>
* 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</code>). 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</code>. 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</code> is set to <code>true</code></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</code> 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</code> 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 –
* also by default – will render the results of the resubmitted
* form. Be sure to override both <code>handleInvalidSubmit</code> and
* <code>renderInvalidSubmit</code> 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</code> 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</code> is not called, then
* <code>processFormSubmission</code> 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</code> method from any action
* phase method. Abstract descendants of this controller should follow
* similar behavior. If there are parameters you need in
* <code>renderFormSubmission</code>, then you need to pass those
* forward from <code>processFormSubmission</code>. 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
*/
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</code>, 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.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -