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 javax.portlet.ActionRequest;
020: import javax.portlet.PortletException;
021: import javax.portlet.PortletRequest;
022: import javax.portlet.PortletSession;
023: import javax.portlet.RenderRequest;
024:
025: import org.springframework.beans.BeanUtils;
026: import org.springframework.beans.PropertyEditorRegistrar;
027: import org.springframework.validation.BindException;
028: import org.springframework.validation.BindingErrorProcessor;
029: import org.springframework.validation.MessageCodesResolver;
030: import org.springframework.validation.ValidationUtils;
031: import org.springframework.validation.Validator;
032: import org.springframework.web.portlet.bind.PortletRequestDataBinder;
033: import org.springframework.web.portlet.handler.PortletSessionRequiredException;
034:
035: /**
036: * <p>Controller implementation which creates an object (the command object) on
037: * receipt of a request and attempts to populate this object with request parameters.</p>
038: *
039: * <p>This controller is the base for all controllers wishing to populate
040: * JavaBeans based on request parameters, validate the content of such
041: * JavaBeans using {@link Validator Validators} and use custom editors (in the form of
042: * {@link java.beans.PropertyEditor PropertyEditors}) to transform
043: * objects into strings and vice versa, for example. Three notions are mentioned here:</p>
044: *
045: * <p><b>Command class:</b><br>
046: * An instance of the command class will be created for each request and populated
047: * with request parameters. A command class can basically be any Java class; the only
048: * requirement is a no-arg constructor. The command class should preferably be a
049: * JavaBean in order to be able to populate bean properties with request parameters.</p>
050: *
051: * <p><b>Populating using request parameters and PropertyEditors:</b><br>
052: * Upon receiving a request, any BaseCommandController will attempt to fill the
053: * command object using the request parameters. This is done using the typical
054: * and well-known JavaBeans property notation. When a request parameter named
055: * <code>'firstName'</code> exists, the framework will attempt to call
056: * <code>setFirstName([value])</code> passing the value of the parameter. Nested properties
057: * are of course supported. For instance a parameter named <code>'address.city'</code>
058: * will result in a <code>getAddress().setCity([value])</code> call on the
059: * command class.</p>
060: *
061: * <p>It's important to realize that you are not limited to String arguments in
062: * your JavaBeans. Using the PropertyEditor-notion as supplied by the
063: * java.beans package, you will be able to transform Strings to Objects and
064: * the other way around. For instance <code>setLocale(Locale loc)</code> is
065: * perfectly possible for a request parameter named <code>locale</code> having
066: * a value of <code>en</code>, as long as you register the appropriate
067: * PropertyEditor in the Controller (see {@link #initBinder initBinder()}
068: * for more information on that matter).</p>
069: *
070: * <p><b>Validators:</b>
071: * After the controller has successfully populated the command object with
072: * parameters from the request, it will use any configured validators to
073: * validate the object. Validation results will be put in a
074: * {@link org.springframework.validation.Errors Errors} object which can be
075: * used in a View to render any input problems.</p>
076: *
077: * <p><b><a name="workflow">Workflow
078: * (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
079: * Since this class is an abstract base class for more specific implementation,
080: * it does not override the <code>handleRequestInternal()</code> methods and also has no
081: * actual workflow. Implementing classes like
082: * {@link AbstractFormController AbstractFormController},
083: * {@link AbstractCommandController AbstractCommandController},
084: * {@link SimpleFormController SimpleFormController} and
085: * {@link AbstractWizardFormController AbstractWizardFormController}
086: * provide actual functionality and workflow.
087: * More information on workflow performed by superclasses can be found
088: * <a href="AbstractController.html#workflow">here</a>.</p>
089: *
090: * <p><b><a name="config">Exposed configuration properties</a>
091: * (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
092: * <table border="1">
093: * <tr>
094: * <td><b>name</b></th>
095: * <td><b>default</b></td>
096: * <td><b>description</b></td>
097: * </tr>
098: * <tr>
099: * <td>commandName</td>
100: * <td>command</td>
101: * <td>the name to use when binding the instantiated command class
102: * to the request</td>
103: * </tr>
104: * <tr>
105: * <td>commandClass</td>
106: * <td><i>null</i></td>
107: * <td>the class to use upon receiving a request and which to fill
108: * using the request parameters. What object is used and whether
109: * or not it should be created is defined by extending classes
110: * and their configuration properties and methods.</td>
111: * </tr>
112: * <tr>
113: * <td>validators</td>
114: * <td><i>null</i></td>
115: * <td>Array of Validator beans. The validator will be called at appropriate
116: * places in the workflow of subclasses (have a look at those for more info)
117: * to validate the command object.</td>
118: * </tr>
119: * <tr>
120: * <td>validator</td>
121: * <td><i>null</i></td>
122: * <td>Short-form property for setting only one Validator bean (usually passed in
123: * using a <ref bean="beanId"/> property.</td>
124: * </tr>
125: * <tr>
126: * <td>validateOnBinding</td>
127: * <td>true</td>
128: * <td>Indicates whether or not to validate the command object after the
129: * object has been populated with request parameters.</td>
130: * </tr>
131: * </table>
132: * </p>
133: *
134: * <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions!
135: *
136: * @author Juergen Hoeller
137: * @author John A. Lewis
138: * @since 2.0
139: */
140: public abstract class BaseCommandController extends AbstractController {
141:
142: /**
143: * Unlike the Servlet version of these classes, we have to deal with the
144: * two-phase nature of the porlet request. To do this, we need to pass
145: * forward the command object and the bind/validation errors that occured
146: * on the command object from the action phase to the render phase.
147: * The only direct way to pass things forward and preserve them for each
148: * render request is through render parameters, but these are limited to
149: * String objects and we need to pass more complicated objects. The only
150: * other way to do this is in the session. The bad thing about using the
151: * session is that we have no way of knowing when we are done re-rendering
152: * the request and so we don't know when we can remove the objects from
153: * the session. So we will end up polluting the session with old objects
154: * when we finally leave the render of this controller and move on to
155: * somthing else. To minimize the pollution, we will use a static string
156: * value as the session attribute name. At least this way we are only ever
157: * leaving one orphaned set behind. The methods that return these names
158: * can be overridden if you want to use a different method, but be aware
159: * of the session pollution that may occur.
160: */
161: private static final String RENDER_COMMAND_SESSION_ATTRIBUTE = "org.springframework.web.portlet.mvc.RenderCommand";
162:
163: private static final String RENDER_ERRORS_SESSION_ATTRIBUTE = "org.springframework.web.portlet.mvc.RenderErrors";
164:
165: public static final String DEFAULT_COMMAND_NAME = "command";
166:
167: private String commandName = DEFAULT_COMMAND_NAME;
168:
169: private Class commandClass;
170:
171: private Validator[] validators;
172:
173: private boolean validateOnBinding = true;
174:
175: private MessageCodesResolver messageCodesResolver;
176:
177: private BindingErrorProcessor bindingErrorProcessor;
178:
179: private PropertyEditorRegistrar[] propertyEditorRegistrars;
180:
181: /**
182: * Set the name of the command in the model.
183: * The command object will be included in the model under this name.
184: */
185: public final void setCommandName(String commandName) {
186: this .commandName = commandName;
187: }
188:
189: /**
190: * Return the name of the command in the model.
191: */
192: public final String getCommandName() {
193: return this .commandName;
194: }
195:
196: /**
197: * Set the command class for this controller.
198: * An instance of this class gets populated and validated on each request.
199: */
200: public final void setCommandClass(Class commandClass) {
201: this .commandClass = commandClass;
202: }
203:
204: /**
205: * Return the command class for this controller.
206: */
207: public final Class getCommandClass() {
208: return this .commandClass;
209: }
210:
211: /**
212: * Set the primary Validator for this controller. The Validator
213: * must support the specified command class. If there are one
214: * or more existing validators set already when this method is
215: * called, only the specified validator will be kept. Use
216: * {@link #setValidators(Validator[])} to set multiple validators.
217: */
218: public final void setValidator(Validator validator) {
219: this .validators = new Validator[] { validator };
220: }
221:
222: /**
223: * @return the primary Validator for this controller.
224: */
225: public final Validator getValidator() {
226: return (validators != null && validators.length > 0 ? validators[0]
227: : null);
228: }
229:
230: /**
231: * Set the Validators for this controller.
232: * The Validator must support the specified command class.
233: */
234: public final void setValidators(Validator[] validators) {
235: this .validators = validators;
236: }
237:
238: /**
239: * Return the Validators for this controller.
240: */
241: public final Validator[] getValidators() {
242: return validators;
243: }
244:
245: /**
246: * Set if the Validator should get applied when binding.
247: */
248: public final void setValidateOnBinding(boolean validateOnBinding) {
249: this .validateOnBinding = validateOnBinding;
250: }
251:
252: /**
253: * Return if the Validator should get applied when binding.
254: */
255: public final boolean isValidateOnBinding() {
256: return validateOnBinding;
257: }
258:
259: /**
260: * Set the strategy to use for resolving errors into message codes.
261: * Applies the given strategy to all data binders used by this controller.
262: * <p>Default is null, i.e. using the default strategy of the data binder.
263: * @see #createBinder
264: * @see org.springframework.validation.DataBinder#setMessageCodesResolver
265: */
266: public final void setMessageCodesResolver(
267: MessageCodesResolver messageCodesResolver) {
268: this .messageCodesResolver = messageCodesResolver;
269: }
270:
271: /**
272: * Return the strategy to use for resolving errors into message codes.
273: */
274: public final MessageCodesResolver getMessageCodesResolver() {
275: return messageCodesResolver;
276: }
277:
278: /**
279: * Set the strategy to use for processing binding errors, that is,
280: * required field errors and <code>PropertyAccessException</code>s.
281: * <p>Default is <code>null</code>, i.e. using the default strategy of
282: * the data binder.
283: * @see #createBinder
284: * @see org.springframework.validation.DataBinder#setBindingErrorProcessor
285: */
286: public final void setBindingErrorProcessor(
287: BindingErrorProcessor bindingErrorProcessor) {
288: this .bindingErrorProcessor = bindingErrorProcessor;
289: }
290:
291: /**
292: * Return the strategy to use for processing binding errors.
293: */
294: public final BindingErrorProcessor getBindingErrorProcessor() {
295: return bindingErrorProcessor;
296: }
297:
298: /**
299: * Specify a single PropertyEditorRegistrar to be applied
300: * to every DataBinder that this controller uses.
301: * <p>Allows for factoring out the registration of PropertyEditors
302: * to separate objects, as an alternative to <code>initBinder</code>.
303: * @see #initBinder
304: */
305: public final void setPropertyEditorRegistrar(
306: PropertyEditorRegistrar propertyEditorRegistrar) {
307: this .propertyEditorRegistrars = new PropertyEditorRegistrar[] { propertyEditorRegistrar };
308: }
309:
310: /**
311: * Specify one or more PropertyEditorRegistrars to be applied
312: * to every DataBinder that this controller uses.
313: * <p>Allows for factoring out the registration of PropertyEditors
314: * to separate objects, as alternative to <code>initBinder</code>.
315: * @see #initBinder
316: */
317: public final void setPropertyEditorRegistrars(
318: PropertyEditorRegistrar[] propertyEditorRegistrars) {
319: this .propertyEditorRegistrars = propertyEditorRegistrars;
320: }
321:
322: /**
323: * Return the PropertyEditorRegistrars to be applied
324: * to every DataBinder that this controller uses.
325: */
326: public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
327: return propertyEditorRegistrars;
328: }
329:
330: protected void initApplicationContext() {
331: if (this .validators != null) {
332: for (int i = 0; i < this .validators.length; i++) {
333: if (this .commandClass != null
334: && !this .validators[i]
335: .supports(this .commandClass))
336: throw new IllegalArgumentException("Validator ["
337: + this .validators[i]
338: + "] does not support command class ["
339: + this .commandClass.getName() + "]");
340: }
341: }
342: }
343:
344: /**
345: * Retrieve a command object for the given request.
346: * <p>Default implementation calls <code>createCommand</code>.
347: * Subclasses can override this.
348: * @param request current portlet request
349: * @return object command to bind onto
350: * @see #createCommand
351: */
352: protected Object getCommand(PortletRequest request)
353: throws Exception {
354: return createCommand();
355: }
356:
357: /**
358: * Create a new command instance for the command class of this controller.
359: * <p>This implementation uses <code>BeanUtils.instantiateClass</code>,
360: * so the command needs to have a no-arg constructor (supposed to be
361: * public, but not required to).
362: * @return the new command instance
363: * @throws Exception if the command object could not be instantiated
364: * @see org.springframework.beans.BeanUtils#instantiateClass(Class)
365: */
366: protected final Object createCommand() throws Exception {
367: if (this .commandClass == null) {
368: throw new IllegalStateException(
369: "Cannot create command without commandClass being set - "
370: + "either set commandClass or (in a form controller) override formBackingObject");
371: }
372: if (logger.isDebugEnabled()) {
373: logger.debug("Creating new command of class ["
374: + this .commandClass.getName() + "]");
375: }
376: return BeanUtils.instantiateClass(this .commandClass);
377: }
378:
379: /**
380: * Check if the given command object is a valid for this controller,
381: * i.e. its command class.
382: * @param command the command object to check
383: * @return if the command object is valid for this controller
384: */
385: protected final boolean checkCommand(Object command) {
386: return (this .commandClass == null || this .commandClass
387: .isInstance(command));
388: }
389:
390: /**
391: * Bind the parameters of the given request to the given command object.
392: * @param request current portlet request
393: * @param command the command to bind onto
394: * @return the PortletRequestDataBinder instance for additional custom validation
395: * @throws Exception in case of invalid state or arguments
396: */
397: protected final PortletRequestDataBinder bindAndValidate(
398: PortletRequest request, Object command) throws Exception {
399:
400: PortletRequestDataBinder binder = createBinder(request, command);
401: if (!suppressBinding(request)) {
402: binder.bind(request);
403: BindException errors = new BindException(binder
404: .getBindingResult());
405: onBind(request, command, errors);
406: if (this .validators != null && isValidateOnBinding()
407: && !suppressValidation(request)) {
408: for (int i = 0; i < this .validators.length; i++) {
409: ValidationUtils.invokeValidator(this .validators[i],
410: command, errors);
411: }
412: }
413: onBindAndValidate(request, command, errors);
414: }
415: return binder;
416: }
417:
418: /**
419: * Return whether to suppress binding for the given request.
420: * <p>Default implementation always returns "false". Can be overridden
421: * in subclasses to suppress validation, for example, if a special
422: * request parameter is set.
423: * @param request current portlet request
424: * @return whether to suppress binding for the given request
425: * @see #suppressValidation
426: */
427: protected boolean suppressBinding(PortletRequest request) {
428: return false;
429: }
430:
431: /**
432: * Create a new binder instance for the given command and request.
433: * <p>Called by <code>bindAndValidate</code>. Can be overridden to plug in
434: * custom PortletRequestDataBinder instances.
435: * <p>The default implementation creates a standard PortletRequestDataBinder and
436: * invokes <code>prepareBinder</code> and <code>initBinder</code>.
437: * <p>Note that neither <code>prepareBinder</code> nor <code>initBinder</code>
438: * will be invoked automatically if you override this method! Call those methods
439: * at appropriate points of your overridden method.
440: * @param request current portlet request
441: * @param command the command to bind onto
442: * @return the new binder instance
443: * @throws Exception in case of invalid state or arguments
444: * @see #bindAndValidate
445: * @see #prepareBinder
446: * @see #initBinder
447: */
448: protected PortletRequestDataBinder createBinder(
449: PortletRequest request, Object command) throws Exception {
450:
451: PortletRequestDataBinder binder = new PortletRequestDataBinder(
452: command, getCommandName());
453: prepareBinder(binder);
454: initBinder(request, binder);
455: return binder;
456: }
457:
458: /**
459: * Prepare the given binder, applying the specified MessageCodesResolver,
460: * BindingErrorProcessor and PropertyEditorRegistrars (if any).
461: * Called by <code>createBinder</code>.
462: * @param binder the new binder instance
463: * @see #createBinder
464: * @see #setMessageCodesResolver
465: * @see #setBindingErrorProcessor
466: */
467: protected final void prepareBinder(PortletRequestDataBinder binder) {
468: if (useDirectFieldAccess()) {
469: binder.initDirectFieldAccess();
470: }
471: if (this .messageCodesResolver != null) {
472: binder.setMessageCodesResolver(this .messageCodesResolver);
473: }
474: if (this .bindingErrorProcessor != null) {
475: binder.setBindingErrorProcessor(this .bindingErrorProcessor);
476: }
477: if (this .propertyEditorRegistrars != null) {
478: for (int i = 0; i < this .propertyEditorRegistrars.length; i++) {
479: this .propertyEditorRegistrars[i]
480: .registerCustomEditors(binder);
481: }
482: }
483: }
484:
485: /**
486: * Determine whether to use direct field access instead of bean property access.
487: * Applied by <code>prepareBinder</code>.
488: * <p>Default is "false". Can be overridden in subclasses.
489: * @see #prepareBinder
490: * @see org.springframework.validation.DataBinder#initDirectFieldAccess()
491: */
492: protected boolean useDirectFieldAccess() {
493: return false;
494: }
495:
496: /**
497: * Initialize the given binder instance, for example with custom editors.
498: * Called by <code>createBinder</code>.
499: * <p>This method allows you to register custom editors for certain fields of your
500: * command class. For instance, you will be able to transform Date objects into a
501: * String pattern and back, in order to allow your JavaBeans to have Date properties
502: * and still be able to set and display them in an HTML interface.
503: * <p>Default implementation is empty.
504: * @param request current portlet request
505: * @param binder new binder instance
506: * @throws Exception in case of invalid state or arguments
507: * @see #createBinder
508: * @see org.springframework.validation.DataBinder#registerCustomEditor
509: * @see org.springframework.beans.propertyeditors.CustomDateEditor
510: */
511: protected void initBinder(PortletRequest request,
512: PortletRequestDataBinder binder) throws Exception {
513: }
514:
515: /**
516: * Callback for custom post-processing in terms of binding.
517: * Called on each submit, after standard binding but before validation.
518: * <p>Default implementation delegates to <code>onBind(request, command)</code>.
519: * @param request current portlet request
520: * @param command the command object to perform further binding on
521: * @param errors validation errors holder, allowing for additional
522: * custom registration of binding errors
523: * @throws Exception in case of invalid state or arguments
524: * @see #bindAndValidate
525: * @see #onBind(PortletRequest, Object)
526: */
527: protected void onBind(PortletRequest request, Object command,
528: BindException errors) throws Exception {
529:
530: onBind(request, command);
531: }
532:
533: /**
534: * Callback for custom post-processing in terms of binding.
535: * Called by the default implementation of the <code>onBind</code> version with
536: * all parameters, after standard binding but before validation.
537: * <p>Default implementation is empty.
538: * @param request current portlet request
539: * @param command the command object to perform further binding on
540: * @throws Exception in case of invalid state or arguments
541: * @see #onBind(PortletRequest, Object, BindException)
542: */
543: protected void onBind(PortletRequest request, Object command)
544: throws Exception {
545: }
546:
547: /**
548: * Return whether to suppress validation for the given request.
549: * <p>Default implementation always returns "false". Can be overridden
550: * in subclasses to suppress validation, for example, if a special
551: * request parameter is set.
552: * @param request current portlet request
553: * @return whether to suppress validation for the given request
554: */
555: protected boolean suppressValidation(PortletRequest request) {
556: return false;
557: }
558:
559: /**
560: * Callback for custom post-processing in terms of binding and validation.
561: * Called on each submit, after standard binding and validation,
562: * but before error evaluation.
563: * <p>Default implementation is empty.
564: * @param request current portlet request
565: * @param command the command object, still allowing for further binding
566: * @param errors validation errors holder, allowing for additional
567: * custom validation
568: * @throws Exception in case of invalid state or arguments
569: * @see #bindAndValidate
570: * @see org.springframework.validation.Errors
571: */
572: protected void onBindAndValidate(PortletRequest request,
573: Object command, BindException errors) throws Exception {
574: }
575:
576: /**
577: * Return the name of the session attribute that holds
578: * the render phase command object for this form controller.
579: * @return the name of the render phase command object session attribute
580: * @see javax.portlet.PortletSession#getAttribute
581: */
582: protected String getRenderCommandSessionAttributeName() {
583: return RENDER_COMMAND_SESSION_ATTRIBUTE;
584: }
585:
586: /**
587: * Return the name of the session attribute that holds
588: * the render phase command object for this form controller.
589: * @return the name of the render phase command object session attribute
590: * @see javax.portlet.PortletSession#getAttribute
591: */
592: protected String getRenderErrorsSessionAttributeName() {
593: return RENDER_ERRORS_SESSION_ATTRIBUTE;
594: }
595:
596: /**
597: * Get the command object cached for the render phase.
598: * @see #getRenderErrors
599: * @see #getRenderCommandSessionAttributeName
600: * @see #setRenderCommandAndErrors
601: */
602: protected final Object getRenderCommand(RenderRequest request)
603: throws PortletException {
604: PortletSession session = request.getPortletSession(false);
605: if (session == null) {
606: throw new PortletSessionRequiredException(
607: "Could not obtain portlet session");
608: }
609: Object command = session
610: .getAttribute(getRenderCommandSessionAttributeName());
611: if (command == null) {
612: throw new PortletSessionRequiredException(
613: "Could not obtain command object from portlet session");
614: }
615: return command;
616: }
617:
618: /**
619: * Get the bind and validation errors cached for the render phase.
620: * @see #getRenderCommand
621: * @see #getRenderErrorsSessionAttributeName
622: * @see #setRenderCommandAndErrors
623: */
624: protected final BindException getRenderErrors(RenderRequest request)
625: throws PortletException {
626: PortletSession session = request.getPortletSession(false);
627: if (session == null) {
628: throw new PortletSessionRequiredException(
629: "Could not obtain portlet session");
630: }
631: BindException errors = (BindException) session
632: .getAttribute(getRenderErrorsSessionAttributeName());
633: if (errors == null) {
634: throw new PortletSessionRequiredException(
635: "Could not obtain errors object from portlet session");
636: }
637: return errors;
638: }
639:
640: /**
641: * Set the command object and errors object for the render phase.
642: * @param request the current action request
643: * @param command the command object to preserve for the render phase
644: * @param errors the errors from binding and validation to preserve for the render phase
645: * @see #getRenderCommand
646: * @see #getRenderErrors
647: * @see #getRenderCommandSessionAttributeName
648: * @see #getRenderErrorsSessionAttributeName
649: */
650: protected final void setRenderCommandAndErrors(
651: ActionRequest request, Object command, BindException errors)
652: throws Exception {
653:
654: logger
655: .debug("Storing command and error objects in session for render phase");
656: PortletSession session = request.getPortletSession();
657: session.setAttribute(getRenderCommandSessionAttributeName(),
658: command);
659: session.setAttribute(getRenderErrorsSessionAttributeName(),
660: errors);
661: }
662:
663: }
|