KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > portlet > mvc > BaseCommandController


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.web.portlet.mvc;
18
19 import javax.portlet.ActionRequest;
20 import javax.portlet.PortletException;
21 import javax.portlet.PortletRequest;
22 import javax.portlet.PortletSession;
23 import javax.portlet.RenderRequest;
24
25 import org.springframework.beans.BeanUtils;
26 import org.springframework.beans.PropertyEditorRegistrar;
27 import org.springframework.validation.BindException;
28 import org.springframework.validation.BindingErrorProcessor;
29 import org.springframework.validation.MessageCodesResolver;
30 import org.springframework.validation.ValidationUtils;
31 import org.springframework.validation.Validator;
32 import org.springframework.web.portlet.bind.PortletRequestDataBinder;
33 import org.springframework.web.portlet.handler.PortletSessionRequiredException;
34
35 /**
36  * <p>Controller implementation which creates an object (the command object) on
37  * receipt of a request and attempts to populate this object with request parameters.</p>
38  *
39  * <p>This controller is the base for all controllers wishing to populate
40  * JavaBeans based on request parameters, validate the content of such
41  * JavaBeans using {@link Validator Validators} and use custom editors (in the form of
42  * {@link java.beans.PropertyEditor PropertyEditors}) to transform
43  * objects into strings and vice versa, for example. Three notions are mentioned here:</p>
44  *
45  * <p><b>Command class:</b><br>
46  * An instance of the command class will be created for each request and populated
47  * with request parameters. A command class can basically be any Java class; the only
48  * requirement is a no-arg constructor. The command class should preferably be a
49  * JavaBean in order to be able to populate bean properties with request parameters.</p>
50  *
51  * <p><b>Populating using request parameters and PropertyEditors:</b><br>
52  * Upon receiving a request, any BaseCommandController will attempt to fill the
53  * command object using the request parameters. This is done using the typical
54  * and well-known JavaBeans property notation. When a request parameter named
55  * <code>'firstName'</code> exists, the framework will attempt to call
56  * <code>setFirstName([value])</code> passing the value of the parameter. Nested properties
57  * are of course supported. For instance a parameter named <code>'address.city'</code>
58  * will result in a <code>getAddress().setCity([value])</code> call on the
59  * command class.</p>
60  *
61  * <p>It's important to realize that you are not limited to String arguments in
62  * your JavaBeans. Using the PropertyEditor-notion as supplied by the
63  * java.beans package, you will be able to transform Strings to Objects and
64  * the other way around. For instance <code>setLocale(Locale loc)</code> is
65  * perfectly possible for a request parameter named <code>locale</code> having
66  * a value of <code>en</code>, as long as you register the appropriate
67  * PropertyEditor in the Controller (see {@link #initBinder initBinder()}
68  * for more information on that matter).</p>
69  *
70  * <p><b>Validators:</b>
71  * After the controller has successfully populated the command object with
72  * parameters from the request, it will use any configured validators to
73  * validate the object. Validation results will be put in a
74  * {@link org.springframework.validation.Errors Errors} object which can be
75  * used in a View to render any input problems.</p>
76  *
77  * <p><b><a name="workflow">Workflow
78  * (<a HREF="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
79  * Since this class is an abstract base class for more specific implementation,
80  * it does not override the <code>handleRequestInternal()</code> methods and also has no
81  * actual workflow. Implementing classes like
82  * {@link AbstractFormController AbstractFormController},
83  * {@link AbstractCommandController AbstractCommandController},
84  * {@link SimpleFormController SimpleFormController} and
85  * {@link AbstractWizardFormController AbstractWizardFormController}
86  * provide actual functionality and workflow.
87  * More information on workflow performed by superclasses can be found
88  * <a HREF="AbstractController.html#workflow">here</a>.</p>
89  *
90  * <p><b><a name="config">Exposed configuration properties</a>
91  * (<a HREF="AbstractController.html#config">and those defined by superclass</a>):</b><br>
92  * <table border="1">
93  * <tr>
94  * <td><b>name</b></th>
95  * <td><b>default</b></td>
96  * <td><b>description</b></td>
97  * </tr>
98  * <tr>
99  * <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 &lt;ref bean="beanId"/&gt; 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  * @author Juergen Hoeller
135  * @author Rainer Schmitz
136  * @author John A. Lewis
137  * @since 2.0
138  */

