0001: /*
0002: * Copyright 2002-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.springframework.web.portlet.mvc;
0018:
0019: import java.util.Arrays;
0020: import java.util.Map;
0021:
0022: import javax.portlet.ActionRequest;
0023: import javax.portlet.ActionResponse;
0024: import javax.portlet.PortletException;
0025: import javax.portlet.PortletRequest;
0026: import javax.portlet.PortletSession;
0027: import javax.portlet.RenderRequest;
0028: import javax.portlet.RenderResponse;
0029:
0030: import org.springframework.validation.BindException;
0031: import org.springframework.validation.Errors;
0032: import org.springframework.web.portlet.ModelAndView;
0033: import org.springframework.web.portlet.bind.PortletRequestDataBinder;
0034: import org.springframework.web.portlet.handler.PortletSessionRequiredException;
0035:
0036: /**
0037: * <p>Form controller that auto-populates a form bean from the request.
0038: * This, either using a new bean instance per request, or using the same bean
0039: * when the <code>sessionForm</code> property has been set to
0040: * <code>true</code>.</p>
0041: *
0042: * <p>This class is the base class for both framework subclasses like
0043: * {@link SimpleFormController SimpleFormController} and
0044: * {@link AbstractWizardFormController AbstractWizardFormController}, and
0045: * custom form controllers you can provide yourself.</p>
0046: *
0047: * <p>Both form-input-views and after-submission-views have to be provided
0048: * programmatically. To provide those views using configuration properties,
0049: * use the {@link SimpleFormController SimpleFormController}.</p>
0050: *
0051: * <p>Subclasses need to override <code>showForm</code> to prepare the form view,
0052: * <code>processFormSubmission</code> to handle submit requests, and
0053: * <code>renderFormSubmission</code> to display the results of the submit.
0054: * For the latter two methods, binding errors like type mismatches will be
0055: * reported via the given "errors" holder. For additional custom form validation,
0056: * a validator (property inherited from BaseCommandController) can be used,
0057: * reporting via the same "errors" instance.</p>
0058: *
0059: * <p>Comparing this Controller to the Struts notion of the <code>Action</code>
0060: * shows us that with Spring, you can use any ordinary JavaBeans or database-
0061: * backed JavaBeans without having to implement a framework-specific class
0062: * (like Struts' <code>ActionForm</code>). More complex properties of JavaBeans
0063: * (Dates, Locales, but also your own application-specific or compound types)
0064: * can be represented and submitted to the controller, by using the notion of
0065: * a <code>java.beans.PropertyEditors</code>. For more information on that
0066: * subject, see the workflow of this controller and the explanation of the
0067: * {@link BaseCommandController BaseCommandController}.</p>
0068: *
0069: * This controller is different from it's servlet counterpart in that it must take
0070:
0071: * into account the two phases of a portlet request: the action phase and the render
0072: * phase. See the JSR-168 spec for more details on these two phases.
0073: * Be especially aware that the action phase is called only once, but that the
0074: * render phase will be called repeatedly by the portal -- it does this every time
0075: * the page containing the portlet is updated, even if the activity is in some other
0076: * portlet. (This is not quite true, the portal can also be told to cache the results of
0077: * the render for a period of time, but assume it is true for programming purposes.)
0078: *
0079: * <p><b><a name="workflow">Workflow
0080: * (<a href="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
0081: * <ol>
0082: * <li><b>The controller receives a request for a new form (typically a
0083: * Render Request only).</b> The render phase will proceed to display
0084: * the form as follows.</li>
0085: * <li>Call to {@link #formBackingObject formBackingObject()} which by
0086: * default, returns an instance of the commandClass that has been
0087: * configured (see the properties the superclass exposes), but can also be
0088: * overridden to e.g. retrieve an object from the database (that needs to
0089: * be modified using the form).</li>
0090: * <li>Call to {@link #initBinder initBinder()} which allows you to
0091: * register custom editors for certain fields (often properties of non-
0092: * primitive or non-String types) of the command class. This will render
0093: * appropriate Strings for those property values, e.g. locale-specific
0094: * date strings. </li>
0095: * <li>The {@link PortletRequestDataBinder PortletRequestDataBinder}
0096: * gets applied to populate the new form object with initial request parameters and the
0097: * {@link #onBindOnNewForm(RenderRequest, Object, BindException)} callback method is invoked.
0098: * (<i>only if <code>bindOnNewForm</code> is set to <code>true</code></i>)
0099: * Make sure that the initial parameters do not include the parameter that indicates a
0100: * form submission has occurred.</li>
0101: * <li>Call to {@link #showForm(RenderRequest, RenderResponse,
0102: * BindException) showForm} to return a View that should be rendered
0103: * (typically the view that renders the form). This method has to be
0104: * implemented in subclasses. </li>
0105: * <li>The showForm() implementation will call {@link #referenceData referenceData},
0106: * which you can implement to provide any relevant reference data you might need
0107: * when editing a form (e.g. a List of Locale objects you're going to let the
0108: * user select one from).</li>
0109: * <li>Model gets exposed and view gets rendered, to let the user fill in
0110: * the form.</li>
0111: * <li><b>The controller receives a form submission (typically an Action
0112: * Request).</b> To use a different way of detecting a form submission,
0113: * override the {@link #isFormSubmission isFormSubmission} method.
0114: * The action phase will proceed to process the form submission as follows.</li>
0115: * <li>If <code>sessionForm</code> is not set, {@link #formBackingObject
0116: * formBackingObject} is called to retrieve a form object. Otherwise,
0117: * the controller tries to find the command object which is already bound
0118: * in the session. If it cannot find the object, the action phase does a
0119: * call to {@link #handleInvalidSubmit handleInvalidSubmit} which - by default -
0120: * tries to create a new form object and resubmit the form. It then sets
0121: * a render parameter that will indicate to the render phase that this was
0122: * an invalid submit.</li>
0123: * <li>Still in the action phase of a valid submit, the {@link
0124: * PortletRequestDataBinder PortletRequestDataBinder} gets applied to populate
0125: * the form object with current request parameters.</li>
0126: * <li>Call to {@link #onBind onBind(PortletRequest, Object, Errors)}
0127: * which allows you to do custom processing after binding but before
0128: * validation (e.g. to manually bind request parameters to bean
0129: * properties, to be seen by the Validator).</li>
0130: * <li>If <code>validateOnBinding</code> is set, a registered Validator
0131: * will be invoked. The Validator will check the form object properties,
0132: * and register corresponding errors via the given {@link Errors Errors}
0133: * object.</li>
0134: * <li>Call to {@link #onBindAndValidate onBindAndValidate} which allows
0135: * you to do custom processing after binding and validation (e.g. to
0136: * manually bind request parameters, and to validate them outside a
0137: * Validator).</li>
0138: * <li>Call to {@link #processFormSubmission processFormSubmission}
0139: * to process the submission, with or without binding errors.
0140: * This method has to be implemented in subclasses and will be called
0141: * only once per form submission.</li>
0142: * <li>The portal will then call the render phase of processing the form
0143: * submission. This phase will be called repeatedly by the portal every
0144: * time the page is refreshed. All processing here should take this into
0145: * account. Any one-time-only actions (such as modifying a database) must
0146: * be done in the action phase.</li>
0147: * <li>If the action phase indicated this is an invalid submit, the render
0148: * phase calls {@link #renderInvalidSubmit renderInvalidSubmit} which –
0149: * also by default – will render the results of the resubmitted
0150: * form. Be sure to override both <code>handleInvalidSubmit</code> and
0151: * <code>renderInvalidSubmit</code> if you want to change this overall
0152: * behavior.</li>
0153: * <li>Finally, call {@link #renderFormSubmission renderFormSubmission} to
0154: * render the results of the submission, with or without binding errors.
0155: * This method has to be implemented in subclasses and will be called
0156: * repeatedly by the portal.</li>
0157: * </ol>
0158: * </p>
0159: *
0160: * <p>In session form mode, a submission without an existing form object in the
0161: * session is considered invalid, like in the case of a resubmit/reload by the browser.
0162: * The {@link #handleInvalidSubmit handleInvalidSubmit} /
0163: * {@link #renderInvalidSubmit renderInvalidSubmit} methods are invoked then,
0164: * by default trying to resubmit. This can be overridden in subclasses to show
0165: * corresponding messages or to redirect to a new form, in order to avoid duplicate
0166: * submissions. The form object in the session can be considered a transaction token
0167: * in that case.</p>
0168: *
0169: * Make sure that any URLs that take you to your form controller are Render URLs, so
0170: * that it will not try to treat the initial call as a form submission. If you use
0171: * Action URLs to link to your controller, you will need to override the
0172: * {@link #isFormSubmission isFormSubmission} method to use a different mechanism for
0173: * determining whether a form has been submitted. Make sure this method will work for
0174: * both the ActionRequest and the RenderRequest objects.
0175: *
0176: * <p>Note that views should never retrieve form beans from the session but always
0177: * from the request, as prepared by the form controller. Remember that some view
0178: * technologies like Velocity cannot even access a HTTP session.</p>
0179: *
0180: * <p><b><a name="config">Exposed configuration properties</a>
0181: * (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
0182: * <table border="1">
0183: * <tr>
0184: * <td><b>name</b></td>
0185: * <td><b>default</b></td>
0186: * <td><b>description</b></td>
0187: * </tr>
0188: * <tr>
0189: * <td>bindOnNewForm</td>
0190: * <td>false</td>
0191: * <td>Indicates whether to bind portlet request parameters when
0192: * creating a new form. Otherwise, the parameters will only be
0193: * bound on form submission attempts.</td>
0194: * </tr>
0195: * <tr>
0196: * <td>sessionForm</td>
0197: * <td>false</td>
0198: * <td>Indicates whether the form object should be kept in the session
0199: * when a user asks for a new form. This allows you e.g. to retrieve
0200: * an object from the database, let the user edit it, and then persist
0201: * it again. Otherwise, a new command object will be created for each
0202: * request (even when showing the form again after validation errors).</td>
0203: * </tr>
0204: * <tr>
0205: * <td>redirectAction</td>
0206: * <td>false</td>
0207: * <td>Specifies whether <code>processFormSubmission</code> is expected to call
0208: * {@link ActionResponse#sendRedirect ActionResponse.sendRedirect}.
0209: * This is important because some methods may not be called before
0210: * {@link ActionResponse#sendRedirect ActionResponse.sendRedirect} (e.g.
0211: * {@link ActionResponse#setRenderParameter ActionResponse.setRenderParameter}).
0212: * Setting this flag will prevent AbstractFormController from setting render
0213: * parameters that it normally needs for the render phase.
0214: * If this is set true and <code>sendRedirect</code> is not called, then
0215: * <code>processFormSubmission</code> must call
0216: * {@link #setFormSubmit setFormSubmit}.
0217: * Otherwise, the render phase will not realize the form was submitted
0218: * and will simply display a new blank form.</td>
0219: * </tr>
0220: * <tr>
0221: * <td>renderParameters</td>
0222: * <td>null</td>
0223: * <td>An array of parameters that will be passed forward from the action
0224: * phase to the render phase if the form needs to be displayed
0225: * again. These can also be passed forward explicitly by calling
0226: * the <code>passRenderParameters</code> method from any action
0227: * phase method. Abstract descendants of this controller should follow
0228: * similar behavior. If there are parameters you need in
0229: * <code>renderFormSubmission</code>, then you need to pass those
0230: * forward from <code>processFormSubmission</code>. If you override the
0231: * default behavior of invalid submits and you set sessionForm to true,
0232: * then you probably will not need to set this because your parameters
0233: * are only going to be needed on the first request.</td>
0234: * </tr>
0235: * </table>
0236: * </p>
0237: *
0238: * <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
0239: *
0240: * @author John A. Lewis
0241: * @author Juergen Hoeller
0242: * @author Alef Arendsen
0243: * @author Rob Harrop
0244: * @since 2.0
0245: * @see #showForm(RenderRequest, RenderResponse, BindException)
0246: * @see SimpleFormController
0247: * @see AbstractWizardFormController
0248: */
0249: public abstract class AbstractFormController extends
0250: BaseCommandController {
0251:
0252: /**
0253: * These render parameters are used to indicate forward to the render phase
0254: * if the form was submitted and if the submission was invalid.
0255: */
0256: private static final String FORM_SUBMISSION_PARAMETER = "form-submit";
0257:
0258: private static final String INVALID_SUBMISSION_PARAMETER = "invalid-submit";
0259:
0260: private static final String TRUE = Boolean.TRUE.toString();
0261:
0262: private boolean bindOnNewForm = false;
0263:
0264: private boolean sessionForm = false;
0265:
0266: private boolean redirectAction = false;
0267:
0268: private String[] renderParameters = null;
0269:
0270: /**
0271: * Create a new AbstractFormController.
0272: * <p>Subclasses should set the following properties, either in the constructor
0273: * or via a BeanFactory: commandName, commandClass, bindOnNewForm, sessionForm.
0274: * Note that commandClass doesn't need to be set when overriding
0275: * <code>formBackingObject</code>, as the latter determines the class anyway.
0276: * <p>"cacheSeconds" is by default set to 0 (-> no caching for all form controllers).
0277: * @see #setCommandName
0278: * @see #setCommandClass
0279: * @see #setBindOnNewForm
0280: * @see #setSessionForm
0281: * @see #formBackingObject
0282: */
0283: public AbstractFormController() {
0284: setCacheSeconds(0);
0285: }
0286:
0287: /**
0288: * Set if request parameters should be bound to the form object
0289: * in case of a non-submitting request, i.e. a new form.
0290: */
0291: public final void setBindOnNewForm(boolean bindOnNewForm) {
0292: this .bindOnNewForm = bindOnNewForm;
0293: }
0294:
0295: /**
0296: * Return if request parameters should be bound in case of a new form.
0297: */
0298: public final boolean isBindOnNewForm() {
0299: return bindOnNewForm;
0300: }
0301:
0302: /**
0303: * Activate/deactivate session form mode. In session form mode,
0304: * the form is stored in the session to keep the form object instance
0305: * between requests, instead of creating a new one on each request.
0306: * <p>This is necessary for either wizard-style controllers that populate a
0307: * single form object from multiple pages, or forms that populate a persistent
0308: * object that needs to be identical to allow for tracking changes.
0309: */
0310: public final void setSessionForm(boolean sessionForm) {
0311: this .sessionForm = sessionForm;
0312: }
0313:
0314: /**
0315: * Return if session form mode is activated.
0316: */
0317: public final boolean isSessionForm() {
0318: return sessionForm;
0319: }
0320:
0321: /**
0322: * Specify whether the action phase is expected to call
0323: * {@link ActionResponse#sendRedirect}.
0324: * This information is important because some methods may not be called
0325: * before {@link ActionResponse#sendRedirect}, e.g.
0326: * {@link ActionResponse#setRenderParameter} and
0327: * {@link ActionResponse#setRenderParameters}.
0328: * @param redirectAction true if ActionResponse#sendRedirect is expected to be called
0329: * @see ActionResponse#sendRedirect
0330: */
0331: public void setRedirectAction(boolean redirectAction) {
0332: this .redirectAction = redirectAction;
0333: }
0334:
0335: /**
0336: * Return if {@link ActionResponse#sendRedirect} is
0337: * expected to be called in the action phase.
0338: */
0339: public boolean isRedirectAction() {
0340: return redirectAction;
0341: }
0342:
0343: /**
0344: * Specify the list of parameters that should be passed forward
0345: * from the action phase to the render phase whenever the form is
0346: * rerendered or when {@link #passRenderParameters} is called.
0347: * @see #passRenderParameters
0348: */
0349: public void setRenderParameters(String[] parameters) {
0350: this .renderParameters = parameters;
0351: }
0352:
0353: /**
0354: * Returns the list of parameters that will be passed forward
0355: * from the action phase to the render phase whenever the form is
0356: * rerendered or when {@link #passRenderParameters} is called.
0357: * @return the list of parameters
0358: * @see #passRenderParameters
0359: */
0360: public String[] getRenderParameters() {
0361: return renderParameters;
0362: }
0363:
0364: /**
0365: * Handles action phase of two cases: form submissions and showing a new form.
0366: * Delegates the decision between the two to <code>isFormSubmission</code>,
0367: * always treating requests without existing form session attribute
0368: * as new form when using session form mode.
0369: * @see #isFormSubmission
0370: * @see #processFormSubmission
0371: * @see #handleRenderRequestInternal
0372: */
0373: protected void handleActionRequestInternal(ActionRequest request,
0374: ActionResponse response) throws Exception {
0375:
0376: // Form submission or new form to show?
0377: if (isFormSubmission(request)) {
0378: // Fetch form object, bind, validate, process submission.
0379: try {
0380: Object command = getCommand(request);
0381: if (logger.isDebugEnabled()) {
0382: logger
0383: .debug("Processing valid submit (redirectAction = "
0384: + isRedirectAction() + ")");
0385: }
0386: if (!isRedirectAction()) {
0387: setFormSubmit(response);
0388: }
0389: PortletRequestDataBinder binder = bindAndValidate(
0390: request, command);
0391: BindException errors = new BindException(binder
0392: .getBindingResult());
0393: processFormSubmission(request, response, command,
0394: errors);
0395: setRenderCommandAndErrors(request, command, errors);
0396: return;
0397: } catch (PortletSessionRequiredException ex) {
0398: // Cannot submit a session form if no form object is in the session.
0399: if (logger.isDebugEnabled()) {
0400: logger.debug("Invalid submit detected: "
0401: + ex.getMessage());
0402: }
0403: setFormSubmit(response);
0404: setInvalidSubmit(response);
0405: handleInvalidSubmit(request, response);
0406: return;
0407: }
0408: }
0409:
0410: else {
0411: logger
0412: .debug("Not a form submit - passing parameters to render phase");
0413: passRenderParameters(request, response);
0414: return;
0415: }
0416: }
0417:
0418: /**
0419: * Handles render phase of two cases: form submissions and showing a new form.
0420: * Delegates the decision between the two to <code>isFormSubmission</code>,
0421: * always treating requests without existing form session attribute
0422: * as new form when using session form mode.
0423: * @see #isFormSubmission
0424: * @see #showNewForm
0425: * @see #processFormSubmission
0426: * @see #handleActionRequestInternal
0427: */
0428: protected ModelAndView handleRenderRequestInternal(
0429: RenderRequest request, RenderResponse response)
0430: throws Exception {
0431:
0432: // Form submission or new form to show?
0433: if (isFormSubmission(request)) {
0434:
0435: // If it is an invalid submit then handle it.
0436: if (isInvalidSubmission(request)) {
0437: logger
0438: .debug("Invalid submit - calling renderInvalidSubmit");
0439: return renderInvalidSubmit(request, response);
0440: }
0441:
0442: // Valid submit -> render.
0443: logger.debug("Valid submit - calling renderFormSubmission");
0444: return renderFormSubmission(request, response,
0445: getRenderCommand(request), getRenderErrors(request));
0446: }
0447:
0448: else {
0449: // New form to show: render form view.
0450: return showNewForm(request, response);
0451: }
0452: }
0453:
0454: /**
0455: * Determine if the given request represents a form submission.
0456: * <p>Default implementation checks to see if this is an ActionRequest
0457: * and treats all action requests as form submission. During the action
0458: * phase it will pass forward a render parameter to indicate to the render
0459: * phase that this is a form submission. This method can check both
0460: * kinds of requests and indicate if this is a form submission.
0461: * <p>Subclasses can override this to use a custom strategy, e.g. a specific
0462: * request parameter (assumably a hidden field or submit button name). Make
0463: * sure that the override can handle both ActionRequest and RenderRequest
0464: * objects properly.
0465: * @param request current request
0466: * @return if the request represents a form submission
0467: */
0468: protected boolean isFormSubmission(PortletRequest request) {
0469: return (request instanceof ActionRequest ? true : TRUE
0470: .equals(request
0471: .getParameter(getFormSubmitParameterName())));
0472: }
0473:
0474: /**
0475: * Determine if the given request represents an invalid form submission.
0476: */
0477: protected boolean isInvalidSubmission(PortletRequest request) {
0478: return TRUE.equals(request
0479: .getParameter(getInvalidSubmitParameterName()));
0480: }
0481:
0482: /**
0483: * Return the name of the render parameter that indicates this
0484: * was a form submission.
0485: * @return the name of the render parameter
0486: * @see javax.portlet.RenderRequest#getParameter
0487: */
0488: protected String getFormSubmitParameterName() {
0489: return FORM_SUBMISSION_PARAMETER;
0490: }
0491:
0492: /**
0493: * Return the name of the render parameter that indicates this
0494: * was an invalid form submission.
0495: * @return the name of the render parameter
0496: * @see javax.portlet.RenderRequest#getParameter
0497: */
0498: protected String getInvalidSubmitParameterName() {
0499: return INVALID_SUBMISSION_PARAMETER;
0500: }
0501:
0502: /**
0503: * Set the action response parameter that indicates this in a form submission.
0504: * @param response the current action response
0505: * @see #getFormSubmitParameterName()
0506: */
0507: protected final void setFormSubmit(ActionResponse response) {
0508: if (logger.isDebugEnabled()) {
0509: logger.debug("Setting render parameter ["
0510: + getFormSubmitParameterName()
0511: + "] to indicate this is a form submission");
0512: }
0513: try {
0514: response.setRenderParameter(getFormSubmitParameterName(),
0515: TRUE);
0516: } catch (IllegalStateException ex) {
0517: // Ignore in case sendRedirect was already set.
0518: }
0519: }
0520:
0521: /**
0522: * Set the action response parameter that indicates this in an invalid submission.
0523: * @param response the current action response
0524: * @see #getInvalidSubmitParameterName()
0525: */
0526: protected final void setInvalidSubmit(ActionResponse response) {
0527: if (logger.isDebugEnabled()) {
0528: logger.debug("Setting render parameter ["
0529: + getInvalidSubmitParameterName()
0530: + "] to indicate this is an invalid submission");
0531: }
0532: try {
0533: response.setRenderParameter(
0534: getInvalidSubmitParameterName(), TRUE);
0535: } catch (IllegalStateException ex) {
0536: // Ignore in case sendRedirect was already set.
0537: }
0538: }
0539:
0540: /**
0541: * Return the name of the PortletSession attribute that holds the form object
0542: * for this form controller.
0543: * <p>Default implementation delegates to the <code>getFormSessionAttributeName</code>
0544: * version without arguments.
0545: * @param request current HTTP request
0546: * @return the name of the form session attribute, or null if not in session form mode
0547: * @see #getFormSessionAttributeName
0548: * @see javax.portlet.PortletSession#getAttribute
0549: */
0550: protected String getFormSessionAttributeName(PortletRequest request) {
0551: return getFormSessionAttributeName();
0552: }
0553:
0554: /**
0555: * Return the name of the PortletSession attribute that holds the form object
0556: * for this form controller.
0557: * <p>Default is an internal name, of no relevance to applications, as the form
0558: * session attribute is not usually accessed directly. Can be overridden to use
0559: * an application-specific attribute name, which allows other code to access
0560: * the session attribute directly.
0561: * @return the name of the form session attribute
0562: * @see javax.portlet.PortletSession#getAttribute
0563: */
0564: protected String getFormSessionAttributeName() {
0565: return getClass().getName() + ".FORM." + getCommandName();
0566: }
0567:
0568: /**
0569: * Pass the specified list of action request parameters to the render phase
0570: * by putting them into the action response object. This may not be called
0571: * when the action will call will call
0572: * {@link ActionResponse#sendRedirect sendRedirect}.
0573: * @param request the current action request
0574: * @param response the current action response
0575: * @see ActionResponse#setRenderParameter
0576: */
0577: protected void passRenderParameters(ActionRequest request,
0578: ActionResponse response) {
0579: if (this .renderParameters == null) {
0580: return;
0581: }
0582: try {
0583: for (int i = 0; i < this .renderParameters.length; i++) {
0584: String paramName = this .renderParameters[i];
0585: String paramValues[] = request
0586: .getParameterValues(paramName);
0587: if (paramValues != null) {
0588: if (logger.isDebugEnabled()) {
0589: logger
0590: .debug("Passing parameter to render phase '"
0591: + paramName
0592: + "' = "
0593: + (paramValues == null ? "NULL"
0594: : Arrays.asList(
0595: paramValues)
0596: .toString()));
0597: }
0598: response.setRenderParameter(paramName, paramValues);
0599: }
0600: }
0601: } catch (IllegalStateException ex) {
0602: // Ignore in case sendRedirect was already set.
0603: }
0604: }
0605:
0606: /**
0607: * Show a new form. Prepares a backing object for the current form
0608: * and the given request, including checking its validity.
0609: * @param request current render request
0610: * @param response current render response
0611: * @return the prepared form view
0612: * @throws Exception in case of an invalid new form object
0613: * @see #getErrorsForNewForm
0614: */
0615: protected final ModelAndView showNewForm(RenderRequest request,
0616: RenderResponse response) throws Exception {
0617:
0618: logger.debug("Displaying new form");
0619: return showForm(request, response, getErrorsForNewForm(request));
0620: }
0621:
0622: /**
0623: * Create a BindException instance for a new form.
0624: * Called by <code>showNewForm</code>.
0625: * <p>Can be used directly when intending to show a new form but with
0626: * special errors registered on it (for example, on invalid submit).
0627: * Usually, the resulting BindException will be passed to
0628: * <code>showForm</code>, after registering the errors on it.
0629: * @param request current render request
0630: * @return the BindException instance
0631: * @throws Exception in case of an invalid new form object
0632: */
0633: protected final BindException getErrorsForNewForm(
0634: RenderRequest request) throws Exception {
0635: // Create form-backing object for new form
0636: Object command = formBackingObject(request);
0637: if (command == null) {
0638: throw new PortletException(
0639: "Form object returned by formBackingObject() must not be null");
0640: }
0641: if (!checkCommand(command)) {
0642: throw new PortletException(
0643: "Form object returned by formBackingObject() must match commandClass");
0644: }
0645:
0646: // Bind without validation, to allow for prepopulating a form, and for
0647: // convenient error evaluation in views (on both first attempt and resubmit).
0648: PortletRequestDataBinder binder = createBinder(request, command);
0649: BindException errors = new BindException(binder
0650: .getBindingResult());
0651:
0652: if (isBindOnNewForm()) {
0653: if (logger.isDebugEnabled()) {
0654: logger.debug("Binding to new form");
0655: }
0656: binder.bind(request);
0657: onBindOnNewForm(request, command, errors);
0658: }
0659:
0660: // Return BindException object that resulted from binding.
0661: return errors;
0662: }
0663:
0664: /**
0665: * Callback for custom post-processing in terms of binding for a new form.
0666: * Called when preparing a new form if <code>bindOnNewForm</code> is <code>true</code>.
0667: * <p>Default implementation delegates to <code>onBindOnNewForm(request, command)</code>.
0668: * @param request current render request
0669: * @param command the command object to perform further binding on
0670: * @param errors validation errors holder, allowing for additional
0671: * custom registration of binding errors
0672: * @throws Exception in case of invalid state or arguments
0673: * @see #onBindOnNewForm(RenderRequest, Object)
0674: * @see #setBindOnNewForm
0675: */
0676: protected void onBindOnNewForm(RenderRequest request,
0677: Object command, BindException errors) throws Exception {
0678:
0679: onBindOnNewForm(request, command);
0680: }
0681:
0682: /**
0683: * Callback for custom post-processing in terms of binding for a new form.
0684: * Called by the default implementation of the <code>onBindOnNewForm</code> version
0685: * with all parameters, after standard binding when displaying the form view.
0686: * Only called if <code>bindOnNewForm</code> is set to <code>true</code>.
0687: * <p>Default implementation is empty.
0688: * @param request current render request
0689: * @param command the command object to perform further binding on
0690: * @throws Exception in case of invalid state or arguments
0691: * @see #onBindOnNewForm(RenderRequest, Object, BindException)
0692: * @see #setBindOnNewForm(boolean)
0693: */
0694: protected void onBindOnNewForm(RenderRequest request, Object command)
0695: throws Exception {
0696: }
0697:
0698: /**
0699: * Return the form object for the given request.
0700: * <p>Calls <code>formBackingObject</code> if the object is not in the session
0701: * @param request current request
0702: * @return object form to bind onto
0703: * @see #formBackingObject
0704: */
0705: protected final Object getCommand(PortletRequest request)
0706: throws Exception {
0707: // If not in session-form mode, create a new form-backing object.
0708: if (!isSessionForm()) {
0709: if (logger.isDebugEnabled()) {
0710: logger
0711: .debug("Not a session-form -- using new formBackingObject");
0712: }
0713: return formBackingObject(request);
0714: }
0715:
0716: // Session-form mode: retrieve form object from portlet session attribute.
0717: PortletSession session = request.getPortletSession(false);
0718: if (session == null) {
0719: throw new PortletSessionRequiredException(
0720: "Must have session when trying to bind (in session-form mode)");
0721: }
0722: String formAttrName = getFormSessionAttributeName(request);
0723: Object sessionFormObject = session.getAttribute(formAttrName);
0724: if (sessionFormObject == null) {
0725: throw new PortletSessionRequiredException(
0726: "Form object not found in session (in session-form mode)");
0727: }
0728:
0729: // Remove form object from porlet session: we might finish the form workflow
0730: // in this request. If it turns out that we need to show the form view again,
0731: // we'll re-bind the form object to the portlet session.
0732: if (logger.isDebugEnabled()) {
0733: logger.debug("Removing form session attribute ["
0734: + formAttrName + "]");
0735: }
0736: session.removeAttribute(formAttrName);
0737:
0738: // Check the command object to make sure its valid
0739: if (!checkCommand(sessionFormObject)) {
0740: throw new PortletSessionRequiredException(
0741: "Object found in session does not match commandClass");
0742: }
0743:
0744: return sessionFormObject;
0745: }
0746:
0747: /**
0748: * Retrieve a backing object for the current form from the given request.
0749: * <p>The properties of the form object will correspond to the form field values
0750: * in your form view. This object will be exposed in the model under the specified
0751: * command name, to be accessed under that name in the view: for example, with
0752: * a "spring:bind" tag. The default command name is "command".
0753: * <p>Note that you need to activate session form mode to reuse the form-backing
0754: * object across the entire form workflow. Else, a new instance of the command
0755: * class will be created for each submission attempt, just using this backing
0756: * object as template for the initial form.
0757: * <p>Default implementation calls <code>BaseCommandController.createCommand</code>,
0758: * creating a new empty instance of the command class.
0759: * Subclasses can override this to provide a preinitialized backing object.
0760: * @param request current portlet request
0761: * @return the backing object
0762: * @throws Exception in case of invalid state or arguments
0763: * @see #setCommandName
0764: * @see #setCommandClass
0765: * @see #createCommand
0766: */
0767: protected Object formBackingObject(PortletRequest request)
0768: throws Exception {
0769: return createCommand();
0770: }
0771:
0772: /**
0773: * Prepare the form model and view, including reference and error data.
0774: * Can show a configured form page, or generate a form view programmatically.
0775: * <p>A typical implementation will call
0776: * <code>showForm(request, errors, "myView")</code>
0777: * to prepare the form view for a specific view name, returning the
0778: * ModelAndView provided there.
0779: * <p>For building a custom ModelAndView, call <code>errors.getModel()</code>
0780: * to populate the ModelAndView model with the command and the Errors instance,
0781: * under the specified command name, as expected by the "spring:bind" tag.
0782: * You also need to include the model returned by <code>referenceData</code>.
0783: * <p>Note: If you decide to have a "formView" property specifying the
0784: * view name, consider using SimpleFormController.
0785: * @param request current render request
0786: * @param response current render response
0787: * @param errors validation errors holder
0788: * @return the prepared form view, or null if handled directly
0789: * @throws Exception in case of invalid state or arguments
0790: * @see #showForm(RenderRequest, BindException, String)
0791: * @see org.springframework.validation.Errors
0792: * @see org.springframework.validation.BindException#getModel
0793: * @see #referenceData(PortletRequest, Object, Errors)
0794: * @see SimpleFormController#setFormView
0795: */
0796: protected abstract ModelAndView showForm(RenderRequest request,
0797: RenderResponse response, BindException errors)
0798: throws Exception;
0799:
0800: /**
0801: * Prepare model and view for the given form, including reference and errors.
0802: * <p>In session form mode: Re-puts the form object in the session when
0803: * returning to the form, as it has been removed by getCommand.
0804: * <p>Can be used in subclasses to redirect back to a specific form page.
0805: * @param request current render request
0806: * @param errors validation errors holder
0807: * @param viewName name of the form view
0808: * @return the prepared form view
0809: * @throws Exception in case of invalid state or arguments
0810: * @see #showForm(RenderRequest, BindException, String, Map)
0811: * @see #showForm(RenderRequest, RenderResponse, BindException)
0812: */
0813: protected final ModelAndView showForm(RenderRequest request,
0814: BindException errors, String viewName) throws Exception {
0815:
0816: return showForm(request, errors, viewName, null);
0817: }
0818:
0819: /**
0820: * Prepare model and view for the given form, including reference and errors,
0821: * adding a controller-specific control model.
0822: * <p>In session form mode: Re-puts the form object in the session when returning
0823: * to the form, as it has been removed by getCommand.
0824: * <p>Can be used in subclasses to redirect back to a specific form page.
0825: * @param request current render request
0826: * @param errors validation errors holder
0827: * @param viewName name of the form view
0828: * @param controlModel model map containing controller-specific control data
0829: * (e.g. current page in wizard-style controllers or special error message)
0830: * @return the prepared form view
0831: * @throws Exception in case of invalid state or arguments
0832: * @see #showForm(RenderRequest, BindException, String)
0833: * @see #showForm(RenderRequest, RenderResponse, BindException)
0834: */
0835: protected final ModelAndView showForm(RenderRequest request,
0836: BindException errors, String viewName, Map controlModel)
0837: throws Exception {
0838:
0839: // In session form mode, re-expose form object as portlet session attribute.
0840: // Re-binding is necessary for proper state handling in a cluster,
0841: // to notify other nodes of changes in the form object.
0842: if (isSessionForm()) {
0843: String formAttrName = getFormSessionAttributeName(request);
0844: if (logger.isDebugEnabled()) {
0845: logger.debug("Setting form session attribute ["
0846: + formAttrName + "] to: " + errors.getTarget());
0847: }
0848: request.getPortletSession().setAttribute(formAttrName,
0849: errors.getTarget());
0850: }
0851:
0852: // Fetch errors model as starting point, containing form object under
0853: // "commandName", and corresponding Errors instance under internal key.
0854: Map model = errors.getModel();
0855:
0856: // Merge reference data into model, if any.
0857: Map referenceData = referenceData(request, errors.getTarget(),
0858: errors);
0859: if (referenceData != null) {
0860: model.putAll(referenceData);
0861: }
0862:
0863: // Merge control attributes into model, if any.
0864: if (controlModel != null) {
0865: model.putAll(controlModel);
0866: }
0867:
0868: // Trigger rendering of the specified view, using the final model.
0869: return new ModelAndView(viewName, model);
0870: }
0871:
0872: /**
0873: * Create a reference data map for the given request, consisting of
0874: * bean name/bean instance pairs as expected by ModelAndView.
0875: * <p>Default implementation returns null.
0876: * Subclasses can override this to set reference data used in the view.
0877: * @param request current render request
0878: * @param command form object with request parameters bound onto it
0879: * @param errors validation errors holder
0880: * @return a Map with reference data entries, or null if none
0881: * @throws Exception in case of invalid state or arguments
0882: * @see ModelAndView
0883: */
0884: protected Map referenceData(PortletRequest request, Object command,
0885: Errors errors) throws Exception {
0886: return null;
0887: }
0888:
0889: /**
0890: * Process render phase of form submission request. Called by <code>handleRequestInternal</code>
0891: * in case of a form submission, with or without binding errors. Implementations
0892: * need to proceed properly, typically showing a form view in case of binding
0893: * errors or rendering the result of a submit action else.
0894: * <p>For a success view, call <code>errors.getModel()</code> to populate the
0895: * ModelAndView model with the command and the Errors instance, under the
0896: * specified command name, as expected by the "spring:bind" tag. For a form view,
0897: * simply return the ModelAndView object privded by <code>showForm</code>.
0898: * @param request current render request
0899: * @param response current render response
0900: * @param command form object with request parameters bound onto it
0901: * @param errors errors holder
0902: * @return the prepared model and view, or null
0903: * @throws Exception in case of errors
0904: * @see #handleRenderRequestInternal
0905: * @see #processFormSubmission
0906: * @see #isFormSubmission
0907: * @see #showForm(RenderRequest, RenderResponse, BindException)
0908: * @see org.springframework.validation.Errors
0909: * @see org.springframework.validation.BindException#getModel
0910: */
0911: protected abstract ModelAndView renderFormSubmission(
0912: RenderRequest request, RenderResponse response,
0913: Object command, BindException errors) throws Exception;
0914:
0915: /**
0916: * Process action phase of form submission request. Called by <code>handleRequestInternal</code>
0917: * in case of a form submission, with or without binding errors. Implementations
0918: * need to proceed properly, typically performing a submit action if there are no binding errors.
0919: * <p>Subclasses can implement this to provide custom submission handling
0920: * like triggering a custom action. They can also provide custom validation
0921: * or proceed with the submission accordingly.
0922: * @param request current action request
0923: * @param response current action response
0924: * @param command form object with request parameters bound onto it
0925: * @param errors errors holder (subclass can add errors if it wants to)
0926: * @throws Exception in case of errors
0927: * @see #handleActionRequestInternal
0928: * @see #renderFormSubmission
0929: * @see #isFormSubmission
0930: * @see org.springframework.validation.Errors
0931: */
0932: protected abstract void processFormSubmission(
0933: ActionRequest request, ActionResponse response,
0934: Object command, BindException errors) throws Exception;
0935:
0936: /**
0937: * Handle an invalid submit request, e.g. when in session form mode but no form object
0938: * was found in the session (like in case of an invalid resubmit by the browser).
0939: * <p>Default implementation simply tries to resubmit the form with a new form object.
0940: * This should also work if the user hit the back button, changed some form data,
0941: * and resubmitted the form.
0942: * <p>Note: To avoid duplicate submissions, you need to override this method.
0943: * Either show some "invalid submit" message, or call <code>showNewForm</code> for
0944: * resetting the form (prepopulating it with the current values if "bindOnNewForm"
0945: * is true). In this case, the form object in the session serves as transaction token.
0946: * <pre>
0947: * protected ModelAndView handleInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
0948: * return showNewForm(request, response);
0949: * }</pre>
0950: * You can also show a new form but with special errors registered on it:
0951: * <pre>
0952: * protected ModelAndView handleInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
0953: * BindException errors = getErrorsForNewForm(request);
0954: * errors.reject("duplicateFormSubmission", "Duplicate form submission");
0955: * return showForm(request, response, errors);
0956: * }</pre>
0957: * <p><b>WARNING:</b> If you override this method, be sure to also override the action
0958: * phase version of this method so that it will not attempt to perform the resubmit
0959: * action by default.
0960: * @param request current render request
0961: * @param response current render response
0962: * @return a prepared view, or null if handled directly
0963: * @throws Exception in case of errors
0964: * @see #handleInvalidSubmit
0965: */
0966: protected ModelAndView renderInvalidSubmit(RenderRequest request,
0967: RenderResponse response) throws Exception {
0968:
0969: return renderFormSubmission(request, response,
0970: getRenderCommand(request), getRenderErrors(request));
0971: }
0972:
0973: /**
0974: * Handle an invalid submit request, e.g. when in session form mode but no form object
0975: * was found in the session (like in case of an invalid resubmit by the browser).
0976: * <p>Default implementation simply tries to resubmit the form with a new form object.
0977: * This should also work if the user hit the back button, changed some form data,
0978: * and resubmitted the form.
0979: * <p>Note: To avoid duplicate submissions, you need to override this method.
0980: * Most likely you will simply want it to do nothing here in the action phase
0981: * and diplay an appropriate error and a new form in the render phase.
0982: * <pre>
0983: * protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
0984: * }</pre>
0985: * <p>If you override this method but you do need a command object and bind errors
0986: * in the render phase, be sure to call {@link #setRenderCommandAndErrors setRenderCommandAndErrors}
0987: * from here.
0988: * @param request current action request
0989: * @param response current action response
0990: * @throws Exception in case of errors
0991: * @see #renderInvalidSubmit
0992: * @see #setRenderCommandAndErrors
0993: */
0994: protected void handleInvalidSubmit(ActionRequest request,
0995: ActionResponse response) throws Exception {
0996: passRenderParameters(request, response);
0997: Object command = formBackingObject(request);
0998: if (command == null) {
0999: throw new PortletException(
1000: "Form object returned by formBackingObject() must not be null");
1001: }
1002: if (!checkCommand(command)) {
1003: throw new PortletException(
1004: "Form object returned by formBackingObject() must match commandClass");
1005: }
1006: PortletRequestDataBinder binder = bindAndValidate(request,
1007: command);
1008: BindException errors = new BindException(binder
1009: .getBindingResult());
1010: processFormSubmission(request, response, command, errors);
1011: setRenderCommandAndErrors(request, command, errors);
1012: }
1013:
1014: }
|