1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 package nl.openedge.baritus;
32
33 import java.util.ArrayList;
34 import java.util.Date;
35 import java.util.Enumeration;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.Map;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43
44 import javax.servlet.ServletException;
45 import javax.servlet.http.HttpServletRequest;
46 import javax.servlet.http.HttpServletResponse;
47 import javax.servlet.http.HttpSession;
48
49 import nl.openedge.baritus.interceptors.FlowException;
50 import nl.openedge.baritus.interceptors.Interceptor;
51 import nl.openedge.baritus.interceptors.ReturnNowFlowException;
52 import nl.openedge.baritus.population.FieldPopulator;
53 import nl.openedge.baritus.util.MessageUtils;
54 import nl.openedge.baritus.util.MultiHashMap;
55 import nl.openedge.baritus.util.ValueUtils;
56 import nl.openedge.baritus.validation.FieldValidator;
57 import nl.openedge.baritus.validation.FormValidator;
58 import nl.openedge.baritus.validation.ValidationActivationRule;
59
60 import org.apache.commons.logging.Log;
61 import org.apache.commons.logging.LogFactory;
62 import org.infohazard.maverick.flow.Controller;
63 import org.infohazard.maverick.flow.ControllerContext;
64
65 /***
66 * FormBeanBase is the class that does the real work within Baritus.
67 * Usually, you should extend the singleton implementation FormBeanCtrl.
68 * However, if you want to have behaviour like Maverick's ThrowawayFormBeanUser
69 * (a new instance of the controller is created on each request), you
70 * can extend from this class directly. Note that as method init(Node) is
71 * specific for the ControllerSingleton that is implemented in FormBeanCtrl,
72 * you do not have this method at your disposal here, hence you should do
73 * initialisation in your constructor instead.
74 *
75 * @author Eelco Hillenius
76 */
77 public abstract class FormBeanCtrlBase implements Controller
78 {
79
80
81
82 /*** Common name for the typical "success" view. */
83 public static final String SUCCESS = "success";
84
85 /*** Common name for the typical "logon" view. */
86 public static final String LOGON = "logon";
87
88 /*** Common name for the typical "error" view. */
89 public static final String ERROR = "error";
90
91 /*** Common name for the "redirect" view. */
92 public static final String REDIRECT = "doredirect";
93
94 /*** session key for the current locale. */
95 public static final String SESSION_KEY_CURRENT_LOCALE = "_currentLocale";
96
97 /***
98 * Key for request attribute that is used to store the formbean context.
99 * This key is used to be able to reuse the formbean context with
100 * multiple commands within the same request without having to figure out
101 * what the last Maverick beanName was (it is possible to override the name
102 * of the bean - 'model' by default - that is stored in the request by Maverick
103 * for each view). Not intended for use outside this class.
104 */
105 public static final String REQUEST_ATTRIBUTE_FORMBEANCONTEXT = "__formBeanContext";
106
107 /***
108 * Key for request attribute that is used to store the execution parameters
109 * for the current request. Not intended for use outside this class.
110 */
111 public static final String REQUEST_ATTRIBUTE_EXECUTION_PARAMS = "__executionParams";
112
113
114
115 /*** log for general stuff. */
116 private static Log log = LogFactory.getLog(LogConstants.BARITUS_LOG);
117
118 /*** population log. */
119 private static Log populationLog = LogFactory.getLog(LogConstants.POPULATION_LOG);
120
121 /*** exeuction params log. */
122 private static Log executionParamsLog =
123 LogFactory.getLog(LogConstants.EXECUTION_PARAMS_LOG);
124
125
126
127 /*** registry for populators by field name and the default populator. */
128 private PopulatorRegistry populatorRegistry = new PopulatorRegistry(this);
129
130 /*** registry for form validators, field validators and rules that apply to them. */
131 private ValidatorRegistry validatorRegistry = new ValidatorRegistry();
132
133 /*** default delegate for validation. Users of this framework can add
134 validator delegates if they want, e.g. ones that are based on
135 commons validator or formproc. This instance of ValidatorDelegate however,
136 will allways be executed (first). */
137 private ValidatorDelegate defaultValidatorDelegate = new DefaultValidatorDelegate(validatorRegistry, this);
138
139 /*** validator delegates. */
140 private List validatorDelegates = null;
141
142 /*** registry for form interceptors. */
143 private InterceptorRegistry interceptorRegistry = new InterceptorRegistry();
144
145 /*** paramters that influence the execution */
146 private ExecutionParams executionParams = new ExecutionParams();
147
148 /*** delegate for handling interception */
149 private InterceptorDelegate intercDlg = new InterceptorDelegate(interceptorRegistry);
150
151
152
153 /***
154 * Executes this controller. You should verride the perform method
155 * to provide application logic. This method handles all callbacks and
156 * interceptors. See the manual for the path(s) of execution.
157 *
158 * @param cctx maverick controller context
159 * @return String name of the view
160 * @throws ServletException
161 *
162 * @see org.infohazard.maverick.flow.ControllerSingleton#go(org.infohazard.maverick.flow.ControllerContext)
163 */
164 public final String go(ControllerContext cctx) throws ServletException
165 {
166 if(log.isDebugEnabled())
167 {
168 log.debug("*** start handling request");
169 }
170 String viewName = SUCCESS;
171
172 Object bean = null;
173 Locale locale = null;
174 boolean populated = true;
175 FormBeanContext formBeanContext = null;
176
177 ExecutionParams execParams = getExecutionParams(cctx);
178 if(executionParamsLog.isDebugEnabled())
179 {
180 executionParamsLog.debug("using " + execParams);
181 }
182 if (execParams.isNoCache())
183 {
184 doSetNoCache(cctx);
185 }
186
187 try
188 {
189
190 formBeanContext = getFormBeanContext(cctx, execParams);
191
192 formBeanContext.setController(this);
193 cctx.setModel(formBeanContext);
194
195 locale = getLocaleForRequest(cctx, formBeanContext);
196 formBeanContext.setCurrentLocale(locale);
197
198
199 intercDlg.doInterceptBeforeMakeFormBean(cctx, formBeanContext);
200
201 try
202 {
203
204 bean = getFormBean(formBeanContext, cctx);
205 formBeanContext.setBean(bean);
206
207
208 intercDlg.doInterceptBeforePopulation(cctx, formBeanContext);
209
210
211
212 if (execParams.isPopulateAndValidate())
213 {
214 populated = populateFormBean(cctx, formBeanContext, execParams);
215 }
216
217 }
218 catch(FlowException fe)
219 {
220 throw fe;
221 }
222 catch (Exception e)
223 {
224
225 log.error("Unexpected exception occured during form population.", e);
226
227 internalPerformError(cctx, execParams, formBeanContext, e);
228 viewName = getErrorView(cctx, formBeanContext);
229
230
231 intercDlg.doInterceptPopulationError(cctx, formBeanContext, e);
232 }
233
234
235
236 if (populated || execParams.isDoPerformIfPopulationFailed())
237 {
238
239 intercDlg.doInterceptAfterPopulation(cctx, formBeanContext);
240
241 try
242 {
243
244 viewName = this.perform(formBeanContext, cctx);
245 }
246 catch (Exception e)
247 {
248 log.error(e.getMessage(), e);
249
250
251 internalPerformError(cctx, execParams, formBeanContext, e);
252
253
254 intercDlg.doInterceptPerformException(cctx, formBeanContext, e);
255
256 viewName = getErrorView(cctx, formBeanContext);
257 }
258
259 }
260 else
261
262
263 {
264 internalPerformError(cctx, execParams, formBeanContext, null);
265 viewName = getErrorView(cctx, formBeanContext);
266
267
268 intercDlg.doInterceptPopulationError(cctx, formBeanContext, null);
269 }
270
271
272 intercDlg.doInterceptAfterPerform(cctx, formBeanContext);
273 }
274 catch (FlowException e)
275 {
276 if(e instanceof ReturnNowFlowException)
277 {
278 viewName = ((ReturnNowFlowException)e).getView();
279 }
280 else
281 {
282 cctx.setModel(null);
283 viewName = null;
284 }
285 }
286
287 if(log.isDebugEnabled())
288 {
289 if(viewName != null)
290 {
291 log.debug("*** end handling request, fbc: " + formBeanContext);
292 }
293 else
294 {
295 log.debug("*** (request was re-dispatched)");
296 }
297 }
298
299 return viewName;
300 }
301
302 /***
303 * Get the FormBeanContext instance.
304 * @param cctx controller context
305 * @param _execParams execution params for this request
306 * @return FormBeanContext instance of FormBeanContext to use
307 */
308 private FormBeanContext getFormBeanContext(ControllerContext cctx, ExecutionParams _execParams)
309 {
310 FormBeanContext formBeanContext = null;
311 if (_execParams.isReuseFormBeanContext())
312
313 {
314 formBeanContext = (FormBeanContext)cctx.getRequest().getAttribute(REQUEST_ATTRIBUTE_FORMBEANCONTEXT);
315 if (formBeanContext == null)
316 {
317 formBeanContext = new FormBeanContext();
318 cctx.getRequest().setAttribute(REQUEST_ATTRIBUTE_FORMBEANCONTEXT, formBeanContext);
319 }
320 else
321 {
322 if(log.isDebugEnabled())
323 {
324 log.debug("reusing " + formBeanContext);
325 }
326 }
327 }
328 else
329 {
330 formBeanContext = new FormBeanContext();
331 }
332 return formBeanContext;
333 }
334
335 /***
336 * Get the form bean for population.
337 * @param formBeanContext current FormBeanContext instance
338 * @param cctx controller contex
339 * @return Object JavaBean that is to be populated
340 */
341 private Object getFormBean(FormBeanContext formBeanContext, ControllerContext cctx)
342 {
343 return this.makeFormBean(formBeanContext, cctx);
344 }
345
346 /***
347 * populate the form.
348 * @param cctx controller context
349 * @param formBeanContext context with bean to populate
350 * @throws Exception
351 * @return true if populate did not have any troubles, false otherwise
352 */
353 private boolean populateFormBean(
354 ControllerContext cctx,
355 FormBeanContext formBeanContext,
356 ExecutionParams _execParams)
357 throws Exception
358 {
359
360 Map allParameters = new HashMap();
361
362
363
364
365
366
367
368 StringBuffer traceMsg = null;
369 Object bean = formBeanContext.getBean();
370 if (populationLog.isDebugEnabled())
371 {
372 traceMsg = new StringBuffer();
373 traceMsg.append("trace ctrl ").append(this).append("; populate of bean ").append(bean).append(
374 " with parameters:");
375 }
376
377
378 if (_execParams.isIncludeControllerParameters() && (cctx.getControllerParams() != null))
379 {
380 Map parameters = new HashMap(cctx.getControllerParams());
381 traceParameters(parameters, traceMsg, "maverick controller params");
382 allParameters.putAll(parameters);
383 }
384
385
386 if (_execParams.isIncludeSessionAttributes())
387 {
388 Map parameters = new HashMap();
389 HttpSession httpSession = cctx.getRequest().getSession();
390 Enumeration enum = httpSession.getAttributeNames();
391 if (enum != null) {
392 while (enum.hasMoreElements())
393 {
394 String attrName = (String)enum.nextElement();
395 parameters.put(attrName, httpSession.getAttribute(attrName));
396 }
397 }
398 traceParameters(parameters, traceMsg, "session attributes");
399 allParameters.putAll(parameters);
400 }
401
402
403 Map reqParameters = new HashMap();
404 reqParameters.putAll(cctx.getRequest().getParameterMap());
405 traceParameters(reqParameters, traceMsg, "request parameters");
406 allParameters.putAll(reqParameters);
407
408
409 if (_execParams.isIncludeRequestAttributes())
410 {
411 Map parameters = new HashMap();
412 HttpServletRequest request = cctx.getRequest();
413 Enumeration enum = request.getAttributeNames();
414 if (enum != null)
415 while (enum.hasMoreElements())
416 {
417 String attrName = (String)enum.nextElement();
418 if ((!REQUEST_ATTRIBUTE_FORMBEANCONTEXT.equals(attrName))
419 && (!REQUEST_ATTRIBUTE_EXECUTION_PARAMS.equals(attrName)))
420 {
421
422 Object param = request.getAttribute(attrName);
423 if (!(param instanceof FormBeanContext))
424 {
425 parameters.put(attrName, param);
426 }
427 }
428 }
429 traceParameters(parameters, traceMsg, "request attributes");
430 allParameters.putAll(parameters);
431 }
432
433 if (populationLog.isDebugEnabled())
434 {
435 if (allParameters.isEmpty())
436 traceMsg.append("\n\tno parameters found");
437 populationLog.debug(traceMsg);
438 }
439
440
441 boolean succeeded = populateWithErrorReport(
442 cctx, formBeanContext, allParameters);
443
444
445 succeeded = defaultValidatorDelegate.doValidation(
446 cctx, formBeanContext, _execParams, allParameters, succeeded);
447
448 List additionalValidators = getValidatorDelegates();
449 if (additionalValidators != null)
450 {
451 for (Iterator i = additionalValidators.iterator(); i.hasNext();)
452 {
453 boolean _succeeded = succeeded;
454
455 ValidatorDelegate valDel = (ValidatorDelegate)i.next();
456 _succeeded = valDel.doValidation(
457 cctx, formBeanContext, executionParams, allParameters, _succeeded);
458
459 if (!_succeeded)
460 succeeded = false;
461 }
462 }
463
464 return succeeded;
465 }
466
467 /***
468 * Debug method for parameters that will be used for population.
469 * @param parameters the parameters
470 * @param msg the message to append to logging
471 * @param parameterSet set name to append to logging
472 */
473 private final void traceParameters(Map parameters, StringBuffer msg, String parameterSet)
474 {
475 if (populationLog.isDebugEnabled() && (parameters != null) && (!parameters.isEmpty()))
476 {
477 msg.append("\n\t").append(parameterSet).append(": ");
478 for (Iterator i = parameters.keySet().iterator(); i.hasNext();)
479 {
480 Object key = i.next();
481 Object value = parameters.get(key);
482 msg.append(key + " = " + ValueUtils.convertToString(value));
483 if (i.hasNext())
484 msg.append(", ");
485 }
486 }
487 }
488
489 /***
490 * Default populate of form: Ognl way; set error if anything goes wrong.
491 * @param cctx controller context
492 * @param formBeanContext context with current form bean
493 * @param parameters map with name/ values
494 * @param locale the prefered locale
495 * @return true if populate did not have any troubles, false otherwise
496 */
497 private boolean populateWithErrorReport(
498 ControllerContext cctx,
499 FormBeanContext formBeanContext,
500 Map parameters)
501 {
502
503 if ((formBeanContext == null) || (parameters == null) || (parameters.isEmpty()))
504 {
505 return true;
506 }
507 boolean succeeded = true;
508
509 succeeded = regexPopulateWithErrorReport(cctx, formBeanContext, parameters);
510
511 succeeded = fieldPopulateWithErrorReport(cctx, formBeanContext, parameters, succeeded);
512 return succeeded;
513 }
514
515 /***
516 * Populate with regex populators if any.
517 * @param cctx controller context
518 * @param formBeanContext context with current form bean
519 * @param parameters map with name/ values. parameters that are found within this method
520 * will be removed, and are thus skipped in the remaining population process
521 * @return true if populate did not have any troubles, false otherwise
522 */
523 private boolean regexPopulateWithErrorReport(
524 ControllerContext cctx,
525 FormBeanContext formBeanContext,
526 Map parameters)
527 {
528 boolean succeeded = true;
529 Map regexFieldPopulators = populatorRegistry.getRegexFieldPopulators();
530
531 if (regexFieldPopulators != null)
532 {
533 List keysToBeRemoved = new ArrayList();
534 for (Iterator i = regexFieldPopulators.keySet().iterator(); i.hasNext();)
535 {
536 Pattern pattern = (Pattern)i.next();
537 for (Iterator j = parameters.keySet().iterator(); j.hasNext();)
538 {
539 String name = (String)j.next();
540
541 if (populatorRegistry.getFieldPopulator(name) != null)
542 {
543 continue;
544 }
545 Object value = parameters.get(name);
546 try
547 {
548 Matcher matcher = pattern.matcher(name);
549 if (matcher.matches())
550 {
551 FieldPopulator fieldPopulator =
552 (FieldPopulator)regexFieldPopulators.get(pattern);
553 keysToBeRemoved.add(name);
554 boolean success;
555 try
556 {
557
558 success = fieldPopulator.setProperty(cctx, formBeanContext, name, value);
559 }
560 catch (Exception e)
561 {
562 populationLog.error(e);
563 if (populationLog.isDebugEnabled())
564 {
565 populationLog.error(e.getMessage(), e);
566 }
567 continue;
568 }
569 if (!success)
570 {
571 succeeded = false;
572 }
573 }
574 }
575 catch (Exception e)
576 {
577 log.error(e);
578 if (log.isDebugEnabled())
579 {
580 log.error(e.getMessage(), e);
581 }
582 continue;
583 }
584 }
585 }
586 if (!keysToBeRemoved.isEmpty())
587 {
588 for (Iterator i = keysToBeRemoved.iterator(); i.hasNext();)
589 {
590 parameters.remove(i.next());
591 }
592 }
593 }
594
595 return succeeded;
596 }
597
598 /***
599 * Populate with field populators.
600 * @param cctx controller context
601 * @param formBeanContext context with current form bean
602 * @param parameters map with name/ values.
603 * @param locale the prefered locale
604 * @param succeeded if any
605 * @return true if populate did not have any troubles AND succeeded was true, false otherwise
606 */
607 private boolean fieldPopulateWithErrorReport(
608 ControllerContext cctx,
609 FormBeanContext formBeanContext,
610 Map parameters,
611 boolean succeeded)
612 {
613
614 Iterator names = parameters.keySet().iterator();
615 while (names.hasNext())
616 {
617 boolean success = true;
618
619 String name = (String)names.next();
620 if (name == null)
621 continue;
622 Object value = parameters.get(name);
623 try
624 {
625
626 FieldPopulator fieldPopulator = populatorRegistry.getFieldPopulator(name);
627 if (fieldPopulator == null)
628 {
629 fieldPopulator = populatorRegistry.getDefaultFieldPopulator();
630 }
631
632 success = fieldPopulator.setProperty(cctx, formBeanContext, name, value);
633 }
634 catch (Exception e)
635 {
636 populationLog.error(e);
637 if (populationLog.isDebugEnabled())
638 {
639 populationLog.error(e.getMessage(), e);
640 }
641 continue;
642 }
643 if (!success)
644 {
645 succeeded = false;
646 }
647 }
648 return succeeded;
649 }
650
651 /***
652 * Called when populating the form failed.
653 * @param cctx maverick context
654 * @param params execution params
655 * @param formBeanContext context with form bean
656 */
657 private void internalPerformError(
658 ControllerContext cctx,
659 ExecutionParams execParams,
660 FormBeanContext formBeanContext,
661 Throwable e)
662 throws ServletException
663 {
664 if (formBeanContext == null)
665 return;
666
667 if (e != null)
668 {
669
670 if (e.getMessage() != null)
671 {
672 formBeanContext.setError(e, false);
673 }
674 else
675 {
676
677 formBeanContext.setError(e, true);
678 }
679 }
680
681
682 if (execParams.isSaveReqParamsAsOverrideFieldsOnError())
683 {
684 formBeanContext.setOverrideField(cctx.getRequest().getParameterMap());
685 }
686 if (populationLog.isDebugEnabled())
687 {
688 traceErrors(formBeanContext);
689 }
690 }
691
692 /***
693 * Extra debugging logging for errors.
694 * @param formBeanContext form bean context
695 */
696 private final void traceErrors(FormBeanContext formBeanContext)
697 {
698 Map errors = formBeanContext.getErrors();
699 if (errors != null)
700 {
701 populationLog.debug("population of bean " + formBeanContext.getBean() + " did not succeed; errors:");
702
703 for (Iterator i = errors.keySet().iterator(); i.hasNext();)
704 {
705 Object key = i.next();
706 Object value = errors.get(key);
707 populationLog.debug("\t " + key + " == " + ValueUtils.convertToString(value));
708 }
709 populationLog.debug("----------------------------------------------------------");
710 }
711 }
712
713 /***
714 * Set error for field with name 'name' in case of a conversion error.
715 * uses getConversionErrorLabelKey to get the specific label.
716 * NOTE: this will be used in case of conversion errors ONLY. If a validator
717 * causes an error (after normal conversion) the error message of the validator
718 * will be used, not this method.
719 *
720 * The message is formatted with objects triedValue, name (by calling getPropertyNameKey)
721 * and t, so you can use {0}, {1} and {2} resp. with your custom message.
722 *
723 * If there is an entry in the default resource bundle that has form:
724 * formname.[name] (eg. formname.firstname and formname.lastname)
725 * the name parameter {1} will be replaced with this value.
726 *
727 * @param cctx controller context
728 * @param formBeanContext context with form bean
729 * @param targetType type of target property
730 * @param name name of field
731 * @param triedValue value that was tried for population
732 * @param t exception
733 */
734 public void setConversionErrorForField(
735 ControllerContext cctx,
736 FormBeanContext formBeanContext,
737 Class targetType,
738 String name,
739 Object triedValue,
740 Throwable t)
741 {
742 try
743 {
744 String key = getConversionErrorLabelKey(targetType, name, triedValue);
745 String msg = null;
746 String msgName = null;
747 msgName = MessageUtils.getLocalizedMessage(getPropertyNameKey(name));
748 String desiredPattern = null;
749 if (t instanceof nl.openedge.baritus.converters.ConversionException)
750 {
751 nl.openedge.baritus.converters.ConversionException ex =
752 (nl.openedge.baritus.converters.ConversionException)t;
753 desiredPattern = ex.getDesiredPattern();
754 }
755 if (msgName != null)
756 {
757 msg = MessageUtils.getLocalizedMessage(key, new Object[] { triedValue, msgName, t, desiredPattern });
758 }
759 else
760 {
761 msg = MessageUtils.getLocalizedMessage(key, new Object[] { triedValue, name, t, desiredPattern });
762 }
763 formBeanContext.setError(name, msg);
764 }
765 catch (Exception e)
766 {
767 log.error(e.getMessage());
768 formBeanContext.setError(name, e.getMessage());
769 }
770 }
771
772 /***
773 * Get the message bundle key for the given property name.
774 *
775 * @param name property name
776 * @return String the message bundle key of the property, defaults to "formname." + name
777 */
778 public String getPropertyNameKey(String name)
779 {
780 return "formname." + name;
781 }
782
783 /***
784 * Get the message bundle key for a conversion error for the given type
785 * and field with the given name.
786 *
787 * @param type type of the target property that threw the conversion error
788 * @param name name of the target property
789 * @param triedValue the value that could not be converted to the type of the
790 * target property
791 * @return String message bundle key
792 */
793 protected String getConversionErrorLabelKey(Class type, String name, Object triedValue)
794 {
795 String key = null;
796
797 if (Date.class.isAssignableFrom(type))
798 {
799 key = "invalid.field.input.date";
800 }
801 else if (Integer.TYPE.isAssignableFrom(type) || (Integer.class.isAssignableFrom(type)))
802 {
803 key = "invalid.field.input.integer";
804 }
805 else if (Double.TYPE.isAssignableFrom(type) || (Double.class.isAssignableFrom(type)))
806 {
807 key = "invalid.field.input.double";
808 }
809 else if (Long.TYPE.isAssignableFrom(type) || (Long.class.isAssignableFrom(type)))
810 {
811 key = "invalid.field.input.long";
812 }
813 else if (Boolean.TYPE.isAssignableFrom(type) || (Boolean.class.isAssignableFrom(type)))
814 {
815 key = "invalid.field.input.boolean";
816 }
817 else
818 {
819 key = "invalid.field.input";
820 }
821
822 return key;
823 }
824
825 /***
826 * Set the override value for the provdied field name.
827 * This method will be called if a property could not be
828 * set on the form or did not pass validation. by registering the 'original' value
829 * (possibly modified by overrides of either this method or the 'getOverrideValue'
830 * of the validator that was the cause of the validation failure) end users can have
831 * their 'wrong' input value shown.
832 *
833 * @param cctx controller context
834 * @param formBeanContext context with form bean
835 * @param name name of the field
836 * @param triedValue the user input value/ currentRequest parameter
837 * @param t exception if known (may be null)
838 * @param validator the validator that was the cause of the validation failure, if one
839 * (is null if this was a conversion error)
840 */
841 public void setOverrideField(
842 ControllerContext cctx,
843 FormBeanContext formBeanContext,
844 String name,
845 Object triedValue,
846 Throwable t,
847 FieldValidator validator)
848 {
849 if (validator != null)
850 {
851 Object value = validator.getOverrideValue(triedValue);
852 formBeanContext.setOverrideField(name, value);
853 }
854 else
855 {
856 formBeanContext.setOverrideField(name, triedValue);
857 }
858 }
859
860 /***
861 * This method must be overriden to perform application logic.
862 * By default, that method will only then be called when population & validation
863 * succeeded, and no FlowExceptions were thrown by interceptors.
864 *
865 * @param formBeanContext context with the populated bean returned by makeFormBean().
866 * @param cctx maverick controller context.
867 *
868 * @return String view to display
869 * @throws Exception As a last fallthrough, exceptions are handled by the framework.
870 * It is advisable however, to keep control of the error reporting, and let this
871 * method do the exception handling
872 */
873 protected abstract String perform(
874 FormBeanContext formBeanContext,
875 ControllerContext cctx)
876 throws Exception;
877
878 /***
879 * This method will be called to produce a bean whose properties
880 * will be populated with the http currentRequest parameters, the resulting object
881 * will be placed in the formBeanContext after this call.
882 *
883 * @param formBeanContext the form bean context. If this is the first control within
884 * a request, the formBeanContext will be empty. If this is a not the first
885 * control that is called within a request (i.e. more controls are linked together),
886 * and execution parameters property reuseFormBeanContext is true (which is the
887 * default), the formBeanContext may allready contain error registrations, and
888 * contains the formBean that was used in the control before this one.
889 * @param cctx controller context with references to request, response etc.
890 *
891 * @return Object instance of bean that should be populated. Right after the call
892 * to makeFormBean, the instance will be set in the formBeanContext as property 'bean'
893 *
894 */
895 protected abstract Object makeFormBean(
896 FormBeanContext formBeanContext,
897 ControllerContext cctx);
898
899 //***************************** validators ********************************************/
900
901 /***
902 * Add a validator delegate.
903 * ValidatorDelegates can do validation on input and populated form beans.
904 * Besides the allways used DefaultValidatorDelegate, users of Baritus
905 * can register additional delegates, for instance to be able to plug in
906 * validator mechanisms like FormProc or Commons Validator.
907 *
908 * @param validatorDelegate
909 */
910 protected void addValidatorDelegate(ValidatorDelegate validatorDelegate)
911
912 {
913 if (validatorDelegates == null)
914 {
915 validatorDelegates = new ArrayList(1);
916 }
917 validatorDelegates.add(validatorDelegate);
918 }
919
920 /***
921 * Remove a validator delegate.
922 *
923 * @param validatorDelegate
924 */
925 protected void removeValidatorDelegate(ValidatorDelegate validatorDelegate)
926 {
927 if (validatorDelegates != null)
928 {
929 validatorDelegates.remove(validatorDelegate);
930 }
931 }
932
933 /***
934 * Get the list of registered validator delegates.
935 *
936 * @return the list of registered validator delegates, possibly null.
937 */
938 protected List getValidatorDelegates()
939 {
940 return validatorDelegates;
941 }
942
943 /***
944 * Register a field validator for the given fieldName.
945 * multiple fieldValidators for one key are allowed.
946 *
947 * @param fieldName name of field
948 * @param validator validator instance
949 */
950 protected void addValidator(String fieldName, FieldValidator validator)
951 {
952 validatorRegistry.addValidator(fieldName, validator);
953 }
954
955 /***
956 * Register a form validator.
957 * form validators will be called after the field level validators executed
958 * successfully, and thus can be used to check consistency etc.
959 *
960 * @param validator the form level validator
961 */
962 protected void addValidator(FormValidator validator)
963 {
964 validatorRegistry.addValidator(validator);
965 }
966
967 /***
968 * De-register the fieldValidators that were registered with the given fieldName.
969 *
970 * @param fieldName name of field
971 */
972 protected void removeValidators(String fieldName)
973 {
974 validatorRegistry.removeValidators(fieldName);
975 }
976
977 /***
978 * De-register the given validator that was registered with the given fieldName.
979 *
980 * @param fieldName name of field
981 * @param validator the validator to remove for the given field
982 */
983 protected void removeValidator(String fieldName, FieldValidator validator)
984 {
985 validatorRegistry.removeValidator(fieldName, validator);
986 }
987
988 /***
989 * De-register the given form level validator.
990 *
991 * @param validator form validator
992 */
993 protected void removeValidator(FormValidator validator)
994 {
995 validatorRegistry.removeValidator(validator);
996 }
997
998 /***
999 * Register the rule for the whole form.
1000 *
1001 * @param rule activation rule
1002 */
1003 protected void addValidationActivationRule(ValidationActivationRule rule)
1004 {
1005 validatorRegistry.addValidationActivationRule(rule);
1006 }
1007
1008 /***
1009 * De-register the given rule for the whole form.
1010 *
1011 * @param rule global rule to remove
1012 */
1013 protected void removeValidationActivationRule(ValidationActivationRule rule)
1014 {
1015 validatorRegistry.removeValidationActivationRule(rule);
1016 }
1017
1018 /***
1019 * Get the fieldValidators that were registered with the given fieldName.
1020 *
1021 * @param fieldName name of the field
1022 * @return MultiMap the fieldValidators that were registered with the given fieldName
1023 */
1024 protected MultiHashMap getValidators(String fieldName)
1025 {
1026 return validatorRegistry.getValidators(fieldName);
1027 }
1028
1029 //***************************** populators ********************************************/
1030
1031 /***
1032 * Register a field populator for the given fieldName.
1033 * Field populators override the default population of a property on the current form.
1034 *
1035 * @param fieldName name of field
1036 * @param populator populator instance
1037 */
1038 protected void addPopulator(String fieldName, FieldPopulator populator)
1039 {
1040 populatorRegistry.addPopulator(fieldName, populator);
1041 }
1042
1043 /***
1044 * De-register the field populator that was registered with the given fieldName.
1045 *
1046 * @param fieldName name of field
1047 */
1048 protected void removePopulator(String fieldName)
1049 {
1050 populatorRegistry.removePopulator(fieldName);
1051 }
1052
1053 /***
1054 * Register a custom populator that overrides the default population
1055 * process for all request parameters that match the regular expression stored in
1056 * the provided pattern.
1057 *
1058 * The registered populators are tried for a match in order of registration. For each match
1059 * that was found, the populator that was registered for it will be used, and the
1060 * request parameter(s) will be removed from the map that is used for population.
1061 * As a consequence, regexFieldPopulators overrule 'normal' field populators, and
1062 * if more than one regex populator would match the parameters, only the first match is used.
1063 * Custom populators are stored by name of the property.
1064 *
1065 * @param pattern regex pattern
1066 * @param populator populator instance
1067 */
1068 protected void addPopulator(Pattern pattern, FieldPopulator populator)
1069 {
1070 populatorRegistry.addPopulator(pattern, populator);
1071 }
1072
1073 /***
1074 * Register a custom populator that overrides the default population
1075 * process for all request parameters that match the regular expression stored in
1076 * the provided pattern.
1077 *
1078 * The registered populators are tried for a match in order of registration. For each match
1079 * that was found, the populator that was registered for it will be used, and the
1080 * request parameter(s) will be removed from the map that is used for population.
1081 * As a consequence, regexFieldPopulators overrule 'normal' field populators, and
1082 * if more than one regex populator would match the parameters, only the first match is used.
1083 * Custom populators are stored by name of the property.
1084 *
1085 * @param pattern regex pattern
1086 */
1087 protected void removePopulator(Pattern pattern)
1088 {
1089 populatorRegistry.removePopulator(pattern);
1090 }
1091
1092 /***
1093 * set the default field populator
1094 * @param populator the default field populator
1095 */
1096 protected void setDefaultPopulator(FieldPopulator populator)
1097 {
1098 populatorRegistry.setDefaultFieldPopulator(populator);
1099 }
1100
1101 /***
1102 * get the default field populator
1103 * @return FieldPopulator the default field populator
1104 */
1105 protected FieldPopulator getDefaultPopulator()
1106 {
1107 return populatorRegistry.getDefaultFieldPopulator();
1108 }
1109
1110 //***************************** interceptors *******************************************/
1111
1112 /***
1113 * Add an interceptor to the current list of interceptors.
1114 *
1115 * @param interceptor the interceptor to add to the current list of interceptors
1116 */
1117 protected void addInterceptor(Interceptor interceptor)
1118 {
1119 interceptorRegistry.addInterceptor(interceptor);
1120 }
1121
1122 /***
1123 * Add an interceptor to the current list of interceptors at the specified position.
1124 *
1125 * @param index index position where to insert the interceptor
1126 * @param interceptor the interceptor to add to the current list of interceptors
1127 */
1128 protected void addInterceptor(int index, Interceptor interceptor)
1129 {
1130 interceptorRegistry.addInterceptor(index, interceptor);
1131 }
1132
1133 /***
1134 * Remove an interceptor from the current list of interceptors.
1135 *
1136 * @param interceptor the interceptor to remove from the current list of interceptors
1137 */
1138 protected void removeInterceptor(Interceptor interceptor)
1139 {
1140 interceptorRegistry.removeInterceptor(interceptor);
1141 }
1142
1143
1144
1145 /***
1146 * Check if the value is null or empty.
1147 *
1148 * @param value object to check on
1149 * @return true if value is not null AND not empty
1150 * (e.g. in case of a String or Collection)
1151 */
1152 protected boolean isNullOrEmpty(Object value)
1153 {
1154 return ValueUtils.isNullOrEmpty(value);
1155 }
1156
1157 /***
1158 * Get localized message for given key.
1159 *
1160 * @param key key of message
1161 * @return String localized message
1162 */
1163 public static String getLocalizedMessage(String key)
1164 {
1165 return getLocalizedMessage(key, Locale.getDefault());
1166 }
1167
1168 /***
1169 * Get localized message for given key and locale.
1170 * If locale is null, the default locale will be used.
1171 *
1172 * @param key key of message
1173 * @param locale locale for message
1174 * @return String localized message
1175 */
1176 protected static String getLocalizedMessage(String key, Locale locale)
1177 {
1178 return MessageUtils.getLocalizedMessage(key, locale);
1179 }
1180
1181 /***
1182 * Get localized message for given key and locale
1183 * and format it with the given parameters.
1184 * If locale is null, the default locale will be used.
1185 *
1186 * @param key key of message
1187 * @param parameters parameters for the message
1188 * @return String localized message
1189 */
1190 protected static String getLocalizedMessage(String key, Object[] parameters)
1191 {
1192 return MessageUtils.getLocalizedMessage(key, parameters);
1193 }
1194
1195 /***
1196 * Get localized message for given key and locale
1197 * and format it with the given parameters.
1198 * If locale is null, the default locale will be used.
1199 *
1200 * @param key key of message
1201 * @param locale locale for message
1202 * @param parameters parameters for the message
1203 * @return String localized message
1204 */
1205 protected static String getLocalizedMessage(String key, Locale locale, Object[] parameters)
1206 {
1207 return MessageUtils.getLocalizedMessage(key, locale, parameters);
1208 }
1209
1210 /***
1211 * Get the prefered locale for the current request.
1212 * IF a user is set in the form, the preferedLocale will be checked for this user.
1213 * IF a locale is found as an attribute in the session with key
1214 * SESSION_KEY_CURRENT_LOCALE, the previous found locale(s)
1215 * will be replaced with this value.
1216 *
1217 * @param cctx controller context
1218 * @param formBeanContext context
1219 * @return Locale the prefered locale
1220 */
1221 protected Locale getLocaleForRequest(ControllerContext cctx, FormBeanContext formBeanContext)
1222 {
1223 Locale locale = null;
1224 HttpSession session = cctx.getRequest().getSession();
1225 Locale temp = (Locale)session.getAttribute(SESSION_KEY_CURRENT_LOCALE);
1226 if (temp != null)
1227 {
1228 locale = temp;
1229 }
1230
1231 if (locale == null)
1232 {
1233 locale = cctx.getRequest().getLocale();
1234 }
1235
1236 return locale;
1237 }
1238
1239 /***
1240 * Get error view. This is 'error' by default.
1241 *
1242 * @param cctx controller context
1243 * @param formBeanContext context
1244 * @return String logical name of view
1245 */
1246 protected String getErrorView(ControllerContext cctx, FormBeanContext formBeanContext)
1247 {
1248 return ERROR;
1249 }
1250
1251 /***
1252 * Set http response headers that indicate that this page should not be cached.
1253 * @param cctx controller context
1254 */
1255 protected void doSetNoCache(ControllerContext cctx)
1256 {
1257 HttpServletResponse response = cctx.getResponse();
1258 response.setHeader("Pragma", "No-cache");
1259 response.setHeader("Cache-Control", "no-cache");
1260 response.setDateHeader("Expires", 1);
1261 }
1262
1263 /***
1264 * Get a deep copy of the execution params that are used to influence the execution
1265 * of the formBeanCtrl (like population, validation, etc). If an instance was found in
1266 * the current request, this will be used.
1267 * NOTE: changes to these parameters will be local for the current request. If you
1268 * want the changes to be kept for all subsequent uses of the control, call fixExecutionParams.
1269 *
1270 * @param cctx Maverick context with the current request, null if it should be ignored.
1271 * @return ExecutionParams the execution params that are used to influence the execution
1272 * of the formBeanCtrl (like population, validation, etc).
1273 */
1274 public ExecutionParams getExecutionParams(ControllerContext cctx)
1275 {
1276 ExecutionParams params = null;
1277 if (cctx != null)
1278 {
1279 HttpServletRequest request = cctx.getRequest();
1280 params = (ExecutionParams)request.getAttribute(REQUEST_ATTRIBUTE_EXECUTION_PARAMS);
1281 if (params == null)
1282 {
1283 params = (ExecutionParams)executionParams.clone();
1284 request.setAttribute(REQUEST_ATTRIBUTE_EXECUTION_PARAMS, params);
1285 }
1286 }
1287 else
1288 {
1289 params = (ExecutionParams)executionParams.clone();
1290 }
1291
1292 return params;
1293 }
1294
1295 /***
1296 * Save the execution params that are used to influence the execution
1297 * of the formBeanCtrl (like population, validation, etc).
1298 *
1299 * @param params the execution params that are used to influence the execution
1300 * of the formBeanCtrl (like population, validation, etc).
1301 */
1302 public void fixExecutionParams(ExecutionParams params)
1303 {
1304 if(executionParamsLog.isDebugEnabled())
1305 {
1306 executionParamsLog.debug("fixing " + params + " for all requests");
1307 }
1308 executionParams = params;
1309 }
1310
1311 }