139 public abstract class BaseCommandController extends AbstractController {
140
141     /**
142      * Unlike the Servlet version of these classes, we have to deal with the
143      * two-phase nature of the porlet request. To do this, we need to pass
144      * forward the command object and the bind/validation errors that occured
145      * on the command object from the action phase to the render phase.
146      * The only direct way to pass things forward and preserve them for each
147      * render request is through render parameters, but these are limited to
148      * String objects and we need to pass more complicated objects. The only
149      * other way to do this is in the session. The bad thing about using the
150      * session is that we have no way of knowing when we are done re-rendering
151      * the request and so we don't know when we can remove the objects from
152      * the session. So we will end up polluting the session with old objects
153      * when we finally leave the render of this controller and move on to
154      * somthing else. To minimize the pollution, we will use a static string
155      * value as the session attribute name. At least this way we are only ever
156      * leaving one orphaned set behind. The methods that return these names
157      * can be overridden if you want to use a different method, but be aware
158      * of the session pollution that may occur.
159      */

160     private static final String JavaDoc RENDER_COMMAND_SESSION_ATTRIBUTE =
161             "org.springframework.web.portlet.mvc.RenderCommand";
162
163     private static final String JavaDoc RENDER_ERRORS_SESSION_ATTRIBUTE =
164             "org.springframework.web.portlet.mvc.RenderErrors";
165
166     public static final String JavaDoc DEFAULT_COMMAND_NAME = "command";
167
168
169     private String JavaDoc commandName = DEFAULT_COMMAND_NAME;
170     
171     private Class JavaDoc commandClass;
172     
173     private Validator[] validators;
174     
175     private boolean validateOnBinding = true;
176     
177     private MessageCodesResolver messageCodesResolver;
178
179     private BindingErrorProcessor bindingErrorProcessor;
180
181     private PropertyEditorRegistrar[] propertyEditorRegistrars;
182
183
184     /**
185      * Set the name of the command in the model.
186      * The command object will be included in the model under this name.
187      */

188     public final void setCommandName(String JavaDoc commandName) {
189         this.commandName = commandName;
190     }
191
192     /**
193      * Return the name of the command in the model.
194      */

195     public final String JavaDoc getCommandName() {
196         return this.commandName;
197     }
198
199     /**
200      * Set the command class for this controller.
201      * An instance of this class gets populated and validated on each request.
202      */

203     public final void setCommandClass(Class JavaDoc commandClass) {
204         this.commandClass = commandClass;
205     }
206
207     /**
208      * Return the command class for this controller.
209      */

210     public final Class JavaDoc getCommandClass() {
211         return this.commandClass;
212     }
213
214     /**
215      * Set the primary Validator for this controller. The Validator
216      * must support the specified command class. If there are one
217      * or more existing validators set already when this method is
218      * called, only the specified validator will be kept. Use
219      * {@link #setValidators(Validator[])} to set multiple validators.
220      */

221     public final void setValidator(Validator validator) {
222         this.validators = new Validator[] {validator};
223     }
224
225     /**
226      * @return the primary Validator for this controller.
227      */

228     public final Validator getValidator() {
229         return (validators != null && validators.length > 0 ? validators[0] : null);
230     }
231
232     /**
233      * Set the Validators for this controller.
234      * The Validator must support the specified command class.
235      */

236     public final void setValidators(Validator[] validators) {
237         this.validators = validators;
238     }
239
240     /**
241      * Return the Validators for this controller.
242      */

243     public final Validator[] getValidators() {
244         return validators;
245     }
246
247     /**
248      * Set if the Validator should get applied when binding.
249      */

250     public final void setValidateOnBinding(boolean validateOnBinding) {
251         this.validateOnBinding = validateOnBinding;
252     }
253
254     /**
255      * Return if the Validator should get applied when binding.
256      */

257     public final boolean isValidateOnBinding() {
258         return validateOnBinding;
259     }
260
261     /**
262      * Set the strategy to use for resolving errors into message codes.
263      * Applies the given strategy to all data binders used by this controller.
264      * <p>Default is null, i.e. using the default strategy of the data binder.
265      * @see #createBinder
266      * @see org.springframework.validation.DataBinder#setMessageCodesResolver
267      */

268     public final void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
269         this.messageCodesResolver = messageCodesResolver;
270     }
271
272     /**
273      * Return the strategy to use for resolving errors into message codes.
274      */

