Removed Spring MVC command/form controller class hierarchy
This commit is contained in:
parent
74021b9e4a
commit
f19f55a59b
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 –
|
||||
* also by default – 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <ref bean="beanId"/> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,42 +16,29 @@
|
|||
|
||||
package org.springframework.web.portlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.portlet.PortletContext;
|
||||
import javax.portlet.PortletException;
|
||||
import javax.portlet.PortletMode;
|
||||
import javax.portlet.PortletSecurityException;
|
||||
import javax.portlet.PortletSession;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.mock.web.portlet.MockActionRequest;
|
||||
import org.springframework.mock.web.portlet.MockActionResponse;
|
||||
import org.springframework.mock.web.portlet.MockEvent;
|
||||
import org.springframework.mock.web.portlet.MockEventRequest;
|
||||
import org.springframework.mock.web.portlet.MockEventResponse;
|
||||
import org.springframework.mock.web.portlet.MockPortletConfig;
|
||||
import org.springframework.mock.web.portlet.MockPortletContext;
|
||||
import org.springframework.mock.web.portlet.MockPortletSession;
|
||||
import org.springframework.mock.web.portlet.MockRenderRequest;
|
||||
import org.springframework.mock.web.portlet.MockRenderResponse;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.portlet.context.PortletApplicationContextUtils;
|
||||
import org.springframework.web.portlet.context.PortletConfigAwareBean;
|
||||
import org.springframework.web.portlet.context.PortletContextAwareBean;
|
||||
import org.springframework.web.portlet.context.PortletRequestAttributes;
|
||||
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
|
||||
import org.springframework.web.portlet.multipart.MultipartActionRequest;
|
||||
import org.springframework.web.portlet.multipart.PortletMultipartResolver;
|
||||
import org.springframework.web.servlet.ViewRendererServlet;
|
||||
|
|
@ -64,25 +51,16 @@ import org.springframework.web.servlet.view.InternalResourceView;
|
|||
*/
|
||||
public class DispatcherPortletTests extends TestCase {
|
||||
|
||||
private MockPortletConfig simplePortletConfig;
|
||||
|
||||
private MockPortletConfig complexPortletConfig;
|
||||
|
||||
private DispatcherPortlet simpleDispatcherPortlet;
|
||||
|
||||
private DispatcherPortlet complexDispatcherPortlet;
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws PortletException {
|
||||
simplePortletConfig = new MockPortletConfig(new MockPortletContext(), "simple");
|
||||
complexPortletConfig = new MockPortletConfig(simplePortletConfig.getPortletContext(), "complex");
|
||||
complexPortletConfig = new MockPortletConfig(new MockPortletContext(), "complex");
|
||||
complexPortletConfig.addInitParameter("publishContext", "false");
|
||||
|
||||
simpleDispatcherPortlet = new DispatcherPortlet();
|
||||
simpleDispatcherPortlet.setContextClass(SimplePortletApplicationContext.class);
|
||||
simpleDispatcherPortlet.init(simplePortletConfig);
|
||||
|
||||
complexDispatcherPortlet = new DispatcherPortlet();
|
||||
complexDispatcherPortlet.setContextClass(ComplexPortletApplicationContext.class);
|
||||
complexDispatcherPortlet.setNamespace("test");
|
||||
|
|
@ -103,14 +81,6 @@ public class DispatcherPortletTests extends TestCase {
|
|||
}
|
||||
|
||||
public void testDispatcherPortlets() {
|
||||
assertTrue("Correct namespace",
|
||||
("simple" + FrameworkPortlet.DEFAULT_NAMESPACE_SUFFIX).equals(simpleDispatcherPortlet.getNamespace()));
|
||||
assertTrue("Correct attribute",
|
||||
(FrameworkPortlet.PORTLET_CONTEXT_PREFIX + "simple").equals(simpleDispatcherPortlet.getPortletContextAttributeName()));
|
||||
assertTrue("Context published",
|
||||
simpleDispatcherPortlet.getPortletApplicationContext() ==
|
||||
getPortletContext().getAttribute(FrameworkPortlet.PORTLET_CONTEXT_PREFIX + "simple"));
|
||||
|
||||
assertTrue("Correct namespace", "test".equals(complexDispatcherPortlet.getNamespace()));
|
||||
assertTrue("Correct attribute",
|
||||
(FrameworkPortlet.PORTLET_CONTEXT_PREFIX + "complex").equals(complexDispatcherPortlet.getPortletContextAttributeName()));
|
||||
|
|
@ -118,192 +88,6 @@ public class DispatcherPortletTests extends TestCase {
|
|||
getPortletContext().getAttribute(FrameworkPortlet.PORTLET_CONTEXT_PREFIX + "complex") == null);
|
||||
}
|
||||
|
||||
public void testSimpleValidActionRequest() throws Exception {
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
request.setParameter("action", "form");
|
||||
request.setParameter("age", "29");
|
||||
simpleDispatcherPortlet.processAction(request, response);
|
||||
String exceptionParam = response.getRenderParameter(DispatcherPortlet.ACTION_EXCEPTION_RENDER_PARAMETER);
|
||||
assertNull(exceptionParam);
|
||||
SimplePortletApplicationContext ac = (SimplePortletApplicationContext)simpleDispatcherPortlet.getPortletApplicationContext();
|
||||
String commandAttribute = ac.getRenderCommandSessionAttributeName();
|
||||
TestBean testBean = (TestBean) request.getPortletSession().getAttribute(commandAttribute);
|
||||
assertEquals(39, testBean.getAge());
|
||||
}
|
||||
|
||||
public void testSimpleInvalidActionRequest() throws Exception {
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
request.setParameter("action", "invalid");
|
||||
simpleDispatcherPortlet.processAction(request, response);
|
||||
String exceptionParam = response.getRenderParameter(DispatcherPortlet.ACTION_EXCEPTION_RENDER_PARAMETER);
|
||||
assertNotNull(exceptionParam);
|
||||
assertTrue(exceptionParam.startsWith(NoHandlerFoundException.class.getName()));
|
||||
}
|
||||
|
||||
|
||||
public void testSimpleInvalidActionRequestWithoutHandling() throws Exception {
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
request.setParameter("action", "invalid");
|
||||
simpleDispatcherPortlet.setForwardActionException(false);
|
||||
try {
|
||||
simpleDispatcherPortlet.processAction(request, response);
|
||||
fail("Should have thrown a " + NoHandlerFoundException.class);
|
||||
}
|
||||
catch (NoHandlerFoundException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testSimpleValidEventRequest() throws Exception {
|
||||
MockEvent event = new MockEvent("test-event");
|
||||
MockEventRequest request = new MockEventRequest(event);
|
||||
MockEventResponse response = new MockEventResponse();
|
||||
request.setParameter("action", "form");
|
||||
simpleDispatcherPortlet.processEvent(request, response);
|
||||
assertEquals("test-event", response.getRenderParameter("event"));
|
||||
}
|
||||
|
||||
public void testSimpleInvalidEventRequest() throws Exception {
|
||||
MockEvent event = new MockEvent("test-event");
|
||||
MockEventRequest request = new MockEventRequest(event);
|
||||
MockEventResponse response = new MockEventResponse();
|
||||
request.setParameter("action", "invalid");
|
||||
try {
|
||||
simpleDispatcherPortlet.processEvent(request, response);
|
||||
fail("Should have thrown a " + NoHandlerFoundException.class);
|
||||
}
|
||||
catch (NoHandlerFoundException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testSimpleInvalidEventRequestWithHandling() throws Exception {
|
||||
MockEvent event = new MockEvent("event");
|
||||
MockEventRequest request = new MockEventRequest(event);
|
||||
MockEventResponse response = new MockEventResponse();
|
||||
request.setParameter("action", "invalid");
|
||||
simpleDispatcherPortlet.setForwardEventException(true);
|
||||
simpleDispatcherPortlet.processEvent(request, response);
|
||||
String exceptionParam = response.getRenderParameter(DispatcherPortlet.ACTION_EXCEPTION_RENDER_PARAMETER);
|
||||
assertNotNull(exceptionParam);
|
||||
assertTrue(exceptionParam.startsWith(NoHandlerFoundException.class.getName()));
|
||||
}
|
||||
|
||||
public void testSimpleFormViewNoBindOnNewForm() throws Exception {
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.setParameter("action", "form");
|
||||
request.setParameter("age", "29");
|
||||
simpleDispatcherPortlet.doDispatch(request, response);
|
||||
assertEquals("5", response.getContentAsString());
|
||||
}
|
||||
|
||||
public void testSimpleFormViewBindOnNewForm() throws Exception {
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.setParameter("action", "form-bind");
|
||||
request.setParameter("age", "29");
|
||||
simpleDispatcherPortlet.doDispatch(request, response);
|
||||
assertEquals("34", response.getContentAsString());
|
||||
}
|
||||
|
||||
public void testSimpleFormViewWithSessionAndBindOnNewForm() throws Exception {
|
||||
MockRenderRequest renderRequest = new MockRenderRequest();
|
||||
MockRenderResponse renderResponse = new MockRenderResponse();
|
||||
renderRequest.setParameter("action", "form-session-bind");
|
||||
renderRequest.setParameter("age", "30");
|
||||
TestBean testBean = new TestBean();
|
||||
testBean.setAge(40);
|
||||
SimplePortletApplicationContext ac =
|
||||
(SimplePortletApplicationContext)simpleDispatcherPortlet.getPortletApplicationContext();
|
||||
String formAttribute = ac.getFormSessionAttributeName();
|
||||
PortletSession session = new MockPortletSession();
|
||||
session.setAttribute(formAttribute, testBean);
|
||||
renderRequest.setSession(session);
|
||||
simpleDispatcherPortlet.doDispatch(renderRequest, renderResponse);
|
||||
assertEquals("35", renderResponse.getContentAsString());
|
||||
}
|
||||
|
||||
public void testSimpleFormViewWithSessionNoBindOnNewForm() throws Exception {
|
||||
MockActionRequest actionRequest = new MockActionRequest();
|
||||
MockActionResponse actionResponse = new MockActionResponse();
|
||||
actionRequest.setSession(new MockPortletSession());
|
||||
actionRequest.setParameter("action", "form-session-nobind");
|
||||
actionRequest.setParameter("age", "27");
|
||||
simpleDispatcherPortlet.processAction(actionRequest, actionResponse);
|
||||
Map renderParameters = actionResponse.getRenderParameterMap();
|
||||
|
||||
MockRenderRequest renderRequest = new MockRenderRequest();
|
||||
MockRenderResponse renderResponse = new MockRenderResponse();
|
||||
renderRequest.setParameters(renderParameters);
|
||||
renderRequest.setParameter("action", "form-session-nobind");
|
||||
renderRequest.setParameter("age", "30");
|
||||
renderRequest.setSession(actionRequest.getPortletSession());
|
||||
simpleDispatcherPortlet.doDispatch(renderRequest, renderResponse);
|
||||
assertEquals("finished42", renderResponse.getContentAsString());
|
||||
}
|
||||
|
||||
public void testSimpleRequiredSessionFormWithoutSession() throws Exception {
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.setParameter("action", "form-session-bind");
|
||||
try {
|
||||
simpleDispatcherPortlet.doDispatch(request, response);
|
||||
fail("Should have thrown PortletSessionRequiredException");
|
||||
}
|
||||
catch (PortletSessionRequiredException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testSimpleFormSubmission() throws Exception {
|
||||
MockActionRequest actionRequest = new MockActionRequest();
|
||||
MockActionResponse actionResponse = new MockActionResponse();
|
||||
actionRequest.setParameter("action", "form");
|
||||
actionRequest.setParameter("age", "29");
|
||||
simpleDispatcherPortlet.processAction(actionRequest, actionResponse);
|
||||
|
||||
MockRenderRequest renderRequest = new MockRenderRequest();
|
||||
MockRenderResponse renderResponse = new MockRenderResponse();
|
||||
renderRequest.setSession(actionRequest.getPortletSession());
|
||||
renderRequest.setParameters(actionResponse.getRenderParameterMap());
|
||||
renderRequest.setParameter("action", "form");
|
||||
simpleDispatcherPortlet.doDispatch(renderRequest, renderResponse);
|
||||
assertEquals("finished44", renderResponse.getContentAsString());
|
||||
}
|
||||
|
||||
public void testSimpleFormSubmissionWithValidationError() throws Exception {
|
||||
MockActionRequest actionRequest = new MockActionRequest();
|
||||
MockActionResponse actionResponse = new MockActionResponse();
|
||||
actionRequest.setParameter("action", "form");
|
||||
actionRequest.setParameter("age", "XX");
|
||||
simpleDispatcherPortlet.processAction(actionRequest, actionResponse);
|
||||
|
||||
MockRenderRequest renderRequest = new MockRenderRequest();
|
||||
MockRenderResponse renderResponse = new MockRenderResponse();
|
||||
renderRequest.setSession(actionRequest.getPortletSession());
|
||||
renderRequest.setParameters(actionResponse.getRenderParameterMap());
|
||||
renderRequest.setParameter("action", "form");
|
||||
simpleDispatcherPortlet.doDispatch(renderRequest, renderResponse);
|
||||
assertEquals("5", renderResponse.getContentAsString());
|
||||
}
|
||||
|
||||
public void testSimpleInvalidRenderRequest() throws Exception {
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.setParameter("action", "invalid");
|
||||
try {
|
||||
simpleDispatcherPortlet.doDispatch(request, response);
|
||||
fail("Should have thrown UnavailableException");
|
||||
}
|
||||
catch (NoHandlerFoundException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testPortletModeParameterMappingHelp1() throws Exception {
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
|
|
@ -859,115 +643,6 @@ public class DispatcherPortletTests extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testValidActionRequestWithExistingThreadLocalRequestContext() throws IOException, PortletException {
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
request.addPreferredLocale(Locale.GERMAN);
|
||||
request.setParameter("action", "form");
|
||||
request.setParameter("age", "29");
|
||||
|
||||
// see RequestContextListener.requestInitialized()
|
||||
try {
|
||||
LocaleContextHolder.setLocale(request.getLocale());
|
||||
RequestContextHolder.setRequestAttributes(new PortletRequestAttributes(request));
|
||||
|
||||
LocaleContext servletLocaleContext = LocaleContextHolder.getLocaleContext();
|
||||
RequestAttributes servletRequestAttrs = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
simpleDispatcherPortlet.processAction(request, response);
|
||||
|
||||
assertSame(servletLocaleContext, LocaleContextHolder.getLocaleContext());
|
||||
assertSame(servletRequestAttrs, RequestContextHolder.getRequestAttributes());
|
||||
}
|
||||
finally {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void testValidRenderRequestWithExistingThreadLocalRequestContext() throws IOException, PortletException {
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.addPreferredLocale(Locale.GERMAN);
|
||||
|
||||
// see RequestContextListener.requestInitialized()
|
||||
try {
|
||||
LocaleContextHolder.setLocale(request.getLocale());
|
||||
RequestContextHolder.setRequestAttributes(new PortletRequestAttributes(request));
|
||||
|
||||
LocaleContext servletLocaleContext = LocaleContextHolder.getLocaleContext();
|
||||
RequestAttributes servletRequestAttrs = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
request.setParameter("action", "form");
|
||||
request.setParameter("age", "29");
|
||||
simpleDispatcherPortlet.doDispatch(request, response);
|
||||
|
||||
assertSame(servletLocaleContext, LocaleContextHolder.getLocaleContext());
|
||||
assertSame(servletRequestAttrs, RequestContextHolder.getRequestAttributes());
|
||||
}
|
||||
finally {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void testInvalidActionRequestWithExistingThreadLocalRequestContext() throws IOException, PortletException {
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
request.addPreferredLocale(Locale.GERMAN);
|
||||
|
||||
// see RequestContextListener.requestInitialized()
|
||||
try {
|
||||
LocaleContextHolder.setLocale(request.getLocale());
|
||||
RequestContextHolder.setRequestAttributes(new PortletRequestAttributes(request));
|
||||
|
||||
LocaleContext servletLocaleContext = LocaleContextHolder.getLocaleContext();
|
||||
RequestAttributes servletRequestAttrs = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
request.setParameter("action", "invalid");
|
||||
simpleDispatcherPortlet.processAction(request, response);
|
||||
String exceptionParam = response.getRenderParameter(DispatcherPortlet.ACTION_EXCEPTION_RENDER_PARAMETER);
|
||||
assertNotNull(exceptionParam); // ensure that an exceptional condition occured
|
||||
|
||||
assertSame(servletLocaleContext, LocaleContextHolder.getLocaleContext());
|
||||
assertSame(servletRequestAttrs, RequestContextHolder.getRequestAttributes());
|
||||
}
|
||||
finally {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void testInvalidRenderRequestWithExistingThreadLocalRequestContext() throws IOException, PortletException {
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.addPreferredLocale(Locale.GERMAN);
|
||||
|
||||
// see RequestContextListener.requestInitialized()
|
||||
try {
|
||||
LocaleContextHolder.setLocale(request.getLocale());
|
||||
RequestContextHolder.setRequestAttributes(new PortletRequestAttributes(request));
|
||||
|
||||
LocaleContext servletLocaleContext = LocaleContextHolder.getLocaleContext();
|
||||
RequestAttributes servletRequestAttrs = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
try {
|
||||
simpleDispatcherPortlet.doDispatch(request, response);
|
||||
fail("should have failed to find a handler and raised an NoHandlerFoundExceptionException");
|
||||
}
|
||||
catch (NoHandlerFoundException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
assertSame(servletLocaleContext, LocaleContextHolder.getLocaleContext());
|
||||
assertSame(servletRequestAttrs, RequestContextHolder.getRequestAttributes());
|
||||
}
|
||||
finally {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void testDispatcherPortletRefresh() throws PortletException {
|
||||
MockPortletContext portletContext = new MockPortletContext("org/springframework/web/portlet/context");
|
||||
DispatcherPortlet portlet = new DispatcherPortlet();
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.portlet.EventRequest;
|
||||
import javax.portlet.EventResponse;
|
||||
import javax.portlet.RenderRequest;
|
||||
import javax.portlet.RenderResponse;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyValue;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.portlet.context.StaticPortletApplicationContext;
|
||||
import org.springframework.web.portlet.handler.ParameterHandlerMapping;
|
||||
import org.springframework.web.portlet.mvc.EventAwareController;
|
||||
import org.springframework.web.portlet.mvc.SimpleFormController;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class SimplePortletApplicationContext extends StaticPortletApplicationContext {
|
||||
|
||||
private String renderCommandSessionAttributeName;
|
||||
private String formSessionAttributeName;
|
||||
|
||||
@Override
|
||||
public void refresh() throws BeansException {
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
registerSingleton("controller1", TestFormController.class, pvs);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("bindOnNewForm", "true");
|
||||
registerSingleton("controller2", TestFormController.class, pvs);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("requireSession", "true");
|
||||
pvs.add("sessionForm", "true");
|
||||
pvs.add("bindOnNewForm", "true");
|
||||
registerSingleton("controller3", TestFormController.class, pvs);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("requireSession", "true");
|
||||
pvs.add("sessionForm", "true");
|
||||
pvs.add("bindOnNewForm", "false");
|
||||
registerSingleton("controller4", TestFormController.class, pvs);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
Map parameterMap = new ManagedMap();
|
||||
parameterMap.put("form", new RuntimeBeanReference("controller1"));
|
||||
parameterMap.put("form-bind", new RuntimeBeanReference("controller2"));
|
||||
parameterMap.put("form-session-bind", new RuntimeBeanReference("controller3"));
|
||||
parameterMap.put("form-session-nobind", new RuntimeBeanReference("controller4"));
|
||||
pvs.addPropertyValue(new PropertyValue("parameterMap", parameterMap));
|
||||
registerSingleton("handlerMapping", ParameterHandlerMapping.class, pvs);
|
||||
|
||||
super.refresh();
|
||||
|
||||
TestFormController controller1 = (TestFormController) getBean("controller1");
|
||||
this.renderCommandSessionAttributeName = controller1.getRenderCommandName();
|
||||
this.formSessionAttributeName = controller1.getFormSessionName();
|
||||
}
|
||||
|
||||
public String getRenderCommandSessionAttributeName() {
|
||||
return this.renderCommandSessionAttributeName;
|
||||
}
|
||||
|
||||
public String getFormSessionAttributeName() {
|
||||
return this.formSessionAttributeName;
|
||||
}
|
||||
|
||||
|
||||
public static class TestFormController extends SimpleFormController implements EventAwareController {
|
||||
|
||||
TestFormController() {
|
||||
super();
|
||||
this.setCommandClass(TestBean.class);
|
||||
this.setCommandName("testBean");
|
||||
this.setFormView("form");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSubmitAction(Object command) {
|
||||
TestBean testBean = (TestBean) command;
|
||||
testBean.setAge(testBean.getAge() + 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors) throws Exception {
|
||||
TestBean testBean = (TestBean) errors.getModel().get(getCommandName());
|
||||
this.writeResponse(response, testBean, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView onSubmitRender(RenderRequest request, RenderResponse response, Object command, BindException errors)
|
||||
throws IOException {
|
||||
TestBean testBean = (TestBean) command;
|
||||
this.writeResponse(response, testBean, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getRenderCommandName() {
|
||||
return this.getRenderCommandSessionAttributeName();
|
||||
}
|
||||
|
||||
private String getFormSessionName() {
|
||||
return this.getFormSessionAttributeName();
|
||||
}
|
||||
|
||||
private void writeResponse(RenderResponse response, TestBean testBean, boolean finished) throws IOException {
|
||||
response.getWriter().write((finished ? "finished" : "") + (testBean.getAge() + 5));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEventRequest(EventRequest request, EventResponse response) throws Exception {
|
||||
response.setRenderParameter("event", request.getEvent().getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,478 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.beans.PropertyEditorSupport;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.portlet.ActionRequest;
|
||||
import javax.portlet.ActionResponse;
|
||||
import javax.portlet.PortletRequest;
|
||||
import javax.portlet.RenderRequest;
|
||||
import javax.portlet.RenderResponse;
|
||||
import javax.portlet.WindowState;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.tests.sample.beans.ITestBean;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.mock.web.portlet.MockActionRequest;
|
||||
import org.springframework.mock.web.portlet.MockActionResponse;
|
||||
import org.springframework.mock.web.portlet.MockRenderRequest;
|
||||
import org.springframework.mock.web.portlet.MockRenderResponse;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.portlet.ModelAndView;
|
||||
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
|
||||
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
@Deprecated
|
||||
public class CommandControllerTests extends TestCase {
|
||||
|
||||
private static final String ERRORS_KEY = "errors";
|
||||
|
||||
public void testRenderRequestWithNoParams() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
request.setContextPath("test");
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
assertEquals("test-view", mav.getViewName());
|
||||
assertNotNull(mav.getModel().get(tc.getCommandName()));
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertNotNull(errors);
|
||||
assertEquals("There should be no errors", 0, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testRenderRequestWithParams() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
int age = 30;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
request.setContextPath("test");
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
assertEquals("test-view", mav.getViewName());
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertEquals("Name should be bound", name, command.getName());
|
||||
assertEquals("Age should be bound", age, command.getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertNotNull(errors);
|
||||
assertEquals("There should be no errors", 0, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testRenderRequestWithMismatch() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "zzz");
|
||||
request.setContextPath("test");
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
assertEquals("test-view", mav.getViewName());
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertNotNull(command);
|
||||
assertEquals("Name should be bound", name, command.getName());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be 1 error", 1, errors.getErrorCount());
|
||||
assertNotNull(errors.getFieldError("age"));
|
||||
assertEquals("typeMismatch", errors.getFieldError("age").getCode());
|
||||
}
|
||||
|
||||
public void testRenderWhenMinimizedReturnsNull() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
assertFalse(tc.isRenderWhenMinimized());
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
request.setWindowState(WindowState.MINIMIZED);
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
assertNull("ModelAndView should be null", mav);
|
||||
}
|
||||
|
||||
public void testAllowRenderWhenMinimized() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
tc.setRenderWhenMinimized(true);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
request.setWindowState(WindowState.MINIMIZED);
|
||||
request.setContextPath("test");
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
assertNotNull("ModelAndView should not be null", mav);
|
||||
assertEquals("test-view", mav.getViewName());
|
||||
assertNotNull(mav.getModel().get(tc.getCommandName()));
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be no errors", 0, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testRequiresSessionWithoutSession() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
tc.setRequireSession(true);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
try {
|
||||
tc.handleRenderRequest(request, response);
|
||||
fail("Should have thrown PortletSessionRequiredException");
|
||||
}
|
||||
catch (PortletSessionRequiredException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testRequiresSessionWithSession() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
tc.setRequireSession(true);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
|
||||
// create the session
|
||||
request.getPortletSession(true);
|
||||
try {
|
||||
tc.handleRenderRequest(request, response);
|
||||
}
|
||||
catch (PortletSessionRequiredException ex) {
|
||||
fail("Should not have thrown PortletSessionRequiredException");
|
||||
}
|
||||
}
|
||||
|
||||
public void testRenderRequestWithoutCacheSetting() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
tc.handleRenderRequest(request, response);
|
||||
String cacheProperty = response.getProperty(RenderResponse.EXPIRATION_CACHE);
|
||||
assertNull("Expiration-cache should be null", cacheProperty);
|
||||
}
|
||||
|
||||
public void testRenderRequestWithNegativeCacheSetting() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
tc.setCacheSeconds(-99);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
tc.handleRenderRequest(request, response);
|
||||
String cacheProperty = response.getProperty(RenderResponse.EXPIRATION_CACHE);
|
||||
assertNull("Expiration-cache should be null", cacheProperty);
|
||||
}
|
||||
|
||||
public void testRenderRequestWithZeroCacheSetting() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
tc.setCacheSeconds(0);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
tc.handleRenderRequest(request, response);
|
||||
String cacheProperty = response.getProperty(RenderResponse.EXPIRATION_CACHE);
|
||||
assertEquals("Expiration-cache should be set to 0 seconds", "0", cacheProperty);
|
||||
}
|
||||
|
||||
public void testRenderRequestWithPositiveCacheSetting() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
tc.setCacheSeconds(30);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
tc.handleRenderRequest(request, response);
|
||||
String cacheProperty = response.getProperty(RenderResponse.EXPIRATION_CACHE);
|
||||
assertEquals("Expiration-cache should be set to 30 seconds", "30", cacheProperty);
|
||||
}
|
||||
|
||||
public void testActionRequest() throws Exception {
|
||||
TestController tc = new TestController();
|
||||
MockActionRequest request = new MockActionRequest();
|
||||
MockActionResponse response = new MockActionResponse();
|
||||
tc.handleActionRequest(request, response);
|
||||
TestBean command = (TestBean)request.getPortletSession().getAttribute(tc.getRenderCommandSessionAttributeName());
|
||||
assertTrue(command.isJedi());
|
||||
}
|
||||
|
||||
public void testSuppressBinding() throws Exception {
|
||||
TestController tc = new TestController() {
|
||||
@Override
|
||||
protected boolean suppressBinding(PortletRequest request) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
int age = 30;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
request.setContextPath("test");
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
assertEquals("test-view", mav.getViewName());
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertNotNull(command);
|
||||
assertTrue("Name should not have been bound", name != command.getName());
|
||||
assertTrue("Age should not have been bound", age != command.getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be no errors", 0, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testWithCustomDateEditor() throws Exception {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
|
||||
TestController tc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
|
||||
}
|
||||
};
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
int age = 30;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
String dateString = "07-03-2006";
|
||||
Date expectedDate = dateFormat.parse(dateString);
|
||||
request.addParameter("date", dateString);
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertEquals(name, command.getName());
|
||||
assertEquals(age, command.getAge());
|
||||
assertEquals(expectedDate, command.getDate());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be no errors", 0, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testWithCustomDateEditorEmptyNotAllowed() throws Exception {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
|
||||
TestController tc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
|
||||
}
|
||||
};
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
int age = 30;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
String emptyString = "";
|
||||
request.addParameter("date", emptyString);
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertEquals(name, command.getName());
|
||||
assertEquals(age, command.getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be 1 error", 1, errors.getErrorCount());
|
||||
assertNotNull(errors.getFieldError("date"));
|
||||
assertEquals("typeMismatch", errors.getFieldError("date").getCode());
|
||||
assertEquals(emptyString, errors.getFieldError("date").getRejectedValue());
|
||||
}
|
||||
|
||||
public void testWithCustomDateEditorEmptyAllowed() throws Exception {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
|
||||
TestController tc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
|
||||
}
|
||||
};
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
int age = 30;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
String dateString = "";
|
||||
request.addParameter("date", dateString);
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertEquals(name, command.getName());
|
||||
assertEquals(age, command.getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be 0 errors", 0, errors.getErrorCount());
|
||||
assertNull("date should be null", command.getDate());
|
||||
}
|
||||
|
||||
public void testNestedBindingWithPropertyEditor() throws Exception {
|
||||
TestController tc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(ITestBean.class, new PropertyEditorSupport() {
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
setValue(new TestBean(text));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
String name = "test";
|
||||
String spouseName = "testSpouse";
|
||||
int age = 30;
|
||||
int spouseAge = 31;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
request.addParameter("spouse", spouseName);
|
||||
request.addParameter("spouse.age", "" + spouseAge);
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertEquals(name, command.getName());
|
||||
assertEquals(age, command.getAge());
|
||||
assertNotNull(command.getSpouse());
|
||||
assertEquals(spouseName, command.getSpouse().getName());
|
||||
assertEquals(spouseAge, command.getSpouse().getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be no errors", 0, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testWithValidatorNotSupportingCommandClass() throws Exception {
|
||||
Validator v = new Validator() {
|
||||
@Override
|
||||
public boolean supports(Class c) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void validate(Object o, Errors e) {}
|
||||
};
|
||||
TestController tc = new TestController();
|
||||
tc.setValidator(v);
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
try {
|
||||
tc.handleRenderRequest(request, response);
|
||||
fail("Should have thrown IllegalArgumentException");
|
||||
}
|
||||
catch(IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testWithValidatorAddingGlobalError() throws Exception {
|
||||
final String errorCode = "someCode";
|
||||
final String defaultMessage = "validation error!";
|
||||
TestController tc = new TestController();
|
||||
tc.setValidator(new Validator() {
|
||||
@Override
|
||||
public boolean supports(Class c) {
|
||||
return TestBean.class.isAssignableFrom(c);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object o, Errors e) {
|
||||
e.reject(errorCode, defaultMessage);
|
||||
}
|
||||
});
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be 1 error", 1, errors.getErrorCount());
|
||||
ObjectError error = errors.getGlobalError();
|
||||
assertEquals(error.getCode(), errorCode);
|
||||
assertEquals(error.getDefaultMessage(), defaultMessage);
|
||||
}
|
||||
|
||||
public void testWithValidatorAndNullFieldError() throws Exception {
|
||||
final String errorCode = "someCode";
|
||||
final String defaultMessage = "validation error!";
|
||||
TestController tc = new TestController();
|
||||
tc.setValidator(new Validator() {
|
||||
@Override
|
||||
public boolean supports(Class c) {
|
||||
return TestBean.class.isAssignableFrom(c);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object o, Errors e) {
|
||||
ValidationUtils.rejectIfEmpty(e, "name", errorCode, defaultMessage);
|
||||
}
|
||||
});
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
int age = 32;
|
||||
request.setParameter("age", "" + age);
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertNull("name should be null", command.getName());
|
||||
assertEquals(age, command.getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be 1 error", 1, errors.getErrorCount());
|
||||
FieldError error = errors.getFieldError("name");
|
||||
assertEquals(error.getCode(), errorCode);
|
||||
assertEquals(error.getDefaultMessage(), defaultMessage);
|
||||
}
|
||||
|
||||
public void testWithValidatorAndWhitespaceFieldError() throws Exception {
|
||||
final String errorCode = "someCode";
|
||||
final String defaultMessage = "validation error!";
|
||||
TestController tc = new TestController();
|
||||
tc.setValidator(new Validator() {
|
||||
@Override
|
||||
public boolean supports(Class c) {
|
||||
return TestBean.class.isAssignableFrom(c);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object o, Errors e) {
|
||||
ValidationUtils.rejectIfEmptyOrWhitespace(e, "name", errorCode, defaultMessage);
|
||||
}
|
||||
});
|
||||
MockRenderRequest request = new MockRenderRequest();
|
||||
int age = 32;
|
||||
String whitespace = " \t ";
|
||||
request.setParameter("age", "" + age);
|
||||
request.setParameter("name", whitespace);
|
||||
MockRenderResponse response = new MockRenderResponse();
|
||||
ModelAndView mav = tc.handleRenderRequest(request, response);
|
||||
TestBean command = (TestBean)mav.getModel().get(tc.getCommandName());
|
||||
assertTrue(command.getName().equals(whitespace));
|
||||
assertEquals(age, command.getAge());
|
||||
BindException errors = (BindException)mav.getModel().get(ERRORS_KEY);
|
||||
assertEquals("There should be 1 error", 1, errors.getErrorCount());
|
||||
FieldError error = errors.getFieldError("name");
|
||||
assertEquals("rejected value should contain whitespace", whitespace, error.getRejectedValue());
|
||||
assertEquals(error.getCode(), errorCode);
|
||||
assertEquals(error.getDefaultMessage(), defaultMessage);
|
||||
}
|
||||
|
||||
private static class TestController extends AbstractCommandController {
|
||||
|
||||
private TestController() {
|
||||
super(TestBean.class, "testBean");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAction(ActionRequest request, ActionResponse response, Object command, BindException errors) {
|
||||
((TestBean)command).setJedi(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView handleRender(RenderRequest request, RenderResponse response, Object command, BindException errors) {
|
||||
assertNotNull(command);
|
||||
assertNotNull(errors);
|
||||
Map model = new HashMap();
|
||||
model.put(getCommandName(), command);
|
||||
model.put(ERRORS_KEY, errors);
|
||||
return new ModelAndView(request.getContextPath() + "-view", model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,108 +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.servlet.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Abstract base class for custom command controllers.
|
||||
*
|
||||
* <p>Autopopulates a command bean from the request. For command validation,
|
||||
* a validator (property inherited from {@link BaseCommandController}) can be
|
||||
* used.
|
||||
*
|
||||
* <p>In most cases this command controller should not be used to handle form
|
||||
* submission, because functionality for forms is offered in more detail by the
|
||||
* {@link org.springframework.web.servlet.mvc.AbstractFormController} and its
|
||||
* corresponding implementations.
|
||||
*
|
||||
* <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 Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @see #setCommandClass
|
||||
* @see #setCommandName
|
||||
* @see #setValidator
|
||||
* @deprecated as of Spring 3.0, in favor of annotated controllers
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractCommandController extends BaseCommandController {
|
||||
|
||||
/**
|
||||
* 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 ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
Object command = getCommand(request);
|
||||
ServletRequestDataBinder binder = bindAndValidate(request, command);
|
||||
BindException errors = new BindException(binder.getBindingResult());
|
||||
return handle(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 HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param command the populated command object
|
||||
* @param errors validation errors holder
|
||||
* @return a ModelAndView to render, or {@code null} if handled directly
|
||||
* @see org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
*/
|
||||
protected abstract ModelAndView handle(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception;
|
||||
|
||||
}
|
||||
|
|
@ -1,678 +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.servlet.mvc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.HttpSessionRequiredException;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* <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,
|
||||
* and {@code processFormSubmission} to handle submit requests. For the latter,
|
||||
* 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.PropertyEditor}. For more information on that
|
||||
* subject, see the workflow of this controller and the explanation of the
|
||||
* {@link BaseCommandController}.</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 GET).</b></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><em>Only if {@code bindOnNewForm} is set to {@code true}</em>, then
|
||||
* {@link org.springframework.web.bind.ServletRequestDataBinder ServletRequestDataBinder}
|
||||
* gets applied to populate the new form object with initial request parameters and the
|
||||
* {@link #onBindOnNewForm(HttpServletRequest, Object, BindException)} callback method is
|
||||
* called. <em>Note:</em> any defined Validators are not applied at this point, to allow
|
||||
* partial binding. However be aware that any Binder customizations applied via
|
||||
* initBinder() (such as
|
||||
* {@link org.springframework.validation.DataBinder#setRequiredFields(String[])} will
|
||||
* still apply. As such, if using bindOnNewForm=true and initBinder() customizations are
|
||||
* used to validate fields instead of using Validators, in the case that only some fields
|
||||
* will be populated for the new form, there will potentially be some bind errors for
|
||||
* missing fields in the errors object. Any view (JSP, etc.) that displays binder errors
|
||||
* needs to be intelligent and for this case take into account whether it is displaying the
|
||||
* initial form view or subsequent post results, skipping error display for the former.</li>
|
||||
* <li>Call to {@link #showForm(HttpServletRequest, HttpServletResponse, 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 a POST).</b>
|
||||
* To use a different way of detecting a form submission, override the
|
||||
* {@link #isFormSubmission isFormSubmission} method.
|
||||
* </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, it does a call to {@link #handleInvalidSubmit handleInvalidSubmit}
|
||||
* which - by default - tries to create a new form object and resubmit the form.</li>
|
||||
* <li>The {@link org.springframework.web.bind.ServletRequestDataBinder ServletRequestDataBinder}
|
||||
* gets applied to populate the form object with current request parameters.
|
||||
* <li>Call to {@link #onBind onBind(HttpServletRequest, 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 org.springframework.validation.Errors Errors}</li> object.
|
||||
* <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 {@link #processFormSubmission(HttpServletRequest, HttpServletResponse,
|
||||
* Object, BindException) processFormSubmission()} to process the submission, with
|
||||
* or without binding errors. This method has to be implemented in subclasses.</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>In session form mode, a submission without an existing form object in the
|
||||
* session is considered invalid, like in case of a resubmit/reload by the browser.
|
||||
* The {@link #handleInvalidSubmit handleInvalidSubmit} method is invoked then,
|
||||
* by default trying to resubmit. It 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>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 servlet 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>
|
||||
* </table>
|
||||
* </p>
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Alef Arendsen
|
||||
* @author Rob Harrop
|
||||
* @author Colin Sampaleanu
|
||||
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
|
||||
* @see #processFormSubmission
|
||||
* @see SimpleFormController
|
||||
* @see AbstractWizardFormController
|
||||
* @deprecated as of Spring 3.0, in favor of annotated controllers
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractFormController extends BaseCommandController {
|
||||
|
||||
private boolean bindOnNewForm = false;
|
||||
|
||||
private boolean sessionForm = false;
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@link #formBackingObject}, since 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 whether request parameters should be bound to the form object
|
||||
* in case of a non-submitting request, that is, a new form.
|
||||
*/
|
||||
public final void setBindOnNewForm(boolean bindOnNewForm) {
|
||||
this.bindOnNewForm = bindOnNewForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} 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.
|
||||
* <p>Please note that the {@link AbstractFormController} class (and all
|
||||
* subclasses of it unless stated to the contrary) do <i>not</i> support
|
||||
* the notion of a conversation. This is important in the context of this
|
||||
* property, because it means that there is only <i>one</i> form per session:
|
||||
* this means that if session form mode is activated and a user opens up
|
||||
* say two tabs in their browser and attempts to edit two distinct objects
|
||||
* using the same form, then the <i>shared</i> session state can potentially
|
||||
* (and most probably will) be overwritten by the last tab to be opened,
|
||||
* which can lead to errors when either of the forms in each is finally
|
||||
* submitted.
|
||||
* <p>If you need to have per-form, per-session state management (that is,
|
||||
* stateful web conversations), the recommendation is to use
|
||||
* <a href="http://www.springframework.org/webflow">Spring WebFlow</a>,
|
||||
* which has full support for conversations and has a much more flexible
|
||||
* usage model overall.
|
||||
* @param sessionForm {@code true} if session form mode is to be activated
|
||||
*/
|
||||
public final void setSessionForm(boolean sessionForm) {
|
||||
this.sessionForm = sessionForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if session form mode is activated.
|
||||
*/
|
||||
public final boolean isSessionForm() {
|
||||
return this.sessionForm;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles two cases: form submissions and showing a new form.
|
||||
* Delegates the decision between the two to {@link #isFormSubmission},
|
||||
* always treating requests without existing form session attribute
|
||||
* as new form when using session form mode.
|
||||
* @see #isFormSubmission
|
||||
* @see #showNewForm
|
||||
* @see #processFormSubmission
|
||||
*/
|
||||
@Override
|
||||
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
// Form submission or new form to show?
|
||||
if (isFormSubmission(request)) {
|
||||
// Fetch form object from HTTP session, bind, validate, process submission.
|
||||
try {
|
||||
Object command = getCommand(request);
|
||||
ServletRequestDataBinder binder = bindAndValidate(request, command);
|
||||
BindException errors = new BindException(binder.getBindingResult());
|
||||
return processFormSubmission(request, response, command, errors);
|
||||
}
|
||||
catch (HttpSessionRequiredException ex) {
|
||||
// Cannot submit a session form if no form object is in the session.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invalid submit detected: " + ex.getMessage());
|
||||
}
|
||||
return handleInvalidSubmit(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
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 treats a POST request as form submission.
|
||||
* Note: If the form session attribute doesn't exist when using session form
|
||||
* mode, the request is always treated as new form by handleRequestInternal.
|
||||
* <p>Subclasses can override this to use a custom strategy, e.g. a specific
|
||||
* request parameter (assumably a hidden field or submit button name).
|
||||
* @param request current HTTP request
|
||||
* @return if the request represents a form submission
|
||||
*/
|
||||
protected boolean isFormSubmission(HttpServletRequest request) {
|
||||
return "POST".equals(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the HttpSession attribute that holds the form object
|
||||
* for this form controller.
|
||||
* <p>The default implementation delegates to the {@link #getFormSessionAttributeName()}
|
||||
* variant 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.servlet.http.HttpSession#getAttribute
|
||||
*/
|
||||
protected String getFormSessionAttributeName(HttpServletRequest request) {
|
||||
return getFormSessionAttributeName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the HttpSession 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.servlet.http.HttpSession#getAttribute
|
||||
*/
|
||||
protected String getFormSessionAttributeName() {
|
||||
return getClass().getName() + ".FORM." + getCommandName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show a new form. Prepares a backing object for the current form
|
||||
* and the given request, including checking its validity.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @return the prepared form view
|
||||
* @throws Exception in case of an invalid new form object
|
||||
* @see #getErrorsForNewForm
|
||||
*/
|
||||
protected final ModelAndView showNewForm(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("Displaying new form");
|
||||
return showForm(request, response, getErrorsForNewForm(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BindException instance for a new form.
|
||||
* Called by {@link #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
|
||||
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)},
|
||||
* after registering the errors on it.
|
||||
* @param request current HTTP request
|
||||
* @return the BindException instance
|
||||
* @throws Exception in case of an invalid new form object
|
||||
* @see #showNewForm
|
||||
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
|
||||
* @see #handleInvalidSubmit
|
||||
*/
|
||||
protected final BindException getErrorsForNewForm(HttpServletRequest request) throws Exception {
|
||||
// Create form-backing object for new form.
|
||||
Object command = formBackingObject(request);
|
||||
if (command == null) {
|
||||
throw new ServletException("Form object returned by formBackingObject() must not be null");
|
||||
}
|
||||
if (!checkCommand(command)) {
|
||||
throw new ServletException("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).
|
||||
ServletRequestDataBinder binder = createBinder(request, command);
|
||||
BindException errors = new BindException(binder.getBindingResult());
|
||||
if (isBindOnNewForm()) {
|
||||
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>The default implementation delegates to {@code onBindOnNewForm(request, command)}.
|
||||
* @param request current HTTP 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(javax.servlet.http.HttpServletRequest, Object)
|
||||
* @see #setBindOnNewForm
|
||||
*/
|
||||
protected void onBindOnNewForm(HttpServletRequest request, Object command, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
onBindOnNewForm(request, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for custom post-processing in terms of binding for a new form.
|
||||
* <p>Called by the default implementation of the
|
||||
* {@link #onBindOnNewForm(HttpServletRequest, Object, BindException)} variant
|
||||
* 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 HTTP request
|
||||
* @param command the command object to perform further binding on
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #onBindOnNewForm(HttpServletRequest, Object, BindException)
|
||||
* @see #setBindOnNewForm(boolean)
|
||||
*/
|
||||
protected void onBindOnNewForm(HttpServletRequest request, Object command) throws Exception {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the form object for the given request.
|
||||
* <p>Calls {@link #formBackingObject} if not in session form mode.
|
||||
* Else, retrieves the form object from the session. Note that the form object
|
||||
* gets removed from the session, but it will be re-added when showing the
|
||||
* form for resubmission.
|
||||
* @param request current HTTP request
|
||||
* @return object form to bind onto
|
||||
* @throws org.springframework.web.HttpSessionRequiredException
|
||||
* if a session was expected but no active session (or session form object) found
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #formBackingObject
|
||||
*/
|
||||
@Override
|
||||
protected final Object getCommand(HttpServletRequest 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 HTTP session attribute.
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
throw new HttpSessionRequiredException("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 HttpSessionRequiredException("Form object not found in session (in session-form mode)");
|
||||
}
|
||||
|
||||
// Remove form object from HTTP 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 HTTP session.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Removing form session attribute [" + formAttrName + "]");
|
||||
}
|
||||
session.removeAttribute(formAttrName);
|
||||
|
||||
return currentFormObject(request, 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 {@link #createCommand()},
|
||||
* creating a new empty instance of the specified command class.
|
||||
* Subclasses can override this to provide a preinitialized backing object.
|
||||
* @param request current HTTP request
|
||||
* @return the backing object
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #setCommandName
|
||||
* @see #setCommandClass
|
||||
* @see #createCommand
|
||||
*/
|
||||
protected Object formBackingObject(HttpServletRequest request) throws Exception {
|
||||
return createCommand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current form object to use for binding and further processing,
|
||||
* based on the passed-in form object as found in the HttpSession.
|
||||
* <p>The default implementation simply returns the session form object as-is.
|
||||
* Subclasses can override this to post-process the session form object,
|
||||
* for example reattaching it to a persistence manager.
|
||||
* @param sessionFormObject the form object retrieved from the HttpSession
|
||||
* @return the form object to use for binding and further processing
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
*/
|
||||
protected Object currentFormObject(HttpServletRequest request, Object sessionFormObject) throws Exception {
|
||||
return sessionFormObject;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {@link #referenceData}.
|
||||
* <p>Note: If you decide to have a "formView" property specifying the
|
||||
* view name, consider using SimpleFormController.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param errors validation errors holder
|
||||
* @return the prepared form view, or {@code null} if handled directly
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #showForm(HttpServletRequest, BindException, String)
|
||||
* @see org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
* @see #referenceData(HttpServletRequest, Object, Errors)
|
||||
* @see SimpleFormController#setFormView
|
||||
*/
|
||||
protected abstract ModelAndView showForm(
|
||||
HttpServletRequest request, HttpServletResponse 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 HTTP 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
|
||||
*/
|
||||
protected final ModelAndView showForm(HttpServletRequest 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 HTTP 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
|
||||
*/
|
||||
protected final ModelAndView showForm(
|
||||
HttpServletRequest request, BindException errors, String viewName, Map controlModel)
|
||||
throws Exception {
|
||||
|
||||
// In session form mode, re-expose form object as HTTP 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.getSession().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 HTTP request
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @param errors validation errors holder
|
||||
* @return a Map with reference data entries, or {@code null} if none
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see ModelAndView
|
||||
*/
|
||||
protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process form submission request. Called by {@link #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 performing a submit action else.
|
||||
* <p>Subclasses can implement this to provide custom submission handling like
|
||||
* triggering a custom action. They can also provide custom validation and call
|
||||
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}
|
||||
* or proceed with the submission accordingly.
|
||||
* <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 provided by
|
||||
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}.
|
||||
* @param request current servlet request
|
||||
* @param response current servlet response
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @param errors holder without errors (subclass can add errors if it wants to)
|
||||
* @return the prepared model and view, or {@code null}
|
||||
* @throws Exception in case of errors
|
||||
* @see #handleRequestInternal
|
||||
* @see #isFormSubmission
|
||||
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
|
||||
* @see org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
*/
|
||||
protected abstract ModelAndView processFormSubmission(
|
||||
HttpServletRequest request, HttpServletResponse 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 {@link #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>
|
||||
* protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse 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 handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
* BindException errors = getErrorsForNewForm(request);
|
||||
* errors.reject("duplicateFormSubmission", "Duplicate form submission");
|
||||
* return showForm(request, response, errors);
|
||||
* }</pre>
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @return a prepared view, or {@code null} if handled directly
|
||||
* @throws Exception in case of errors
|
||||
* @see #showNewForm
|
||||
* @see #getErrorsForNewForm
|
||||
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
|
||||
* @see #setBindOnNewForm
|
||||
*/
|
||||
protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
Object command = formBackingObject(request);
|
||||
ServletRequestDataBinder binder = bindAndValidate(request, command);
|
||||
BindException errors = new BindException(binder.getBindingResult());
|
||||
return processFormSubmission(request, response, command, errors);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,751 +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.servlet.mvc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* 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, that is, perform its
|
||||
* final action, and thus requiring 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 {@link #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.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 25.04.2003
|
||||
* @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
|
||||
* {@link #getViewName(HttpServletRequest, Object, int)} to
|
||||
* determine the view name for each page dynamically.
|
||||
* @see #getViewName(javax.servlet.http.HttpServletRequest, 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
|
||||
* {@link #getPageCount(HttpServletRequest, Object)} to determine
|
||||
* the page count dynamically. The default implementation of that extended
|
||||
* {@code getPageCount} variant returns the static page count as
|
||||
* determined by this {@code getPageCount()} method.
|
||||
* @see #getPageCount(javax.servlet.http.HttpServletRequest, 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, that is, 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, that is, 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(HttpServletRequest 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 standand
|
||||
* validation on binding but rather applies page-specific validation
|
||||
* on processing the form submission.
|
||||
* @param request current HTTP 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(HttpServletRequest request, Object command, BindException errors, int page)
|
||||
throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider an explicit finish or cancel request as a form submission too.
|
||||
* @see #isFinishRequest(javax.servlet.http.HttpServletRequest)
|
||||
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isFormSubmission(HttpServletRequest request) {
|
||||
return super.isFormSubmission(request) || isFinishRequest(request) || isCancelRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls page-specific referenceData method.
|
||||
*/
|
||||
@Override
|
||||
protected final Map referenceData(HttpServletRequest 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 referenceData(HttpServletRequest, int).
|
||||
* Subclasses can override this to set reference data used in the view.
|
||||
* @param request current HTTP 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 {@code null} if none
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #referenceData(HttpServletRequest, int)
|
||||
* @see ModelAndView
|
||||
*/
|
||||
protected Map referenceData(HttpServletRequest 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 HTTP request
|
||||
* @param page current wizard page
|
||||
* @return a Map with reference data entries, or {@code null} if none
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see ModelAndView
|
||||
*/
|
||||
protected Map referenceData(HttpServletRequest 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(
|
||||
HttpServletRequest request, HttpServletResponse 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 {@link #processFinish} implementations,
|
||||
* to show the corresponding page in case of validation errors.
|
||||
* @param request current HTTP 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(HttpServletRequest 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.getSession().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 ServletException("Invalid wizard page number: " + page);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the page count for this wizard form controller.
|
||||
* The default implementation delegates to {@link #getPageCount()}.
|
||||
* <p>Can be overridden to dynamically adapt the page count.
|
||||
* @param request current HTTP request
|
||||
* @param command the command object as returned by formBackingObject
|
||||
* @return the current page count
|
||||
* @see #getPageCount
|
||||
*/
|
||||
protected int getPageCount(HttpServletRequest 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.
|
||||
* <p>Can be overridden to dynamically switch the page view or to return view names
|
||||
* for dynamically defined pages.
|
||||
* @param request current HTTP request
|
||||
* @param command the command object as returned by formBackingObject
|
||||
* @param page the current page number
|
||||
* @return the current page count
|
||||
* @see #getPageCount
|
||||
*/
|
||||
protected String getViewName(HttpServletRequest request, Object command, int page) {
|
||||
return getPages()[page];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial page of the wizard, that is, the page shown at wizard startup.
|
||||
* <p>The default implementation delegates to {@link #getInitialPage(HttpServletRequest)}.
|
||||
* @param request current HTTP request
|
||||
* @param command the command object as returned by formBackingObject
|
||||
* @return the initial page number
|
||||
* @see #getInitialPage(HttpServletRequest)
|
||||
* @see #formBackingObject
|
||||
*/
|
||||
protected int getInitialPage(HttpServletRequest request, Object command) {
|
||||
return getInitialPage(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial page of the wizard, that is, the page shown at wizard startup.
|
||||
* <p>The default implementation returns 0 for first page.
|
||||
* @param request current HTTP request
|
||||
* @return the initial page number
|
||||
*/
|
||||
protected int getInitialPage(HttpServletRequest request) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the HttpSession attribute that holds the page object
|
||||
* for this wizard form controller.
|
||||
* <p>The default implementation delegates to the {@link #getPageSessionAttributeName()}
|
||||
* variant 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 #getPageSessionAttributeName
|
||||
* @see #getFormSessionAttributeName(javax.servlet.http.HttpServletRequest)
|
||||
* @see javax.servlet.http.HttpSession#getAttribute
|
||||
*/
|
||||
protected String getPageSessionAttributeName(HttpServletRequest request) {
|
||||
return getPageSessionAttributeName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the HttpSession 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.servlet.http.HttpSession#getAttribute
|
||||
*/
|
||||
protected String getPageSessionAttributeName() {
|
||||
return getClass().getName() + ".PAGE." + getCommandName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 HTTP request
|
||||
* @param response current HTTP response
|
||||
* @return a prepared view, or {@code null} if handled directly
|
||||
* @throws Exception in case of errors
|
||||
* @see #showNewForm
|
||||
* @see #setBindOnNewForm
|
||||
*/
|
||||
@Override
|
||||
protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
return showNewForm(request, response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply wizard workflow: finish, cancel, page change.
|
||||
*/
|
||||
@Override
|
||||
protected final ModelAndView processFormSubmission(
|
||||
HttpServletRequest request, HttpServletResponse 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.getSession().removeAttribute(pageAttrName);
|
||||
}
|
||||
request.setAttribute(pageAttrName, new Integer(currentPage));
|
||||
|
||||
// cancel?
|
||||
if (isCancelRequest(request)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
|
||||
}
|
||||
return processCancel(request, response, command, errors);
|
||||
}
|
||||
|
||||
// finish?
|
||||
if (isFinishRequest(request)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
|
||||
}
|
||||
return validatePagesAndFinish(request, response, command, errors, currentPage);
|
||||
}
|
||||
|
||||
// Normal submit: validate current page and show specified target page.
|
||||
if (!suppressValidation(request, command, errors)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Validating wizard page " + currentPage + " for form bean '" + getCommandName() + "'");
|
||||
}
|
||||
validatePage(command, errors, currentPage, false);
|
||||
}
|
||||
|
||||
// Give subclasses a change to perform custom post-procession
|
||||
// of the current page and its command object.
|
||||
postProcessPage(request, command, errors, currentPage);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 HTTP request
|
||||
* @return the current page number
|
||||
* @see #getPageSessionAttributeName()
|
||||
*/
|
||||
protected int getCurrentPage(HttpServletRequest 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.getSession().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 HTTP request
|
||||
* @return whether the request indicates to finish form processing
|
||||
* @see #PARAM_FINISH
|
||||
*/
|
||||
protected boolean isFinishRequest(HttpServletRequest request) {
|
||||
return WebUtils.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").
|
||||
* @return whether the request indicates to cancel form processing
|
||||
* @param request current HTTP request
|
||||
* @see #PARAM_CANCEL
|
||||
*/
|
||||
protected boolean isCancelRequest(HttpServletRequest request) {
|
||||
return WebUtils.hasSubmitParameter(request, PARAM_CANCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the target page specified in the request.
|
||||
* <p>The default implementation delegates to {@link #getTargetPage(HttpServletRequest, int)}.
|
||||
* Subclasses can override this for customized target page determination.
|
||||
* @param request current HTTP 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(HttpServletRequest, int)
|
||||
*/
|
||||
protected int getTargetPage(HttpServletRequest 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 HTTP 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(HttpServletRequest request, int currentPage) {
|
||||
return WebUtils.getTargetPage(request, PARAM_TARGET, currentPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all pages and process finish.
|
||||
* If there are page validation errors, show the corresponding view page.
|
||||
*/
|
||||
private ModelAndView validatePagesAndFinish(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors,
|
||||
int currentPage) throws Exception {
|
||||
|
||||
// In case of binding errors -> show current page.
|
||||
if (errors.hasErrors()) {
|
||||
return showPage(request, errors, currentPage);
|
||||
}
|
||||
|
||||
if (!suppressValidation(request, command, errors)) {
|
||||
// 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()) {
|
||||
return showPage(request, errors, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No remaining errors -> proceed with finish.
|
||||
return processFinish(request, response, command, errors);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Template method for custom validation logic for individual pages.
|
||||
* The default implementation calls {@link #validatePage(Object, Errors, int)}.
|
||||
* <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 HTTP 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(HttpServletRequest request, Object command, Errors errors, int page)
|
||||
throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for processing the final action of this wizard.
|
||||
* <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.
|
||||
* <p>You can call the {@link #showPage} method to return back to the wizard,
|
||||
* in case of last-minute validation errors having been found that you would
|
||||
* like to present to the user within the original wizard form.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP 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 org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
* @see #showPage(javax.servlet.http.HttpServletRequest, org.springframework.validation.BindException, int)
|
||||
*/
|
||||
protected abstract ModelAndView processFinish(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Template method for processing the cancel action of this wizard.
|
||||
* <p>The default implementation throws a ServletException, saying that a cancel
|
||||
* operation 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 HTTP request
|
||||
* @param response current HTTP 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 org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
*/
|
||||
protected ModelAndView processCancel(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
throw new ServletException(
|
||||
"Wizard form controller class [" + getClass().getName() + "] does not support a cancel operation");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,595 +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.servlet.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
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.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
/**
|
||||
* <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 org.springframework.validation.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 realise 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 handleRequestInternal() method 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 <ref bean="beanId"/> 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>
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @deprecated as of Spring 3.0, in favor of annotated controllers
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BaseCommandController extends AbstractController {
|
||||
|
||||
/** Default command name used for binding command objects: "command" */
|
||||
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}, that is, 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 {@link #initBinder}.
|
||||
* @see #initBinder
|
||||
*/
|
||||
public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
|
||||
this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify multiple PropertyEditorRegistrars 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 {@link #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 HTTP request
|
||||
* @return object command to bind onto
|
||||
* @throws Exception if the command object could not be obtained
|
||||
* @see #createCommand
|
||||
*/
|
||||
protected Object getCommand(HttpServletRequest 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 HTTP request
|
||||
* @param command the command to bind onto
|
||||
* @return the ServletRequestDataBinder instance for additional custom validation
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
*/
|
||||
protected final ServletRequestDataBinder bindAndValidate(HttpServletRequest request, Object command)
|
||||
throws Exception {
|
||||
|
||||
ServletRequestDataBinder binder = createBinder(request, command);
|
||||
BindException errors = new BindException(binder.getBindingResult());
|
||||
if (!suppressBinding(request)) {
|
||||
binder.bind(request);
|
||||
onBind(request, command, errors);
|
||||
if (this.validators != null && isValidateOnBinding() && !suppressValidation(request, command, errors)) {
|
||||
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 "false". Can be overridden
|
||||
* in subclasses to suppress validation, for example, if a special
|
||||
* request parameter is set.
|
||||
* @param request current HTTP request
|
||||
* @return whether to suppress binding for the given request
|
||||
* @see #suppressValidation
|
||||
*/
|
||||
protected boolean suppressBinding(HttpServletRequest request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new binder instance for the given command and request.
|
||||
* <p>Called by {@link #bindAndValidate}. Can be overridden to plug in
|
||||
* custom ServletRequestDataBinder instances.
|
||||
* <p>The default implementation creates a standard ServletRequestDataBinder
|
||||
* and invokes {@link #prepareBinder} and {@link #initBinder}.
|
||||
* <p>Note that neither {@link #prepareBinder} nor {@link #initBinder} will
|
||||
* be invoked automatically if you override this method! Call those methods
|
||||
* at appropriate points of your overridden method.
|
||||
* @param request current HTTP 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 ServletRequestDataBinder createBinder(HttpServletRequest request, Object command)
|
||||
throws Exception {
|
||||
|
||||
ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName());
|
||||
prepareBinder(binder);
|
||||
initBinder(request, binder);
|
||||
return binder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given binder, applying the specified MessageCodesResolver,
|
||||
* BindingErrorProcessor and PropertyEditorRegistrars (if any).
|
||||
* Called by {@link #createBinder}.
|
||||
* @param binder the new binder instance
|
||||
* @see #createBinder
|
||||
* @see #setMessageCodesResolver
|
||||
* @see #setBindingErrorProcessor
|
||||
*/
|
||||
protected final void prepareBinder(ServletRequestDataBinder 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 {@link #prepareBinder}.
|
||||
* <p>Default is "false". Can be overridden in subclasses.
|
||||
* @return whether to use direct field access ({@code true})
|
||||
* or bean property access ({@code false})
|
||||
* @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 {@link #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 HTTP request
|
||||
* @param binder the 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(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
|
||||
if (this.webBindingInitializer != null) {
|
||||
this.webBindingInitializer.initBinder(binder, new ServletWebRequest(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 {@link #onBind(HttpServletRequest, Object)}.
|
||||
* @param request current HTTP 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(HttpServletRequest, Object)
|
||||
*/
|
||||
protected void onBind(HttpServletRequest request, Object command, BindException errors) throws Exception {
|
||||
onBind(request, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for custom post-processing in terms of binding.
|
||||
* <p>Called by the default implementation of the
|
||||
* {@link #onBind(HttpServletRequest, Object, BindException)} variant
|
||||
* with all parameters, after standard binding but before validation.
|
||||
* <p>The default implementation is empty.
|
||||
* @param request current HTTP request
|
||||
* @param command the command object to perform further binding on
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #onBind(HttpServletRequest, Object, BindException)
|
||||
*/
|
||||
protected void onBind(HttpServletRequest request, Object command) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to suppress validation for the given request.
|
||||
* <p>The default implementation delegates to {@link #suppressValidation(HttpServletRequest, Object)}.
|
||||
* @param request current HTTP request
|
||||
* @param command the command object to validate
|
||||
* @param errors validation errors holder, allowing for additional
|
||||
* custom registration of binding errors
|
||||
* @return whether to suppress validation for the given request
|
||||
*/
|
||||
protected boolean suppressValidation(HttpServletRequest request, Object command, BindException errors) {
|
||||
return suppressValidation(request, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to suppress validation for the given request.
|
||||
* <p>Called by the default implementation of the
|
||||
* {@link #suppressValidation(HttpServletRequest, Object, BindException)} variant
|
||||
* with all parameters.
|
||||
* <p>The default implementation delegates to {@link #suppressValidation(HttpServletRequest)}.
|
||||
* @param request current HTTP request
|
||||
* @param command the command object to validate
|
||||
* @return whether to suppress validation for the given request
|
||||
*/
|
||||
protected boolean suppressValidation(HttpServletRequest request, Object command) {
|
||||
return suppressValidation(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to suppress validation for the given request.
|
||||
* <p>Called by the default implementation of the
|
||||
* {@link #suppressValidation(HttpServletRequest, Object)} variant
|
||||
* with all parameters.
|
||||
* <p>The default implementation is empty.
|
||||
* @param request current HTTP request
|
||||
* @return whether to suppress validation for the given request
|
||||
* @deprecated as of Spring 2.0.4, in favor of the
|
||||
* {@link #suppressValidation(HttpServletRequest, Object)} variant
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean suppressValidation(HttpServletRequest 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 HTTP 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(HttpServletRequest request, Object command, BindException errors)
|
||||
throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,210 +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.servlet.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* <p>Extension of {@code SimpleFormController} that supports "cancellation"
|
||||
* of form processing. By default, this controller looks for a given parameter in the
|
||||
* request, identified by the {@code cancelParamKey}. If this parameter is present,
|
||||
* then the controller will return the configured {@code cancelView}, otherwise
|
||||
* processing is passed back to the superclass.</p>
|
||||
*
|
||||
* <p><b><a name="workflow">Workflow
|
||||
* (<a href="SimpleFormController.html#workflow">in addition to the superclass</a>):</b><br>
|
||||
* <ol>
|
||||
* <li>Call to {@link #processFormSubmission processFormSubmission} which calls
|
||||
* {@link #isCancelRequest} to see if the incoming request is to cancel the
|
||||
* current form entry. By default, {@link #isCancelRequest} returns {@code true}
|
||||
* if the configured {@code cancelParamKey} exists in the request.
|
||||
* This behavior can be overridden in subclasses.</li>
|
||||
* <li>If {@link #isCancelRequest} returns {@code false}, then the controller
|
||||
* will delegate all processing back to {@link SimpleFormController SimpleFormController},
|
||||
* otherwise it will call the {@link #onCancel} version with all parameters.
|
||||
* By default, that method will delegate to the {@link #onCancel} version with just
|
||||
* the command object, which will in turn simply return the configured
|
||||
* {@code cancelView}. This behavior can be overridden in subclasses.</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>Thanks to Erwin Bolwidt for submitting the original prototype
|
||||
* of such a cancellable form controller!</p>
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.2.3
|
||||
* @see #setCancelParamKey
|
||||
* @see #setCancelView
|
||||
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
|
||||
* @see #onCancel(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object)
|
||||
* @deprecated as of Spring 3.0, in favor of annotated controllers
|
||||
*/
|
||||
@Deprecated
|
||||
public class CancellableFormController extends SimpleFormController {
|
||||
|
||||
/**
|
||||
* Default parameter triggering the cancel action.
|
||||
* Can be called even with validation errors on the form.
|
||||
*/
|
||||
private static final String PARAM_CANCEL = "_cancel";
|
||||
|
||||
|
||||
private String cancelParamKey = PARAM_CANCEL;
|
||||
|
||||
private String cancelView;
|
||||
|
||||
|
||||
/**
|
||||
* Set the key of the request parameter used to identify a cancel request.
|
||||
* Default is "_cancel".
|
||||
* <p>The parameter is recognized both when sent as a plain parameter
|
||||
* ("_cancel") or when triggered by an image button ("_cancel.x").
|
||||
*/
|
||||
public final void setCancelParamKey(String cancelParamKey) {
|
||||
this.cancelParamKey = cancelParamKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the request parameter used to identify a cancel request.
|
||||
*/
|
||||
public final String getCancelParamKey() {
|
||||
return this.cancelParamKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the cancel view.
|
||||
*/
|
||||
public final void setCancelView(String cancelView) {
|
||||
this.cancelView = cancelView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the cancel view.
|
||||
*/
|
||||
public final String getCancelView() {
|
||||
return this.cancelView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consider an explicit cancel request as a form submission too.
|
||||
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isFormSubmission(HttpServletRequest request) {
|
||||
return super.isFormSubmission(request) || isCancelRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress validation for an explicit cancel request too.
|
||||
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
protected boolean suppressValidation(HttpServletRequest request, Object command) {
|
||||
return super.suppressValidation(request, command) || isCancelRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation first checks to see if the incoming is a cancel request,
|
||||
* through a call to {@link #isCancelRequest}. If so, control is passed to
|
||||
* {@link #onCancel}; otherwise, control is passed up to
|
||||
* {@link SimpleFormController#processFormSubmission}.
|
||||
* @see #isCancelRequest
|
||||
* @see #onCancel(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object)
|
||||
* @see SimpleFormController#processFormSubmission
|
||||
*/
|
||||
@Override
|
||||
protected ModelAndView processFormSubmission(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
if (isCancelRequest(request)) {
|
||||
return onCancel(request, response, command);
|
||||
}
|
||||
else {
|
||||
return super.processFormSubmission(request, response, command, errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 configured {@code cancelParamKey} 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 HTTP request
|
||||
* @see #setCancelParamKey
|
||||
* @see #PARAM_CANCEL
|
||||
*/
|
||||
protected boolean isCancelRequest(HttpServletRequest request) {
|
||||
return WebUtils.hasSubmitParameter(request, getCancelParamKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for handling a cancel request. Called if {@link #isCancelRequest}
|
||||
* returns {@code true}.
|
||||
* <p>Default implementation delegates to {@code onCancel(Object)} to return
|
||||
* the configured {@code cancelView}. Subclasses may override either of the two
|
||||
* methods to build a custom {@link ModelAndView ModelAndView} that may contain model
|
||||
* parameters used in the cancel view.
|
||||
* <p>If you simply want to move the user to a new view and you don't want to add
|
||||
* additional model parameters, use {@link #setCancelView(String)} rather than
|
||||
* overriding an {@code onCancel} method.
|
||||
* @param request current servlet request
|
||||
* @param response current servlet response
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @return the prepared model and view, or {@code null}
|
||||
* @throws Exception in case of errors
|
||||
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
|
||||
* @see #onCancel(Object)
|
||||
* @see #setCancelView
|
||||
*/
|
||||
protected ModelAndView onCancel(HttpServletRequest request, HttpServletResponse response, Object command)
|
||||
throws Exception {
|
||||
|
||||
return onCancel(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple {@code onCancel} version. Called by the default implementation
|
||||
* of the {@code onCancel} version with all parameters.
|
||||
* <p>Default implementation returns eturns the configured {@code cancelView}.
|
||||
* Subclasses may override this method to build a custom {@link ModelAndView ModelAndView}
|
||||
* that may contain model parameters used in the cancel view.
|
||||
* <p>If you simply want to move the user to a new view and you don't want to add
|
||||
* additional model parameters, use {@link #setCancelView(String)} rather than
|
||||
* overriding an {@code onCancel} method.
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @return the prepared model and view, or {@code null}
|
||||
* @throws Exception in case of errors
|
||||
* @see #onCancel(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object)
|
||||
* @see #setCancelView
|
||||
*/
|
||||
protected ModelAndView onCancel(Object command) throws Exception {
|
||||
return new ModelAndView(getCancelView());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,468 +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.servlet.mvc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.servlet.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} and
|
||||
* {@link #processFormSubmission processFormSubmission}: A form view and a
|
||||
* success view can be configured declaratively.</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 #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException) onSubmit}
|
||||
* using all parameters, which in case of the default implementation delegates to
|
||||
* {@link #onSubmit(Object, BindException) onSubmit} with just the command object.
|
||||
* The default implementation of the latter method will return the configured
|
||||
* {@code successView}. Consider implementing {@link #doSubmitAction} doSubmitAction
|
||||
* for simply performing a submit action and rendering the success view.</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>The submit behavior can be customized by overriding one of the
|
||||
* {@link #onSubmit onSubmit} methods. Submit actions can also perform
|
||||
* custom validation if necessary (typically database-driven checks), calling
|
||||
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException) showForm}
|
||||
* in case of validation errors to show the form view again.</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 #onSubmit(Object) onSubmit()} methods.</td>
|
||||
* </tr>
|
||||
* <table>
|
||||
* </p>
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @since 05.05.2003
|
||||
* @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
|
||||
* @see #setCommandName
|
||||
* @see #setSessionForm
|
||||
* @see #setFormView
|
||||
* @see #setSuccessView
|
||||
* @see #formBackingObject
|
||||
*/
|
||||
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
|
||||
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException, Map)}
|
||||
* variant with a "controlModel" argument.
|
||||
* <p>Can be called within
|
||||
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}
|
||||
* implementations, to redirect back to the form in case of custom validation errors
|
||||
* (errors 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(HttpServletRequest, HttpServletResponse, BindException, Map)
|
||||
*/
|
||||
@Override
|
||||
protected ModelAndView showForm(
|
||||
HttpServletRequest request, HttpServletResponse response, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
return showForm(request, response, errors, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation shows the configured form view.
|
||||
* <p>Can be called within
|
||||
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}
|
||||
* implementations, to redirect back to the form in case of custom validation errors
|
||||
* (errors 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 HTTP 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(
|
||||
HttpServletRequest request, HttpServletResponse 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(HttpServletRequest)}.
|
||||
* Subclasses can override this to set reference data used in the view.
|
||||
* @param request current HTTP request
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @param errors validation errors holder
|
||||
* @return a Map with reference data entries, or {@code null} if none
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see ModelAndView
|
||||
*/
|
||||
@Override
|
||||
protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
|
||||
return referenceData(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reference data map for the given request.
|
||||
* Called by the {@link #referenceData(HttpServletRequest, Object, Errors)}
|
||||
* variant 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 HTTP request
|
||||
* @return a Map with reference data entries, or {@code null} if none
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
* @see #referenceData(HttpServletRequest, Object, Errors)
|
||||
* @see ModelAndView
|
||||
*/
|
||||
protected Map referenceData(HttpServletRequest request) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation calls
|
||||
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}
|
||||
* in case of errors, and delegates to the full
|
||||
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}'s
|
||||
* variant 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 onSubmit}
|
||||
* methods or {@link #doSubmitAction}.
|
||||
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
|
||||
* @see #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)
|
||||
* @see #onSubmit(Object, BindException)
|
||||
* @see #onSubmit(Object)
|
||||
* @see #doSubmitAction(Object)
|
||||
*/
|
||||
@Override
|
||||
protected ModelAndView processFormSubmission(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Data binding errors: " + errors.getErrorCount());
|
||||
}
|
||||
return showForm(request, response, errors);
|
||||
}
|
||||
else if (isFormChangeRequest(request, command)) {
|
||||
logger.debug("Detected form change request -> routing request to onFormChange");
|
||||
onFormChange(request, response, command, errors);
|
||||
return showForm(request, response, errors);
|
||||
}
|
||||
else {
|
||||
logger.debug("No errors -> processing submit");
|
||||
return onSubmit(request, response, command, errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation delegates to {@link #isFormChangeRequest(HttpServletRequest, Object)}:
|
||||
* 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(HttpServletRequest request, Object command) {
|
||||
return isFormChangeRequest(request, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 delegates to
|
||||
* {@link #isFormChangeRequest(javax.servlet.http.HttpServletRequest)}.
|
||||
* @param request current HTTP request
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @return whether the given request is a form change request
|
||||
* @see #suppressValidation
|
||||
* @see #processFormSubmission
|
||||
*/
|
||||
protected boolean isFormChangeRequest(HttpServletRequest request, Object command) {
|
||||
return isFormChangeRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpler {@code isFormChangeRequest} variant, called by the full
|
||||
* variant {@link #isFormChangeRequest(HttpServletRequest, Object)}.
|
||||
* <p>The default implementation returns {@code false}.
|
||||
* @param request current HTTP request
|
||||
* @return whether the given request is a form change request
|
||||
* @see #suppressValidation
|
||||
* @see #processFormSubmission
|
||||
*/
|
||||
protected boolean isFormChangeRequest(HttpServletRequest request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during form submission if
|
||||
* {@link #isFormChangeRequest(javax.servlet.http.HttpServletRequest)}
|
||||
* 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
|
||||
* {@link #onFormChange(HttpServletRequest, HttpServletResponse, Object, BindException)}.
|
||||
* @param request current servlet request
|
||||
* @param response current servlet 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(HttpServletRequest)
|
||||
* @see #onFormChange(HttpServletRequest, HttpServletResponse, Object)
|
||||
*/
|
||||
protected void onFormChange(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
onFormChange(request, response, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpler {@code onFormChange} variant, called by the full variant
|
||||
* {@link #onFormChange(HttpServletRequest, HttpServletResponse, Object, BindException)}.
|
||||
* <p>The default implementation is empty.
|
||||
* @param request current servlet request
|
||||
* @param response current servlet response
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @throws Exception in case of errors
|
||||
* @see #onFormChange(HttpServletRequest, HttpServletResponse, Object, BindException)
|
||||
*/
|
||||
protected void onFormChange(HttpServletRequest request, HttpServletResponse response, Object command)
|
||||
throws Exception {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Submit 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 #onSubmit(Object, BindException)}.
|
||||
* For simply performing a submit action and rendering the specified success
|
||||
* view, consider implementing {@link #doSubmitAction} rather than an
|
||||
* {@code onSubmit} variant.
|
||||
* <p>Subclasses can override this to provide custom submission handling like storing
|
||||
* the object to the database. Implementations can also perform custom validation and
|
||||
* call showForm to return to the form. Do <i>not</i> implement multiple onSubmit
|
||||
* 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 servlet request
|
||||
* @param response current servlet 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, or {@code null}
|
||||
* @throws Exception in case of errors
|
||||
* @see #onSubmit(Object, BindException)
|
||||
* @see #doSubmitAction
|
||||
* @see #showForm
|
||||
* @see org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
*/
|
||||
protected ModelAndView onSubmit(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws Exception {
|
||||
|
||||
return onSubmit(command, errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpler {@code onSubmit} variant.
|
||||
* Called by the default implementation of the
|
||||
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}
|
||||
* variant with all parameters.
|
||||
* <p>The default implementation calls {@link #onSubmit(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
|
||||
* @throws Exception in case of errors
|
||||
* @see #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)
|
||||
* @see #onSubmit(Object)
|
||||
* @see #setSuccessView
|
||||
* @see org.springframework.validation.Errors
|
||||
* @see org.springframework.validation.BindException#getModel
|
||||
*/
|
||||
protected ModelAndView onSubmit(Object command, BindException errors) throws Exception {
|
||||
ModelAndView mv = onSubmit(command);
|
||||
if (mv != null) {
|
||||
// simplest onSubmit variant implemented in custom subclass
|
||||
return mv;
|
||||
}
|
||||
else {
|
||||
// default behavior: render success view
|
||||
if (getSuccessView() == null) {
|
||||
throw new ServletException("successView isn't set");
|
||||
}
|
||||
return new ModelAndView(getSuccessView(), errors.getModel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplest {@code onSubmit} variant. Called by the default implementation
|
||||
* of the {@link #onSubmit(Object, BindException)} variant.
|
||||
* <p>This implementation calls {@link #doSubmitAction(Object)} and returns
|
||||
* {@code null} as ModelAndView, making the calling {@code onSubmit}
|
||||
* 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. It's preferable to use either
|
||||
* {@link #onSubmit(Object, BindException)} or {@link #doSubmitAction(Object)},
|
||||
* though: Use the former when you want to build your own ModelAndView; use the
|
||||
* latter when you want to perform an action and forward to the successView.
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @return the prepared model and view, or {@code null} for default
|
||||
* (that is, rendering the configured "successView")
|
||||
* @throws Exception in case of errors
|
||||
* @see #onSubmit(Object, BindException)
|
||||
* @see #doSubmitAction
|
||||
* @see #setSuccessView
|
||||
*/
|
||||
protected ModelAndView onSubmit(Object command) throws Exception {
|
||||
doSubmitAction(command);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for submit actions. Called by the default implementation
|
||||
* of the simplest {@link #onSubmit(Object)} variant.
|
||||
* <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>
|
||||
* You don't need to care about the success ModelAndView here.
|
||||
* @param command form object with request parameters bound onto it
|
||||
* @throws Exception in case of errors
|
||||
* @see #onSubmit(Object)
|
||||
* @see #setSuccessView
|
||||
*/
|
||||
protected void doSubmitAction(Object command) throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -60,7 +60,6 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
|||
import org.springframework.web.servlet.mvc.Controller;
|
||||
import org.springframework.web.servlet.mvc.ParameterizableViewController;
|
||||
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.SimpleFormController;
|
||||
import org.springframework.web.servlet.support.RequestContextUtils;
|
||||
import org.springframework.web.servlet.theme.SessionThemeResolver;
|
||||
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
|
||||
|
|
@ -111,7 +110,7 @@ public class ComplexWebApplicationContext extends StaticWebApplicationContext {
|
|||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add(
|
||||
"mappings", "/form.do=formHandler\n/head.do=headController\n" +
|
||||
"mappings", "/head.do=headController\n" +
|
||||
"body.do=bodyController\n/noview*=noviewController\n/noview/simple*=noviewController");
|
||||
pvs.add("order", "1");
|
||||
registerSingleton("handlerMapping", SimpleUrlHandlerMapping.class, pvs);
|
||||
|
|
@ -130,11 +129,6 @@ public class ComplexWebApplicationContext extends StaticWebApplicationContext {
|
|||
pvs.add("suffix", ".jsp");
|
||||
registerSingleton("viewResolver2", InternalResourceViewResolver.class, pvs);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("commandClass", "org.springframework.tests.sample.beans.TestBean");
|
||||
pvs.add("formView", "form");
|
||||
registerSingleton("formHandler", SimpleFormController.class, pvs);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("viewName", "form");
|
||||
registerSingleton("viewHandler", ParameterizableViewController.class, pvs);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package org.springframework.web.servlet;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
|
|
@ -31,7 +30,6 @@ import junit.framework.TestCase;
|
|||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyValue;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.DummyEnvironment;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
|
|
@ -39,7 +37,6 @@ import org.springframework.mock.web.test.MockHttpServletResponse;
|
|||
import org.springframework.mock.web.test.MockServletConfig;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.web.bind.EscapedErrors;
|
||||
import org.springframework.web.context.ConfigurableWebEnvironment;
|
||||
import org.springframework.web.context.ServletConfigAwareBean;
|
||||
import org.springframework.web.context.ServletContextAwareBean;
|
||||
|
|
@ -51,11 +48,8 @@ import org.springframework.web.multipart.MultipartHttpServletRequest;
|
|||
import org.springframework.web.multipart.MultipartResolver;
|
||||
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.BaseCommandController;
|
||||
import org.springframework.web.servlet.mvc.Controller;
|
||||
import org.springframework.web.servlet.support.RequestContext;
|
||||
import org.springframework.web.servlet.support.RequestContextUtils;
|
||||
import org.springframework.web.servlet.theme.AbstractThemeResolver;
|
||||
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
|
|
@ -155,44 +149,6 @@ public class DispatcherServletTests extends TestCase {
|
|||
assertEquals(0, listener.counter);
|
||||
}
|
||||
|
||||
public void testFormRequest() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/form.do");
|
||||
request.addPreferredLocale(Locale.CANADA);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
simpleDispatcherServlet.service(request, response);
|
||||
assertTrue("forwarded to form", "form".equals(response.getForwardedUrl()));
|
||||
DefaultMessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(new String[]{"test"});
|
||||
RequestContext rc = new RequestContext(request);
|
||||
|
||||
assertTrue("hasn't RequestContext attribute", request.getAttribute("rc") == null);
|
||||
assertTrue("Correct WebApplicationContext",
|
||||
RequestContextUtils.getWebApplicationContext(request) instanceof SimpleWebApplicationContext);
|
||||
assertTrue("Correct context path", rc.getContextPath().equals(request.getContextPath()));
|
||||
assertTrue("Correct locale", Locale.CANADA.equals(RequestContextUtils.getLocale(request)));
|
||||
assertTrue("Correct theme", AbstractThemeResolver.ORIGINAL_DEFAULT_THEME_NAME.equals(
|
||||
RequestContextUtils.getTheme(request).getName()));
|
||||
assertTrue("Correct message", "Canadian & test message".equals(rc.getMessage("test")));
|
||||
|
||||
assertTrue("Correct WebApplicationContext",
|
||||
rc.getWebApplicationContext() == simpleDispatcherServlet.getWebApplicationContext());
|
||||
assertTrue("Correct Errors",
|
||||
!(rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME) instanceof EscapedErrors));
|
||||
assertTrue("Correct Errors",
|
||||
!(rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME, false) instanceof EscapedErrors));
|
||||
assertTrue("Correct Errors",
|
||||
rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME, true) instanceof EscapedErrors);
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test"));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", null, false));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", null, true));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable, false));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable, true));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", "default"));
|
||||
assertEquals("Correct message", "default", rc.getMessage("testa", "default"));
|
||||
assertEquals("Correct message", "default &", rc.getMessage("testa", null, "default &", true));
|
||||
}
|
||||
|
||||
public void testParameterizableViewController() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/view.do");
|
||||
request.addUserRole("role1");
|
||||
|
|
@ -227,51 +183,6 @@ public class DispatcherServletTests extends TestCase {
|
|||
assertTrue("Exception exposed", request.getAttribute("exception").getClass().equals(ServletException.class));
|
||||
}
|
||||
|
||||
public void testAnotherFormRequest() throws Exception {
|
||||
MockHttpServletRequest request =
|
||||
new MockHttpServletRequest(getServletContext(), "GET", "/form.do;jsessionid=xxx");
|
||||
request.addPreferredLocale(Locale.CANADA);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
complexDispatcherServlet.service(request, response);
|
||||
assertTrue("forwarded to form", "myform.jsp".equals(response.getForwardedUrl()));
|
||||
assertTrue("has RequestContext attribute", request.getAttribute("rc") != null);
|
||||
DefaultMessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(new String[]{"test"});
|
||||
|
||||
RequestContext rc = (RequestContext) request.getAttribute("rc");
|
||||
assertTrue("Not in HTML escaping mode", !rc.isDefaultHtmlEscape());
|
||||
assertTrue("Correct WebApplicationContext",
|
||||
rc.getWebApplicationContext() == complexDispatcherServlet.getWebApplicationContext());
|
||||
assertTrue("Correct context path", rc.getContextPath().equals(request.getContextPath()));
|
||||
assertTrue("Correct locale", Locale.CANADA.equals(rc.getLocale()));
|
||||
assertTrue("Correct Errors",
|
||||
!(rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME) instanceof EscapedErrors));
|
||||
assertTrue("Correct Errors",
|
||||
!(rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME, false) instanceof EscapedErrors));
|
||||
assertTrue("Correct Errors",
|
||||
rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME, true) instanceof EscapedErrors);
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test"));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", null, false));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", null, true));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable, false));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable, true));
|
||||
|
||||
rc.setDefaultHtmlEscape(true);
|
||||
assertTrue("Is in HTML escaping mode", rc.isDefaultHtmlEscape());
|
||||
assertTrue("Correct Errors", rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME) instanceof EscapedErrors);
|
||||
assertTrue("Correct Errors",
|
||||
!(rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME, false) instanceof EscapedErrors));
|
||||
assertTrue("Correct Errors",
|
||||
rc.getErrors(BaseCommandController.DEFAULT_COMMAND_NAME, true) instanceof EscapedErrors);
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test"));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", null, false));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage("test", null, true));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable, false));
|
||||
assertEquals("Correct message", "Canadian & test message", rc.getMessage(resolvable, true));
|
||||
}
|
||||
|
||||
public void testAnotherLocaleRequest() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/locale.do;abc=def");
|
||||
request.addPreferredLocale(Locale.CANADA);
|
||||
|
|
@ -568,7 +479,6 @@ public class DispatcherServletTests extends TestCase {
|
|||
request = new MockHttpServletRequest(getServletContext(), "GET", "/form.do");
|
||||
response = new MockHttpServletResponse();
|
||||
complexDispatcherServlet.service(request, response);
|
||||
assertTrue("forwarded to form", "myform.jsp".equals(response.getForwardedUrl()));
|
||||
}
|
||||
|
||||
public void testNotDetectAllHandlerAdapters() throws ServletException, IOException {
|
||||
|
|
@ -641,7 +551,6 @@ public class DispatcherServletTests extends TestCase {
|
|||
|
||||
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/form.do");
|
||||
simpleDispatcherServlet.service(request, response);
|
||||
assertTrue("forwarded to form", "form".equals(response.getIncludedUrl()));
|
||||
|
||||
assertEquals("value1", request.getAttribute("test1"));
|
||||
assertEquals("value2", request.getAttribute("test2"));
|
||||
|
|
@ -663,12 +572,10 @@ public class DispatcherServletTests extends TestCase {
|
|||
|
||||
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/form.do");
|
||||
simpleDispatcherServlet.service(request, response);
|
||||
assertTrue("forwarded to form", "form".equals(response.getIncludedUrl()));
|
||||
|
||||
assertEquals("value1", request.getAttribute("test1"));
|
||||
assertEquals("value2", request.getAttribute("test2"));
|
||||
assertSame(wac, request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE));
|
||||
assertSame(command, request.getAttribute("command"));
|
||||
}
|
||||
|
||||
public void testNoCleanupAfterInclude() throws ServletException, IOException {
|
||||
|
|
@ -685,12 +592,10 @@ public class DispatcherServletTests extends TestCase {
|
|||
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/form.do");
|
||||
simpleDispatcherServlet.setCleanupAfterInclude(false);
|
||||
simpleDispatcherServlet.service(request, response);
|
||||
assertTrue("forwarded to form", "form".equals(response.getIncludedUrl()));
|
||||
|
||||
assertEquals("value1", request.getAttribute("test1"));
|
||||
assertEquals("value2", request.getAttribute("test2"));
|
||||
assertSame(wac, request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE));
|
||||
assertNotSame(command, request.getAttribute("command"));
|
||||
}
|
||||
|
||||
public void testServletHandlerAdapter() throws Exception {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import org.springframework.web.context.support.StaticWebApplicationContext;
|
|||
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
|
||||
import org.springframework.web.servlet.mvc.Controller;
|
||||
import org.springframework.web.servlet.mvc.LastModified;
|
||||
import org.springframework.web.servlet.mvc.SimpleFormController;
|
||||
import org.springframework.web.servlet.support.RequestContextUtils;
|
||||
import org.springframework.web.servlet.theme.AbstractThemeResolver;
|
||||
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||
|
|
@ -52,7 +51,6 @@ public class SimpleWebApplicationContext extends StaticWebApplicationContext {
|
|||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("commandClass", "org.springframework.tests.sample.beans.TestBean");
|
||||
pvs.add("formView", "form");
|
||||
registerSingleton("/form.do", SimpleFormController.class, pvs);
|
||||
|
||||
registerSingleton("/locale.do", LocaleChecker.class);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.servlet.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class CancellableFormControllerTests extends TestCase {
|
||||
|
||||
public void testFormViewRequest() throws Exception {
|
||||
String formView = "theFormView";
|
||||
|
||||
TestController ctl = new TestController();
|
||||
ctl.setFormView(formView);
|
||||
ctl.setBindOnNewForm(true);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
String name = "Rob Harrop";
|
||||
int age = 23;
|
||||
|
||||
request.setMethod("GET");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
|
||||
assertEquals("Incorrect view name", formView, mv.getViewName());
|
||||
|
||||
TestBean command = (TestBean) mv.getModel().get(ctl.getCommandName());
|
||||
|
||||
testCommandIsBound(command, name, age);
|
||||
}
|
||||
|
||||
public void testFormSubmissionRequestWithoutCancel() throws Exception {
|
||||
String successView = "successView";
|
||||
|
||||
TestController ctl = new TestController();
|
||||
ctl.setSuccessView(successView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
String name = "Rob Harrop";
|
||||
int age = 23;
|
||||
|
||||
request.setMethod("POST");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
|
||||
assertEquals("Incorrect view name", successView, mv.getViewName());
|
||||
|
||||
TestBean command = (TestBean) mv.getModel().get(ctl.getCommandName());
|
||||
|
||||
testCommandIsBound(command, name, age);
|
||||
}
|
||||
|
||||
public void testFormSubmissionWithErrors() throws Exception {
|
||||
String successView = "successView";
|
||||
String formView = "formView";
|
||||
|
||||
TestController ctl = new TestController();
|
||||
ctl.setSuccessView(successView);
|
||||
ctl.setFormView(formView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
request.setMethod("POST");
|
||||
request.addParameter("name", "Rob Harrop");
|
||||
request.addParameter("age", "xxx23");
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
assertEquals("Incorrect view name", formView, mv.getViewName());
|
||||
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + ctl.getCommandName());
|
||||
assertNotNull("No errors", errors);
|
||||
assertEquals(1, errors.getErrorCount());
|
||||
}
|
||||
|
||||
public void testFormSubmissionWithValidationError() throws Exception {
|
||||
String successView = "successView";
|
||||
String formView = "formView";
|
||||
|
||||
TestController ctl = new TestController();
|
||||
ctl.setSuccessView(successView);
|
||||
ctl.setFormView(formView);
|
||||
TestValidator val = new TestValidator();
|
||||
ctl.setValidator(val);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
request.setMethod("POST");
|
||||
request.addParameter("name", "Rob Harrop");
|
||||
request.addParameter("age", "23");
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
assertEquals("Incorrect view name", formView, mv.getViewName());
|
||||
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + ctl.getCommandName());
|
||||
assertNotNull("No errors", errors);
|
||||
assertEquals(1, errors.getErrorCount());
|
||||
assertTrue(val.invoked);
|
||||
}
|
||||
|
||||
public void testCancelSubmission() throws Exception {
|
||||
String cancelView = "cancelView";
|
||||
String cancelParameterKey = "cancelRequest";
|
||||
|
||||
TestController ctl = new TestController();
|
||||
ctl.setCancelParamKey(cancelParameterKey);
|
||||
ctl.setCancelView(cancelView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
request.setMethod("POST");
|
||||
request.addParameter("cancelRequest", "true");
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
assertEquals("Incorrect view name", cancelView, mv.getViewName());
|
||||
}
|
||||
|
||||
public void testCancelSubmissionWithValidationError() throws Exception {
|
||||
String cancelView = "cancelView";
|
||||
String cancelParameterKey = "cancelRequest";
|
||||
|
||||
TestController ctl = new TestController();
|
||||
ctl.setCancelParamKey(cancelParameterKey);
|
||||
ctl.setCancelView(cancelView);
|
||||
TestValidator val = new TestValidator();
|
||||
ctl.setValidator(val);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
request.setMethod("POST");
|
||||
request.addParameter("name", "Rob Harrop");
|
||||
request.addParameter("age", "23");
|
||||
request.addParameter("cancelRequest", "true");
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
assertEquals("Incorrect view name", cancelView, mv.getViewName());
|
||||
|
||||
assertFalse(val.invoked);
|
||||
}
|
||||
|
||||
public void testCancelSubmissionWithCustomModelParams() throws Exception {
|
||||
String cancelView = "cancelView";
|
||||
String cancelParameterKey = "cancelRequest";
|
||||
final String reason = "Because I wanted to";
|
||||
|
||||
TestController ctl = new TestController() {
|
||||
@Override
|
||||
protected ModelAndView onCancel(HttpServletRequest request, HttpServletResponse response, Object command) {
|
||||
return new ModelAndView(getCancelView(), "reason", reason);
|
||||
}
|
||||
};
|
||||
|
||||
ctl.setCancelParamKey(cancelParameterKey);
|
||||
ctl.setCancelView(cancelView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
request.setMethod("POST");
|
||||
request.addParameter("cancelRequest", "true");
|
||||
|
||||
ModelAndView mv = ctl.handleRequest(request, response);
|
||||
assertEquals("Incorrect view name", cancelView, mv.getViewName());
|
||||
assertEquals("Model parameter reason not correct", reason, mv.getModel().get("reason"));
|
||||
}
|
||||
|
||||
private void testCommandIsBound(TestBean command, String name, int age) {
|
||||
assertNotNull("Command bean should not be null", command);
|
||||
assertEquals("Name not bound", name, command.getName());
|
||||
assertEquals("Age not bound", age, command.getAge());
|
||||
}
|
||||
|
||||
|
||||
private static class TestController extends CancellableFormController {
|
||||
|
||||
public TestController() {
|
||||
setCommandClass(TestBean.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestValidator implements Validator {
|
||||
|
||||
private boolean invoked = false;
|
||||
|
||||
@Override
|
||||
public boolean supports(Class clazz) {
|
||||
return TestBean.class.isAssignableFrom(clazz);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
this.invoked = true;
|
||||
TestBean tb = (TestBean) target;
|
||||
if (tb.getAge() < 25) {
|
||||
errors.rejectValue("age", "TOO_YOUNG");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,522 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.servlet.mvc;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.beans.propertyeditors.CustomNumberEditor;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockHttpSession;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
*/
|
||||
public class CommandControllerTests extends TestCase {
|
||||
|
||||
public void testNoArgsNoErrors() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(request.getServletPath()));
|
||||
TestBean person = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("command and errors non null", person != null && errors != null);
|
||||
assertTrue("no errors", !errors.hasErrors());
|
||||
assertTrue("Correct caching", response.getHeader("Cache-Control") == null);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires") == null);
|
||||
}
|
||||
|
||||
public void test2ArgsNoErrors() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
String name = "Rod";
|
||||
int age = 32;
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(request.getServletPath()));
|
||||
TestBean person = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("command and errors non null", person != null && errors != null);
|
||||
assertTrue("no errors", !errors.hasErrors());
|
||||
assertTrue("command name bound ok", person.getName().equals(name));
|
||||
assertTrue("command age bound ok", person.getAge() == age);
|
||||
}
|
||||
|
||||
public void test2Args1Mismatch() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
String name = "Rod";
|
||||
String age = "32x";
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(request.getServletPath()));
|
||||
TestBean person = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("command and errors non null", person != null && errors != null);
|
||||
assertTrue("has 1 errors", errors.getErrorCount() == 1);
|
||||
assertTrue("command name bound ok", person.getName().equals(name));
|
||||
assertTrue("command age default", person.getAge() == new TestBean().getAge());
|
||||
assertTrue("has error on field age", errors.hasFieldErrors("age"));
|
||||
FieldError fe = errors.getFieldError("age");
|
||||
assertTrue("Saved invalid value", fe.getRejectedValue().equals(age));
|
||||
assertTrue("Correct field", fe.getField().equals("age"));
|
||||
}
|
||||
|
||||
public void testSupportedMethods() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setSupportedMethods(new String[] {"POST"});
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
try {
|
||||
mc.handleRequest(request, response);
|
||||
fail("Should have thrown ServletException");
|
||||
}
|
||||
catch (ServletException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testRequireSessionWithoutSession() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setRequireSession(true);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
try {
|
||||
mc.handleRequest(request, response);
|
||||
fail("Should have thrown ServletException");
|
||||
}
|
||||
catch (ServletException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testRequireSessionWithSession() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setRequireSession(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
request.setSession(new MockHttpSession(null));
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
}
|
||||
|
||||
public void testNoCaching() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setCacheSeconds(0);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires").equals("1"));
|
||||
List cacheControl = response.getHeaders("Cache-Control");
|
||||
assertTrue("Correct cache control", cacheControl.contains("no-cache"));
|
||||
assertTrue("Correct cache control", cacheControl.contains("no-store"));
|
||||
}
|
||||
|
||||
public void testNoCachingWithoutExpires() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setCacheSeconds(0);
|
||||
mc.setUseExpiresHeader(false);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("No expires header", response.getHeader("Expires") == null);
|
||||
List cacheControl = response.getHeaders("Cache-Control");
|
||||
assertTrue("Correct cache control", cacheControl.contains("no-cache"));
|
||||
assertTrue("Correct cache control", cacheControl.contains("no-store"));
|
||||
}
|
||||
|
||||
public void testNoCachingWithoutCacheControl() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setCacheSeconds(0);
|
||||
mc.setUseCacheControlHeader(false);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires").equals("1"));
|
||||
assertTrue("No cache control", response.getHeader("Cache-Control") == null);
|
||||
}
|
||||
|
||||
public void testCaching() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setCacheSeconds(10);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires") != null);
|
||||
assertTrue("Correct cache control", response.getHeader("Cache-Control").equals("max-age=10"));
|
||||
}
|
||||
|
||||
public void testCachingWithoutExpires() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setCacheSeconds(10);
|
||||
mc.setUseExpiresHeader(false);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("No expires header", response.getHeader("Expires") == null);
|
||||
assertTrue("Correct cache control", response.getHeader("Cache-Control").equals("max-age=10"));
|
||||
}
|
||||
|
||||
public void testCachingWithoutCacheControl() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
mc.setCacheSeconds(10);
|
||||
mc.setUseCacheControlHeader(false);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires") != null);
|
||||
assertTrue("No cache control", response.getHeader("Cache-Control") == null);
|
||||
}
|
||||
|
||||
public void testCachingWithLastModified() throws Exception {
|
||||
class LastModifiedTestController extends TestController implements LastModified {
|
||||
@Override
|
||||
public long getLastModified(HttpServletRequest request) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
LastModifiedTestController mc = new LastModifiedTestController();
|
||||
mc.setCacheSeconds(10);
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires") != null);
|
||||
assertTrue("Correct cache control", response.getHeader("Cache-Control").equals("max-age=10, must-revalidate"));
|
||||
}
|
||||
|
||||
public void testCachingWithCustomCacheForSecondsCall() throws Exception {
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
|
||||
cacheForSeconds(response, 5);
|
||||
return super.handle(request, response, command, errors);
|
||||
}
|
||||
};
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires") != null);
|
||||
assertTrue("Correct cache control", response.getHeader("Cache-Control").equals("max-age=5"));
|
||||
}
|
||||
|
||||
public void testCachingWithCustomApplyCacheSecondsCall1() throws Exception {
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
|
||||
applyCacheSeconds(response, 5);
|
||||
return super.handle(request, response, command, errors);
|
||||
}
|
||||
};
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires") != null);
|
||||
assertTrue("Correct cache control", response.getHeader("Cache-Control").equals("max-age=5"));
|
||||
}
|
||||
|
||||
public void testCachingWithCustomApplyCacheSecondsCall2() throws Exception {
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
|
||||
applyCacheSeconds(response, 0);
|
||||
return super.handle(request, response, command, errors);
|
||||
}
|
||||
};
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("Correct expires header", response.getHeader("Expires").equals("1"));
|
||||
List cacheControl = response.getHeaders("Cache-Control");
|
||||
assertTrue("Correct cache control", cacheControl.contains("no-cache"));
|
||||
assertTrue("Correct cache control", cacheControl.contains("no-store"));
|
||||
}
|
||||
|
||||
public void testCachingWithCustomApplyCacheSecondsCall3() throws Exception {
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
|
||||
applyCacheSeconds(response, -1);
|
||||
return super.handle(request, response, command, errors);
|
||||
}
|
||||
};
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/ok.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
mc.handleRequest(request, response);
|
||||
assertTrue("No expires header", response.getHeader("Expires") == null);
|
||||
assertTrue("No cache control", response.getHeader("Cache-Control") == null);
|
||||
}
|
||||
|
||||
public void testCustomDateEditorWithAllowEmpty() throws Exception {
|
||||
final DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.GERMAN);
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class, new CustomDateEditor(df, true));
|
||||
}
|
||||
};
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("date", "1.5.2003");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("No field error", !errors.hasFieldErrors("date"));
|
||||
assertTrue("Correct date property", df.parse("1.5.2003").equals(tb.getDate()));
|
||||
assertTrue("Correct date value", "01.05.2003".equals(errors.getFieldValue("date")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("date", "");
|
||||
response = new MockHttpServletResponse();
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("No field error", !errors.hasFieldErrors("date"));
|
||||
assertTrue("Correct date property", tb.getDate() == null);
|
||||
assertTrue("Correct date value", "".equals(errors.getFieldValue("date")));
|
||||
}
|
||||
|
||||
public void testCustomDateEditorWithoutAllowEmpty() throws Exception {
|
||||
final DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.GERMAN);
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class, new CustomDateEditor(df, false));
|
||||
}
|
||||
};
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("date", "1.5.2003");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("No field error", !errors.hasFieldErrors("date"));
|
||||
assertTrue("Correct date property", df.parse("1.5.2003").equals(tb.getDate()));
|
||||
assertTrue("Correct date value", "01.05.2003".equals(errors.getFieldValue("date")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("date", "");
|
||||
response = new MockHttpServletResponse();
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Has field error", errors.hasFieldErrors("date"));
|
||||
assertTrue("Correct date property", tb.getDate() != null);
|
||||
assertTrue("Correct date value", errors.getFieldValue("date") != null);
|
||||
}
|
||||
|
||||
public void testCustomNumberEditorWithAllowEmpty() throws Exception {
|
||||
final NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN);
|
||||
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, nf, true));
|
||||
}
|
||||
};
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("myFloat", "5,1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("No field error", !errors.hasFieldErrors("myFloat"));
|
||||
assertTrue("Correct float property", (new Float(5.1)).equals(tb.getMyFloat()));
|
||||
assertTrue("Correct float value", "5,1".equals(errors.getFieldValue("myFloat")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("myFloat", "");
|
||||
response = new MockHttpServletResponse();
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("No field error", !errors.hasFieldErrors("myFloat"));
|
||||
assertTrue("Correct float property", tb.getMyFloat() == null);
|
||||
assertTrue("Correct float value", "".equals(errors.getFieldValue("myFloat")));
|
||||
}
|
||||
|
||||
public void testCustomNumberEditorWithoutAllowEmpty() throws Exception {
|
||||
final NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN);
|
||||
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
|
||||
binder.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, nf, false));
|
||||
}
|
||||
};
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("myFloat", "5,1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("No field error", !errors.hasFieldErrors("myFloat"));
|
||||
assertTrue("Correct float property", (new Float(5.1)).equals(tb.getMyFloat()));
|
||||
assertTrue("Correct float value", "5,1".equals(errors.getFieldValue("myFloat")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("myFloat", "");
|
||||
response = new MockHttpServletResponse();
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Has field error", errors.hasFieldErrors("myFloat"));
|
||||
assertTrue("Correct float property", tb.getMyFloat() != null);
|
||||
assertTrue("Correct float value", errors.getFieldValue("myFloat") != null);
|
||||
}
|
||||
|
||||
public void testResetEmptyStringField() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_name", "visible");
|
||||
request.addParameter("name", "test");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Correct name property", "test".equals(tb.getName()));
|
||||
assertTrue("Correct name value", "test".equals(errors.getFieldValue("name")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_name", "visible");
|
||||
request.addParameter("_someNonExistingField", "visible");
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Correct name property", tb.getName() == null);
|
||||
assertTrue("Correct name value", errors.getFieldValue("name") == null);
|
||||
}
|
||||
|
||||
public void testResetEmptyBooleanField() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_postProcessed", "visible");
|
||||
request.addParameter("postProcessed", "true");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Correct postProcessed property", tb.isPostProcessed());
|
||||
assertTrue("Correct postProcessed value", Boolean.TRUE.equals(errors.getFieldValue("postProcessed")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_postProcessed", "visible");
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Correct postProcessed property", !tb.isPostProcessed());
|
||||
assertTrue("Correct postProcessed value", Boolean.FALSE.equals(errors.getFieldValue("postProcessed")));
|
||||
}
|
||||
|
||||
public void testResetEmptyStringArrayField() throws Exception {
|
||||
TestController mc = new TestController();
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_stringArray", "visible");
|
||||
request.addParameter("stringArray", "value1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
assertTrue("Correct stringArray property",
|
||||
tb.getStringArray() != null && "value1".equals(tb.getStringArray()[0]));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_stringArray", "visible");
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
assertTrue("Correct stringArray property", tb.getStringArray() != null && tb.getStringArray().length == 0);
|
||||
}
|
||||
|
||||
public void testResetEmptyFieldsTurnedOff() throws Exception {
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected Object getCommand(HttpServletRequest request) throws Exception {
|
||||
return new TestBean("original", 99);
|
||||
}
|
||||
@Override
|
||||
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
|
||||
binder.setFieldMarkerPrefix(null);
|
||||
}
|
||||
};
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_name", "visible");
|
||||
request.addParameter("name", "test");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
TestBean tb = (TestBean) mv.getModel().get("command");
|
||||
Errors errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Correct name property", "test".equals(tb.getName()));
|
||||
assertTrue("Correct name value", "test".equals(errors.getFieldValue("name")));
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("_name", "true");
|
||||
mv = mc.handleRequest(request, response);
|
||||
tb = (TestBean) mv.getModel().get("command");
|
||||
errors = (Errors) mv.getModel().get("errors");
|
||||
assertTrue("Correct name property", "original".equals(tb.getName()));
|
||||
assertTrue("Correct name value", "original".equals(errors.getFieldValue("name")));
|
||||
}
|
||||
|
||||
|
||||
private static class TestController extends AbstractCommandController {
|
||||
|
||||
private TestController() {
|
||||
super(TestBean.class, "person");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
|
||||
Map m = new HashMap();
|
||||
assertTrue("Command not null", command != null);
|
||||
assertTrue("errors not null", errors != null);
|
||||
m.put("errors", errors);
|
||||
m.put("command", command);
|
||||
return new ModelAndView(request.getServletPath(), m);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,661 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.servlet.mvc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.tests.sample.beans.IndexedTestBean;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@Deprecated
|
||||
public class FormControllerTests extends TestCase {
|
||||
|
||||
public void testReferenceDataOnForm() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
RefController mc = new RefController();
|
||||
mc.setFormView(formView);
|
||||
mc.setCommandName("tb");
|
||||
mc.setSuccessView(successView);
|
||||
mc.refDataCount = 0;
|
||||
|
||||
HttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(formView));
|
||||
|
||||
assertTrue("refDataCount == 1", mc.refDataCount == 1);
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
|
||||
int[] numbers = (int[]) mv.getModel().get(mc.NUMBERS_ATT);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("numbers is non null", numbers != null);
|
||||
}
|
||||
|
||||
public void testReferenceDataOnResubmit() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
RefController mc = new RefController();
|
||||
mc.setFormView(formView);
|
||||
mc.setCommandName("tb");
|
||||
mc.setSuccessView(successView);
|
||||
mc.refDataCount = 0;
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
request.addParameter("age", "23x");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(formView));
|
||||
assertTrue("has errors", mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName()) != null);
|
||||
|
||||
assertTrue("refDataCount == 1", mc.refDataCount == 1);
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
|
||||
int[] numbers = (int[]) mv.getModel().get(mc.NUMBERS_ATT);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("numbers is non null", numbers != null);
|
||||
}
|
||||
|
||||
public void testForm() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("name", "rod");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(formView));
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("bean age default ok", person.getAge() == TestController.DEFAULT_AGE);
|
||||
assertTrue("name not set", person.getName() == null);
|
||||
}
|
||||
|
||||
public void testBindOnNewForm() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
final Integer someNumber = new Integer(12);
|
||||
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected void onBindOnNewForm(HttpServletRequest request, Object command) throws Exception {
|
||||
TestBean testBean = (TestBean)command;
|
||||
testBean.setSomeNumber(new Integer(12));
|
||||
}
|
||||
};
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
mc.setBindOnNewForm(true);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
request.addParameter("name", "rod");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertEquals("returned correct view name", formView, mv.getViewName());
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
assertNotNull("model is non null", person);
|
||||
assertEquals("bean age default ok", person.getAge(), TestController.DEFAULT_AGE);
|
||||
assertEquals("name set", "rod", person.getName());
|
||||
assertEquals("Property [someNumber] not set in onBindOnNewForm callback", someNumber, person.getSomeNumber());
|
||||
}
|
||||
|
||||
public void testSubmitWithoutErrors() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
|
||||
String name = "Rod";
|
||||
int age = 32;
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
|
||||
assertEquals("returned correct view name", successView, mv.getViewName());
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("errors is non null", errors != null);
|
||||
assertTrue("bean name bound ok", person.getName().equals(name));
|
||||
assertTrue("bean age bound ok", person.getAge() == age);
|
||||
}
|
||||
|
||||
public void testSubmitWithoutValidation() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
|
||||
String name = "Rod";
|
||||
int age = 32;
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
request.addParameter("formChange", "true");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
|
||||
assertEquals("returned correct view name", formView, mv.getViewName());
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("errors is non null", errors != null);
|
||||
assertTrue("bean name bound ok", person.getName().equals(name));
|
||||
assertTrue("bean age bound ok", person.getAge() == age);
|
||||
}
|
||||
|
||||
public void testSubmitWithCustomOnSubmit() throws Exception {
|
||||
String formView = "f";
|
||||
|
||||
TestControllerWithCustomOnSubmit mc = new TestControllerWithCustomOnSubmit();
|
||||
mc.setFormView(formView);
|
||||
|
||||
String name = "Rod";
|
||||
int age = 32;
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertEquals("returned correct view name", "mySuccess", mv.getViewName());
|
||||
assertTrue("no model", mv.getModel().isEmpty());
|
||||
}
|
||||
|
||||
public void testSubmitPassedByValidator() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
mc.setValidator(new TestValidator());
|
||||
|
||||
String name = "Roderick Johnson";
|
||||
int age = 32;
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name: expected '" + successView + "', not '" + mv.getViewName() + "'",
|
||||
mv.getViewName().equals(successView));
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("bean name bound ok", person.getName().equals(name));
|
||||
assertTrue("bean age bound ok", person.getAge() == age);
|
||||
}
|
||||
|
||||
public void testSubmit1Mismatch() throws Exception {
|
||||
String formView = "fred";
|
||||
String successView = "tony";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
|
||||
String name = "Rod";
|
||||
String age = "xxx";
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
|
||||
mv.getViewName().equals(formView));
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("bean name bound ok", person.getName().equals(name));
|
||||
assertTrue("bean age is default", person.getAge() == TestController.DEFAULT_AGE);
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
|
||||
assertTrue("errors returned in model", errors != null);
|
||||
assertTrue("One error", errors.getErrorCount() == 1);
|
||||
FieldError fe = errors.getFieldError("age");
|
||||
assertTrue("Saved invalid value", fe.getRejectedValue().equals(age));
|
||||
assertTrue("Correct field", fe.getField().equals("age"));
|
||||
}
|
||||
|
||||
public void testSubmit1Mismatch1Invalidated() throws Exception {
|
||||
String formView = "fred";
|
||||
String successView = "tony";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
mc.setValidators(new Validator[] {new TestValidator(), new TestValidator2()});
|
||||
|
||||
String name = "Rod";
|
||||
// will be rejected
|
||||
String age = "xxx";
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
|
||||
request.addParameter("name", name);
|
||||
request.addParameter("age", "" + age);
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
|
||||
mv.getViewName().equals(formView));
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
|
||||
// yes, but it was rejected after binding by the validator
|
||||
assertTrue("bean name bound ok", person.getName().equals(name));
|
||||
assertTrue("bean age is default", person.getAge() == TestController.DEFAULT_AGE);
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
|
||||
assertTrue("errors returned in model", errors != null);
|
||||
assertTrue("3 errors", errors.getErrorCount() == 3);
|
||||
FieldError fe = errors.getFieldError("age");
|
||||
assertTrue("Saved invalid value", fe.getRejectedValue().equals(age));
|
||||
assertTrue("Correct field", fe.getField().equals("age"));
|
||||
|
||||
// raised by first validator
|
||||
fe = errors.getFieldError("name");
|
||||
assertTrue("Saved invalid value", fe.getRejectedValue().equals(name));
|
||||
assertTrue("Correct field", fe.getField().equals("name"));
|
||||
assertTrue("Correct validation code: expected '" +TestValidator.TOOSHORT + "', not '" +
|
||||
fe.getCode() + "'", fe.getCode().equals(TestValidator.TOOSHORT));
|
||||
|
||||
// raised by second validator
|
||||
ObjectError oe = errors.getGlobalError();
|
||||
assertEquals("test", oe.getCode());
|
||||
assertEquals("testmessage", oe.getDefaultMessage());
|
||||
}
|
||||
|
||||
public void testSessionController() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
mc.setSessionForm(true);
|
||||
|
||||
// first request: GET form
|
||||
HttpServletRequest request1 = new MockHttpServletRequest("GET", "/welcome.html");
|
||||
HttpServletResponse response1 = new MockHttpServletResponse();
|
||||
ModelAndView mv1 = mc.handleRequest(request1, response1);
|
||||
assertTrue("returned correct view name", mv1.getViewName().equals(formView));
|
||||
TestBean person = (TestBean) mv1.getModel().get(TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("Bean age default ok", person.getAge() == TestController.DEFAULT_AGE);
|
||||
|
||||
// second request, using same session: POST submit
|
||||
MockHttpServletRequest request2 = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
request2.setSession(request1.getSession(false));
|
||||
HttpServletResponse response2 = new MockHttpServletResponse();
|
||||
ModelAndView mv2 = mc.handleRequest(request2, response2);
|
||||
assertTrue("returned correct view name", mv2.getViewName().equals(successView));
|
||||
TestBean person2 = (TestBean) mv2.getModel().get(TestController.BEAN_NAME);
|
||||
assertTrue("model is same object", person == person2);
|
||||
}
|
||||
|
||||
public void testDefaultInvalidSubmit() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController();
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
mc.setSessionForm(true);
|
||||
|
||||
// invalid request: POST submit
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name", mv.getViewName().equals(successView));
|
||||
TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
|
||||
assertTrue("model is non null", person != null);
|
||||
}
|
||||
|
||||
public void testSpecialInvalidSubmit() throws Exception {
|
||||
String formView = "f";
|
||||
String successView = "s";
|
||||
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
throw new ServletException("invalid submit");
|
||||
}
|
||||
};
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
mc.setSessionForm(true);
|
||||
|
||||
// invalid request: POST submit
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
try {
|
||||
mc.handleRequest(request, response);
|
||||
fail("Should have thrown ServletException");
|
||||
}
|
||||
catch (ServletException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testSubmitWithIndexedProperties() throws Exception {
|
||||
String formView = "fred";
|
||||
String successView = "tony";
|
||||
|
||||
SimpleFormController mc = new SimpleFormController();
|
||||
mc.setCommandClass(IndexedTestBean.class);
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
|
||||
request.addParameter("array[0].name", "name3");
|
||||
request.addParameter("array[1].age", "name2");
|
||||
request.addParameter("list[0].name", "name1");
|
||||
request.addParameter("list[1].age", "name0");
|
||||
request.addParameter("list[2]", "listobj");
|
||||
request.addParameter("map[key1]", "mapobj1");
|
||||
request.addParameter("map[key3]", "mapobj2");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
|
||||
mv.getViewName().equals(formView));
|
||||
|
||||
IndexedTestBean bean = (IndexedTestBean) mv.getModel().get(mc.getCommandName());
|
||||
assertTrue("model is non null", bean != null);
|
||||
assertEquals("name3", bean.getArray()[0].getName());
|
||||
assertEquals("name1", ((TestBean) bean.getList().get(0)).getName());
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
|
||||
assertTrue("errors returned in model", errors != null);
|
||||
assertTrue("2 errors", errors.getErrorCount() == 2);
|
||||
FieldError fe1 = errors.getFieldError("array[1].age");
|
||||
assertTrue("Saved invalid value", fe1.getRejectedValue().equals("name2"));
|
||||
assertTrue("Correct field", fe1.getField().equals("array[1].age"));
|
||||
FieldError fe2 = errors.getFieldError("list[1].age");
|
||||
assertTrue("Saved invalid value", fe2.getRejectedValue().equals("name0"));
|
||||
assertTrue("Correct field", fe2.getField().equals("list[1].age"));
|
||||
|
||||
assertEquals("listobj", bean.getList().get(2));
|
||||
assertEquals("mapobj1", bean.getMap().get("key1"));
|
||||
assertEquals("mapobj2", bean.getMap().get("key3"));
|
||||
}
|
||||
|
||||
public void testFormChangeRequest() throws Exception {
|
||||
String formView = "fred";
|
||||
String successView = "tony";
|
||||
final Float myFloat = new Float("123.45");
|
||||
|
||||
TestController mc = new TestController() {
|
||||
@Override
|
||||
protected boolean isFormChangeRequest(HttpServletRequest request) {
|
||||
return (request.getParameter("formChange") != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFormChange(HttpServletRequest request, HttpServletResponse response, Object command) {
|
||||
assertNotNull("Command should not be null", command);
|
||||
assertEquals("Incorrect command class", TestBean.class, command.getClass());
|
||||
((TestBean)command).setMyFloat(myFloat);
|
||||
}
|
||||
};
|
||||
mc.setFormView(formView);
|
||||
mc.setSuccessView(successView);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
|
||||
request.addParameter("name", "Rod");
|
||||
request.addParameter("age", "99");
|
||||
request.addParameter("formChange", "true");
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = mc.handleRequest(request, response);
|
||||
assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
|
||||
mv.getViewName().equals(formView));
|
||||
|
||||
TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
|
||||
assertTrue("model is non null", person != null);
|
||||
assertTrue("bean name bound ok", person.getName().equals("Rod"));
|
||||
assertTrue("bean age is 99", person.getAge() == 99);
|
||||
assertEquals("Command property myFloat not updated in onFormChange", myFloat, person.getMyFloat());
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
|
||||
assertTrue("errors returned in model", errors != null);
|
||||
assertTrue("No errors", errors.getErrorCount() == 0);
|
||||
}
|
||||
|
||||
public void testFormBindingOfNestedBooleans() throws Exception {
|
||||
BooleanBindingFormController controller = new BooleanBindingFormController();
|
||||
controller.setCommandClass(ListForm.class);
|
||||
MockHttpServletRequest req = new MockHttpServletRequest("POST", "/myurl");
|
||||
MockHttpServletResponse res = new MockHttpServletResponse();
|
||||
req.addParameter("oks[0].ok", "true");
|
||||
ModelAndView mav = controller.handleRequest(req, res);
|
||||
ListForm form = (ListForm) mav.getModelMap().get("command");
|
||||
Boolean ok = ((Ok) form.getOks().get(0)).getOk();
|
||||
assertNotNull(ok);
|
||||
}
|
||||
|
||||
public void testFormControllerInWebApplicationContext() {
|
||||
StaticWebApplicationContext ctx = new StaticWebApplicationContext();
|
||||
ctx.setServletContext(new MockServletContext());
|
||||
RefController mc = new RefController();
|
||||
mc.setApplicationContext(ctx);
|
||||
try {
|
||||
mc.invokeWebSpecificStuff();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
fail("Shouldn't have thrown exception: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testFormControllerInNonWebApplicationContext() {
|
||||
StaticApplicationContext ctx = new StaticApplicationContext();
|
||||
RefController mc = new RefController();
|
||||
mc.setApplicationContext(ctx);
|
||||
try {
|
||||
mc.invokeWebSpecificStuff();
|
||||
fail("Should have thrown IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestValidator implements Validator {
|
||||
|
||||
public static String TOOSHORT = "tooshort";
|
||||
|
||||
@Override
|
||||
public boolean supports(Class clazz) { return true; }
|
||||
|
||||
@Override
|
||||
public void validate(Object obj, Errors errors) {
|
||||
TestBean tb = (TestBean) obj;
|
||||
if (tb.getName() == null || "".equals(tb.getName()))
|
||||
errors.rejectValue("name", "needname", null, "need name");
|
||||
else if (tb.getName().length() < 5)
|
||||
errors.rejectValue("name", TOOSHORT, null, "need full name");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestValidator2 implements Validator {
|
||||
|
||||
public static String TOOSHORT = "tooshort";
|
||||
|
||||
@Override
|
||||
public boolean supports(Class clazz) { return true; }
|
||||
|
||||
@Override
|
||||
public void validate(Object obj, Errors errors) {
|
||||
errors.reject("test", "testmessage");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestController extends SimpleFormController {
|
||||
|
||||
public static String BEAN_NAME = "person";
|
||||
|
||||
public static int DEFAULT_AGE = 52;
|
||||
|
||||
public TestController() {
|
||||
setCommandClass(TestBean.class);
|
||||
setCommandName(BEAN_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
|
||||
TestBean person = new TestBean();
|
||||
person.setAge(DEFAULT_AGE);
|
||||
return person;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFormChangeRequest(HttpServletRequest request) {
|
||||
return (request.getParameter("formChange") != null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestControllerWithCustomOnSubmit extends TestController {
|
||||
|
||||
@Override
|
||||
protected ModelAndView onSubmit(Object command) throws Exception {
|
||||
return new ModelAndView("mySuccess");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class RefController extends SimpleFormController {
|
||||
|
||||
final String NUMBERS_ATT = "NUMBERS";
|
||||
|
||||
static final int[] NUMBERS = { 1, 2, 3, 4 };
|
||||
|
||||
int refDataCount;
|
||||
|
||||
public RefController() {
|
||||
setCommandClass(TestBean.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map referenceData(HttpServletRequest request) {
|
||||
++refDataCount;
|
||||
Map m = new HashMap();
|
||||
m.put(NUMBERS_ATT, NUMBERS);
|
||||
return m;
|
||||
}
|
||||
|
||||
public void invokeWebSpecificStuff() {
|
||||
getTempDir();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class BooleanBindingFormController extends AbstractFormController {
|
||||
|
||||
@Override
|
||||
protected ModelAndView processFormSubmission
|
||||
(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
|
||||
ModelAndView mav = new ModelAndView();
|
||||
mav.addObject("command", command);
|
||||
return mav;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView showForm(
|
||||
HttpServletRequest req, HttpServletResponse resp, BindException err) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Ok {
|
||||
|
||||
private Boolean ok;
|
||||
|
||||
public Boolean getOk () {
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void setOk(Boolean ok) {
|
||||
this.ok = ok;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ListForm {
|
||||
|
||||
private List oks = new ArrayList();
|
||||
|
||||
public ListForm () {
|
||||
for (int index = 0; index < 5; index++) {
|
||||
Ok ok = new Ok();
|
||||
oks.add(ok);
|
||||
}
|
||||
}
|
||||
|
||||
public List getOks() {
|
||||
return oks;
|
||||
}
|
||||
|
||||
public void setOks(List oks) {
|
||||
this.oks = oks;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,438 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.servlet.mvc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @since 29.04.2003
|
||||
*/
|
||||
@Deprecated
|
||||
public class WizardFormControllerTests extends TestCase {
|
||||
|
||||
public void testNoDirtyPageChange() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController();
|
||||
wizard.setAllowDirtyBack(false);
|
||||
wizard.setAllowDirtyForward(false);
|
||||
wizard.setPageAttribute("currentPage");
|
||||
|
||||
assertTrue(wizard.getFormSessionAttributeName() != wizard.getPageSessionAttributeName());
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, "currentPage");
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, null, 0, null, 0, "currentPage");
|
||||
// not allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_PAGE, "0");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1.x", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1.y", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("date", "not a date");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1.y", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// not allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_PAGE, "1");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 0, "myname", 32, "currentPage");
|
||||
// age set -> now allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, null);
|
||||
}
|
||||
|
||||
public void testCustomSessionAttributes() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController() {
|
||||
@Override
|
||||
protected String getFormSessionAttributeName() {
|
||||
return "myFormAttr";
|
||||
}
|
||||
@Override
|
||||
protected String getPageSessionAttributeName() {
|
||||
return "myPageAttr";
|
||||
}
|
||||
};
|
||||
wizard.setAllowDirtyBack(false);
|
||||
wizard.setAllowDirtyForward(false);
|
||||
wizard.setPageAttribute("currentPage");
|
||||
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, "currentPage");
|
||||
assertTrue(session.getAttribute("myFormAttr") instanceof TestBean);
|
||||
assertEquals(new Integer(0), session.getAttribute("myPageAttr"));
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, null, 0, null, 0, "currentPage");
|
||||
// not allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_PAGE, "0");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, "currentPage");
|
||||
}
|
||||
|
||||
public void testCustomRequestDependentSessionAttributes() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController() {
|
||||
@Override
|
||||
protected String getFormSessionAttributeName(HttpServletRequest request) {
|
||||
return "myFormAttr" + request.getParameter("formAttr");
|
||||
}
|
||||
@Override
|
||||
protected String getPageSessionAttributeName(HttpServletRequest request) {
|
||||
return "myPageAttr" + request.getParameter("pageAttr");
|
||||
}
|
||||
};
|
||||
wizard.setAllowDirtyBack(false);
|
||||
wizard.setAllowDirtyForward(false);
|
||||
wizard.setPageAttribute("currentPage");
|
||||
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, "currentPage");
|
||||
assertTrue(session.getAttribute("myFormAttr1") instanceof TestBean);
|
||||
assertEquals(new Integer(0), session.getAttribute("myPageAttr2"));
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, null, 0, null, 0, "currentPage");
|
||||
// not allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_PAGE, "0");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, "currentPage");
|
||||
}
|
||||
|
||||
public void testDirtyBack() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController();
|
||||
wizard.setAllowDirtyBack(true);
|
||||
wizard.setAllowDirtyForward(false);
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, null);
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 0, null, 0, null);
|
||||
// not allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, null);
|
||||
// name set -> now allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 0, "myname", 0, null);
|
||||
// dirty back -> allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, null);
|
||||
// finish while dirty -> show dirty page (1)
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, null);
|
||||
// age set -> now allowed to finish
|
||||
}
|
||||
|
||||
public void testDirtyForward() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController();
|
||||
wizard.setAllowDirtyBack(false);
|
||||
wizard.setAllowDirtyForward(true);
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, null);
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, null, 0, null);
|
||||
// dirty forward -> allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 1, null, 0, null);
|
||||
// not allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 0, null, 32, null);
|
||||
// age set -> now allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, 0, null, 32, null);
|
||||
// finish while dirty -> show dirty page (0)
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH + ".x", "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, null);
|
||||
// name set -> now allowed to finish
|
||||
}
|
||||
|
||||
public void testSubmitWithoutValidation() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController();
|
||||
wizard.setAllowDirtyBack(false);
|
||||
wizard.setAllowDirtyForward(false);
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, null);
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty("formChange", "true");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, null, 0, null);
|
||||
// no validation -> allowed to go to 1
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 1, null, 0, null);
|
||||
// not allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 0, null, 32, null);
|
||||
// age set -> now allowed to go to 0
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, 0, null, 32, null);
|
||||
// finish while dirty -> show dirty page (0)
|
||||
|
||||
params.clear();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH + ".x", "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, null);
|
||||
// name set -> now allowed to finish
|
||||
}
|
||||
|
||||
public void testCancel() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController();
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, null);
|
||||
Properties params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_CANCEL, "value");
|
||||
performRequest(wizard, session, params, -2, null, 0, null);
|
||||
|
||||
assertTrue(session.getAttribute(wizard.getFormSessionAttributeName()) == null);
|
||||
assertTrue(session.getAttribute(wizard.getPageSessionAttributeName()) == null);
|
||||
|
||||
session = performRequest(wizard, null, null, 0, null, 0, null);
|
||||
params = new Properties();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_CANCEL + ".y", "value");
|
||||
performRequest(wizard, session, params, -2, null, 0, null);
|
||||
}
|
||||
|
||||
public void testInvalidSubmit() throws Exception {
|
||||
AbstractWizardFormController wizard = new TestWizardController();
|
||||
wizard.setAllowDirtyBack(false);
|
||||
wizard.setAllowDirtyForward(false);
|
||||
wizard.setPageAttribute("currentPage");
|
||||
HttpSession session = performRequest(wizard, null, null, 0, null, 0, "currentPage");
|
||||
|
||||
Properties params = new Properties();
|
||||
params.setProperty("name", "myname");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "1", "value");
|
||||
performRequest(wizard, session, params, 1, "myname", 0, "currentPage");
|
||||
|
||||
params.clear();
|
||||
params.setProperty("age", "32");
|
||||
params.setProperty(AbstractWizardFormController.PARAM_TARGET + "0", "value");
|
||||
performRequest(wizard, session, params, 0, "myname", 32, "currentPage");
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, -1, "myname", 32, null);
|
||||
|
||||
params.clear();
|
||||
params.setProperty(AbstractWizardFormController.PARAM_FINISH, "value");
|
||||
performRequest(wizard, session, params, 0, null, 0, "currentPage");
|
||||
// returned to initial page of new wizard form
|
||||
}
|
||||
|
||||
private HttpSession performRequest(
|
||||
AbstractWizardFormController wizard, HttpSession session, Properties params,
|
||||
int target, String name, int age, String pageAttr) throws Exception {
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest((params != null ? "POST" : "GET"), "/wizard");
|
||||
request.addParameter("formAttr", "1");
|
||||
request.addParameter("pageAttr", "2");
|
||||
if (params != null) {
|
||||
for (Iterator it = params.keySet().iterator(); it.hasNext();) {
|
||||
String param = (String) it.next();
|
||||
request.addParameter(param, params.getProperty(param));
|
||||
}
|
||||
}
|
||||
request.setSession(session);
|
||||
request.setAttribute("target", new Integer(target));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
ModelAndView mv = wizard.handleRequest(request, response);
|
||||
if (target >= 0) {
|
||||
assertTrue("Page " + target + " returned", ("page" + target).equals(mv.getViewName()));
|
||||
if (pageAttr != null) {
|
||||
assertTrue("Page attribute set", (new Integer(target)).equals(mv.getModel().get(pageAttr)));
|
||||
assertTrue("Correct model size", mv.getModel().size() == 3);
|
||||
}
|
||||
else {
|
||||
assertTrue("Correct model size", mv.getModel().size() == 2);
|
||||
}
|
||||
assertTrue(
|
||||
request.getSession().getAttribute(wizard.getFormSessionAttributeName(request)) instanceof TestBean);
|
||||
assertEquals(new Integer(target),
|
||||
request.getSession().getAttribute(wizard.getPageSessionAttributeName(request)));
|
||||
}
|
||||
else if (target == -1) {
|
||||
assertTrue("Success target returned", "success".equals(mv.getViewName()));
|
||||
assertTrue("Correct model size", mv.getModel().size() == 1);
|
||||
assertTrue(request.getSession().getAttribute(wizard.getFormSessionAttributeName(request)) == null);
|
||||
assertTrue(request.getSession().getAttribute(wizard.getPageSessionAttributeName(request)) == null);
|
||||
}
|
||||
else if (target == -2) {
|
||||
assertTrue("Cancel view returned", "cancel".equals(mv.getViewName()));
|
||||
assertTrue("Correct model size", mv.getModel().size() == 1);
|
||||
assertTrue(request.getSession().getAttribute(wizard.getFormSessionAttributeName(request)) == null);
|
||||
assertTrue(request.getSession().getAttribute(wizard.getPageSessionAttributeName(request)) == null);
|
||||
}
|
||||
TestBean tb = (TestBean) mv.getModel().get("tb");
|
||||
assertTrue("Has model", tb != null);
|
||||
assertTrue("Name is " + name, ObjectUtils.nullSafeEquals(name, tb.getName()));
|
||||
assertTrue("Age is " + age, tb.getAge() == age);
|
||||
Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + "tb");
|
||||
if (params != null && params.containsKey("formChange")) {
|
||||
assertNotNull(errors);
|
||||
assertFalse(errors.hasErrors());
|
||||
}
|
||||
return request.getSession(false);
|
||||
}
|
||||
|
||||
|
||||
private static class TestWizardController extends AbstractWizardFormController {
|
||||
|
||||
public TestWizardController() {
|
||||
setCommandClass(TestBean.class);
|
||||
setCommandName("tb");
|
||||
setPages(new String[] {"page0", "page1"});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map referenceData(HttpServletRequest request, int page) throws Exception {
|
||||
assertEquals(new Integer(page), request.getAttribute("target"));
|
||||
return super.referenceData(request, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean suppressValidation(HttpServletRequest request, Object command) {
|
||||
return (request.getParameter("formChange") != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validatePage(Object command, Errors errors, int page) {
|
||||
TestBean tb = (TestBean) command;
|
||||
switch (page) {
|
||||
case 0:
|
||||
if (tb.getName() == null) {
|
||||
errors.rejectValue("name", "NAME_REQUIRED", null, "Name is required");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (tb.getAge() == 0) {
|
||||
errors.rejectValue("age", "AGE_REQUIRED", null, "Age is required");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid page number");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView processFinish(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws ServletException, IOException {
|
||||
assertTrue(getCurrentPage(request) == 0 || getCurrentPage(request) == 1);
|
||||
return new ModelAndView("success", getCommandName(), command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView processCancel(
|
||||
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
|
||||
throws ServletException, IOException {
|
||||
assertTrue(getCurrentPage(request) == 0 || getCurrentPage(request) == 1);
|
||||
return new ModelAndView("cancel", getCommandName(), command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
|
|
@ -16,12 +16,17 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.mapping;
|
||||
|
||||
import org.springframework.web.servlet.mvc.SimpleFormController;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
*/
|
||||
@Deprecated
|
||||
public class BuyForm extends SimpleFormController {
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.mvc.Controller;
|
||||
|
||||
public class BuyForm implements Controller {
|
||||
|
||||
@Override
|
||||
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue