001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.portlet.mvc;
018:
019: import java.util.Map;
020:
021: import javax.portlet.ActionRequest;
022: import javax.portlet.ActionResponse;
023: import javax.portlet.PortletException;
024: import javax.portlet.PortletRequest;
025: import javax.portlet.RenderRequest;
026: import javax.portlet.RenderResponse;
027:
028: import org.springframework.validation.BindException;
029: import org.springframework.validation.Errors;
030: import org.springframework.web.portlet.ModelAndView;
031:
032: /**
033: * <p>Concrete FormController implementation that provides configurable
034: * form and success views, and an onSubmit chain for convenient overriding.
035: * Automatically resubmits to the form view in case of validation errors,
036: * and renders the success view in case of a valid submission.</p>
037: *
038: * <p>The workflow of this Controller does not differ much from the one described
039: * in the {@link AbstractFormController AbstractFormController}. The difference
040: * is that you do not need to implement {@link #showForm showForm},
041: * {@link #processFormSubmission processFormSubmission}, and
042: * {@link #renderFormSubmission renderFormSubmission}: A form view and a
043: * success view can be configured declaratively.</p>
044: *
045: * <p>This controller is different from it's servlet counterpart in that it must take
046: * into account the two phases of a portlet request: the action phase and the render
047: * phase. See the JSR-168 spec for more details on these two phases.
048: * Be especially aware that the action phase is called only once, but that the
049: * render phase will be called repeatedly by the portal -- it does this every time
050: * the page containing the portlet is updated, even if the activity is in some other
051: * portlet. The main difference in the methods in this class is that the
052: * <code>onSubmit</code> methods have all been split into <code>onSubmitAction</code>
053: * and <code>onSubmitRender</code> to account for the two phases.</p>
054: *
055: * <p><b><a name="workflow">Workflow
056: * (<a href="AbstractFormController.html#workflow">in addition to the superclass</a>):</b><br>
057: * <ol>
058: * <li>Call to {@link #processFormSubmission processFormSubmission} which inspects
059: * the {@link org.springframework.validation.Errors Errors} object to see if
060: * any errors have occurred during binding and validation.</li>
061: * <li>If errors occured, the controller will return the configured formView,
062: * showing the form again (possibly rendering according error messages).</li>
063: * <li>If {@link #isFormChangeRequest isFormChangeRequest} is overridden and returns
064: * true for the given request, the controller will return the formView too.
065: * In that case, the controller will also suppress validation. Before returning the formView,
066: * the controller will invoke {@link #onFormChange}, giving sub-classes a chance
067: * to make modification to the command object.
068: * This is intended for requests that change the structure of the form,
069: * which should not cause validation and show the form in any case.</li>
070: * <li>If no errors occurred, the controller will call
071: * {@link #onSubmitAction(ActionRequest, ActionResponse, Object, BindException) onSubmitAction}
072: * during the action phase and then {@link #onSubmitRender(RenderRequest, RenderResponse,
073: * Object, BindException) onSubmitRender} during the render phase, which in case of the
074: * default implementation delegate to {@link #onSubmitAction(Object, BindException)
075: * onSubmitAction} and {@link #onSubmitRender(Object, BindException) onSubmitRender}
076: * with just the command object.
077: * The default implementation of the latter method will return the configured
078: * <code>successView</code>. Consider just implementing {@link #doSubmitAction doSubmitAction}
079: * for simply performing a submit action during the action phase and then rendering
080: * the success view during the render phase.</li>
081: * </ol>
082: * </p>
083: *
084: * <p>The submit behavior can be customized by overriding one of the
085: * {@link #onSubmitAction onSubmitAction} or {@link #onSubmitRender onSubmitRender}
086: * methods. Submit actions can also perform custom validation if necessary
087: * (typically database-driven checks), calling {@link #showForm(RenderRequest,
088: * RenderResponse, BindException) showForm} in case of validation errors to show
089: * the form view again. You do not have to override both the <code>onSubmitAction</code> and
090: * <code>onSubmitRender</code> methods at a given level unless you truly have custom logic to
091: * perform in both.<p>
092: *
093: * <p><b>WARNING:</b> Make sure that any one-time system updates (such as database
094: * updates or file writes) are performed in either an {@link #onSubmitAction onSubmitAction}
095: * method or the {@link #doSubmitAction doSubmitAction} method. Logic in the
096: * {@link #onSubmitRender onSubmitRender} methods may be executed repeatedly by
097: * the portal whenever the page containing the portlet is updated.</p>
098: *
099: * <p><b><a name="config">Exposed configuration properties</a>
100: * (<a href="AbstractFormController.html#config">and those defined by superclass</a>):</b><br>
101: * <table border="1">
102: * <tr>
103: * <td><b>name</b></td>
104: * <td><b>default</b></td>
105: * <td><b>description</b></td>
106: * </tr>
107: * <tr>
108: * <td>formView</td>
109: * <td><i>null</i></td>
110: * <td>Indicates what view to use when the user asks for a new form
111: * or when validation errors have occurred on form submission.</td>
112: * </tr>
113: * <tr>
114: * <td>successView</td>
115: * <td><i>null</i></td>
116: * <td>Indicates what view to use when successful form submissions have
117: * occurred. Such a success view could e.g. display a submission summary.
118: * More sophisticated actions can be implemented by overriding one of
119: * the {@link #onSubmitRender(Object) onSubmitRender()} methods.</td>
120: * </tr>
121: * <table>
122: * </p>
123: *
124: * <p>Parameters indicated with <code>setPassRenderParameters</code> will be
125: * preserved if the form has errors or if a form change request occurs.
126: * If there are render parameters you need in <code>onSubmitRender</code>,
127: * then you need to pass those forward from <code>onSubmitAction</code>.
128: *
129: * <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
130: *
131: * @author John A. Lewis
132: * @author Juergen Hoeller
133: * @author Rob Harrop
134: * @since 2.0
135: */
136: public class SimpleFormController extends AbstractFormController {
137:
138: private String formView;
139:
140: private String successView;
141:
142: /**
143: * Create a new SimpleFormController.
144: * <p>Subclasses should set the following properties, either in the constructor
145: * or via a BeanFactory: commandName, commandClass, sessionForm, formView,
146: * successView. Note that commandClass doesn't need to be set when overriding
147: * <code>formBackingObject</code>, as this determines the class anyway.
148: * @see #setCommandClass(Class)
149: * @see #setCommandName(String)
150: * @see #setSessionForm(boolean)
151: * @see #setFormView
152: * @see #setSuccessView
153: * @see #formBackingObject(PortletRequest)
154: */
155: public SimpleFormController() {
156: // AbstractFormController sets default cache seconds to 0.
157: super ();
158: }
159:
160: /**
161: * Set the name of the view that should be used for form display.
162: */
163: public final void setFormView(String formView) {
164: this .formView = formView;
165: }
166:
167: /**
168: * Return the name of the view that should be used for form display.
169: */
170: public final String getFormView() {
171: return this .formView;
172: }
173:
174: /**
175: * Set the name of the view that should be shown on successful submit.
176: */
177: public final void setSuccessView(String successView) {
178: this .successView = successView;
179: }
180:
181: /**
182: * Return the name of the view that should be shown on successful submit.
183: */
184: public final String getSuccessView() {
185: return this .successView;
186: }
187:
188: /**
189: * This implementation shows the configured form view, delegating to the
190: * analogous showForm version with a controlModel argument.
191: * <p>Can be called within onSubmit implementations, to redirect back to the form
192: * in case of custom validation errors (i.e. not determined by the validator).
193: * <p>Can be overridden in subclasses to show a custom view, writing directly
194: * to the response or preparing the response before rendering a view.
195: * <p>If calling showForm with a custom control model in subclasses, it's preferable
196: * to override the analogous showForm version with a controlModel argument
197: * (which will handle both standard form showing and custom form showing then).
198: * @see #setFormView
199: * @see #showForm(RenderRequest, RenderResponse, BindException, Map)
200: */
201: protected ModelAndView showForm(RenderRequest request,
202: RenderResponse response, BindException errors)
203: throws Exception {
204:
205: return showForm(request, response, errors, null);
206: }
207:
208: /**
209: * This implementation shows the configured form view.
210: * <p>Can be called within onSubmit implementations, to redirect back to the form
211: * in case of custom validation errors (i.e. not determined by the validator).
212: * <p>Can be overridden in subclasses to show a custom view, writing directly
213: * to the response or preparing the response before rendering a view.
214: * @param request current render request
215: * @param errors validation errors holder
216: * @param controlModel model map containing controller-specific control data
217: * (e.g. current page in wizard-style controllers or special error message)
218: * @return the prepared form view
219: * @throws Exception in case of invalid state or arguments
220: * @see #setFormView
221: */
222: protected ModelAndView showForm(RenderRequest request,
223: RenderResponse response, BindException errors,
224: Map controlModel) throws Exception {
225:
226: return showForm(request, errors, getFormView(), controlModel);
227: }
228:
229: /**
230: * Create a reference data map for the given request and command,
231: * consisting of bean name/bean instance pairs as expected by ModelAndView.
232: * <p>Default implementation delegates to referenceData(request).
233: * Subclasses can override this to set reference data used in the view.
234: * @param request current portlet request
235: * @param command form object with request parameters bound onto it
236: * @param errors validation errors holder
237: * @return a Map with reference data entries, or null if none
238: * @throws Exception in case of invalid state or arguments
239: * @see ModelAndView
240: */
241: protected Map referenceData(PortletRequest request, Object command,
242: Errors errors) throws Exception {
243: return referenceData(request);
244: }
245:
246: /**
247: * Create a reference data map for the given request.
248: * Called by referenceData version with all parameters.
249: * <p>Default implementation returns null.
250: * Subclasses can override this to set reference data used in the view.
251: * @param request current portlet request
252: * @return a Map with reference data entries, or null if none
253: * @throws Exception in case of invalid state or arguments
254: * @see #referenceData(PortletRequest, Object, Errors)
255: * @see ModelAndView
256: */
257: protected Map referenceData(PortletRequest request)
258: throws Exception {
259: return null;
260: }
261:
262: /**
263: * This implementation calls <code>showForm</code> in case of errors,
264: * and delegates to <code>onSubmitRender<code>'s full version else.
265: * <p>This can only be overridden to check for an action that should be executed
266: * without respect to binding errors, like a cancel action. To just handle successful
267: * submissions without binding errors, override one of the <code>onSubmitRender</code>
268: * methods.
269: * @see #showForm(RenderRequest, RenderResponse, BindException)
270: * @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
271: * @see #onSubmitRender(Object, BindException)
272: * @see #onSubmitRender(Object)
273: * @see #processFormSubmission(ActionRequest, ActionResponse, Object, BindException)
274: */
275: protected ModelAndView renderFormSubmission(RenderRequest request,
276: RenderResponse response, Object command,
277: BindException errors) throws Exception {
278:
279: if (errors.hasErrors() || isFormChangeRequest(request)) {
280: return showForm(request, response, errors);
281: } else {
282: return onSubmitRender(request, response, command, errors);
283: }
284: }
285:
286: /**
287: * This implementation does nothing in case of errors,
288: * and delegates to <code>onSubmitAction</code>'s full version else.
289: * <p>This can only be overridden to check for an action that should be executed
290: * without respect to binding errors, like a cancel action. To just handle successful
291: * submissions without binding errors, override one of the <code>onSubmitAction</code>
292: * methods or <code>doSubmitAction</code>.
293: * @see #showForm
294: * @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
295: * @see #onSubmitAction(Object, BindException)
296: * @see #onSubmitAction(Object)
297: * @see #doSubmitAction(Object)
298: * @see #renderFormSubmission(RenderRequest, RenderResponse, Object, BindException)
299: */
300: protected void processFormSubmission(ActionRequest request,
301: ActionResponse response, Object command,
302: BindException errors) throws Exception {
303:
304: if (errors.hasErrors()) {
305: if (logger.isDebugEnabled()) {
306: logger.debug("Data binding errors: "
307: + errors.getErrorCount());
308: }
309: if (isRedirectAction()) {
310: setFormSubmit(response);
311: }
312: passRenderParameters(request, response);
313: } else if (isFormChangeRequest(request)) {
314: logger
315: .debug("Detected form change request -> routing request to onFormChange");
316: if (isRedirectAction()) {
317: setFormSubmit(response);
318: }
319: passRenderParameters(request, response);
320: onFormChange(request, response, command, errors);
321: } else {
322: logger.debug("No errors - processing submit");
323: onSubmitAction(request, response, command, errors);
324: }
325: }
326:
327: /**
328: * This implementation delegates to <code>isFormChangeRequest</code>:
329: * A form change request changes the appearance of the form
330: * and should not get validated but just show the new form.
331: * @see #isFormChangeRequest
332: */
333: protected boolean suppressValidation(PortletRequest request) {
334: return isFormChangeRequest(request);
335: }
336:
337: /**
338: * Determine whether the given request is a form change request.
339: * A form change request changes the appearance of the form
340: * and should always show the new form, without validation.
341: * <p>Gets called by suppressValidation and processFormSubmission.
342: * Consequently, this single method determines to suppress validation
343: * <i>and</i> to show the form view in any case.
344: * @param request current portlet request
345: * @return whether the given request is a form change request
346: * @see #suppressValidation
347: * @see #processFormSubmission
348: */
349: protected boolean isFormChangeRequest(PortletRequest request) {
350: return false;
351: }
352:
353: /**
354: * Called during form submission if
355: * {@link #isFormChangeRequest(PortletRequest)}
356: * returns <code>true</code>. Allows subclasses to implement custom logic
357: * to modify the command object to directly modify data in the form.
358: * <p>Default implementation delegates to
359: * <code>onFormChange(request, response, command)</code>.
360: * @param request current action request
361: * @param response current action response
362: * @param command form object with request parameters bound onto it
363: * @param errors validation errors holder, allowing for additional
364: * custom validation
365: * @throws Exception in case of errors
366: * @see #isFormChangeRequest(PortletRequest)
367: * @see #onFormChange(ActionRequest, ActionResponse, Object)
368: */
369: protected void onFormChange(ActionRequest request,
370: ActionResponse response, Object command,
371: BindException errors) throws Exception {
372:
373: onFormChange(request, response, command);
374: }
375:
376: /**
377: * Simpler <code>onFormChange</code> variant, called by the full version
378: * <code>onFormChange(request, response, command, errors)</code>.
379: * <p>Default implementation is empty.
380: * @param request current action request
381: * @param response current action response
382: * @param command form object with request parameters bound onto it
383: * @throws Exception in case of errors
384: * @see #onFormChange(ActionRequest, ActionResponse, Object, BindException)
385: */
386: protected void onFormChange(ActionRequest request,
387: ActionResponse response, Object command) throws Exception {
388: }
389:
390: /**
391: * Submit render phase callback with all parameters. Called in case of submit without errors
392: * reported by the registered validator, or on every submit if no validator.
393: * <p>Default implementation delegates to <code>onSubmitRender(Object, BindException)</code>.
394: * For simply performing a submit action and rendering the specified success view,
395: * do not implement an <code>onSubmitRender</code> at all.
396: * <p>Subclasses can override this to provide custom rendering to display results of
397: * the action phase. Implementations can also call <code>showForm</code> to return to the form
398: * if the <code>onSubmitAction</code> failed custom validation. Do <i>not</i> implement multiple
399: * <code>onSubmitRender</code> methods: In that case,
400: * just this method will be called by the controller.
401: * <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
402: * with the command and the Errors instance, under the specified command name,
403: * as expected by the "spring:bind" tag.
404: * @param request current render request
405: * @param response current render response
406: * @param command form object with request parameters bound onto it
407: * @param errors Errors instance without errors (subclass can add errors if it wants to)
408: * @return the prepared model and view
409: * @throws Exception in case of errors
410: * @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
411: * @see #onSubmitRender(Object, BindException)
412: * @see #doSubmitAction
413: * @see #showForm
414: * @see org.springframework.validation.Errors
415: * @see org.springframework.validation.BindException#getModel
416: */
417: protected ModelAndView onSubmitRender(RenderRequest request,
418: RenderResponse response, Object command,
419: BindException errors) throws Exception {
420:
421: return onSubmitRender(command, errors);
422: }
423:
424: /**
425: * Submit action phase callback with all parameters. Called in case of submit without errors
426: * reported by the registered validator respectively on every submit if no validator.
427: * <p>Default implementation delegates to <code>onSubmitAction(Object, BindException)</code>.
428: * For simply performing a submit action consider implementing <code>doSubmitAction</code>
429: * rather than an <code>onSubmitAction</code> version.
430: * <p>Subclasses can override this to provide custom submission handling like storing
431: * the object to the database. Implementations can also perform custom validation and
432: * signal the render phase to call <code>showForm</code> to return to the form. Do <i>not</i>
433: * implement multiple <code>onSubmitAction</code> methods: In that case,
434: * just this method will be called by the controller.
435: * @param request current action request
436: * @param response current action response
437: * @param command form object with request parameters bound onto it
438: * @param errors Errors instance without errors (subclass can add errors if it wants to)
439: * @throws Exception in case of errors
440: * @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
441: * @see #onSubmitAction(Object, BindException)
442: * @see #doSubmitAction
443: * @see org.springframework.validation.Errors
444: */
445: protected void onSubmitAction(ActionRequest request,
446: ActionResponse response, Object command,
447: BindException errors) throws Exception {
448:
449: onSubmitAction(command, errors);
450: }
451:
452: /**
453: * Simpler <code>onSubmitRender</code> version. Called by the default implementation
454: * of the <code>onSubmitRender</code> version with all parameters.
455: * <p>Default implementation calls <code>onSubmitRender(command)</code>, using the
456: * returned ModelAndView if actually implemented in a subclass. Else, the
457: * default behavior will apply: rendering the success view with the command
458: * and Errors instance as model.
459: * <p>Subclasses can override this to provide custom submission handling that
460: * does not need request and response.
461: * <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
462: * with the command and the Errors instance, under the specified command name,
463: * as expected by the "spring:bind" tag.
464: * @param command form object with request parameters bound onto it
465: * @param errors Errors instance without errors
466: * @return the prepared model and view, or null
467: * @throws Exception in case of errors
468: * @see #onSubmitRender(RenderRequest, RenderResponse, Object, BindException)
469: * @see #onSubmitRender(Object)
470: * @see #onSubmitAction(Object, BindException)
471: * @see #setSuccessView
472: * @see org.springframework.validation.Errors
473: * @see org.springframework.validation.BindException#getModel
474: */
475: protected ModelAndView onSubmitRender(Object command,
476: BindException errors) throws Exception {
477: ModelAndView mv = onSubmitRender(command);
478: if (mv != null) {
479: // simplest onSubmit version implemented in custom subclass
480: return mv;
481: } else {
482: // default behavior: render success view
483: if (getSuccessView() == null) {
484: throw new PortletException("successView isn't set");
485: }
486: return new ModelAndView(getSuccessView(), errors.getModel());
487: }
488: }
489:
490: /**
491: * Simpler <code>onSubmitAction</code> version. Called by the default implementation
492: * of the <code>onSubmitAction</code> version with all parameters.
493: * <p>Default implementation calls <code>onSubmitAction(command)</code>.
494: * <p>Subclasses can override this to provide custom submission handling that
495: * does not need request and response.
496: * @param command form object with request parameters bound onto it
497: * @param errors Errors instance without errors
498: * @throws Exception in case of errors
499: * @see #onSubmitAction(ActionRequest, ActionResponse, Object, BindException)
500: * @see #onSubmitAction(Object)
501: * @see #onSubmitRender(Object, BindException)
502: * @see org.springframework.validation.Errors
503: */
504: protected void onSubmitAction(Object command, BindException errors)
505: throws Exception {
506: onSubmitAction(command);
507: }
508:
509: /**
510: * Simplest <code>onSubmitRender</code> version. Called by the default implementation
511: * of the <code>onSubmitRender</code> version with command and BindException parameters.
512: * <p>This implementation returns null as ModelAndView, making the calling
513: * onSubmitRender method perform its default rendering of the success view.
514: * <p>Subclasses can override this to provide custom submission handling
515: * that just depends on the command object.
516: * @param command form object with request parameters bound onto it
517: * @return the prepared model and view, or null for default (i.e. successView)
518: * @throws Exception in case of errors
519: * @see #onSubmitRender(Object, BindException)
520: * @see #onSubmitAction(Object)
521: * @see #doSubmitAction
522: * @see #setSuccessView
523: */
524: protected ModelAndView onSubmitRender(Object command)
525: throws Exception {
526: return null;
527: }
528:
529: /**
530: * Simplest <code>onSubmitAction</code> version. Called by the default implementation
531: * of the <code>onSubmitAction</code> version with command and BindException parameters.
532: * <p>This implementation calls <code>doSubmitAction</code>.
533: * <p>Subclasses can override this to provide custom submission handling
534: * that just depends on the command object.
535: * @param command form object with request parameters bound onto it
536: * @throws Exception in case of errors
537: * @see #onSubmitAction(Object, BindException)
538: * @see #onSubmitRender(Object)
539: * @see #doSubmitAction
540: */
541: protected void onSubmitAction(Object command) throws Exception {
542: doSubmitAction(command);
543: }
544:
545: /**
546: * Template method for submit actions. Called by the default implementation
547: * of the simplest onSubmitAction version.
548: * <p><b>This is the preferred submit callback to implement if you want to
549: * perform an action (like storing changes to the database) and then render
550: * the success view with the command and Errors instance as model.</b>
551: * @param command form object with request parameters bound onto it
552: * @throws Exception in case of errors
553: * @see #onSubmitAction(Object)
554: * @see #onSubmitRender(Object)
555: * @see #setSuccessView
556: */
557: protected void doSubmitAction(Object command) throws Exception {
558: }
559:
560: }
|