275     public final MessageCodesResolver getMessageCodesResolver() {
276         return messageCodesResolver;
277     }
278
279     /**
280      * Set the strategy to use for processing binding errors, that is,
281      * required field errors and <code>PropertyAccessException</code>s.
282      * <p>Default is <code>null</code>, i.e. using the default strategy of
283      * the data binder.
284      * @see #createBinder
285      * @see org.springframework.validation.DataBinder#setBindingErrorProcessor
286      */

287     public final void setBindingErrorProcessor(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(PropertyEditorRegistrar propertyEditorRegistrar) {
306         this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
307     }
308
309     /**
310      * Specify one or more PropertyEditorRegistrars to be applied
311      * to every DataBinder that this controller uses.
312      * <p>Allows for factoring out the registration of PropertyEditors
313      * to separate objects, as alternative to <code>initBinder</code>.
314      * @see #initBinder
315      */

316     public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
317         this.propertyEditorRegistrars = propertyEditorRegistrars;
318     }
319
320     /**
321      * Return the PropertyEditorRegistrars to be applied
322      * to every DataBinder that this controller uses.
323      */

324     public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
325         return propertyEditorRegistrars;
326     }
327
328
329     protected void initApplicationContext() {
330         if (this.validators != null) {
331             for (int i = 0; i < this.validators.length; i++) {
332                 if (this.commandClass != null && !this.validators[i].supports(this.commandClass))
333                     throw new IllegalArgumentException JavaDoc("Validator [" + this.validators[i] +
334                             "] does not support command class [" +
335                             this.commandClass.getName() + "]");
336             }
337         }
338     }
339
340
341     /**
342      * Retrieve a command object for the given request.
343      * <p>Default implementation calls <code>createCommand</code>.
344      * Subclasses can override this.
345      * @param request current portlet request
346      * @return object command to bind onto
347      * @see #createCommand
348      */

349     protected Object JavaDoc getCommand(PortletRequest request) throws Exception JavaDoc {
350         return createCommand();
351     }
352
353     /**
354      * Create a new command instance for the command class of this controller.
355      * <p>This implementation uses <code>BeanUtils.instantiateClass</code>,
356      * so the command needs to have a no-arg constructor (supposed to be
357      * public, but not required to).
358      * @return the new command instance
359      * @throws Exception if the command object could not be instantiated
360      * @see org.springframework.beans.BeanUtils#instantiateClass(Class)
361      */

362     protected final Object JavaDoc createCommand() throws Exception JavaDoc {
363         if (this.commandClass == null) {
364             throw new IllegalStateException JavaDoc("Cannot create command without commandClass being set - " +
365                     "either set commandClass or (in a form controller) override formBackingObject");
366         }
367         if (logger.isDebugEnabled()) {
368             logger.debug("Creating new command of class [" + this.commandClass.getName() + "]");
369         }
370         return BeanUtils.instantiateClass(this.commandClass);
371     }
372
373     /**
374      * Check if the given command object is a valid for this controller,
375      * i.e. its command class.
376      * @param command the command object to check
377      * @return if the command object is valid for this controller
378      */

379     protected final boolean checkCommand(Object JavaDoc command) {
380         return (this.commandClass == null || this.commandClass.isInstance(command));
381     }
382     
383
384     /**
385      * Bind the parameters of the given request to the given command object.
386      * @param request current portlet request
387      * @param command the command to bind onto
388      * @return the PortletRequestDataBinder instance for additional custom validation
389      * @throws Exception in case of invalid state or arguments
390      */

391     protected final PortletRequestDataBinder bindAndValidate(PortletRequest request, Object JavaDoc command)
392             throws Exception JavaDoc {
393                 
394         PortletRequestDataBinder binder = createBinder(request, command);
395         if (!suppressBinding(request)) {
396             binder.bind(request);
397             BindException errors = new BindException(binder.getBindingResult());
398             onBind(request, command, errors);
399             if (this.validators != null && isValidateOnBinding() && !suppressValidation(request)) {
400                 for (int i = 0; i < this.validators.length; i++) {
401                     ValidationUtils.invokeValidator(this.validators[i], command, errors);
402                 }
403             }
404             onBindAndValidate(request, command, errors);
405         }
406         return binder;
407     }
408
409     /**
410      * Return whether to suppress binding for the given request.
411      * <p>Default implementation always returns "false". Can be overridden
412      * in subclasses to suppress validation, for example, if a special
413      * request parameter is set.
414      * @param request current portlet request
415      * @return whether to suppress binding for the given request
416      * @see #suppressValidation
417      */

