001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.form; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026 027import javax.servlet.http.HttpServletRequest; 028 029import org.apache.commons.fileupload.FileUploadBase; 030import org.apache.commons.fileupload.FileUploadException; 031import org.apache.wicket.Component; 032import org.apache.wicket.IGenericComponent; 033import org.apache.wicket.IRequestListener; 034import org.apache.wicket.Page; 035import org.apache.wicket.WicketRuntimeException; 036import org.apache.wicket.ajax.AjaxRequestTarget; 037import org.apache.wicket.behavior.Behavior; 038import org.apache.wicket.core.util.string.CssUtils; 039import org.apache.wicket.event.IEvent; 040import org.apache.wicket.markup.ComponentTag; 041import org.apache.wicket.markup.MarkupStream; 042import org.apache.wicket.markup.head.IHeaderResponse; 043import org.apache.wicket.markup.head.OnEventHeaderItem; 044import org.apache.wicket.markup.html.WebMarkupContainer; 045import org.apache.wicket.markup.html.form.upload.FileUploadField; 046import org.apache.wicket.markup.html.form.validation.FormValidatorAdapter; 047import org.apache.wicket.markup.html.form.validation.IFormValidator; 048import org.apache.wicket.model.IModel; 049import org.apache.wicket.model.Model; 050import org.apache.wicket.protocol.http.servlet.MultipartServletWebRequest; 051import org.apache.wicket.protocol.http.servlet.ServletWebRequest; 052import org.apache.wicket.request.IRequestParameters; 053import org.apache.wicket.request.Request; 054import org.apache.wicket.request.Response; 055import org.apache.wicket.request.mapper.parameter.PageParameters; 056import org.apache.wicket.request.parameter.EmptyRequestParameters; 057import org.apache.wicket.util.encoding.UrlDecoder; 058import org.apache.wicket.util.lang.Args; 059import org.apache.wicket.util.lang.Bytes; 060import org.apache.wicket.util.lang.Generics; 061import org.apache.wicket.util.string.AppendingStringBuffer; 062import org.apache.wicket.util.string.PrependingStringBuffer; 063import org.apache.wicket.util.string.Strings; 064import org.apache.wicket.util.string.interpolator.MapVariableInterpolator; 065import org.apache.wicket.util.value.LongValue; 066import org.apache.wicket.util.visit.ClassVisitFilter; 067import org.apache.wicket.util.visit.IVisit; 068import org.apache.wicket.util.visit.IVisitor; 069import org.apache.wicket.util.visit.Visits; 070import org.slf4j.Logger; 071import org.slf4j.LoggerFactory; 072 073 074/** 075 * Container for {@link FormComponent}s (such as {@link CheckBox}es, {@link ListChoice}s or 076 * {@link TextField}s). Subclass this class to receive submit notifications through 077 * {@link #onSubmit()} or nest multiple {@link IFormSubmittingComponent}s if you want to vary submit 078 * behavior. In the former case it is not necessary to use any of Wicket's classes (such as 079 * {@link Button} or {@link SubmitLink}), just putting e.g. <input type="submit" value="go"/> 080 * suffices. 081 * <p> 082 * As a {@link IRequestListener} the form gets notified of listener requests in 083 * {@link #onRequest()}. By default, the processing of this submit works like this: 084 * <ul> 085 * <li>All nested {@link FormComponent}s are notified of new input via 086 * {@link FormComponent#inputChanged()}</li> 087 * <li>The form submitter is looked up, e.g. a {@link Button} is contained in the component 088 * hierarchy of this form and was clicked by the user: 089 * <ul> 090 * <li>If an {@link IFormSubmitter} was found which 091 * {@link IFormSubmitter#getDefaultFormProcessing()} returns {@code false} (default is {@code true} 092 * ), it's {@link IFormSubmitter#onSubmit()} method will be called right away, thus all further 093 * processing is skipped. This has the same effect as nesting a normal link in the form. <br> 094 * If needed the form submitter can continue processing however, by calling {@link #validate()} to 095 * execute form validation, {@link #hasError()} to find out whether validate() resulted in 096 * validation errors, and {@link #updateFormComponentModels()} to update the models of nested form 097 * components.</li> 098 * <li>Otherwise this form is further processed via {@link #process(IFormSubmitter)}, resulting in 099 * all nested components being validated via {@link FormComponent#validate()}. <br> 100 * <ul> 101 * <li>If form validation failed, all nested form components will be marked invalid, and 102 * {@link #onError()} is called to allow clients to provide custom error handling code.</li> 103 * <li>Otherwise the nested components will be asked to update their models via 104 * {@link FormComponent#updateModel()}. After that submit notification is delegated to the 105 * {@link IFormSubmitter#onSubmit()} (if just found) before calling {@link #onSubmit()} on this 106 * form. Subclasses may override {@link #delegateSubmit(IFormSubmitter)} if they want a different 107 * behavior.</li> 108 * </ul> 109 * </li> 110 * </ul> 111 * </li> 112 * </ul> 113 * 114 * A Form can be configured for handling uploads with multipart requests (e.g. files) by calling 115 * {@link #setMultiPart(boolean)} (although Wicket will try to automatically detect this for you). 116 * Use this with {@link FileUploadField} components. You can attach multiple {@link FileUploadField} 117 * components for multiple file uploads. 118 * <p> 119 * In case of an upload error two resource keys are available to specify error messages: 120 * {@code uploadTooLarge} and {@code uploadFailed}, i.e. for a form with id {@code myform} in 121 * {@code MyPage.properties}: 122 * 123 * <pre> 124 * myform.uploadTooLarge=You have uploaded a file that is over the allowed limit of 2Mb 125 * </pre> 126 * 127 * Forms can be nested. You can put a form in another form. Since HTML doesn't allow nested 128 * <form> tags, the inner forms will be rendered using the <div> tag. You have to submit 129 * the inner forms using explicit components (like {@link Button} or {@link SubmitLink}), you can't 130 * rely on implicit submit behavior (by using just <input type="submit"> that is not attached 131 * to a component). 132 * <p> 133 * When a nested form is submitted, the user entered values in outer (parent) forms are preserved 134 * and only the fields in the submitted form are validated. </b> 135 * 136 * @author Jonathan Locke 137 * @author Juergen Donnerstag 138 * @author Eelco Hillenius 139 * @author Cameron Braid 140 * @author Johan Compagner 141 * @author Igor Vaynberg (ivaynberg) 142 * @author David Leangen 143 * 144 * @param <T> 145 * The model object type 146 */ 147public class Form<T> extends WebMarkupContainer 148 implements 149 IRequestListener, 150 IGenericComponent<T, Form<T>> 151{ 152 public static final String ENCTYPE_MULTIPART_FORM_DATA = "multipart/form-data"; 153 154 public static final String HIDDEN_FIELDS_CSS_CLASS_KEY = CssUtils 155 .key(Form.class, "hidden-fields"); 156 157 /** 158 * Visitor used for validation 159 * 160 * @author Igor Vaynberg (ivaynberg) 161 */ 162 public abstract static class ValidationVisitor implements IVisitor<FormComponent<?>, Void> 163 { 164 @Override 165 public void component(final FormComponent<?> formComponent, final IVisit<Void> visit) 166 { 167 168 Form<?> form = formComponent.getForm(); 169 if (!form.isVisibleInHierarchy() || !form.isEnabledInHierarchy()) 170 { 171 // do not validate formComponent or any of formComponent's children 172 visit.dontGoDeeper(); 173 return; 174 } 175 176 if (formComponent.isVisibleInHierarchy() && formComponent.isEnabledInHierarchy()) 177 { 178 validate(formComponent); 179 } 180 if (formComponent.processChildren() == false) 181 { 182 visit.dontGoDeeper(); 183 } 184 } 185 186 /** 187 * Callback that should be used to validate form component 188 * 189 * @param formComponent 190 */ 191 public abstract void validate(FormComponent<?> formComponent); 192 } 193 194 195 /** 196 * Visitor used to update component models 197 * 198 * @author Igor Vaynberg (ivaynberg) 199 */ 200 private static class FormModelUpdateVisitor implements IVisitor<Component, Void> 201 { 202 private final Form<?> formFilter; 203 204 /** 205 * Constructor 206 * 207 * @param formFilter 208 */ 209 public FormModelUpdateVisitor(Form<?> formFilter) 210 { 211 this.formFilter = formFilter; 212 } 213 214 /** {@inheritDoc} */ 215 @Override 216 public void component(final Component component, final IVisit<Void> visit) 217 { 218 if (component instanceof IFormModelUpdateListener) 219 { 220 final Form<?> form = Form.findForm(component); 221 if (form != null) 222 { 223 if (this.formFilter == null || this.formFilter == form) 224 { 225 if (form.isEnabledInHierarchy()) 226 { 227 if (component.isVisibleInHierarchy() && 228 component.isEnabledInHierarchy()) 229 { 230 ((IFormModelUpdateListener)component).updateModel(); 231 } 232 } 233 } 234 } 235 } 236 } 237 } 238 239 /** 240 * Constant for specifying how a form is submitted, in this case using get. 241 */ 242 public static final String METHOD_GET = "get"; 243 244 /** 245 * Constant for specifying how a form is submitted, in this case using post. 246 */ 247 public static final String METHOD_POST = "post"; 248 249 /** Flag that indicates this form has been submitted during this request */ 250 private static final short FLAG_SUBMITTED = FLAG_RESERVED1; 251 252 /** Log. */ 253 private static final Logger log = LoggerFactory.getLogger(Form.class); 254 255 private static final long serialVersionUID = 1L; 256 257 private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed"; 258 259 private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge"; 260 private static final String UPLOAD_SINGLE_FILE_TOO_LARGE_RESOURCE_KEY = "uploadSingleFileTooLarge"; 261 262 /** 263 * Any default IFormSubmittingComponent. If set, a hidden submit component will be rendered 264 * right after the form tag, so that when users press enter in a textfield, this submit 265 * component's action will be selected. If no default IFormSubmittingComponent is set, nothing 266 * additional is rendered. 267 * <p> 268 * WARNING: note that this is a best effort only. Unfortunately having a 'default' 269 * IFormSubmittingComponent in a form is ill defined in the standards, and of course IE has it's 270 * own way of doing things. 271 * </p> 272 */ 273 private IFormSubmittingComponent defaultSubmittingComponent; 274 275 /** 276 * Maximum size of an upload in bytes. If null, the setting 277 * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. 278 */ 279 private Bytes maxSize = null; 280 281 /** 282 * Maximum size of file of upload in bytes (if there are more than one) in request. 283 */ 284 private Bytes fileMaxSize; 285 286 /** True if the form has enctype of multipart/form-data */ 287 private short multiPart = 0; 288 289 /** 290 * A user has explicitly called {@link #setMultiPart(boolean)} with value {@code true} forcing 291 * it to be true 292 */ 293 private static final short MULTIPART_HARD = 0x01; 294 295 /** 296 * The form has discovered a multipart component before rendering and is marking itself as 297 * multipart until next render 298 */ 299 private static final short MULTIPART_HINT_YES = 0x02; 300 301 /** 302 * The form has discovered no multipart component before rendering and is marking itself as 303 * not multipart until next render 304 */ 305 private static final short MULTIPART_HINT_NO = 0x04; 306 307 /** 308 * The index of the hidden fields used to pass parameters. 309 */ 310 private static final int HIDDEN_FIELDS_PARAMS_IDX = 0; 311 312 /** 313 * The index of the hidden fields used for the default submit button. 314 */ 315 private static final int HIDDEN_FIELDS_SUBMIT_IDX = 1; 316 317 /** 318 * Constructs a form with no validation. 319 * 320 * @param id 321 * See Component 322 */ 323 public Form(final String id) 324 { 325 this(id, null); 326 } 327 328 /** 329 * @param id 330 * See Component 331 * @param model 332 * See Component 333 * @see org.apache.wicket.Component#Component(String, IModel) 334 */ 335 public Form(final String id, final IModel<T> model) 336 { 337 super(id, model); 338 setOutputMarkupId(true); 339 } 340 341 /** 342 * Adds a form validator to the form. 343 * 344 * @param validator 345 * validator 346 * @throws IllegalArgumentException 347 * if validator is null 348 * @see IFormValidator 349 */ 350 public void add(final IFormValidator validator) 351 { 352 Args.notNull(validator, "validator"); 353 354 if (validator instanceof Behavior) 355 { 356 add((Behavior)validator); 357 } 358 else 359 { 360 add(new FormValidatorAdapter(validator)); 361 } 362 } 363 364 /** 365 * Removes a form validator from the form. 366 * 367 * @param validator 368 * validator 369 * @throws IllegalArgumentException 370 * if validator is null 371 * @see IFormValidator 372 */ 373 public void remove(final IFormValidator validator) 374 { 375 Args.notNull(validator, "validator"); 376 377 Behavior match = null; 378 for (Behavior behavior : getBehaviors()) 379 { 380 if (behavior.equals(validator)) 381 { 382 match = behavior; 383 break; 384 } 385 else if (behavior instanceof FormValidatorAdapter) 386 { 387 if (((FormValidatorAdapter)behavior).getValidator().equals(validator)) 388 { 389 match = behavior; 390 break; 391 } 392 } 393 } 394 395 if (match != null) 396 { 397 remove(match); 398 } 399 else 400 { 401 402 throw new IllegalStateException( 403 "Tried to remove form validator that was not previously added. " 404 + "Make sure your validator's equals() implementation is sufficient"); 405 } 406 } 407 408 /** 409 * Clears the input from the form's nested children of type {@link FormComponent}. This method 410 * is typically called when a form needs to be reset. 411 */ 412 public final void clearInput() 413 { 414 // Visit all the (visible) form components and clear the input on each. 415 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 416 { 417 @Override 418 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 419 { 420 if (formComponent.isVisibleInHierarchy()) 421 { 422 // Clear input from form component 423 formComponent.clearInput(); 424 } 425 } 426 }); 427 } 428 429 /** 430 * Registers an error feedback message for this component 431 * 432 * @param error 433 * error message 434 * @param args 435 * argument replacement map for ${key} variables 436 */ 437 public final void error(String error, Map<String, Object> args) 438 { 439 error(new MapVariableInterpolator(error, args).toString()); 440 } 441 442 /** 443 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! 444 * <p> 445 * Gets the IFormSubmittingComponent which submitted this form. 446 * 447 * @return The component which submitted this form, or null if the processing was not triggered 448 * by a registered IFormSubmittingComponent 449 */ 450 public final IFormSubmittingComponent findSubmitter() 451 { 452 final IRequestParameters parameters = getRequestParameters(this); 453 454 IFormSubmittingComponent submittingComponent = getPage().visitChildren( 455 IFormSubmittingComponent.class, new IVisitor<Component, IFormSubmittingComponent>() 456 { 457 @Override 458 public void component(final Component component, 459 final IVisit<IFormSubmittingComponent> visit) 460 { 461 // Get submitting component 462 final IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)component; 463 final Form<?> form = submittingComponent.getForm(); 464 465 // Check for component-name or component-name.x request string 466 if ((form != null) && (form.getRootForm() == Form.this)) 467 { 468 String name = submittingComponent.getInputName(); 469 if ((!parameters.getParameterValue(name).isNull()) || 470 !parameters.getParameterValue(name + ".x").isNull()) 471 { 472 visit.stop(submittingComponent); 473 } 474 } 475 } 476 }); 477 478 return submittingComponent; 479 } 480 481 /** 482 * Gets the default IFormSubmittingComponent. If set (not null), a hidden submit component will 483 * be rendered right after the form tag, so that when users press enter in a textfield, this 484 * submit component's action will be selected. If no default component is set (it is null), 485 * nothing additional is rendered. 486 * <p> 487 * WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a 488 * form is ill defined in the standards, and of course IE has it's own way of doing things. 489 * </p> 490 * There can be only one default submit component per form hierarchy. So if you want to get the 491 * default component on a nested form, it will actually delegate the call to root form. </b> 492 * 493 * @return The submit component to set as the default IFormSubmittingComponent, or null when you 494 * want to 'unset' any previously set default IFormSubmittingComponent 495 */ 496 public final IFormSubmittingComponent getDefaultButton() 497 { 498 if (isRootForm()) 499 { 500 return defaultSubmittingComponent; 501 } 502 else 503 { 504 return getRootForm().getDefaultButton(); 505 } 506 } 507 508 /** 509 * Gets all {@link IFormValidator}s added to this form 510 * 511 * @return unmodifiable collection of {@link IFormValidator}s 512 */ 513 public final Collection<IFormValidator> getFormValidators() 514 { 515 List<IFormValidator> validators = new ArrayList<>(); 516 517 for (Behavior behavior : getBehaviors()) 518 { 519 if (behavior instanceof IFormValidator) 520 { 521 validators.add((IFormValidator)behavior); 522 } 523 } 524 525 return Collections.unmodifiableCollection(validators); 526 } 527 528 /** 529 * Generate a piece of JavaScript that submits the form to the given URL of an {@link IRequestListener}. 530 * 531 * Warning: This code should only be called in the rendering phase for form components inside 532 * the form because it uses the css/javascript id of the form which can be stored in the markup. 533 * 534 * @param url 535 * The listener url to be submitted to 536 * @return the javascript code that submits the form. 537 */ 538 public final CharSequence getJsForListenerUrl(CharSequence url) 539 { 540 Form<?> root = getRootForm(); 541 542 AppendingStringBuffer buffer = new AppendingStringBuffer(); 543 544 String action = url.toString(); 545 if (root.encodeUrlInHiddenFields()) { 546 buffer.append(String.format("document.getElementById('%s').innerHTML = '", 547 root.getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX))); 548 549 // parameter must be sent as hidden field, as it would be ignored in the action URL 550 int i = action.indexOf('?'); 551 if (i != -1) 552 { 553 writeParamsAsHiddenFields(Strings.split(action.substring(i + 1), '&'), buffer); 554 555 action = action.substring(0, i); 556 } 557 558 buffer.append("';"); 559 } 560 buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId())); 561 buffer.append(String.format("f.action='%s';", action)); 562 buffer.append("Wicket.Event.fire(f, 'submit');"); 563 return buffer; 564 } 565 566 /** 567 * Generate a piece of JavaScript that submits the form with the given 568 * {@link IFormSubmittingComponent}. 569 * 570 * @param submitter 571 * the submitter 572 * @param triggerEvent 573 * When true, the form will be submited via a javascript submit event, when false via 574 * the {@code submit()} method. 575 * @return the javascript code that submits the form. 576 * 577 * @see #findSubmitter() 578 */ 579 public final CharSequence getJsForSubmitter(IFormSubmittingComponent submitter, boolean triggerEvent) 580 { 581 Form<?> root = getRootForm(); 582 583 String param = submitter.getInputName() + "=x"; 584 585 AppendingStringBuffer buffer = new AppendingStringBuffer(); 586 buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId())); 587 buffer.append(String.format("document.getElementById('%s').innerHTML += '", 588 root.getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX))); 589 writeParamsAsHiddenFields(new String[] {param}, buffer); 590 buffer.append("';"); 591 592 if (triggerEvent) 593 { 594 buffer.append("Wicket.Event.fire(f, 'submit');"); 595 } 596 else 597 { 598 buffer.append("f.submit();"); 599 } 600 return buffer; 601 } 602 603 /** 604 * Gets the maximum size for uploads. If null, the setting 605 * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. 606 * 607 * 608 * @return the maximum size 609 */ 610 public final Bytes getMaxSize() 611 { 612 /* 613 * NOTE: This method should remain final otherwise it will be impossible to set a default 614 * max size smaller then the one specified in applications settings because the inner form 615 * will return the default unless it is specifically set in the traversal. With this method 616 * remaining final we can tell when the value is explicitly set by the user. 617 * 618 * If the value needs to be dynamic it can be set in oncofigure() instead of overriding this 619 * method. 620 */ 621 622 final Bytes[] maxSize = { this.maxSize }; 623 if (maxSize[0] == null) 624 { 625 visitChildren(Form.class, new IVisitor<Form<?>, Bytes>() 626 { 627 @Override 628 public void component(Form<?> component, IVisit<Bytes> visit) 629 { 630 maxSize[0] = LongValue.maxNullSafe(maxSize[0], component.maxSize); 631 } 632 }); 633 } 634 if (maxSize[0] == null) 635 { 636 return getApplication().getApplicationSettings().getDefaultMaximumUploadSize(); 637 } 638 return maxSize[0]; 639 } 640 641 /** 642 * Gets maximum size for each file of an upload. 643 * 644 * @return 645 */ 646 public Bytes getFileMaxSize() 647 { 648 return fileMaxSize; 649 } 650 651 /** 652 * Returns the root form or this, if this is the root form. 653 * 654 * @return root form or this form 655 */ 656 public Form<?> getRootForm() 657 { 658 Form<?> form; 659 Form<?> parent = this; 660 do 661 { 662 form = parent; 663 parent = form.findParent(Form.class); 664 } 665 while (parent != null); 666 667 return form; 668 } 669 670 /** 671 * Returns the prefix used when building validator keys. This allows a form to use a separate 672 * "set" of keys. For example if prefix "short" is returned, validator key short.Required will 673 * be tried instead of Required key. 674 * <p> 675 * This can be useful when different designs are used for a form. In a form where error messages 676 * are displayed next to their respective form components as opposed to at the top of the form, 677 * the ${label} attribute is of little use and only causes redundant information to appear in 678 * the message. Forms like these can return the "short" (or any other string) validator prefix 679 * and declare key: short.Required=required to override the longer message which is usually 680 * declared like this: Required=${label} is a required field 681 * <p> 682 * Returned prefix will be used for all form components. The prefix can also be overridden on 683 * form component level by overriding {@link FormComponent#getValidatorKeyPrefix()} 684 * 685 * @return prefix prepended to validator keys 686 */ 687 public String getValidatorKeyPrefix() 688 { 689 return null; 690 } 691 692 /** 693 * Gets whether the current form has any error registered. 694 * 695 * @return True if this form has at least one error. 696 */ 697 public final boolean hasError() 698 { 699 // if this form itself has an error message 700 if (hasErrorMessage()) 701 { 702 return true; 703 } 704 705 // the form doesn't have any errors, now check any nested form 706 // components 707 return anyFormComponentError(); 708 } 709 710 /** 711 * Returns whether the form is a root form, which means that there's no other form in it's 712 * parent hierarchy. 713 * 714 * @return true if form is a root form, false otherwise 715 */ 716 public boolean isRootForm() 717 { 718 return findParent(Form.class) == null; 719 } 720 721 /** 722 * Checks if this form has been submitted during the current request 723 * 724 * @return true if the form has been submitted during this request, false otherwise 725 */ 726 public final boolean isSubmitted() 727 { 728 return getFlag(FLAG_SUBMITTED); 729 } 730 731 /** 732 * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. 733 * 734 * Handles form submissions. 735 * 736 * @see #onFormSubmitted(IFormSubmitter) 737 */ 738 @Override 739 public final void onRequest() 740 { 741 onFormSubmitted(null); 742 } 743 744 /** 745 * Called when a form has been submitted using a method differing from return value of 746 * {@link #getMethod()}. For example, someone can copy and paste the action url and invoke the 747 * form using a {@code GET} instead of the desired {@code POST}. This method allows the user to 748 * react to this situation. 749 * 750 * @return response that can either abort or continue the processing of the form 751 */ 752 protected MethodMismatchResponse onMethodMismatch() 753 { 754 return MethodMismatchResponse.CONTINUE; 755 } 756 757 /** 758 * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. 759 * 760 * Handles form submissions. 761 * 762 * @param submitter 763 * listener that will receive form processing events, if {@code null} the form will 764 * attempt to locate one 765 * 766 * @see Form#validate() 767 */ 768 public final void onFormSubmitted(IFormSubmitter submitter) 769 { 770 // check methods match 771 if (getRequest().getContainerRequest() instanceof HttpServletRequest) 772 { 773 String desiredMethod = getMethod(); 774 String actualMethod = ((HttpServletRequest)getRequest().getContainerRequest()).getMethod(); 775 if (!actualMethod.equalsIgnoreCase(desiredMethod)) 776 { 777 MethodMismatchResponse response = onMethodMismatch(); 778 switch (response) 779 { 780 case ABORT : 781 return; 782 case CONTINUE : 783 break; 784 default : 785 throw new IllegalStateException("Invalid " + 786 MethodMismatchResponse.class.getName() + " value: " + response); 787 } 788 } 789 } 790 791 markFormsSubmitted(submitter); 792 793 if (handleMultiPart()) 794 { 795 // Tells FormComponents that a new user input has come 796 inputChanged(); 797 798 // First, see if the processing was triggered by a IFormSubmittingComponent 799 if (submitter == null) 800 { 801 submitter = findSubmitter(); 802 803 if (submitter instanceof IFormSubmittingComponent) 804 { 805 IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)submitter; 806 Component component = (Component)submitter; 807 808 if (!component.isVisibleInHierarchy()) 809 { 810 throw new WicketRuntimeException("Submit Button " + 811 submittingComponent.getInputName() + " (path=" + 812 component.getPageRelativePath() + ") is not visible"); 813 } 814 815 if (!component.isEnabledInHierarchy()) 816 { 817 throw new WicketRuntimeException("Submit Button " + 818 submittingComponent.getInputName() + " (path=" + 819 component.getPageRelativePath() + ") is not enabled"); 820 } 821 } 822 } 823 824 // When processing was triggered by a Wicket IFormSubmittingComponent and that 825 // component indicates it wants to be called immediately 826 // (without processing), call the IFormSubmittingComponent.onSubmit* methods right 827 // away. 828 if (submitter != null && !submitter.getDefaultFormProcessing()) 829 { 830 submitter.onSubmit(); 831 submitter.onAfterSubmit(); 832 } 833 else 834 { 835 // the submit request might be for one of the nested forms, so let's 836 // find the right one: 837 final Form<?> formToProcess = findFormToProcess(submitter); 838 839 // process the form for this request 840 formToProcess.process(submitter); 841 } 842 } 843 // If multi part did fail check if an error is registered and call 844 // onError 845 else if (hasError()) 846 { 847 callOnError(submitter); 848 } 849 850 // update auto labels if we are inside an ajax request 851 getRequestCycle().find(AjaxRequestTarget.class).ifPresent(target -> { 852 visitChildren(FormComponent.class, new IVisitor<FormComponent<?>, Void>() 853 { 854 @Override 855 public void component(FormComponent<?> component, IVisit<Void> visit) 856 { 857 component.updateAutoLabels(target); 858 } 859 }); 860 }); 861 } 862 863 /** 864 * This method finds the correct form that should be processed based on the submitting component 865 * (if there is one) and correctly handles nested forms by also looking at 866 * {@link #wantSubmitOnNestedFormSubmit()} throughout the form hierarchy. The form that needs to 867 * be processed is: 868 * <ul> 869 * <li>if there is no submitting component (i.e. a "default submit"): this form.</li> 870 * <li>if only one form exists (this): this form.</li> 871 * <li>if nested forms exist: 872 * <ul> 873 * <li>if the submitting component points at the root form: the root form</li> 874 * <li>if the submitting component points at a nested form: 875 * <ul> 876 * <li>starting at that nested form, the outermost form that returns true for 877 * {@link #wantSubmitOnNestedFormSubmit()}</li> 878 * <li>if no outer form returns true for that, the nested form is returned.</li> 879 * </ul> 880 * </li> 881 * </ul> 882 * </li> 883 * </ul> 884 * 885 * @param submitter 886 * The submitting component, if any. May be null. 887 * @return The form that needs to be processed. 888 */ 889 private Form<?> findFormToProcess(IFormSubmitter submitter) 890 { 891 if (submitter == null) 892 { 893 // no submitting component => default form submit => so *this* is the 894 // form to process 895 return this; 896 } 897 else 898 { 899 // some button submitted this request, this is the form it belongs to: 900 final Form<?> targetedForm = submitter.getForm(); 901 if (targetedForm == null) 902 { 903 throw new IllegalStateException( 904 "submitting component must not return 'null' on getForm()"); 905 } 906 907 final Form<?> rootForm = getRootForm(); 908 if (targetedForm == rootForm) 909 { 910 // the submitting component points at the root form => so let's just go with 911 // root, everything else will be submitted with it anyway. 912 return rootForm; 913 } 914 else 915 { 916 // a different form was targeted. let's find the outermost form that wants to be 917 // submitted. 918 Form<?> formThatWantsToBeSubmitted = targetedForm; 919 Form<?> current = targetedForm.findParent(Form.class); 920 while (current != null) 921 { 922 if (current.wantSubmitOnNestedFormSubmit()) 923 { 924 formThatWantsToBeSubmitted = current; 925 } 926 current = current.findParent(Form.class); 927 } 928 return formThatWantsToBeSubmitted; 929 } 930 } 931 } 932 933 /** 934 * Whether this form wants to be submitted too if a nested form is submitted. By default, this 935 * is false, so when a nested form is submitted, this form will <em>not</em> be submitted. If 936 * this method is overridden to return true, this form <em>will</em> be submitted. 937 * 938 * @return Whether this form wants to be submitted too if a nested form is submitted. 939 */ 940 // TODO wicket-7 migration guide: changed from public to protected 941 protected boolean wantSubmitOnNestedFormSubmit() 942 { 943 return false; 944 } 945 946 /** 947 * Whether this *nested* form wants to be submitted when parent form is submitted. By default, 948 * this is true, so when a parent form is submitted, the nested form is also submitted. If this 949 * method is overridden to return false, it will not be validated, processed nor submitted. 950 * 951 * @return {@code true} by default 952 */ 953 protected boolean wantSubmitOnParentFormSubmit() 954 { 955 return true; 956 } 957 958 /** 959 * Process the form. Though you can override this method to provide your own algorithm, it is 960 * not recommended to do so. 961 * 962 * <p> 963 * See the class documentation for further details on the form processing 964 * </p> 965 * 966 * @param submittingComponent 967 * component responsible for submitting the form, or <code>null</code> if none (eg 968 * the form has been submitted via the enter key or javascript calling form.submit()) 969 * 970 * @see #delegateSubmit(IFormSubmitter) for an easy way to process submitting component in the 971 * default manner 972 */ 973 public void process(IFormSubmitter submittingComponent) 974 { 975 if (!isEnabledInHierarchy() || !isVisibleInHierarchy()) 976 { 977 // since process() can be called outside of the default form workflow, an additional 978 // check is needed 979 980 // FIXME throw listener exception 981 return; 982 } 983 984 // run validation 985 validate(); 986 987 // If a validation error occurred 988 if (hasError()) 989 { 990 // mark all children as invalid 991 markFormComponentsInvalid(); 992 993 // let subclass handle error 994 callOnError(submittingComponent); 995 } 996 else 997 { 998 // mark all children as valid 999 markFormComponentsValid(); 1000 1001 // before updating, call the interception method for clients 1002 beforeUpdateFormComponentModels(); 1003 1004 // Update model using form data 1005 updateFormComponentModels(); 1006 1007 // validate model objects after input values have been bound 1008 internalOnValidateModelObjects(); 1009 if (hasError()) 1010 { 1011 callOnError(submittingComponent); 1012 return; 1013 } 1014 1015 // Form has no error 1016 delegateSubmit(submittingComponent); 1017 } 1018 } 1019 1020 /** 1021 * Calls onError on this {@link Form} and any enabled and visible nested form, if the respective 1022 * {@link Form} actually has errors. 1023 * 1024 * @param submitter 1025 */ 1026 protected void callOnError(IFormSubmitter submitter) 1027 { 1028 final Form<?> processingForm = findFormToProcess(submitter); 1029 1030 if (submitter != null) 1031 { 1032 submitter.onError(); 1033 } 1034 1035 // invoke Form#onSubmit(..) going from innermost to outermost 1036 Visits.visitPostOrder(processingForm, new IVisitor<Form<?>, Void>() 1037 { 1038 @Override 1039 public void component(Form<?> form, IVisit<Void> visit) 1040 { 1041 if (!form.isEnabledInHierarchy() || !form.isVisibleInHierarchy()) 1042 { 1043 visit.dontGoDeeper(); 1044 return; 1045 } 1046 if (form.hasError()) 1047 { 1048 form.onError(); 1049 } 1050 } 1051 }, new ClassVisitFilter(Form.class)); 1052 } 1053 1054 1055 /** 1056 * Sets FLAG_SUBMITTED to true on this form and every enabled nested form. 1057 * @param submitter 1058 */ 1059 private void markFormsSubmitted(IFormSubmitter submitter) 1060 { 1061 setFlag(FLAG_SUBMITTED, true); 1062 Form<?> formToProcess = findFormToProcess(submitter); 1063 1064 visitChildren(Form.class, new IVisitor<Component, Void>() 1065 { 1066 @Override 1067 public void component(final Component component, final IVisit<Void> visit) 1068 { 1069 Form<?> form = (Form<?>)component; 1070 if ((form.wantSubmitOnParentFormSubmit() || form == formToProcess) 1071 && form.isEnabledInHierarchy() && form.isVisibleInHierarchy()) 1072 { 1073 form.setFlag(FLAG_SUBMITTED, true); 1074 return; 1075 } 1076 visit.dontGoDeeper(); 1077 } 1078 }); 1079 } 1080 1081 /** 1082 * Sets the default IFormSubmittingComponent. If set (not null), a hidden submit component will 1083 * be rendered right after the form tag, so that when users press enter in a textfield, this 1084 * submit component's action will be selected. If no default component is set (so unset by 1085 * calling this method with null), nothing additional is rendered. 1086 * <p> 1087 * WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a 1088 * form is ill defined in the standards, and of course IE has it's own way of doing things. 1089 * </p> 1090 * There can be only one default button per form hierarchy. So if you set default button on a 1091 * nested form, it will actually delegate the call to root form. </b> 1092 * 1093 * @param submittingComponent 1094 * The component to set as the default submitting component, or null when you want to 1095 * 'unset' any previously set default component 1096 */ 1097 public final void setDefaultButton(IFormSubmittingComponent submittingComponent) 1098 { 1099 if (isRootForm()) 1100 { 1101 defaultSubmittingComponent = submittingComponent; 1102 } 1103 else 1104 { 1105 getRootForm().setDefaultButton(submittingComponent); 1106 } 1107 } 1108 1109 /** 1110 * Sets the maximum size for uploads. If null, the setting 1111 * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. 1112 * 1113 * @param maxSize 1114 * The maximum size 1115 */ 1116 public void setMaxSize(final Bytes maxSize) 1117 { 1118 this.maxSize = maxSize; 1119 } 1120 1121 /** 1122 * Sets maximum size of each file in upload request. 1123 * 1124 * @param fileMaxSize 1125 */ 1126 public void setFileMaxSize(Bytes fileMaxSize) 1127 { 1128 this.fileMaxSize = fileMaxSize; 1129 } 1130 1131 /** 1132 * Set to true to use enctype='multipart/form-data', and to process file uploads by default 1133 * multiPart = false 1134 * 1135 * @param multiPart 1136 * whether this form should behave as a multipart form 1137 */ 1138 public void setMultiPart(boolean multiPart) 1139 { 1140 if (multiPart) 1141 { 1142 this.multiPart |= MULTIPART_HARD; 1143 } 1144 else 1145 { 1146 this.multiPart &= ~MULTIPART_HARD; 1147 } 1148 } 1149 1150 /** 1151 * @see org.apache.wicket.Component#setVersioned(boolean) 1152 */ 1153 @Override 1154 public final Component setVersioned(final boolean isVersioned) 1155 { 1156 super.setVersioned(isVersioned); 1157 1158 // Search for FormComponents like TextField etc. 1159 visitFormComponents(new IVisitor<FormComponent<?>, Void>() 1160 { 1161 @Override 1162 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1163 { 1164 formComponent.setVersioned(isVersioned); 1165 } 1166 }); 1167 return this; 1168 } 1169 1170 /** 1171 * Convenient and typesafe way to visit all the form components on a form. 1172 * 1173 * @param <R> 1174 * return object type 1175 * @param visitor 1176 * The visitor interface to call 1177 * @return user provided in callback 1178 */ 1179 public final <R> R visitFormComponents(final IVisitor<FormComponent<?>, R> visitor) 1180 { 1181 return visitChildren(FormComponent.class, visitor); 1182 } 1183 1184 /** 1185 * Convenient and typesafe way to visit all the form components on a form postorder (deepest 1186 * first) 1187 * 1188 * @param <R> 1189 * Return object type 1190 * @param visitor 1191 * The visitor interface to call 1192 * @return whatever you provided 1193 */ 1194 public final <R> R visitFormComponentsPostOrder( 1195 final IVisitor<? extends FormComponent<?>, R> visitor) 1196 { 1197 return FormComponent.visitFormComponentsPostOrder(this, visitor); 1198 } 1199 1200 /** 1201 * Find out whether there is any registered error for a form component. 1202 * 1203 * @return whether there is any registered error for a form component 1204 */ 1205 private boolean anyFormComponentError() 1206 { 1207 // Check ALL children for error messages irrespective of FormComponents or not 1208 Boolean error = visitChildren(Component.class, new IVisitor<Component, Boolean>() 1209 { 1210 @Override 1211 public void component(final Component component, final IVisit<Boolean> visit) 1212 { 1213 if (component.hasErrorMessage() && component.isVisibleInHierarchy() && component.isEnabledInHierarchy()) 1214 { 1215 visit.stop(true); 1216 } 1217 } 1218 }); 1219 1220 return (error != null) && error; 1221 } 1222 1223 /** 1224 * Visits the form's children FormComponents and inform them that a new user input is available 1225 * in the Request 1226 */ 1227 private void inputChanged() 1228 { 1229 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1230 { 1231 @Override 1232 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1233 { 1234 formComponent.inputChanged(); 1235 } 1236 }); 1237 } 1238 1239 /** 1240 * If a default IFormSubmittingComponent was set on this form, this method will be called to 1241 * render an extra field with an invisible style so that pressing enter in one of the textfields 1242 * will do a form submit using this component. This method is overridable as what we do is best 1243 * effort only, and may not what you want in specific situations. So if you have specific 1244 * usability concerns, or want to follow another strategy, you may override this method. 1245 * 1246 * @see #addDefaultSubmitButtonHandler(IHeaderResponse) 1247 */ 1248 protected void appendDefaultButtonField() 1249 { 1250 AppendingStringBuffer buffer = new AppendingStringBuffer(); 1251 1252 // hidden div 1253 buffer.append(String.format("<div hidden=\"\" class=\"%s\">", 1254 getString(HIDDEN_FIELDS_CSS_CLASS_KEY))); 1255 1256 // add an empty textfield (otherwise IE doesn't work) 1257 buffer.append("<input type=\"text\" tabindex=\"-1\" autocomplete=\"off\"/>"); 1258 1259 // add the submitting component 1260 buffer 1261 .append(String.format("<input id=\"%s\" type=\"submit\" tabindex=\"-1\" name=\"%s\" />", 1262 getHiddenFieldsId(HIDDEN_FIELDS_SUBMIT_IDX), 1263 defaultSubmittingComponent.getInputName())); 1264 1265 // close div 1266 buffer.append("</div>"); 1267 1268 getResponse().write(buffer); 1269 } 1270 1271 /** 1272 * Where {@link #appendDefaultButtonField()} renders the markup for default submit button 1273 * handling, this method attaches the event handler to its 'click' event. The 'click' event on 1274 * the hidden submit button will be dispatched to the selected default submit button. As with 1275 * {@link #appendDefaultButtonField()} this method can be overridden when the generated code 1276 * needs to be adjusted for a specific usecase. 1277 * 1278 * @param headerResponse 1279 * The header response. 1280 */ 1281 protected void addDefaultSubmitButtonHandler(IHeaderResponse headerResponse) 1282 { 1283 final Component submittingComponent = (Component) defaultSubmittingComponent; 1284 AppendingStringBuffer buffer = new AppendingStringBuffer(); 1285 buffer.append("var b=document.getElementById('"); 1286 buffer.append(submittingComponent.getMarkupId()); 1287 buffer.append("'); if (b!=null && b.onclick!=null && typeof(b.onclick) != 'undefined') "); 1288 buffer.append( 1289 "{ var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } else { b.click(); }; return false;"); 1290 headerResponse.render(OnEventHeaderItem 1291 .forMarkupId(getHiddenFieldsId(HIDDEN_FIELDS_SUBMIT_IDX), "click", buffer.toString())); 1292 } 1293 1294 /** 1295 * Template method to allow clients to do any processing (like recording the current model so 1296 * that, in case onSubmit does further validation, the model can be rolled back) before the 1297 * actual updating of form component models is done. 1298 */ 1299 protected void beforeUpdateFormComponentModels() 1300 { 1301 } 1302 1303 /** 1304 * Called (by the default implementation of 'process') when all fields validated, the form was 1305 * updated and it's data was allowed to be persisted. It is meant for delegating further 1306 * processing to clients. 1307 * <p> 1308 * This implementation first finds out whether the form processing was triggered by a nested 1309 * IFormSubmittingComponent of this form. If that is the case, that component's 1310 * onSubmitBefore/AfterForm methods are called appropriately.. 1311 * </p> 1312 * <p> 1313 * Regardless of whether a submitting component was found, the form's onSubmit method is called 1314 * next. 1315 * </p> 1316 * 1317 * @param submittingComponent 1318 * the component that triggered this form processing, or null if the processing was 1319 * triggered by something else (like a non-Wicket submit button or a javascript 1320 * execution) 1321 */ 1322 protected void delegateSubmit(IFormSubmitter submittingComponent) 1323 { 1324 final Form<?> processingForm = findFormToProcess(submittingComponent); 1325 1326 // collect all forms innermost to outermost before any hierarchy is changed 1327 final List<Form<?>> forms = Generics.newArrayList(3); 1328 Visits.visitPostOrder(processingForm, new IVisitor<Form<?>, Void>() 1329 { 1330 @Override 1331 public void component(Form<?> form, IVisit<Void> visit) 1332 { 1333 if (form.isSubmitted()) 1334 { 1335 forms.add(form); 1336 } 1337 } 1338 }, new ClassVisitFilter(Form.class)); 1339 1340 // process submitting component (if specified) 1341 if (submittingComponent != null) 1342 { 1343 // invoke submit on component 1344 submittingComponent.onSubmit(); 1345 } 1346 1347 // invoke Form#onSubmit(..) 1348 for (Form<?> form : forms) 1349 { 1350 form.onSubmit(); 1351 } 1352 1353 if (submittingComponent != null) 1354 { 1355 submittingComponent.onAfterSubmit(); 1356 } 1357 } 1358 1359 /** 1360 * Returns the id which will be used for the hidden div containing all parameter fields. 1361 * 1362 * @param idx 1363 * The index of the div to keep different divs apart. 1364 * @return the id of the hidden div 1365 */ 1366 private final String getHiddenFieldsId(int idx) 1367 { 1368 return getInputNamePrefix() + getMarkupId() + "_hf_" + idx; 1369 } 1370 1371 /** 1372 * Gets the HTTP submit method that will appear in form markup. If no method is specified in the 1373 * template, "post" is the default. Note that the markup-declared HTTP method may not correspond 1374 * to the one actually used to submit the form; in an Ajax submit, for example, JavaScript event 1375 * handlers may submit the form with a "get" even when the form method is declared as "post." 1376 * Therefore this method should not be considered a guarantee of the HTTP method used, but a 1377 * value for the markup only. Override if you have a requirement to alter this behavior. 1378 * 1379 * @return the submit method specified in markup. 1380 */ 1381 protected String getMethod() 1382 { 1383 String method = getMarkupAttributes().getString("method"); 1384 return (method != null) ? method : METHOD_POST; 1385 } 1386 1387 /** 1388 * 1389 * @see org.apache.wicket.Component#getStatelessHint() 1390 */ 1391 @Override 1392 protected boolean getStatelessHint() 1393 { 1394 return false; 1395 } 1396 1397 /** 1398 * @return True if is multipart 1399 */ 1400 public boolean isMultiPart() 1401 { 1402 if (multiPart == 0) 1403 { 1404 Boolean anyEmbeddedMultipart = visitChildren(Component.class, 1405 new IVisitor<Component, Boolean>() 1406 { 1407 @Override 1408 public void component(final Component component, final IVisit<Boolean> visit) 1409 { 1410 boolean isMultiPart = false; 1411 if (component instanceof Form<?>) 1412 { 1413 Form<?> form = (Form<?>)component; 1414 if (form.isVisibleInHierarchy() && form.isEnabledInHierarchy()) 1415 { 1416 isMultiPart = (form.multiPart & MULTIPART_HARD) != 0; 1417 } 1418 } 1419 else if (component instanceof FormComponent<?>) 1420 { 1421 FormComponent<?> fc = (FormComponent<?>)component; 1422 if (fc.isVisibleInHierarchy() && fc.isEnabledInHierarchy()) 1423 { 1424 isMultiPart = fc.isMultiPart(); 1425 } 1426 } 1427 1428 if (isMultiPart) 1429 { 1430 visit.stop(true); 1431 } 1432 } 1433 1434 }); 1435 1436 if (Boolean.TRUE.equals(anyEmbeddedMultipart)) { 1437 multiPart |= MULTIPART_HINT_YES; 1438 } else { 1439 multiPart |= MULTIPART_HINT_NO; 1440 } 1441 } 1442 1443 return (multiPart & (MULTIPART_HARD | MULTIPART_HINT_YES)) != 0; 1444 } 1445 1446 /** 1447 * Handles multi-part processing of the submitted data. 1448 * <strong>WARNING</strong> If this method is overridden it can break {@link FileUploadField}s on this form 1449 * 1450 * @return false if form is multipart and upload failed 1451 */ 1452 protected boolean handleMultiPart() 1453 { 1454 if (isMultiPart()) 1455 { 1456 // Change the request to a multipart web request so parameters are 1457 // parsed out correctly 1458 try 1459 { 1460 ServletWebRequest request = (ServletWebRequest)getRequest(); 1461 final MultipartServletWebRequest multipartWebRequest = request.newMultipartWebRequest( 1462 getMaxSize(), getPage().getId()); 1463 multipartWebRequest.setFileMaxSize(getFileMaxSize()); 1464 multipartWebRequest.parseFileParts(); 1465 1466 // TODO: Can't this be detected from header? 1467 getRequestCycle().setRequest(multipartWebRequest); 1468 } 1469 catch (final FileUploadException fux) 1470 { 1471 // Create model with exception and maximum size values 1472 final Map<String, Object> model = new HashMap<>(); 1473 model.put("exception", fux); 1474 model.put("maxSize", getMaxSize()); 1475 model.put("fileMaxSize", getFileMaxSize()); 1476 1477 onFileUploadException(fux, model); 1478 1479 // don't process the form if there is a FileUploadException 1480 return false; 1481 } 1482 } 1483 return true; 1484 } 1485 1486 /** 1487 * The default message may look like ".. may not exceed 10240 Bytes..". Which is ok, but 1488 * sometimes you may want something like "10KB". By subclassing this method you may replace 1489 * maxSize in the model or add you own property and use that in your error message. 1490 * <p> 1491 * Don't forget to call super.onFileUploadException(e, model) at the end of your method. 1492 * 1493 * @param e 1494 * @param model 1495 */ 1496 protected void onFileUploadException(final FileUploadException e, 1497 final Map<String, Object> model) 1498 { 1499 if (e instanceof FileUploadBase.SizeLimitExceededException) 1500 { 1501 String msg = getString(UPLOAD_TOO_LARGE_RESOURCE_KEY, Model.ofMap(model)); 1502 error(msg); 1503 } 1504 else if (e instanceof FileUploadBase.FileSizeLimitExceededException) 1505 { 1506 String msg = getString(UPLOAD_SINGLE_FILE_TOO_LARGE_RESOURCE_KEY, Model.ofMap(model)); 1507 error(msg); 1508 } 1509 else 1510 { 1511 String msg = getString(UPLOAD_FAILED_RESOURCE_KEY, Model.ofMap(model)); 1512 error(msg); 1513 1514 log.warn(msg, e); 1515 } 1516 } 1517 1518 /** 1519 * @see org.apache.wicket.Component#internalOnModelChanged() 1520 */ 1521 @Override 1522 protected void internalOnModelChanged() 1523 { 1524 // Visit all the form components and validate each 1525 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1526 { 1527 @Override 1528 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1529 { 1530 // If form component is using form model 1531 if (formComponent.sameInnermostModel(Form.this)) 1532 { 1533 formComponent.modelChanged(); 1534 } 1535 } 1536 }); 1537 } 1538 1539 /** 1540 * Mark each form component on this form invalid. 1541 */ 1542 protected final void markFormComponentsInvalid() 1543 { 1544 // call invalidate methods of all nested form components 1545 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1546 { 1547 @Override 1548 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1549 { 1550 if (formComponent.isVisibleInHierarchy()) 1551 { 1552 formComponent.invalid(); 1553 } 1554 } 1555 }); 1556 } 1557 1558 /** 1559 * Mark each form component on this form and on nested forms valid. 1560 */ 1561 protected final void markFormComponentsValid() 1562 { 1563 internalMarkFormComponentsValid(); 1564 markNestedFormComponentsValid(); 1565 } 1566 1567 /** 1568 * Mark each form component on nested form valid. 1569 */ 1570 private void markNestedFormComponentsValid() 1571 { 1572 visitChildren(Form.class, new IVisitor<Form<?>, Void>() 1573 { 1574 @Override 1575 public void component(final Form<?> form, final IVisit<Void> visit) 1576 { 1577 if (form.isSubmitted()) 1578 { 1579 form.internalMarkFormComponentsValid(); 1580 } 1581 else 1582 { 1583 visit.dontGoDeeper(); 1584 } 1585 } 1586 }); 1587 } 1588 1589 /** 1590 * Mark each form component on this form valid. 1591 */ 1592 private void internalMarkFormComponentsValid() 1593 { 1594 // call valid methods of all nested form components 1595 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1596 { 1597 @Override 1598 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1599 { 1600 if (formComponent.getForm() == Form.this && formComponent.isVisibleInHierarchy()) 1601 { 1602 formComponent.valid(); 1603 } 1604 } 1605 }); 1606 } 1607 1608 /** 1609 * @see org.apache.wicket.Component#onComponentTag(ComponentTag) 1610 */ 1611 @Override 1612 protected void onComponentTag(final ComponentTag tag) 1613 { 1614 super.onComponentTag(tag); 1615 1616 if (isRootForm()) 1617 { 1618 checkComponentTag(tag, "form"); 1619 1620 String method = getMethod().toLowerCase(Locale.ROOT); 1621 tag.put("method", method); 1622 String url = getActionUrl().toString(); 1623 if (encodeUrlInHiddenFields()) 1624 { 1625 int i = url.indexOf('?'); 1626 String action = (i > -1) ? url.substring(0, i) : ""; 1627 tag.put("action", action); 1628 // alternatively, we could just put an empty string here, so 1629 // that mounted paths stay in good order. I decided against this 1630 // as I'm not sure whether that could have side effects with 1631 // other encoders 1632 } 1633 else 1634 { 1635 tag.put("action", url); 1636 } 1637 1638 if (isMultiPart()) 1639 { 1640 if (METHOD_GET.equalsIgnoreCase(method)) 1641 { 1642 log.warn("Form with id '{}' is multipart. It should use method 'POST'!", 1643 getId()); 1644 tag.put("method", METHOD_POST.toLowerCase(Locale.ROOT)); 1645 } 1646 1647 tag.put("enctype", ENCTYPE_MULTIPART_FORM_DATA); 1648 // 1649 // require the application-encoding for multipart/form-data to be sure to 1650 // get multipart-uploaded characters with the proper encoding on the following 1651 // request. 1652 // 1653 // for details see: http://stackoverflow.com/questions/546365 1654 // 1655 tag.put("accept-charset", getApplication().getRequestCycleSettings() 1656 .getResponseRequestEncoding()); 1657 } 1658 else 1659 { 1660 // sanity check 1661 String enctype = (String)tag.getAttributes().get("enctype"); 1662 if (ENCTYPE_MULTIPART_FORM_DATA.equalsIgnoreCase(enctype)) 1663 { 1664 // though not set explicitly in Java, this is a multipart 1665 // form 1666 setMultiPart(true); 1667 } 1668 } 1669 } 1670 else 1671 { 1672 adjustNestedTagName(tag); 1673 tag.remove("method"); 1674 tag.remove("action"); 1675 tag.remove("enctype"); 1676 } 1677 } 1678 1679 // WICKET-6658 form is not allowed, anything else can stay as is 1680 private void adjustNestedTagName(ComponentTag tag) { 1681 if ("form".equalsIgnoreCase(tag.getName())) 1682 { 1683 tag.setName("div"); 1684 } 1685 } 1686 1687 /** 1688 * Generates the action url for the form 1689 * 1690 * @return action url 1691 */ 1692 protected CharSequence getActionUrl() 1693 { 1694 return urlForListener(new PageParameters()); 1695 } 1696 1697 /** 1698 * @see org.apache.wicket.Component#renderPlaceholderTag(org.apache.wicket.markup.ComponentTag, 1699 * org.apache.wicket.request.Response) 1700 */ 1701 @Override 1702 protected void renderPlaceholderTag(ComponentTag tag, Response response) 1703 { 1704 if (!isRootForm()) 1705 { 1706 // WICKET-2166 1707 adjustNestedTagName(tag); 1708 } 1709 1710 super.renderPlaceholderTag(tag, response); 1711 } 1712 1713 /** 1714 * Should URL query parameters be encoded in hidden fields, by default <code>true</code> 1715 * for {@link #METHOD_GET} only. 1716 * <p> 1717 * In that case, the parameters must <em>not</em> be written as query parameters, as the browser 1718 * would strip them from the action url before appending the form values. 1719 * 1720 * @return true if form's method is 'get' 1721 * 1722 * @see #getMethod() 1723 */ 1724 protected boolean encodeUrlInHiddenFields() 1725 { 1726 return METHOD_GET.equalsIgnoreCase(getMethod()); 1727 } 1728 1729 /** 1730 * Append an additional hidden input tag to support anchor tags that can submit a form. 1731 * 1732 * @param markupStream 1733 * The markup stream 1734 * @param openTag 1735 * The open tag for the body 1736 */ 1737 @Override 1738 public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) 1739 { 1740 if (isRootForm()) 1741 { 1742 // get the hidden field id 1743 writeHiddenFields(); 1744 } 1745 1746 // do the rest of the processing 1747 super.onComponentTagBody(markupStream, openTag); 1748 } 1749 1750 @Override 1751 public void renderHead(IHeaderResponse response) 1752 { 1753 super.renderHead(response); 1754 1755 if (hasDefaultSubmittingComponent()) 1756 { 1757 addDefaultSubmitButtonHandler(response); 1758 } 1759 } 1760 1761 /** 1762 * Writes the markup for the hidden input fields and default button field if applicable to the 1763 * current response. 1764 */ 1765 public final void writeHiddenFields() 1766 { 1767 getResponse().write(String.format("<div id=\"%s\" hidden=\"\" class=\"%s\">", 1768 getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX), 1769 getString(HIDDEN_FIELDS_CSS_CLASS_KEY))); 1770 // if the parameters are not in the action attribute, they have to be written as hidden fields 1771 if (encodeUrlInHiddenFields()) 1772 { 1773 AppendingStringBuffer buffer = new AppendingStringBuffer(); 1774 1775 String url = getActionUrl().toString(); 1776 int i = url.indexOf('?'); 1777 String queryString = (i > -1) ? url.substring(i + 1) : url; 1778 String[] params = Strings.split(queryString, '&'); 1779 1780 writeParamsAsHiddenFields(params, buffer); 1781 1782 getResponse().write(buffer); 1783 } 1784 getResponse().write("</div>"); 1785 1786 // if a default submitting component was set, handle the rendering of that 1787 if (hasDefaultSubmittingComponent()) 1788 { 1789 appendDefaultButtonField(); 1790 } 1791 } 1792 1793 private boolean hasDefaultSubmittingComponent() 1794 { 1795 if (defaultSubmittingComponent instanceof Component) 1796 { 1797 final Component submittingComponent = (Component) defaultSubmittingComponent; 1798 return submittingComponent.isVisibleInHierarchy() 1799 && submittingComponent.isEnabledInHierarchy(); 1800 } 1801 return false; 1802 } 1803 1804 /** 1805 * 1806 * @param params 1807 * @param buffer 1808 */ 1809 protected void writeParamsAsHiddenFields(String[] params, AppendingStringBuffer buffer) 1810 { 1811 for (String param : params) 1812 { 1813 String[] pair = Strings.split(param, '='); 1814 1815 buffer.append("<input type=\"hidden\" name=\"") 1816 .append(recode(pair[0])) 1817 .append("\" value=\"") 1818 .append(pair.length > 1 ? recode(pair[1]) : "") 1819 .append("\" />"); 1820 } 1821 } 1822 1823 /** 1824 * Take URL-encoded query string value, decode it and return HTML-escaped version 1825 * 1826 * @param s 1827 * value to decode 1828 * @return URL decoded and HTML escaped value 1829 */ 1830 private String recode(String s) 1831 { 1832 String un = UrlDecoder.QUERY_INSTANCE.decode(s, getRequest().getCharset()); 1833 return Strings.escapeMarkup(un).toString(); 1834 } 1835 1836 /** 1837 * @see org.apache.wicket.Component#onDetach() 1838 */ 1839 @Override 1840 protected void onDetach() 1841 { 1842 setFlag(FLAG_SUBMITTED, false); 1843 1844 super.onDetach(); 1845 } 1846 1847 /** 1848 * Method to override if you want to do something special when an error occurs (other than 1849 * simply displaying validation errors). 1850 */ 1851 protected void onError() 1852 { 1853 } 1854 1855 @Override 1856 public void onEvent(IEvent<?> event) { 1857 if (event.getPayload() instanceof AjaxRequestTarget) { 1858 // WICKET-6171 clear multipart hint, it might change during Ajax requests without this form being rendered 1859 this.multiPart &= MULTIPART_HARD; 1860 } 1861 } 1862 1863 @Override 1864 protected void onBeforeRender() 1865 { 1866 // clear multipart hint, it will be reevaluated by #isMultiPart() 1867 this.multiPart &= MULTIPART_HARD; 1868 1869 super.onBeforeRender(); 1870 } 1871 1872 /** 1873 * Implemented by subclasses to deal with form submits. 1874 */ 1875 protected void onSubmit() 1876 { 1877 } 1878 1879 /** 1880 * Update the model of all components on this form and nested forms using the fields that were 1881 * sent with the current request. This method only updates models when the Form.validate() is 1882 * called first that takes care of the conversion for the FormComponents. 1883 * 1884 * Normally this method will not be called when a validation error occurs in one of the form 1885 * components. 1886 * 1887 * @see org.apache.wicket.markup.html.form.FormComponent#updateModel() 1888 */ 1889 protected final void updateFormComponentModels() 1890 { 1891 internalUpdateFormComponentModels(); 1892 updateNestedFormComponentModels(); 1893 } 1894 1895 /** 1896 * Update the model of all components on nested forms. 1897 * 1898 * @see #updateFormComponentModels() 1899 */ 1900 private void updateNestedFormComponentModels() 1901 { 1902 visitChildren(Form.class, new IVisitor<Form<?>, Void>() 1903 { 1904 @Override 1905 public void component(final Form<?> form, final IVisit<Void> visit) 1906 { 1907 if (form.isSubmitted()) 1908 { 1909 form.internalUpdateFormComponentModels(); 1910 } 1911 else 1912 { 1913 visit.dontGoDeeper(); 1914 } 1915 } 1916 }); 1917 } 1918 1919 /** 1920 * Update the model of all components on this form. 1921 * 1922 * @see #updateFormComponentModels() 1923 */ 1924 private void internalUpdateFormComponentModels() 1925 { 1926 FormComponent.visitComponentsPostOrder(this, new FormModelUpdateVisitor(this)); 1927 } 1928 1929 /** 1930 * Validates the form by checking required fields, converting raw input and running validators 1931 * for every form component, and last running global form validators. This method is typically 1932 * called before updating any models. 1933 * <p> 1934 * NOTE: in most cases, custom validations on the form can be achieved using an IFormValidator 1935 * that can be added using addValidator(). 1936 * </p> 1937 */ 1938 protected final void validate() 1939 { 1940 // since this method can be called directly by users, this additional check is needed 1941 if (isEnabledInHierarchy() && isVisibleInHierarchy()) 1942 { 1943 validateNestedForms(); 1944 validateComponents(); 1945 validateFormValidators(); 1946 onValidate(); 1947 } 1948 } 1949 1950 /** 1951 * Callback during the validation stage of the form 1952 */ 1953 protected void onValidate() 1954 { 1955 1956 } 1957 1958 /** 1959 * Calls {@linkplain #onValidateModelObjects()} on this form and all nested forms that are 1960 * visible and enabled 1961 */ 1962 private void internalOnValidateModelObjects() 1963 { 1964 onValidateModelObjects(); 1965 visitChildren(Form.class, new IVisitor<Form<?>, Void>() 1966 { 1967 @Override 1968 public void component(Form<?> form, IVisit<Void> visit) 1969 { 1970 if (form.isSubmitted()) 1971 { 1972 form.onValidateModelObjects(); 1973 } 1974 else 1975 { 1976 visit.dontGoDeeper(); 1977 } 1978 } 1979 }); 1980 } 1981 1982 /** 1983 * Called after form components have updated their models. This is a late-stage validation that 1984 * allows outside frameworks to validate any beans that the form is updating. 1985 * 1986 * This validation method is not preferred because at this point any errors will not unroll any 1987 * changes to the model object, so the model object is in a modified state potentially 1988 * containing illegal values. However, with external frameworks there may not be an alternate 1989 * way to validate the model object. A good example of this is a JSR303 Bean Validator 1990 * validating the model object to check any class-level constraints, in order to check such 1991 * constraints the model object must contain the values set by the user. 1992 */ 1993 protected void onValidateModelObjects() 1994 { 1995 } 1996 1997 /** 1998 * Triggers type conversion on form components 1999 */ 2000 protected final void validateComponents() 2001 { 2002 visitFormComponentsPostOrder(new ValidationVisitor() 2003 { 2004 @Override 2005 public void validate(final FormComponent<?> formComponent) 2006 { 2007 final Form<?> form = formComponent.getForm(); 2008 if (form == Form.this && form.isEnabledInHierarchy() && form.isVisibleInHierarchy()) 2009 { 2010 formComponent.validate(); 2011 } 2012 } 2013 }); 2014 } 2015 2016 /** 2017 * Checks if the specified form component visible and is attached to a page 2018 * 2019 * @param fc 2020 * form component 2021 * 2022 * @return true if the form component and all its parents are visible and there component is in 2023 * page's hierarchy 2024 */ 2025 private boolean isFormComponentVisibleInPage(FormComponent<?> fc) 2026 { 2027 if (fc == null) 2028 { 2029 throw new IllegalArgumentException("Argument `fc` cannot be null"); 2030 } 2031 return fc.isVisibleInHierarchy(); 2032 } 2033 2034 2035 /** 2036 * Validates form with the given form validator 2037 * 2038 * @param validator 2039 */ 2040 protected final void validateFormValidator(final IFormValidator validator) 2041 { 2042 Args.notNull(validator, "validator"); 2043 2044 final FormComponent<?>[] dependents = validator.getDependentFormComponents(); 2045 2046 boolean validate = true; 2047 2048 if (dependents != null) 2049 { 2050 for (final FormComponent<?> dependent : dependents) 2051 { 2052 // check if the dependent component is valid 2053 if (!dependent.isValid()) 2054 { 2055 validate = false; 2056 break; 2057 } 2058 // check if the dependent component is visible and is attached to 2059 // the page 2060 else if (!isFormComponentVisibleInPage(dependent)) 2061 { 2062 if (log.isWarnEnabled()) 2063 { 2064 log.warn("IFormValidator in form `" + 2065 getPageRelativePath() + 2066 "` depends on a component that has been removed from the page or is no longer visible. " + 2067 "Offending component id `" + dependent.getId() + "`."); 2068 } 2069 validate = false; 2070 break; 2071 } 2072 } 2073 } 2074 2075 if (validate) 2076 { 2077 validator.validate(this); 2078 } 2079 } 2080 2081 /** 2082 * Triggers any added {@link IFormValidator}s. 2083 */ 2084 protected final void validateFormValidators() 2085 { 2086 for (Behavior behavior : getBehaviors()) 2087 { 2088 if (behavior instanceof IFormValidator) 2089 { 2090 validateFormValidator((IFormValidator)behavior); 2091 } 2092 } 2093 } 2094 2095 /** 2096 * Validates {@link FormComponent}s as well as {@link IFormValidator}s in nested {@link Form}s. 2097 * 2098 * @see #validate() 2099 */ 2100 private void validateNestedForms() 2101 { 2102 Visits.visitPostOrder(this, new IVisitor<Form<?>, Void>() 2103 { 2104 @Override 2105 public void component(final Form<?> form, final IVisit<Void> visit) 2106 { 2107 if (form == Form.this) 2108 { 2109 // skip self, only process children 2110 visit.stop(); 2111 return; 2112 } 2113 2114 if (form.isSubmitted()) 2115 { 2116 form.validateComponents(); 2117 form.validateFormValidators(); 2118 form.onValidate(); 2119 } 2120 } 2121 }, new ClassVisitFilter(Form.class)); 2122 } 2123 2124 /** 2125 * Allows to customize input names of form components inside this form. 2126 * 2127 * @return String that well be used as prefix to form component input names 2128 */ 2129 protected String getInputNamePrefix() 2130 { 2131 return ""; 2132 } 2133 2134 /** 2135 * @param component 2136 * @return The parent form for component 2137 */ 2138 public static Form<?> findForm(Component component) 2139 { 2140 return component.findParent(Form.class); 2141 } 2142 2143 /** 2144 * Utility method to assemble an id to distinct form components from different nesting levels. 2145 * Useful to generate input names attributes. 2146 * 2147 * @param component 2148 * @return form relative identification string 2149 */ 2150 public static String getRootFormRelativeId(Component component) 2151 { 2152 String id = component.getId(); 2153 final PrependingStringBuffer inputName = new PrependingStringBuffer(id.length()); 2154 Component c = component; 2155 while (true) 2156 { 2157 inputName.prepend(id); 2158 c = c.getParent(); 2159 if (c == null || (c instanceof Form<?> && ((Form<?>)c).isRootForm()) || 2160 c instanceof Page) 2161 { 2162 break; 2163 } 2164 inputName.prepend(Component.PATH_SEPARATOR); 2165 id = c.getId(); 2166 } 2167 2168 /* 2169 * Certain input names causes problems with JavaScript. If the input name would cause a 2170 * problem, we create a replacement unique name by prefixing the name with a path that would 2171 * otherwise never be used (blank id in path). 2172 * 2173 * Input names must start with [A-Za-z] according to HTML 4.01 spec. HTML 5 allows almost 2174 * anything. 2175 */ 2176 if (JavaScriptReservedNames.isNameReserved(inputName.toString())) 2177 { 2178 inputName.prepend(Component.PATH_SEPARATOR); 2179 inputName.prepend(Component.PATH_SEPARATOR); 2180 inputName.prepend("p"); 2181 } 2182 return inputName.toString(); 2183 } 2184 2185 /** 2186 * Get the request parameters for a form submit, 2187 * according to the request's method or the form's method as fallback. 2188 * 2189 * @param component any component inside the form or the form itself 2190 * @return parameters 2191 */ 2192 static IRequestParameters getRequestParameters(Component component) { 2193 String method = Form.METHOD_POST; 2194 final Request request = component.getRequest(); 2195 if (request.getContainerRequest() instanceof HttpServletRequest) 2196 { 2197 method = ((HttpServletRequest)request.getContainerRequest()).getMethod(); 2198 } 2199 else 2200 { 2201 final Form<?> form; 2202 if (component instanceof Form) { 2203 form = (Form<?>)component; 2204 } else { 2205 form = component.findParent(Form.class); 2206 } 2207 2208 if (form != null) 2209 { 2210 method = form.getMethod(); 2211 } 2212 } 2213 2214 final IRequestParameters parameters; 2215 switch (method.toLowerCase(Locale.ROOT)) 2216 { 2217 case Form.METHOD_POST: 2218 parameters = request.getPostParameters(); 2219 break; 2220 case Form.METHOD_GET: 2221 parameters = request.getQueryParameters(); 2222 break; 2223 default: 2224 parameters = EmptyRequestParameters.INSTANCE; 2225 } 2226 2227 return parameters; 2228 } 2229 2230 /** 2231 * Response when a submission method mismatch is detected 2232 * 2233 * @see Form#getMethod() 2234 * 2235 * @author igor 2236 */ 2237 public static enum MethodMismatchResponse { 2238 /** 2239 * Continue processing. 2240 */ 2241 CONTINUE, 2242 2243 /** 2244 * Abort processing. 2245 */ 2246 ABORT 2247 } 2248}