View Javadoc

1   /*
2    * $Id: FormBeanCtrlBase.java,v 1.16 2004/06/22 17:57:24 eelco12 Exp $
3    * $Revision: 1.16 $
4    * $Date: 2004/06/22 17:57:24 $
5    *
6    * ====================================================================
7    * Copyright (c) 2003, Open Edge B.V.
8    * All rights reserved.
9    * Redistribution and use in source and binary forms, with or without 
10   * modification, are permitted provided that the following conditions are met:
11   * Redistributions of source code must retain the above copyright notice, 
12   * this list of conditions and the following disclaimer. Redistributions 
13   * in binary form must reproduce the above copyright notice, this list of 
14   * conditions and the following disclaimer in the documentation and/or other 
15   * materials provided with the distribution. Neither the name of OpenEdge B.V. 
16   * nor the names of its contributors may be used to endorse or promote products 
17   * derived from this software without specific prior written permission.
18   * 
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
20   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
21   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
22   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
23   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
24   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
25   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
26   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
27   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
29   * THE POSSIBILITY OF SUCH DAMAGE.
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  	//-------------------------- constants ---------------------------------------/
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 	//--------------------- logs --------------------------------------------------/
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 	//------------------------ registries ----------------------------------------/
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 	//------------------------- methods ------------------------------------------/
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; // default view
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); // set no cache headers
185 		}
186 
187 		try
188 		{
189 			// get the form bean context and set it in the flow interceptor context
190 			formBeanContext = getFormBeanContext(cctx, execParams);
191 			// set the current controller to be able to use methods like getPropertyNameKey etc
192 			formBeanContext.setController(this);
193 			cctx.setModel(formBeanContext); // set context as model
194 
195 			locale = getLocaleForRequest(cctx, formBeanContext); // get the locale
196 			formBeanContext.setCurrentLocale(locale); // and set in context
197 
198 			// intercept before make form bean
199 			intercDlg.doInterceptBeforeMakeFormBean(cctx, formBeanContext);
200 
201 			try
202 			{
203 				// let controller create form
204 				bean = getFormBean(formBeanContext, cctx);
205 				formBeanContext.setBean(bean);
206 
207 				// intercept before population
208 				intercDlg.doInterceptBeforePopulation(cctx, formBeanContext);
209 
210 				// populate if property of formBeanContext populateAndValidate is true 
211 				// (as it is by default)
212 				if (execParams.isPopulateAndValidate())
213 				{
214 					populated = populateFormBean(cctx, formBeanContext, execParams);
215 				}
216 
217 			}
218             catch(FlowException fe)
219             {
220                 throw fe; // rethrow to outer catch
221             }
222 			catch (Exception e)
223 			{
224 				// as we should normally not get here, log stacktrace
225 				log.error("Unexpected exception occured during form population.", e);
226 
227 				internalPerformError(cctx, execParams, formBeanContext, e);
228 				viewName = getErrorView(cctx, formBeanContext);
229 
230 				// intercept population error
231 				intercDlg.doInterceptPopulationError(cctx, formBeanContext, e);
232 			}
233 
234 			// was the bean population successful or should we execute perform
235 			// regardless of the population/ validation outcome?
236 			if (populated || execParams.isDoPerformIfPopulationFailed())
237 			{
238 				// flow intercept after population
239 				intercDlg.doInterceptAfterPopulation(cctx, formBeanContext);
240 
241 				try
242 				{
243 					// execute command en get the view
244 					viewName = this.perform(formBeanContext, cctx);
245 				}
246 				catch (Exception e)
247 				{
248 					log.error(e.getMessage(), e);
249 
250 					// prepare for error command and execute it
251 					internalPerformError(cctx, execParams, formBeanContext, e);
252 
253 					// flow intercept on perform error
254 					intercDlg.doInterceptPerformException(cctx, formBeanContext, e);
255 
256 					viewName = getErrorView(cctx, formBeanContext);
257 				}
258 
259 			}
260 			else // bean population was not successful 
261 				// this is the normal place of handling a failed (by either populators
262 				// or validators) population attempt.
263 				{
264 				internalPerformError(cctx, execParams, formBeanContext, null);
265 				viewName = getErrorView(cctx, formBeanContext);
266 
267 				// intercept on population error
268 				intercDlg.doInterceptPopulationError(cctx, formBeanContext, null);
269 			}
270 
271 			// intercept after perform
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; //TODO is there a more quiet way of aborting?
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 			// if true, see if an instance was save earlier request
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(); // for use with validators later on
361 
362 		// The order in which parameters/ attributes are used for population:
363 		//	1. controller parameters (if includeControllerParameters == true)
364 		//	2. session attributes (if includeSessionAttributes == true)
365 		//	3. request parameters
366 		//	4. request attributes (if includeRequestAttributes == true)
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 		// controller parameters
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 		// session attributes
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 		// request parameters
403 		Map reqParameters = new HashMap();
404 		reqParameters.putAll(cctx.getRequest().getParameterMap());
405 		traceParameters(reqParameters, traceMsg, "request parameters");
406 		allParameters.putAll(reqParameters);
407 
408 		// request attributes
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 						// extra check for Model
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 		// do population
441 		boolean succeeded = populateWithErrorReport(
442 		    cctx, formBeanContext, allParameters);
443 		
444 		// do custom validation
445 		succeeded = defaultValidatorDelegate.doValidation(
446 		    cctx, formBeanContext, _execParams, allParameters, succeeded);
447 
448 		List additionalValidators = getValidatorDelegates();
449 		if (additionalValidators != null) // if there are any delegates registered
450 		{
451 			for (Iterator i = additionalValidators.iterator(); i.hasNext();)
452 			{ // loop through them
453 				boolean _succeeded = succeeded; // set to last known val
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 		// Do nothing unless both arguments have been specified or parameters is empty
503 		if ((formBeanContext == null) || (parameters == null) || (parameters.isEmpty()))
504 		{
505 			return true;
506 		}
507 		boolean succeeded = true;
508 		// try regex population
509 		succeeded = regexPopulateWithErrorReport(cctx, formBeanContext, parameters);
510 		// populate remainder
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 		// first, see if there are matches with registered regex populators
531 		if (regexFieldPopulators != null) // there are registrations
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 					// See if we have a custom populator registered for the given field
541 					if (populatorRegistry.getFieldPopulator(name) != null)
542 					{
543 						continue; // do not match on regexp
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 								// execute population on form
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); // print stacktrace
581 						}
582 						continue;
583 					}
584 				}
585 			}
586 			if (!keysToBeRemoved.isEmpty()) // for all found matches, remove the parameter
587 			{
588 				for (Iterator i = keysToBeRemoved.iterator(); i.hasNext();)
589 				{
590 					parameters.remove(i.next());
591 				}
592 			}
593 		} // else nothing to do
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 		// Loop through the property name/value pairs to be set
614 		Iterator names = parameters.keySet().iterator();
615 		while (names.hasNext())
616 		{
617 			boolean success = true;
618 			// Identify the property name and value(s) to be assigned
619 			String name = (String)names.next();
620 			if (name == null)
621 				continue;
622 			Object value = parameters.get(name);
623 			try
624 			{
625 				// See if we have a custom populator registered for the given field
626 				FieldPopulator fieldPopulator = populatorRegistry.getFieldPopulator(name);
627 				if (fieldPopulator == null) // if no custom populator was found, we use the default
628 				{
629 					fieldPopulator = populatorRegistry.getDefaultFieldPopulator();
630 				}
631 				// execute population on form
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 			// save the exception so it can be displayed in the view
670 			if (e.getMessage() != null)
671 			{
672 				formBeanContext.setError(e, false);
673 			}
674 			else
675 			{
676 				// as a fallback, save the stacktrace
677 				formBeanContext.setError(e, true);
678 			}
679 		}
680 
681 		// set overrides for the current request parameters if params allow
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 	// ************************ misc utility- and property methods *********************/
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 }