418     protected boolean suppressBinding(PortletRequest request) {
419         return false;
420     }
421
422     /**
423      * Create a new binder instance for the given command and request.
424      * <p>Called by <code>bindAndValidate</code>. Can be overridden to plug in
425      * custom PortletRequestDataBinder instances.
426      * <p>The default implementation creates a standard PortletRequestDataBinder and
427      * invokes <code>prepareBinder</code> and <code>initBinder</code>.
428      * <p>Note that neither <code>prepareBinder</code> nor <code>initBinder</code>
429      * will be invoked automatically if you override this method! Call those methods
430      * at appropriate points of your overridden method.
431      * @param request current portlet request
432      * @param command the command to bind onto
433      * @return the new binder instance
434      * @throws Exception in case of invalid state or arguments
435      * @see #bindAndValidate
436      * @see #prepareBinder
437      * @see #initBinder
438      */

439     protected PortletRequestDataBinder createBinder(PortletRequest request, Object JavaDoc command)
440             throws Exception JavaDoc {
441             
442         PortletRequestDataBinder binder = new PortletRequestDataBinder(command, getCommandName());
443         prepareBinder(binder);
444         initBinder(request, binder);
445         return binder;
446     }
447
448     /**
449      * Prepare the given binder, applying the specified MessageCodesResolver,
450      * BindingErrorProcessor and PropertyEditorRegistrars (if any).
451      * Called by <code>createBinder</code>.
452      * @param binder the new binder instance
453      * @see #createBinder
454      * @see #setMessageCodesResolver
455      * @see #setBindingErrorProcessor
456      */

457     protected final void prepareBinder(PortletRequestDataBinder binder) {
458         if (useDirectFieldAccess()) {
459             binder.initDirectFieldAccess();
460         }
461         if (this.messageCodesResolver != null) {
462             binder.setMessageCodesResolver(this.messageCodesResolver);
463         }
464         if (this.bindingErrorProcessor != null) {
465             binder.setBindingErrorProcessor(this.bindingErrorProcessor);
466         }
467         if (this.propertyEditorRegistrars != null) {
468             for (int i = 0; i < this.propertyEditorRegistrars.length; i++) {
469                 this.propertyEditorRegistrars[i].registerCustomEditors(binder);
470             }
471         }
472     }
473
474     /**
475      * Determine whether to use direct field access instead of bean property access.
476      * Applied by <code>prepareBinder</code>.
477      * <p>Default is "false". Can be overridden in subclasses.
478      * @see #prepareBinder
479      * @see org.springframework.validation.DataBinder#initDirectFieldAccess()
480      */

481     protected boolean useDirectFieldAccess() {
482         return false;
483     }
484
485     /**
486      * Initialize the given binder instance, for example with custom editors.
487      * Called by <code>createBinder</code>.
488      * <p>This method allows you to register custom editors for certain fields of your
489      * command class. For instance, you will be able to transform Date objects into a
490      * String pattern and back, in order to allow your JavaBeans to have Date properties
491      * and still be able to set and display them in an HTML interface.
492      * <p>Default implementation is empty.
493      * @param request current portlet request
494      * @param binder new binder instance
495      * @throws Exception in case of invalid state or arguments
496      * @see #createBinder
497      * @see org.springframework.validation.DataBinder#registerCustomEditor
498      * @see org.springframework.beans.propertyeditors.CustomDateEditor
499      */

500     protected void initBinder(PortletRequest request, PortletRequestDataBinder binder)
501             throws Exception JavaDoc {
502     }
503
504     /**
505      * Callback for custom post-processing in terms of binding.
506      * Called on each submit, after standard binding but before validation.
507      * <p>Default implementation delegates to <code>onBind(request, command)</code>.
508      * @param request current portlet request
509      * @param command the command object to perform further binding on
510      * @param errors validation errors holder, allowing for additional
511      * custom registration of binding errors
512      * @throws Exception in case of invalid state or arguments
513      * @see #bindAndValidate
514      * @see #onBind(PortletRequest, Object)
515      */

516     protected void onBind(PortletRequest request, Object JavaDoc command, BindException errors)
517             throws Exception JavaDoc {
518         
519         onBind(request, command);
520     }
521
522     /**
523      * Callback for custom post-processing in terms of binding.
524      * Called by the default implementation of the <code>onBind</code> version with
525      * all parameters, after standard binding but before validation.
526      * <p>Default implementation is empty.
527      * @param request current portlet request
528      * @param command the command object to perform further binding on
529      * @throws Exception in case of invalid state or arguments
530      * @see #onBind(PortletRequest, Object, BindException)
531      */

