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