001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002 // The Molly framework is freely distributable under the terms of an 003 // MIT-style license. For details, see the molly pages web site at: 004 // http://www.mollypages.org/. Use, modify, have fun ! 005 006 package fc.web.forms; 007 008 import javax.servlet.*; 009 import javax.servlet.http.*; 010 import java.io.*; 011 import java.util.*; 012 import java.sql.*; 013 014 import fc.jdbc.*; 015 import fc.io.*; 016 import fc.util.*; 017 018 /** 019 Represents an HTML form. It is possible to instantiate a different form 020 object per user and store each form in that users session scope. However, 021 more typically, instances of this object are instantiated once per unique 022 form and stored in web application scope. 023 <p> 024 Method in this class are thread safe since form processing is done 025 entirely within various methods and the form processing results in the 026 creation of seperate {@link FormData} objects for each request. 027 <p> 028 Various {@link Field Fields} can be added to the form at form creation 029 time. Some fields (like {@link Hidden} can also be added per user/request, 030 dynamically, to the {@link FormData} object. This is useful while saving 031 some user or request specific information. 032 <p> 033 A form collects data from the front-end/web-client and often saves it to a 034 back end database entity. Form data validation can be done in 2 ways: 035 <ol> 036 <li>at the <i>form</i> level where the form collects data and validates 037 it via validators associated with the form object. 038 <li>at the <i>field</i> level where the form itself does no higher 039 level validation but populates a field and then asks that field to 040 validate itself. 041 </ol> 042 In both cases, any errors associated with the form can be shown back to the 043 user. This framework allows both types of validation methods to be used. 044 Using validation and/or showing validation errors is optional. This framework 045 can simply be used to maintain form state and validation can be done via 046 other ad-hoc code (outside of this framework) as needed. 047 <p> 048 An HTML form presented to the user can contain fields from more than 1 049 database table. For example, there could to 2 database tables, say 050 "person" and "office" both with a "other" column. Since <u>each field 051 contained in the form object must have a unique name</u>, there will be a 052 name clash between the fields/attributes of the entities when 2 fields 053 with the name "other" are added to the form. 054 <p> 055 There are 2 general approaches to solving this problem. 056 <ol> 057 <li>Seperate database tables should be represented by seperate Form objects 058 (all of which can be rendered in the same HTML document shown to the user). 059 Different instances of form objects (say 1 per table) act as seperate 060 name-spaces and fields having the same name can exist in these seperate 061 instances. However, the dynamic server page where the form object is 062 rendered/submitted has to obtain these seperate instances from the application 063 object (or wherever all forms are stored) -- which adds some complexity. 064 <li>Create 1 instance of a form object containing fields from more than one 065 table. In this case, some or all field names can be prepended (or suffixed) 066 with the name of the database table. This removes any name clashes if done 067 only for clashing fields; if done for every field may help in documenting 068 which table that field belongs to. There is more typing involved with this 069 approach. 070 </ol> 071 Either of the above approaches is valid. 072 <p> 073 Sometimes, other methods need to obtain a field (for a particular table) by 074 name. For example, the managers generated by {@link fc.jdbc.dbo.Generate} 075 have a <tt>addValidators(form)</tt> method that extract the fields for a 076 particular table and add validators to that field. This method in 077 particular need to be passed either the appropriate form object in which 078 that field exists (if there are multiple form objects) or in the case of 079 only 1 form object, passed the prefix for that particular field, if that 080 field was added with a prefix to it's name. 081 082 @author hursh jain 083 **/ 084 public class Form 085 { 086 //useful for method that return empty lists/maps in lieu of nulls 087 static final public List empty_list = 088 Collections.unmodifiableList(new ArrayList()); 089 090 static final public Map empty_map = 091 Collections.unmodifiableMap(new HashMap()); 092 093 protected Log log; 094 //fields have field-level validators 095 protected LinkedHashMap fields; 096 //collections of fields -- t hese fields are directly added 097 //to the fields map also 098 protected LinkedHashMap fieldRef; 099 protected LinkedHashMap formValidators; //form-level validators 100 protected List dependencies; 101 protected String name; //the form name 102 protected String method; 103 protected String actionURL; 104 protected String displayURL; 105 //protected String fieldNamePrefix; 106 protected java.util.Timer timer; 107 protected SubmitHackedHandler hackHandler; 108 /** 109 Creates a new form with the specified name, method (get/post) and URL 110 **/ 111 public Form(String name, String method, String actionURL, Log log, 112 SubmitHackedHandler handler) 113 { 114 this.name = name; 115 this.method = method; 116 this.actionURL = actionURL; 117 fields = new LinkedHashMap(); 118 fieldRef = new LinkedHashMap(); 119 formValidators = new LinkedHashMap(); 120 dependencies = new ArrayList(); 121 this.log = log; 122 123 this.hackHandler = handler; 124 if (handler == null) { 125 hackHandler = new SubmitHackedHandler(this); 126 } 127 } 128 129 /* 130 Creates a new form with the specified name, method (get/post) and URL 131 and the default {@link SubmitHackedHandler} 132 **/ 133 public Form(String name, String method, String actionURL, Log log) 134 { 135 this(name, method, actionURL, log, null); 136 } 137 138 /** 139 Creates a new form with logging to the default log retreived by 140 {@link Log#getDefault} and the default {@link SubmitHackedHandler} 141 **/ 142 public Form(String name, String method, String actionURL) 143 { 144 this(name, method, actionURL, Log.getDefault(), null); 145 } 146 147 /** 148 Creates a new form with the specified name, method=POST and no action URL 149 **/ 150 public Form(String name) 151 { 152 this(name, "post", null); 153 } 154 155 /** 156 This method should be called when the form is removed from session or 157 application scope. It is very important to do this if there are background 158 update tasks because this method stops such background refresh timer thread 159 (which would otherwise continue to run). 160 */ 161 public void destroy() 162 { 163 timer.cancel(); 164 fields.clear(); 165 } 166 167 /** 168 Removes the specified field from the form. See {@link 169 java.util.Map#remove(Object)} for the general contract. 170 171 @param field the field to remove from the form. 172 **/ 173 public Object remove(Field field) { 174 return fields.remove(field.getName()); 175 } 176 177 /** 178 Adds the specified element to the form. The added field can 179 later be retrieved via it's name. The field iteration order 180 will be the same order in which fields are added via this 181 method. 182 <p> 183 Note, field names have to be unique. "Grouped" fields (such 184 as HTML form checkboxes or radio buttons) which require the 185 same field name are encapsulated by a single appropriate 186 grouped field object such as {@link RadioGroup} and {@link 187 CheckboxGroup}. This field object itself should have a 188 unique name. 189 190 @return A reference to this form as a convenience for method chaining 191 @throws IllegalArgumentException if an element with the same name 192 already exists in this form. 193 **/ 194 public Form add(Field field) 195 { 196 String name = field.getName(); 197 198 //not needed, prefix each field manually if needed. 199 /* 200 if (fieldNamePrefix != null) { 201 name = fieldNamePrefix + name; 202 } 203 */ 204 if (fields.containsKey(name)) { 205 throw new IllegalArgumentException("A form element by that name [" + name + "] already exists"); 206 } 207 fields.put(name, field); 208 field.setParentForm(this); 209 210 return this; 211 } 212 213 /** 214 Adds the specified dependency to the form. 215 */ 216 public Form add(Dependency d) 217 { 218 dependencies.add(d); 219 220 return this; 221 } 222 223 224 /** 225 Adds a {@link FieldRef} to this Form. This is useful when 226 to track a set of related fields in a form. (Note: those fields 227 <b>must</b> also directly be added to the form). 228 */ 229 public Form add(FieldRef ref) 230 { 231 String name = ref.getName(); 232 if (fieldRef.containsKey(name)) { 233 throw new IllegalArgumentException("A fieldRef by that name [" + name+ "] already exists"); 234 } 235 fieldRef.put(name, ref); 236 return this; 237 } 238 239 /** 240 Adds a <b>form level</b> validation error message. This method is needed when 241 form level validation is done via classes that are <b>not</b> subclasses 242 of {@link FormValidator} (these classes are used outside of the 243 validation framework and their error message still needs to be added to 244 the form data). 245 <p> 246 <b> This is a very useful method for adding the results of arbitrary code 247 to this form. </b> 248 249 @param fd form data 250 @param errorKey an arbitrary/optional validation key which allows 251 to later retrieve and display a particular 252 form error. Specify <tt>null</tt> for no 253 key. 254 @param errorMessage the form error message 255 256 @see Field#addError 257 **/ 258 public void addError(FormData fd, String errorKey, String errorMessage) 259 { 260 fd.addFormError(errorKey, errorMessage); 261 } 262 263 /** 264 Convenience method that invokes {@link addError(FormData, String, String) 265 addError} with an empty validation <tt>errorKey</tt>. 266 */ 267 public void addError(FormData fd, String errorMessage) 268 { 269 addError(fd, null, errorMessage); 270 } 271 272 /** 273 Adds a {@link Hidden} field to this form. This field exists only for the 274 duration of this form data object and is useful for request specific 275 data. 276 */ 277 public void addDynamicField(FormData fd, Hidden f) { 278 fd.addDynamicField(f); 279 } 280 281 /** 282 Adds a form level validator to this form. Note, validator 283 names have to be unique and not clash with other form level 284 validator names. 285 286 @throws IllegalArgumentException if a validator with the same name 287 already exists in this form. 288 **/ 289 public void addValidator(FormValidator validator) { 290 String name = validator.getName(); 291 if (formValidators.containsKey(name)) { 292 throw new IllegalArgumentException("A validator by that name [" + name + "] already exists in this form"); 293 } 294 formValidators.put(name, validator); 295 } 296 297 /** 298 Adds an arbitrary message for this request. Messages can include arbitrary 299 warnings, usage help etc. 300 */ 301 public void addMessage(FormData fd, String key, String value) 302 { 303 fd.addMessage(key, value); 304 } 305 306 /** 307 Returns true if the form contains the specified field. 308 */ 309 public boolean containsField(String fieldName) 310 { 311 return fields.containsKey(fieldName); 312 } 313 314 /** 315 Retrieves a previously added message or <tt>null</tt> if the speccified 316 form data was <tt>null</tt> or if the message was not found. 317 */ 318 public String getMessage(FormData fd, String key) 319 { 320 if (fd == null) 321 return null; 322 323 return fd.getMessage(key); 324 } 325 326 public void addSubmitHackedHandler(SubmitHackedHandler handler) 327 { 328 Argcheck.notnull(handler, "handler param was null"); 329 this.hackHandler = handler; 330 } 331 332 protected void handleSubmitHacked(HttpServletRequest req, String msg) 333 throws SubmitHackedException 334 { 335 hackHandler.handle(req, msg); 336 } 337 338 /** 339 Processes the submitted form. <b>This method must be called after each form 340 submission by the servlet or page code that handles form submits</b>. This 341 method does the following: 342 <ol> 343 <li>populates the fields of the form from the submitted request. 344 <li>if the form has any dependencies and if the dependencies need updating, 345 then those dependencies are updated. Validation is not carried out in this 346 case since the form has changed and the user needs to typically carry out 347 some other action before submitting the form. 348 <li>if there are no updated dependencies, validates the form via any 349 validators attached to the form and it's fields. 350 </ol> 351 Note: To see if there were any validation errors, invoke the {@link 352 hasError(FormData)} method with the formdata returned by this method. 353 Seeing validation errors is optional of course, and so is using validators 354 in the first place. 355 356 @param req the HttpServletRequest corresponding to the request 357 being processed by this form. 358 359 @return a {@link FormData} object containing populated 360 field data and the results of the validation. 361 **/ 362 public FormData handleSubmit(HttpServletRequest req) 363 throws ServletException, IOException 364 { 365 return handleSubmit(req, null); 366 } 367 368 /** 369 This method is similar to {@link handleSubmit(HttpServletRequest)} except 370 it stores the specified connection in the form data. This connection can 371 be used by any attached dependency classes for database queries when 372 updating form depedencies. 373 <p> 374 Note, we can set arbitrary objects in the {@link HttpServletRequest} via 375 it's {@link HttpServletRequest#setattribute setAttribute} method. 376 However, specifying and storing the connection in the FormData is easier 377 to remember and use. 378 <p> 379 There are 2 cases: 380 <ol> 381 <li>The dependent fields (if any) will internally use {@link WebApp#getConnection} 382 to obtain a connection and no connection has to be set in the form data.</li> 383 The {@link handleSubmit(HttpServletRequest)} can then be used. 384 <li>The dependent fields (if any) classes will expect a connection in the form data. In 385 that case, this method should be invoked.</li> 386 </ol> 387 If there are no dependent fields, then the {@link handleSubmit(HttpServletRequest)} 388 is sufficient. 389 390 @param req the HttpServletRequest corresponding to the request 391 being processed by this form. 392 @param con a connection object. The connection is not closed and 393 it is the callers responsiblity to do so. 394 */ 395 public FormData handleSubmit(HttpServletRequest req, Connection con) 396 throws ServletException, IOException 397 { 398 FormData fd = populate(req); //each field sets it's submitted value 399 if (con != null) 400 fd.setConnection(con); 401 402 updateDependencies(fd, req); //this can throw an exception. 403 404 if (! fd.dependencyUpdated) 405 validate(fd, req); 406 407 return fd; 408 } 409 410 411 public FormData refresh(HttpServletRequest req) 412 throws ServletException, IOException 413 { 414 return refresh(req, null); 415 } 416 417 /** 418 Refreshes the form for this request by running all attached refreshers 419 and dependencies. Maintains form state. Does not run any validatators. 420 Useful for re-displaying a form when lookup fields have changed in the 421 database. 422 <p> 423 <u>To refresh the form for all users/requests from this point onwards, 424 create a new instance of the form and replace the existing instance 425 of the form with the newly created instance.</u> 426 */ 427 public FormData refresh(HttpServletRequest req, Connection con) 428 throws ServletException, IOException 429 { 430 FormData fd = populate(req); //each field sets it's submitted value 431 if (con != null) 432 fd.setConnection(con); 433 434 updateDependencies(fd, req); //this can throw an exception. 435 436 return fd; 437 } 438 439 /** 440 Populates field's based on the parameters submitted via the specified 441 request and stores it into the specified FormData object. 442 **/ 443 protected FormData populate(HttpServletRequest req) 444 throws SubmitHackedException 445 { 446 FormData fd = new FormData(); 447 Iterator it = fields.entrySet().iterator(); 448 while (it.hasNext()) { 449 Map.Entry entry = (Map.Entry) it.next(); 450 Field f = (Field) entry.getValue(); 451 f.setValueFromSubmit(fd, req); 452 } 453 return fd; 454 } 455 456 protected void updateDependencies(FormData fd, HttpServletRequest req) 457 throws DependencyException 458 { 459 for (int i = 0; i < dependencies.size(); i++) 460 { 461 Dependency d = (Dependency) dependencies.get(i); 462 d.updateValues(fd, req); 463 } 464 } 465 466 /** 467 Validates each field and also calls each form level validator. 468 <p> 469 Validation is a 2-step process. First, only enabled fields are validated. 470 Some validators may enable or disable fields as part of their validation 471 strategy (for example, if the user chooses a radio button marked 'detail', 472 then fields corresponding to the detail section need to be enabled). (other 473 fields might be disabled). Fields that are re-enabled in this fashion are 474 validated by this method. 475 <p> 476 After running through all the validators, returns <tt>false</tt> is there 477 is a validation error, <tt>true</tt> otherwise. The field and form 478 validation errors can then be retrieved via the {@link 479 FormData#getFieldErrors} and {@link FormData#getFormErrorsCollection}, 480 {@link FormData#getFormErrorsMap} methods. 481 **/ 482 protected boolean validate(FormData fd, HttpServletRequest req) 483 { 484 boolean result = true; 485 Map errors = fd.getFieldErrors(); 486 Field f = null; 487 Iterator it = null; 488 //List checkAgainList = new ArrayList(); 489 490 //for all form validators 491 it = formValidators.values().iterator(); 492 while (it.hasNext()) { 493 FormValidator v = (FormValidator) it.next(); 494 if (! v.validate(fd, req)) { 495 result = false; //latch 496 String tmp = v.getErrorMessage(); 497 String vname = v.getName(); 498 log.bug("Group validate error: ", vname, tmp); 499 fd.addFormError(vname, tmp); 500 } 501 } 502 503 //for all field validators, (some could actually be 504 //disabled by the form validators above). 505 it = fields.values().iterator(); 506 while (it.hasNext()) 507 { 508 f = (Field) it.next(); 509 510 if (f.isEnabled(fd)) 511 { 512 if (! f.validate(fd, req)) { 513 result = false; //latch 514 } 515 } 516 } //!while 517 518 return result; 519 } 520 521 /** 522 Returns <tt>true</tt> if validation did not succeed. 523 */ 524 public static boolean hasError(FormData fd) 525 { 526 if (fd == null) 527 return false; //no data so could not validate 528 529 //FieldErrors = map: fieldname->list of errors 530 //entries in the map (via field.validate) should 531 //only be created if there is a validation error 532 //an empty list of errors should not be created 533 //otherwise the size() below will not work 534 if (fd.getFormErrorsCollection().size() > 0 || 535 fd.getFieldErrors().size() > 0) 536 { 537 return true; 538 } 539 540 return false; 541 } 542 543 /** 544 Convenience method to render <i>form-level</i> validation errors. This method 545 writes validation error message for the specified form level validator. 546 The error messages are encapulated in a html <div> tag and the class 547 of this tag is set to <tt>form-errmsg</tt>. Each error message within this 548 div is assigned it's own div with class set to <tt>form-errmsg-item</tt> 549 550 @param writer the Writer to render errors to 551 **/ 552 public void renderError(FormData fd, Writer writer) 553 throws IOException 554 { 555 if (fd == null) 556 return; 557 558 Collection errors = fd.getFormErrorsCollection(); 559 560 int size = errors.size(); 561 if (size == 0) 562 return; 563 564 writer.write("<div class='form-errmsg'>\n"); 565 Iterator it = errors.iterator(); 566 while (it.hasNext()) 567 { 568 String msg = (String) it.next(); 569 if (msg == null) 570 continue; 571 writer.write("<div class='form-errmsg-item'>"); 572 writer.write(msg); 573 writer.write("</div>\n"); 574 } 575 writer.write("</div>\n"); 576 } 577 578 579 /** 580 Convenience method to render a validation error for a particluar form level 581 validator. The error messages are encapulated in a html <div> tag and 582 the class of this tag is set to <tt>form-errmsg</tt>. 583 584 @param writer the Writer to render errors to 585 @param formValidatorName the name of a form level validator 586 **/ 587 public void renderErrorFor( 588 FormData fd, Writer writer, String formValidatorName) 589 throws IOException 590 { 591 if (fd == null) 592 return; 593 594 List errors = (List) getFormErrorsFor(fd, formValidatorName); 595 596 int size = errors.size(); 597 if (size == 0) 598 return; 599 600 writer.write("<div class='form-errmsg'>\n"); 601 Iterator it = errors.iterator(); 602 while (it.hasNext()) 603 { 604 String msg = (String) it.next(); 605 if (msg == null) 606 continue; 607 writer.write("<div class='form-errmsg-item'>"); 608 writer.write(msg); 609 writer.write("</div>\n"); 610 } 611 writer.write("</div>\n"); 612 } 613 614 /** 615 @param fd a non-null form data object 616 @return a map containing field names as keys and the 617 field's validation error as the value. The data 618 type of each value is that returned by the {@link 619 Field#getValidateErrors} method 620 **/ 621 public Map getFieldErrors(FormData fd) { 622 return fd.getFieldErrors(); 623 } 624 625 /** 626 @param fd a non-null form data object 627 @return a list containing form level errors. The data type 628 of each value is that returned by the {@link 629 FormValidator#getErrorMessage()} method 630 **/ 631 public Collection getFormErrorsCollection(FormData fd) { 632 return fd.getFormErrorsCollection(); 633 } 634 635 /** 636 Returns a list containing all form-level errors for the specified 637 validator. If there are no errors for the specified validator, returns an 638 empty list. 639 640 @param fd a non-null form data object 641 @param formValidatorName a form validator name 642 */ 643 public List getFormErrorsFor(FormData fd, String formValidatorName) 644 { 645 Map map = fd.getFormErrorsMap(); 646 List list = (List) map.get(formValidatorName); 647 if (list == null) 648 return empty_list; 649 650 return list; 651 } 652 653 /** 654 Renders <b>all</b> dynamically added hidden fields of the form. Dynamic 655 fields are created/ added per user per submit to the {@link FormData} 656 if/as needed. 657 <p> 658 <b>Note</b>: Dynamic fields are <b>not</b> part of a form's fields and 659 are not available via 660 */ 661 public void renderDynamicFields(FormData fd, Writer out) 662 throws SQLException, IOException 663 { 664 if (fd == null || fd.dynamicFields == null) 665 return; 666 667 Iterator it = fd.dynamicFields.values().iterator(); 668 while (it.hasNext()) { 669 Hidden h = (Hidden) it.next(); 670 h.render(out); 671 } 672 } 673 674 /** Returns the name of this form **/ 675 public String getName() { 676 return name; 677 } 678 679 /** 680 Sets an arbitrary application specific string that can 681 later be retrieved. Some applications may find 682 this method useful. 683 **/ 684 public void setActionURL(String url) { 685 this.actionURL = url; 686 } 687 688 /** 689 Returns the action URL string. Returns <tt>null</tt> 690 if no url has been set. 691 **/ 692 public String getActionURL() { 693 return actionURL; 694 } 695 696 697 /** 698 Sets an arbitrary application specific string. Intended 699 to store the URL to the jsp or front end page that will 700 render this form for the user. 701 **/ 702 public void setDisplayURL(String url) { 703 this.displayURL = url; 704 } 705 706 /** 707 Returns the display URL string. Returns <tt>null</tt> 708 if no display url has been set. 709 **/ 710 public String getDisplayURL() { 711 return displayURL; 712 } 713 714 715 /** 716 Returns a collection view of all the fields contained in this 717 form. 718 **/ 719 public Collection getAllFields() { 720 return fields.values(); 721 } 722 723 724 /* 725 *This is overkill, manually prefix fields as needed 726 * 727 *Automatically appends the specified prefix to the names of all fields 728 *added after this method is called. Suppose there are 2 tables contained in 729 *1 form and both tables contain a field called <tt>id</tt>. Then, since 730 *field names in the form must be unique, one can either add those fields 731 *under different names (say, "table1.id", and "table2.id") or can do the 732 *following: 733 *<blockquote> 734 *form.setFieldNamePrefix("table1"); 735 *form.add(id); 736 *form.setFieldNamePrefix("table2"); 737 *form.add(id); 738 *</blockquote> 739 *This latter way is useful when many field names clash. 740 * 741 *@param prefix the prefix string. Specify <tt>null</tt> 742 * or the empty string to have no prefix. 743 * (<tt>null</tt> is more efficient) 744 * 745 *public void setFieldNamePrefix(String prefix) { 746 * if ( (prefix == null) || prefix.equals("") ) 747 * { 748 * this.fieldNamePrefix = null; 749 * return; 750 * } 751 * this.fieldNamePrefix = prefix + "_"; 752 * } 753 */ 754 755 //get various Field types 756 /** 757 Returns the form field with the specified name. 758 759 @param name the name of the field 760 761 @return the field or <tt>null</tt> if no element with that name 762 was found in this form. 763 **/ 764 public Field get(String name) 765 { 766 Field result = (Field) fields.get(name); 767 768 if (result == null) 769 log.warn("Tried to obtain a non-existent field from the form. Did you add a field with this name to the form ? Fieldname: [", name, "]"); 770 771 return result; 772 } 773 774 /** 775 Gets a {@link Hidden} field added to the form data object. Returns 776 <tt>null</tt> if the form data object is null or if the specified 777 field does not exist. <u>Dyanamic fields returned by this method are only 778 available for the duration of the form data object and <u>can not 779 obtain a field from a submitted form.</u> (use {@link getDynamicField(FormData, HttpServletRequest)} 780 for that purpose. 781 */ 782 public Hidden getDynamicField(FormData fd, String fieldName) 783 { 784 if (fd == null) 785 return null; 786 787 return fd.getDynamicField(fieldName); 788 } 789 790 791 /** 792 Once the form is displayed to the user (with, say, hidden fields that 793 were added dynamically for that request), then when that form is 794 submitted, those dynamic fields can be retrieved via any of: 795 <ul> 796 <li> the {@link HttpServletRequest#getParameter} method (often the easiest 797 way) 798 <li> the {@link #getSubmittedField(HttpServletRequest)} method (which will 799 attempt to read the request parameter with the specified name and return 800 is as a Hidden field. (this is slightly more convenient if there is more 801 than 1 hidden field with the same name, since all hidden fields with the 802 same name are encapsulated by one object of type {@link Hidden}). 803 </ul> 804 */ 805 public Hidden getSubmittedField(HttpServletRequest req, String name) 806 { 807 String str = req.getParameter(name); 808 809 if (str == null) 810 str = ""; 811 812 Hidden hidden = new Hidden(name, str); 813 hidden.dynamicallyAdded = true; 814 return hidden; 815 } 816 817 /** 818 Returns all the fields that <b>contains</b> the specified name. This is 819 useful when a bunch of fields are created via database lookup tables (and 820 each such field is prefixed/suffixed by say <tt>lookup_name + some 821 number</tt>). 822 823 @param name the name of the field 824 825 @return a List containing fields with matching names or or an empty list 826 if no elements containing that name were found in this form. 827 **/ 828 public List getContaining(String name) 829 { 830 final List list = new ArrayList(); 831 final Iterator it = fields.values().iterator(); 832 while (it.hasNext()) 833 { 834 final Field f = (Field) it.next(); 835 if ( f.getName().indexOf(name) >= 0 ) 836 list.add(f); 837 } 838 return list; 839 } 840 841 /** 842 Returns the {@link FieldRef} with the specified key. This is 843 used to retrieve an arbitrary object added the via {@link 844 #addObject} method. Returns <tt>null</tt> if no object is 845 found for the specified key. 846 */ 847 public FieldRef getFieldRef(String key) 848 { 849 return (FieldRef) fieldRef.get(key); 850 } 851 852 public java.util.Timer getTimer() 853 { 854 synchronized (this) { 855 if (timer == null) { 856 timer = new java.util.Timer(); 857 } 858 } 859 860 return timer; 861 } 862 863 /** 864 Returns the field with the specific name as a {@link Select}. 865 866 @throws IllegalArgumentException if a field with the specified name does not exist or 867 if the field exists but is not of type Select 868 **/ 869 public Select getSelect(String name) 870 { 871 return (Select) get(name); 872 } 873 874 /** 875 Returns the field with the specific name as a {@link RefreshableSelect}. 876 877 @throws IllegalArgumentException if a field with the specified name does not exist or 878 if the field exists but is not of type RefreshableSelect 879 **/ 880 public RefreshableSelect getUpdatableSelect(String name) 881 { 882 return (RefreshableSelect) get(name); 883 } 884 885 886 887 /** 888 Returns the field with the specific name as a {@link Checkbox}. 889 890 @throws IllegalArgumentException if a field with the specified name does not exist or 891 if the field exists but is not of type Checkbox 892 **/ 893 public Checkbox getCheckbox(String name) 894 { 895 return (Checkbox) get(name); 896 } 897 898 /** 899 Returns the field with the specific name as a {@link 900 Checkbox}. 901 902 @throws IllegalArgumentException if a field with the specified name does not exist or 903 if the field exists but is not of type Checkbox 904 **/ 905 public CheckboxGroup getCheckboxGroup(String name) 906 { 907 return (CheckboxGroup) get(name); 908 } 909 910 /** 911 Returns the field with the specific name as a {@link 912 Hidden}. 913 914 @throws IllegalArgumentException if a field with the specified name does not exist or 915 if the field exists but is not of type Hidden 916 **/ 917 public Hidden getHidden(String name) 918 { 919 return (Hidden) get(name); 920 } 921 922 /** 923 Returns the field with the specific name as a {@link Text}. 924 925 @throws IllegalArgumentException if a field with the specified name does not exist or 926 if the field exists but is not of type Text 927 **/ 928 public Text getText(String name) 929 { 930 return (Text) get(name); 931 } 932 933 /** 934 Returns the field with the specific name as a {@link 935 TextArea}. 936 937 @throws IllegalArgumentException if a field with the specified name does not exist or 938 if the field exists but is not of type TextArea 939 **/ 940 public TextArea getTextArea(String name) 941 { 942 return (TextArea) get(name); 943 } 944 945 /** 946 Returns the field with the specific name as a {@link 947 Password}. 948 949 @throws IllegalArgumentException if a field with the specified name does not exist or 950 if the field exists but is not of type Password 951 **/ 952 public Password getPassword(String name) 953 { 954 return (Password) get(name); 955 } 956 957 /** 958 Returns the field with the specific name as a {@link Radio}. 959 960 @throws IllegalArgumentException if a field with the specified name does not exist or 961 if the field exists but is not of type Radio 962 **/ 963 public Radio getRadio(String name) 964 { 965 return (Radio) get(name); 966 } 967 968 /** 969 Returns the field with the specific name as a {@link 970 Checkbox}. 971 972 @throws IllegalArgumentException if a field with the specified name does not exist or 973 if the field exists but is not of type Checkbox 974 **/ 975 public RadioGroup getRadioGroup(String name) 976 { 977 return (RadioGroup) get(name); 978 } 979 980 public String toString() 981 { 982 final int border_len = 70; 983 final String nl = fc.io.IOUtil.LINE_SEP; 984 985 final int formval_size = formValidators.size(); 986 final int fieldref_size = fieldRef.size(); 987 StringBuffer buf = new StringBuffer(256); 988 StringBuffer tmp = null; 989 990 buf.append(nl); 991 buf.append(StringUtil.repeat('-', border_len)); 992 buf .append(nl) 993 .append("Form Name: ") 994 .append(name) 995 .append(" [") 996 .append(fields.size()) 997 .append(" Fields]") 998 .append(" [") 999 .append(formval_size) 1000 .append(" FormValidators]") 1001 .append(" [") 1002 .append(fieldref_size) 1003 .append(" FieldRefs]"); 1004 1005 if (timer != null) { 1006 buf.append(" [Attached Timer: ") 1007 .append(timer) 1008 .append(" ]"); 1009 } 1010 1011 buf.append(nl); 1012 1013 1014 ByteArrayOutputStream out = null; 1015 TablePrinter.PrintConfig config = null; 1016 TablePrinter p = null; 1017 1018 1019 out = new ByteArrayOutputStream(2048); 1020 config = new TablePrinter.PrintConfig(); 1021 config.setPrintBorders(false); 1022 config.setCellSpacing(1); 1023 config.setCellPadding(0); 1024 config.setAutoFit(true); 1025 p = new TablePrinter(3, new PrintStream(out), config); 1026 p.startTable(); 1027 1028 p.startRow(); 1029 p.printCell("FIELD"); 1030 p.printCell("TYPE"); 1031 p.printCell("FIELD_VALIDATORS"); 1032 p.endRow(); 1033 1034 Iterator it = fields.values().iterator(); 1035 while(it.hasNext()) 1036 { 1037 Field f = (Field) it.next(); 1038 p.startRow(); 1039 p.printCell(f.getName()); 1040 p.printCell(f.getType().toString()); 1041 List list = f.getValidators(); 1042 int size = list.size(); 1043 if (size == 0) { 1044 p.printCell("-"); 1045 } 1046 else{ 1047 tmp = new StringBuffer(256); 1048 for (int n = 0; n < size; n++) 1049 { 1050 if (n != 0) 1051 tmp.append(", "); 1052 FieldValidator fv = (FieldValidator) list.get(n); 1053 if (fv instanceof VText) { 1054 tmp.append(fv); //prints a bit more info. 1055 } 1056 else{ 1057 String s = fv.getClass().getName(); 1058 tmp.append(s.substring((s.lastIndexOf(".") + 1), s.length())); 1059 } 1060 } 1061 p.printCell(tmp.toString()); 1062 } 1063 p.endRow(); 1064 } 1065 1066 p.endTable(); 1067 buf.append(out.toString()); 1068 1069 if (formval_size > 0) 1070 { 1071 buf.append(nl); 1072 1073 out.reset(); 1074 config = new TablePrinter.PrintConfig(); 1075 config.setPrintBorders(false); 1076 config.setCellSpacing(1); 1077 config.setCellPadding(0); 1078 config.setAutoFit(true); 1079 p = new TablePrinter(1, new PrintStream(out), config); 1080 p.startTable(); 1081 1082 p.startRow(); 1083 p.printCell("Form-Validators...."); 1084 p.endRow(); 1085 1086 it = formValidators.entrySet().iterator(); 1087 while (it.hasNext()) 1088 { 1089 p.startRow(); 1090 Map.Entry me = (Map.Entry) it.next(); 1091 String key = (String) me.getKey(); 1092 tmp = new StringBuffer(tmp.length() + 2); 1093 tmp.append("\t"); 1094 tmp.append(key); 1095 p.printCell(tmp.toString()); 1096 p.endRow(); 1097 } 1098 1099 p.endTable(); 1100 buf.append(out.toString()); 1101 } 1102 1103 if (fieldref_size > 0) 1104 { 1105 buf.append(nl); 1106 1107 out.reset(); 1108 config = new TablePrinter.PrintConfig(); 1109 config.setPrintBorders(false); 1110 config.setCellSpacing(1); 1111 config.setCellPadding(0); 1112 config.setAutoFit(true); 1113 p = new TablePrinter(2, new PrintStream(out), config); 1114 p.startTable(); 1115 1116 p.startRow(); 1117 p.printCell("Field References...."); 1118 p.printCell(""); 1119 p.endRow(); 1120 1121 it = fieldRef.values().iterator(); 1122 while (it.hasNext()) 1123 { 1124 p.startRow(); 1125 FieldRef fr = (FieldRef) it.next(); 1126 tmp = new StringBuffer(tmp.length() + 2); 1127 tmp.append("\t"); 1128 tmp.append(fr.getName()); 1129 p.printCell(tmp.toString()); 1130 tmp = new StringBuffer(128); 1131 if (fr.getList() != null) { 1132 tmp.append(fr.getList().size()); 1133 tmp.append(" fields"); 1134 } 1135 else if (fr.getMap() != null) { 1136 tmp.append(fr.getMap().size()); 1137 tmp.append(" fields"); 1138 } 1139 else { 1140 tmp.append(fr.getObject()); 1141 } 1142 p.printCell(tmp.toString()); 1143 p.endRow(); 1144 } 1145 1146 p.endTable(); 1147 buf.append(out.toString()); 1148 } 1149 1150 buf.append(StringUtil.repeat('-', border_len)); 1151 return buf.toString(); 1152 } 1153 1154 } //~class Form