532     protected void onBind(PortletRequest request, Object JavaDoc command) throws Exception JavaDoc {
533     }
534
535     /**
536      * Return whether to suppress validation for the given request.
537      * <p>Default implementation always returns "false". Can be overridden
538      * in subclasses to suppress validation, for example, if a special
539      * request parameter is set.
540      * @param request current portlet request
541      * @return whether to suppress validation for the given request
542      */

543     protected boolean suppressValidation(PortletRequest request) {
544         return false;
545     }
546
547     /**
548      * Callback for custom post-processing in terms of binding and validation.
549      * Called on each submit, after standard binding and validation,
550      * but before error evaluation.
551      * <p>Default implementation is empty.
552      * @param request current portlet request
553      * @param command the command object, still allowing for further binding
554      * @param errors validation errors holder, allowing for additional
555      * custom validation
556      * @throws Exception in case of invalid state or arguments
557      * @see #bindAndValidate
558      * @see org.springframework.validation.Errors
559      */

560     protected void onBindAndValidate(PortletRequest request, Object JavaDoc command, BindException errors)
561             throws Exception JavaDoc {
562     }
563
564
565     /**
566      * Return the name of the session attribute that holds
567      * the render phase command object for this form controller.
568      * @return the name of the render phase command object session attribute
569      * @see javax.portlet.PortletSession#getAttribute
570      */

571     protected String JavaDoc getRenderCommandSessionAttributeName() {
572         return RENDER_COMMAND_SESSION_ATTRIBUTE;
573     }
574
575     /**
576      * Return the name of the session attribute that holds
577      * the render phase command object for this form controller.
578      * @return the name of the render phase command object session attribute
579      * @see javax.portlet.PortletSession#getAttribute
580      */

581     protected String JavaDoc getRenderErrorsSessionAttributeName() {
582         return RENDER_ERRORS_SESSION_ATTRIBUTE;
583     }
584
585     /**
586      * Get the command object cached for the render phase.
587      * @see #getRenderErrors
588      * @see #getRenderCommandSessionAttributeName
589      * @see #setRenderCommandAndErrors
590      */

591     protected final Object JavaDoc getRenderCommand(RenderRequest request) throws PortletException {
592         PortletSession session = request.getPortletSession(false);
593         if (session == null) {
594             throw new PortletSessionRequiredException("Could not obtain portlet session");
595         }
596         Object JavaDoc command = session.getAttribute(getRenderCommandSessionAttributeName());
597         if (command == null) {
598             throw new PortletSessionRequiredException("Could not obtain command object from portlet session");
599         }
600         return command;
601     }
602
603     /**
604      * Get the bind and validation errors cached for the render phase.
605      * @see #getRenderCommand
606      * @see #getRenderErrorsSessionAttributeName
607      * @see #setRenderCommandAndErrors
608      */

609     protected final BindException getRenderErrors(RenderRequest request) throws PortletException {
610         PortletSession session = request.getPortletSession(false);
611         if (session == null) {
612             throw new PortletSessionRequiredException("Could not obtain portlet session");
613         }
614         BindException errors = (BindException) session.getAttribute(getRenderErrorsSessionAttributeName());
615         if (errors == null) {
616             throw new PortletSessionRequiredException("Could not obtain errors object from portlet session");
617         }
618         return errors;
619     }
620
621     /**
622      * Set the command object and errors object for the render phase.
623      * @param request the current action request
624      * @param command the command object to preserve for the render phase
625      * @param errors the errors from binding and validation to preserve for the render phase
626      * @see #getRenderCommand
627      * @see #getRenderErrors
628      * @see #getRenderCommandSessionAttributeName
629      * @see #getRenderErrorsSessionAttributeName
630      */

631     protected final void setRenderCommandAndErrors(
632             ActionRequest request, Object JavaDoc command, BindException errors) throws Exception JavaDoc {
633
634         logger.debug("Storing command and error objects in session for render phase");
635         PortletSession session = request.getPortletSession();
636         session.setAttribute(getRenderCommandSessionAttributeName(), command);
637         session.setAttribute(getRenderErrorsSessionAttributeName(), errors);
638     }
639
640 }
641
Popular Tags