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