View Javadoc

1   /*
2    * $Id: FormBeanContext.java,v 1.11 2004/06/22 17:57:24 eelco12 Exp $
3    * $Revision: 1.11 $
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.io.ByteArrayOutputStream;
34  import java.io.PrintWriter;
35  import java.util.Collection;
36  import java.util.HashMap;
37  import java.util.Iterator;
38  import java.util.Locale;
39  import java.util.Map;
40  import java.util.Set;
41  
42  import nl.openedge.baritus.converters.Converter;
43  import nl.openedge.baritus.converters.Formatter;
44  import nl.openedge.baritus.util.ValueUtils;
45  import ognl.Ognl;
46  
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  
50  /***
51   * FormBeanContext wraps the form bean, errors the current locale and overrideFields.
52   * Furthermore, it acts as a decorator for a HashMap where you can optionally store 
53   * attributes you do not want to include as properties in your form bean. 
54   *  
55   * @author Eelco Hillenius
56   * @author Maurice Marrink
57   */
58  public final class FormBeanContext implements Map
59  {
60  
61  	/* the current locale */
62  	private Locale currentLocale = null;
63  	
64  	/*
65  	 * The form bean. This is the bean that will be populated, 
66  	 * and that is returned by makeFormbean 
67  	 */
68  	private Object bean = null;
69  
70  	/* errors */
71  	private Map errors = null;
72  
73  	/* overriden values as strings */
74  	private Map overrideFields = null;
75  	
76  	/* the current controller */
77  	private FormBeanCtrlBase controller = null;
78  	
79  	/* 
80  	 * if, for some reason, you want to store extra attributes in this context
81  	 * instead in the formBean, you can do this here (e.g. messages)
82  	 */
83  	private Map attributes = null;
84  
85  	/* log for this class */
86  	private static Log log = LogFactory.getLog(FormBeanCtrlBase.class);
87  	
88  	/* format log */
89  	private static Log formattingLog = LogFactory.getLog(LogConstants.FORMATTING_LOG);
90  
91  	/*** error key for stacktrace if any */
92  	public final static String ERROR_KEY_STACKTRACE = "stacktrace";
93  
94  	/*** error key for stacktrace if any */
95  	public final static String ERROR_KEY_EXCEPTION = "exception";
96  
97  	//	----------------------- PROPERTY METHODS -----------------------------//
98  
99  	/***
100 	 * Get the current locale.
101 	 * @return Locale current locale
102 	 */
103 	public Locale getCurrentLocale()
104 	{
105 		return currentLocale;
106 	}
107 
108 	/***
109 	 * Set the current locale.
110 	 * @param locale current locale
111 	 */
112 	public void setCurrentLocale(Locale locale)
113 	{
114 		currentLocale = locale;
115 	}
116 	
117 	/***
118 	 * Get the form bean. The current control provided a bean to populate with method
119 	 * makeFormBean. That bean is saved (before population) in the formBeanContext.
120 	 * 
121 	 * @return Object the bean that will be populated, and that is returned by makeFormbean
122 	 */
123 	public Object getBean()
124 	{
125 		return bean;
126 	}
127 
128 	/*** 
129 	 * Set the form bean. This is the bean that will be populated, 
130 	 * and that is returned by make formbean.
131 	 * 
132 	 * @param bean the bean that will be populated, and that is returned by makeFormbean
133 	 */
134 	public void setBean(Object bean)
135 	{
136 		this.bean = bean;
137 	}
138 
139 	//	----------------------- ERROR/ OVERRIDE METHODS -----------------------------//
140 
141 	/***
142 	 * Get the map with errors. Returns null if no errors are registered.
143 	 * @return Map map with errors or null if no errors are registered.
144 	 */
145 	public Map getErrors()
146 	{
147 		return errors;
148 	}
149 
150 	/***
151 	 * Get the registered error for one field. Returns null if no error is registered
152 	 * for the given field name.
153 	 * @param field name of the field to lookup the error for.
154 	 * @return String the error that was registered for the provided field name, or null
155 	 * if no error was registered for the provided field name.
156 	 */
157 	public String getError(String field)
158 	{
159 		return (errors != null) ? (String)errors.get(field) : null;
160 	}
161 
162 	/***
163 	 * Set the map of errors.
164 	 * @param errors The map of errors to set
165 	 */
166 	public void setErrors(Map errors)
167 	{
168 		this.errors = errors;
169 	}
170 
171 	/***
172 	 * Either add this exception with the given key, or add the stacktrace
173 	 * of this exception with the given key. Any value that was registered with
174 	 * the same key prior to this is overwritten.
175 	 * @param key key to store error under
176 	 * @param t exception
177 	 * @param asStackTrace if true, the stacktrace is added; otherwise the exception
178 	 *  is added
179 	 */
180 	public void setError(String key, Throwable t, boolean asStackTrace)
181 	{
182 		String value = null;
183 		if (asStackTrace)
184 		{
185 			value = getErrorMessage(t);
186 		}
187 		else
188 		{
189 			value = t.getMessage();
190 		}
191 		setError(key, value);
192 	}
193 
194 	/***
195 	 * Add exception and its stacktrace. Any value that was registered with
196 	 * the same key prior to this is overwritten.
197 	 * @param exceptionKey key to use for exception
198 	 * @param stackTraceKey key to use for stacktrace
199 	 * @param t exception
200 	 */
201 	public void setError(
202 		String exceptionKey,
203 		String stackTraceKey,
204 		Throwable t)
205 	{
206 		String stackTrace = getErrorMessage(t);
207 		String errorMessage = t.getMessage();
208 		setError(stackTraceKey, stackTrace);
209 		setError(exceptionKey, errorMessage);
210 	}
211 
212 	/***
213 	 * Adds an exception with key 'exception' and adds the stacktrace
214 	 * of this exception with key 'stacktrace'. Any value that was registered with
215 	 * the same keys prior to this are overwritten.
216 	 * @param t exception
217 	 */
218 	public void setError(Throwable t)
219 	{
220 		setError(ERROR_KEY_EXCEPTION, ERROR_KEY_STACKTRACE, t);
221 	}
222 
223 	/***
224 	 * Adds an exception with key 'exception' and adds either the stacktrace
225 	 * of this exception with key 'stacktrace' if asStackTrace is true, or add the
226 	 * exception message with key 'exception' if asStackTrace is false.
227 	 * Any value that was registered with the same keys prior to this are overwritten.
228 	 * @param t exception
229 	 * @param asStackTrace if true, the stacktrace is added; otherwise the exception
230 	 */
231 	public void setError(Throwable t, boolean asStackTrace)
232 	{
233 		setError(ERROR_KEY_EXCEPTION, t, asStackTrace);
234 	}
235 
236 	/***
237 	 * Register (or overwrite) an error with the provided key and value.
238 	 * @param key the key that the error should be registered with.
239 	 * @param value the value (message) of the error.
240 	 */
241 	public void setError(String key, String value)
242 	{
243 		if (errors == null) errors = new HashMap();
244 		
245 		errors.put(key, value);
246 	}
247 
248 	/***
249 	 * De-register (remove) an error that was registered with the provided key.
250 	 * @param key the key that the error was registered with.
251 	 */
252 	public void removeError(String key)
253 	{
254 		if (errors != null)
255 		{
256 			errors.remove(key);
257 		}
258 	}
259 
260 	/*
261 	 * get errormessage; try stacktrace
262 	 */
263 	private String getErrorMessage(Throwable t)
264 	{
265 		String msg;
266 		try
267 		{
268 			ByteArrayOutputStream bos = new ByteArrayOutputStream();
269 			PrintWriter pw = new PrintWriter(bos);
270 			t.printStackTrace(pw);
271 			pw.flush();
272 			pw.close();
273 			bos.flush();
274 			bos.close();
275 			msg = bos.toString();
276 		}
277 		catch (Exception e)
278 		{
279 			msg = t.getMessage();
280 		}
281 		return msg;
282 	}
283 
284 	/***
285 	 * Get the map of failed field values.
286 	 * @return Map map with override fields
287 	 */
288 	public Map getOverrideFields()
289 	{
290 		return overrideFields;
291 	}
292 
293 	/***
294 	 * Set value of field that overrides. WILL overwrite any allready registered override
295 	 * @param name name of the field/ property
296 	 * @param value usually the original input value
297 	 */
298 	public void setOverrideField(String name, Object value)
299 	{
300 		if (overrideFields == null)
301 		{
302 			overrideFields = new HashMap();
303 		}
304 		overrideFields.put(name, value);
305 	}
306 
307 	/***
308 	 * Get the value of the field that was overridden.
309 	 * E.g. we got in a formbean property 'myDate' of type date. 
310 	 * If form submit gives 'blah', this cannot be parsed as a date.
311 	 * Now, if setFailedField('myDate', 'blah') is called, 
312 	 * the view can show the 'wrong' value transparently, as an 
313 	 * Velocity EventCardridge will override any property with a 
314 	 * failed field if set.
315 	 * @param name name of the field/ property
316 	 * @return Object usually the original input value
317 	 */
318 	public Object getOverrideField(String name)
319 	{
320 		return (overrideFields != null) ? overrideFields.get(name) : null;
321 	}
322 
323 	/***
324 	 * Set values of fields that have overrides. 
325 	 * This WILL NOT overwrite allready registered overrides.
326 	 * @param fields map of fields to override the current values.
327 	 */
328 	public void setOverrideField(Map fields)
329 	{
330 		if (fields != null)
331 		{
332 			if (overrideFields == null)
333 			{
334 				overrideFields = new HashMap();
335 				overrideFields.putAll(fields);
336 			}
337 			else
338 			{
339 				for (Iterator i = fields.keySet().iterator(); i.hasNext();)
340 				{
341 					String key = (String)i.next();
342 					if (!overrideFields.containsKey(key))
343 					{
344 						overrideFields.put(key, fields.get(key));
345 					}
346 				}
347 			}
348 		}
349 	}
350 	
351 	/***
352 	 * Whether any errors were registered during population/ validation.
353 	 * @return boolean are there any errors registered for this formBean? True if so, false otherwise.
354 	 */
355 	public boolean hasErrors()
356 	{
357 		return (errors != null && (!errors.isEmpty()));
358 	}
359 
360 	// ----------------------- DISPLAY/ OUTPUT METHODS ---------------------//
361 
362 	/***
363 	* Get the display string of the property with the given name without using a pattern.
364 	* 
365 	* If an object was found for the given property name, it will be formatted with the 
366 	* formatter found as follows:
367 	* 	1. look in the ConverterRegistry if a formatter was stored with the fieldname
368 	* 			and optionally locale as key.
369 	* 	2. if not found, look in the ConverterRegistry if a Converter was stored for the 
370 	* 			type of the property that implements Formatter (as well as Converter). 
371 	* If a formatter was found, it will be used for formatting the property 
372 	* (using the format(property, pattern) method). If not, ConvertUtils of the 
373 	* BeanUtils package is used to get the string representation of the property.
374 	* 
375 	* @param name name of the property
376 	* @return String the formatted value of the property of the current bean instance
377 	* or the registered override value if any was registered.
378 	*/
379 	public String displayProperty(String name)
380 	{
381 		return displayProperty(name, null);
382 	}
383 
384 	/***
385 	* Get the display string of the property with the given name, optionally 
386 	* using the given pattern.
387 	* 
388 	* If an object was found for the given property name, it will be formatted with the 
389 	* formatter found as follows:
390 	* 	1. look in the ConverterRegistry if a formatter was stored with the fieldname
391 	* 			and optionally locale as key.
392 	* 	2. if not found, look in the ConverterRegistry if a formatter was stored with
393 	* 			the pattern and optionally the locale as key.
394 	* 	3. if not found, look in the ConverterRegistry if a Converter was stored for the 
395 	* 			type of the property that implements Formatter (as well as Converter). 
396 	* If a formatter was found, it will be used for formatting the property 
397 	* (using the format(property, pattern) method). If not, ConvertUtils of the 
398 	* BeanUtils package is used to get the string representation of the property.
399 	* 
400 	* @param name name of the property
401 	* @param pattern optional pattern to use for formatting
402 	* @return String the formatted value (using the pattern) of the property of the 
403 	* current bean instance or the registered override value if any was registered.
404 	*/
405 	public String displayProperty(String name, String pattern)
406 	{
407 		if (name == null)
408 			return null;
409 
410 		String displayString = null;
411 		
412 		// first, check if there is a registration in the override fields
413 		Map _overrideFields = getOverrideFields();
414 		boolean wasOverriden = false;
415 		if (_overrideFields != null)
416 		{
417 			Object storedRawValue = _overrideFields.get(name);
418 			String storedValue = null;
419 			// first, try default
420 			if (storedRawValue != null) // an entry is found
421 			{
422 				wasOverriden = true; // set flag
423 				if (storedRawValue instanceof String) // no conversion
424 				{
425 					storedValue = (String)storedRawValue;
426 				}
427 				else if(storedRawValue instanceof String[]) 
428 					// this is a  String[]; convert to a simple String
429 				{
430 					storedValue = ValueUtils.convertToString(storedRawValue);
431 				}
432 				else // another type, try to format 
433 				{
434 					try
435 					{
436 						storedValue = format(name, storedRawValue, pattern);
437 					}
438 					catch(Exception e)
439 					{
440 						storedValue = ValueUtils.convertToString(storedRawValue);
441 					}
442 				}
443 				if (storedValue != null)
444 				{
445 					displayString = storedValue;
446 				}
447 			}
448 			
449 			if(formattingLog.isDebugEnabled())
450 			{
451 				formattingLog.debug(
452 					"using override for property " + name + ": " + displayString);
453 			}
454 		}
455 		
456 		if(!wasOverriden) // no override value found?
457 		{
458 			try
459 			{
460 				Object _value = Ognl.getValue(name, bean); // get the property value
461 				if (_value != null)
462 				{
463 					// format string
464 					displayString = format(name, _value, pattern);
465 				}
466 			}
467 			catch (Exception e)
468 			{
469 				if(log.isDebugEnabled())
470 				{
471 					log.error(e.getMessage(), e);	
472 				}
473 				return null;
474 			}	
475 		}
476 
477 		return displayString;
478 	}
479 
480 	/***
481 	 * Get the formatter for the given fieldname/ class/ locale.
482 	 * 
483 	 * 	1. look in the ConverterRegistry if a formatter was stored with the formatterName
484 	 * 			and optionally locale as key.
485 	 * 	2. if not found, look in the ConverterRegistry if a formatter was stored with
486 	 * 			the pattern and optionally the locale as key.
487 	 * 	3. if not found, look in the ConverterRegistry if a Converter was stored for the 
488 	 * 			type of the property that implements Formatter (as well as Converter). 
489 	 * 
490 	 * @param formatterName name of formatter
491 	 * @param pattern pattern: might be used as a key to store a Formatter
492 	 * @param clazz class of property
493 	 * @param locale locale to get Formatter for
494 	 * @return Formatter instance of Formatter if found, null otherwise
495 	 */
496 	public Formatter getFormatter(String formatterName, String pattern, Class clazz, Locale locale)
497 	{
498 		Formatter formatter = null;
499 		ConverterRegistry reg = ConverterRegistry.getInstance();
500 
501 		try
502 		{
503 			// first look up on fieldname
504 			if(formatterName != null)
505 			{
506 				formatter = reg.lookup(formatterName);
507 			}
508 			
509 			if(formatter == null && (pattern != null)) // not found, try pattern
510 			{
511 				formatter = reg.lookup(pattern);
512 			}
513 
514 			if (formatter == null && (clazz != null)) // not found, try converter
515 			{
516 				Converter converter = reg.lookup(clazz, getCurrentLocale());
517 				if ((converter != null) && (converter instanceof Formatter))
518 				{
519 					formatter = (Formatter)converter;
520 				} // else will return null
521 			}
522 		}
523 		catch (Exception e)
524 		{
525 			log.error(e);
526 			if(log.isDebugEnabled())
527 			{
528 				log.error(e.getMessage(), e);
529 			}
530 			// ignore
531 		}
532 
533 		return formatter;
534 	}
535 
536 	// ----------------------- UTILITY METHODS -----------------------------//
537 	
538 	/***
539 	 * Format the given value, independent of the current form, using the class
540 	 * of the value to lookup a formatter.
541 	 * @param value value to format
542 	 * @return String formatted value
543 	 */
544 	public String format(Object value)
545 	{
546 		return format(null, value, null);
547 	}
548 	
549 	/***
550 	 * Format the given value, independent of the current form, using the class of the
551 	 * value to lookup a formatter using the provided pattern.
552 	 * @param value value to format
553 	 * @param pattern pattern for format
554 	 * @return String formatted value
555 	 */
556 	public String format(Object value, String pattern)
557 	{
558 		return format(null, value, pattern);
559 	}
560 	
561 	/***
562 	 * Format the given value, independent of the current form, using the provided
563 	 * name of the formatter to lookup the formatter or - if no formatter was found -
564 	 * using the class of the value to lookup the formatter.
565 	 * @param formatterName name of formatter.
566 	 * @param value value to format
567 	 * @return String formatted value
568 	 */
569 	public String format(String formatterName, Object value)
570 	{
571 		return format(formatterName, value, null);
572 	}
573 	
574 	/***
575 	 * Format the given value, independent of the current form using:
576 	 * 	1. look in the ConverterRegistry if a formatter was stored with the formatterName
577 	 * 			and optionally locale as key;
578 	 * 	2. if not found, look in the ConverterRegistry if a formatter was stored with
579 	 * 			the pattern and optionally the locale as key;
580 	 * 	3. if not found, look in the ConverterRegistry if a Converter was stored for the 
581 	 * 			type of the property that implements Formatter (as well as Converter);
582 	 * @param formatterName name of formatter.
583 	 * @param value value to format
584 	 * @param pattern pattern for format
585 	 * @return String formatted value
586 	 */
587 	public String format(String formatterName, Object value, String pattern)
588 	{
589 		if(value == null) return null;
590 		
591 		String formatted = null;
592 		Formatter formatter =
593 			getFormatter(formatterName, pattern, value.getClass(), getCurrentLocale());
594 		
595 		if (formatter != null)
596 		{
597 			if(formattingLog.isDebugEnabled())
598 			{
599 				formattingLog.debug(
600 					"using formatter " + formatter +
601 					" for property " + formatterName);
602 			}
603 			formatted = formatter.format(value, pattern);
604 		}
605 		else
606 		{
607 			if(formattingLog.isDebugEnabled())
608 			{
609 				formattingLog.debug(
610 					"using default convertUtils formatter for property " + formatterName);
611 			}
612 			formatted = ValueUtils.convertToString(value);
613 		}
614 		return formatted;
615 	}
616 	
617 	// ------------- ATTRIBUTES, the decorator methods for a HashMap -------------//
618 
619 	/***
620 	 * Returns the value to which the attributes map maps the specified key. 
621 	 * Returns null if the map contains no mapping for this key.
622 	 * @param key key whose associated value is to be returned
623 	 * @return Object the value to which the attributes map maps the specified key, 
624 	 * or null if the map contains no mapping for this key.
625 	 * @see java.util.Map#get(java.lang.Object)
626 	 */
627 	public Object get(Object key)
628 	{
629 		return (attributes != null) ? attributes.get(key) : null;
630 	}
631 	
632 	/*** 
633 	 * Associates the specified value with the specified key in the attribute map. 
634 	 * If the attribute map previously contained a mapping for this key, the old value 
635 	 * is replaced by the specified value.
636 	 * @param key key with which the specified value is to be associated.
637 	 * @param value value to be associated with the specified key.
638 	 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
639 	 */
640 	public Object put(Object key, Object value)
641 	{
642 		if(attributes == null) attributes = new HashMap();
643 		return attributes.put(key, value);
644 	}
645 
646 	/***
647 	 * get the attribute values
648 	 * @return Collection the attribute values or null if no attributes were set
649 	 * @see java.util.Map#values()
650 	 */
651 	public Collection values()
652 	{
653 		return (attributes != null) ? attributes.values() : null;
654 	}
655 
656 	/***
657 	 * get the key set of the attributes
658 	 * @return Set the key set of the attributes or null if no attributes were set
659 	 * @see java.util.Map#keySet()
660 	 */
661 	public Set keySet()
662 	{
663 		return (attributes != null) ? attributes.keySet() : null;
664 	}
665 
666 	/***
667 	 * clear the attributes
668 	 * @see java.util.Map#clear()
669 	 */
670 	public void clear()
671 	{
672 		this.attributes = null;
673 	}
674 
675 	/***
676 	 * get the number of attributes
677 	 * @return int the number of attributes
678 	 * @see java.util.Map#size()
679 	 */
680 	public int size()
681 	{
682 		return (attributes != null) ? attributes.size() : 0;
683 	}
684 
685 	/***
686 	 * put the provided map in the attribute map 
687 	 * @param t map to put in attributes
688 	 * @see java.util.Map#putAll(java.util.Map)
689 	 */
690 	public void putAll(Map t)
691 	{
692 		if(attributes == null) attributes = new HashMap();
693 		attributes.putAll(t);
694 	}
695 
696 	/***
697 	 * get the entries of the attributes
698 	 * @return Set the entries of the attributes or null if no attributes were set
699 	 * @see java.util.Map#entrySet()
700 	 */
701 	public Set entrySet()
702 	{
703 		return (attributes != null) ? attributes.entrySet() : null;
704 	}
705 
706 	/***
707 	 * is the provided key the key of an attribute
708 	 * @param key the key to look for
709 	 * @return boolean is the provided key the key of an attribute
710 	 * @see java.util.Map#containsKey(java.lang.Object)
711 	 */
712 	public boolean containsKey(Object key)
713 	{
714 		return (attributes != null) ? attributes.containsKey(key) : false;
715 	}
716 
717 	/***
718 	 * are there any attributes
719 	 * @return boolean are there any attributes
720 	 * @see java.util.Map#isEmpty()
721 	 */
722 	public boolean isEmpty()
723 	{
724 		return (attributes != null) ? attributes.isEmpty() : true;
725 	}
726 
727 	/***
728 	 * remove an attribute
729 	 * @param key key of the attribute to remove
730 	 * @return Object the object that was stored under the given key, if any
731 	 * @see java.util.Map#remove(java.lang.Object)
732 	 */
733 	public Object remove(Object key)
734 	{
735 		return (attributes != null) ? attributes.remove(key) : null;
736 	}
737 
738 	/***
739 	 * is the provided value stored as an attribute
740 	 * @param value the value to look for
741 	 * @return boolean is the provided value stored as an attribute
742 	 * @see java.util.Map#containsValue(java.lang.Object value)
743 	 */
744 	public boolean containsValue(Object value)
745 	{
746 		return (attributes != null) ? attributes.containsValue(value) : false;
747 	}
748 
749 	/***
750 	 * get current controller
751 	 * @return FormBeanCtrlBase
752 	 */
753 	public FormBeanCtrlBase getController()
754 	{
755 		return controller;
756 	}
757 
758 	/***
759 	 * set current controller
760 	 * @param controller current controller
761 	 */
762 	public void setController(FormBeanCtrlBase controller)
763 	{
764 		this.controller = controller;
765 	}
766 
767 	/***
768 	 * String rep.
769 	 * @see java.lang.Object#toString()
770 	 */
771 	public String toString()
772 	{
773         StringBuffer b = new StringBuffer(super.toString());
774         b.append(" { bean=")
775          .append(bean)
776          .append(", ctrl=")
777          .append(controller);
778         if(attributes != null && (!attributes.isEmpty()))
779         {
780             b.append(", attributes{");
781             for(Iterator i = attributes.keySet().iterator(); i.hasNext(); )
782             {
783                 String key = (String)i.next();
784                 b.append(key)
785                  .append("=")
786                  .append(attributes.get(key));
787                 if(i.hasNext())
788                 {
789                     b.append(",");
790                 }
791             }
792             b.append("}");
793         }
794         b.append(", errors{");
795         if(errors != null)
796         {
797             for(Iterator i = errors.keySet().iterator(); i.hasNext(); )
798             {
799                 String errorKey = (String)i.next();
800                 b.append(errorKey)
801                  .append("=")
802                  .append(getError(errorKey))
803                  .append("(override=")
804                  .append(getOverrideField(errorKey))
805                  .append(")");
806                 if(i.hasNext())
807                 {
808                     b.append(",");
809                 }
810             }
811         }
812         b.append("}}");
813         return b.toString();
814 	}
815 
816 }