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 private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed"; 259 260 private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge"; 261 private static final String UPLOAD_SINGLE_FILE_TOO_LARGE_RESOURCE_KEY = "uploadSingleFileTooLarge"; 262 private 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 submited via a javascript submit event, when false via 582 * the {@code submit()} method. 583 * @return the javascript code that submits the form. 584 * 585 * @see #findSubmitter() 586 */ 587 public final CharSequence getJsForSubmitter(IFormSubmittingComponent submitter, boolean triggerEvent) 588 { 589 Form<?> root = getRootForm(); 590 591 String param = submitter.getInputName() + "=x"; 592 593 AppendingStringBuffer buffer = new AppendingStringBuffer(); 594 buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId())); 595 buffer.append(String.format("document.getElementById('%s').innerHTML += '", 596 root.getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX))); 597 writeParamsAsHiddenFields(new String[] {param}, buffer); 598 buffer.append("';"); 599 600 if (triggerEvent) 601 { 602 buffer.append("Wicket.Event.fire(f, 'submit');"); 603 } 604 else 605 { 606 buffer.append("f.submit();"); 607 } 608 return buffer; 609 } 610 611 /** 612 * Gets the maximum size for uploads. If null, the setting 613 * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. 614 * 615 * 616 * @return the maximum size 617 */ 618 public final Bytes getMaxSize() 619 { 620 /* 621 * NOTE: This method should remain final otherwise it will be impossible to set a default 622 * max size smaller then the one specified in applications settings because the inner form 623 * will return the default unless it is specifically set in the traversal. With this method 624 * remaining final we can tell when the value is explicitly set by the user. 625 * 626 * If the value needs to be dynamic it can be set in oncofigure() instead of overriding this 627 * method. 628 */ 629 630 final Bytes[] maxSize = { this.maxSize }; 631 if (maxSize[0] == null) 632 { 633 visitChildren(Form.class, new IVisitor<Form<?>, Bytes>() 634 { 635 @Override 636 public void component(Form<?> component, IVisit<Bytes> visit) 637 { 638 maxSize[0] = LongValue.maxNullSafe(maxSize[0], component.maxSize); 639 } 640 }); 641 } 642 if (maxSize[0] == null) 643 { 644 return getApplication().getApplicationSettings().getDefaultMaximumUploadSize(); 645 } 646 return maxSize[0]; 647 } 648 649 /** 650 * Gets maximum size for each file of an upload. 651 * 652 * @return 653 */ 654 public Bytes getFileMaxSize() 655 { 656 return fileMaxSize; 657 } 658 659 /** 660 * Gets maximum count of files in the form 661 * 662 * @return 663 */ 664 public long getFileCountMax() 665 { 666 return fileCountMax; 667 } 668 669 /** 670 * Returns the root form or this, if this is the root form. 671 * 672 * @return root form or this form 673 */ 674 public Form<?> getRootForm() 675 { 676 Form<?> form; 677 Form<?> parent = this; 678 do 679 { 680 form = parent; 681 parent = form.findParent(Form.class); 682 } 683 while (parent != null); 684 685 return form; 686 } 687 688 /** 689 * Returns the prefix used when building validator keys. This allows a form to use a separate 690 * "set" of keys. For example if prefix "short" is returned, validator key short.Required will 691 * be tried instead of Required key. 692 * <p> 693 * This can be useful when different designs are used for a form. In a form where error messages 694 * are displayed next to their respective form components as opposed to at the top of the form, 695 * the ${label} attribute is of little use and only causes redundant information to appear in 696 * the message. Forms like these can return the "short" (or any other string) validator prefix 697 * and declare key: short.Required=required to override the longer message which is usually 698 * declared like this: Required=${label} is a required field 699 * <p> 700 * Returned prefix will be used for all form components. The prefix can also be overridden on 701 * form component level by overriding {@link FormComponent#getValidatorKeyPrefix()} 702 * 703 * @return prefix prepended to validator keys 704 */ 705 public String getValidatorKeyPrefix() 706 { 707 return null; 708 } 709 710 /** 711 * Gets whether the current form has any error registered. 712 * 713 * @return True if this form has at least one error. 714 */ 715 public final boolean hasError() 716 { 717 // if this form itself has an error message 718 if (hasErrorMessage()) 719 { 720 return true; 721 } 722 723 // the form doesn't have any errors, now check any nested form 724 // components 725 return anyFormComponentError(); 726 } 727 728 /** 729 * Returns whether the form is a root form, which means that there's no other form in it's 730 * parent hierarchy. 731 * 732 * @return true if form is a root form, false otherwise 733 */ 734 public boolean isRootForm() 735 { 736 return findParent(Form.class) == null; 737 } 738 739 /** 740 * Checks if this form has been submitted during the current request 741 * 742 * @return true if the form has been submitted during this request, false otherwise 743 */ 744 public final boolean isSubmitted() 745 { 746 return getFlag(FLAG_SUBMITTED); 747 } 748 749 /** 750 * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. 751 * 752 * Handles form submissions. 753 * 754 * @see #onFormSubmitted(IFormSubmitter) 755 */ 756 @Override 757 public final void onRequest() 758 { 759 onFormSubmitted(null); 760 } 761 762 /** 763 * Called when a form has been submitted using a method differing from return value of 764 * {@link #getMethod()}. For example, someone can copy and paste the action url and invoke the 765 * form using a {@code GET} instead of the desired {@code POST}. This method allows the user to 766 * react to this situation. 767 * 768 * @return response that can either abort or continue the processing of the form 769 */ 770 protected MethodMismatchResponse onMethodMismatch() 771 { 772 return MethodMismatchResponse.CONTINUE; 773 } 774 775 /** 776 * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. 777 * 778 * Handles form submissions. 779 * 780 * @param submitter 781 * listener that will receive form processing events, if {@code null} the form will 782 * attempt to locate one 783 * 784 * @see Form#validate() 785 */ 786 public final void onFormSubmitted(IFormSubmitter submitter) 787 { 788 // check methods match 789 if (getRequest().getContainerRequest() instanceof HttpServletRequest) 790 { 791 String desiredMethod = getMethod(); 792 String actualMethod = ((HttpServletRequest)getRequest().getContainerRequest()).getMethod(); 793 if (!actualMethod.equalsIgnoreCase(desiredMethod)) 794 { 795 MethodMismatchResponse response = onMethodMismatch(); 796 switch (response) 797 { 798 case ABORT : 799 return; 800 case CONTINUE : 801 break; 802 default : 803 throw new IllegalStateException("Invalid " + 804 MethodMismatchResponse.class.getName() + " value: " + response); 805 } 806 } 807 } 808 809 markFormsSubmitted(submitter); 810 811 if (handleMultiPart()) 812 { 813 // Tells FormComponents that a new user input has come 814 inputChanged(); 815 816 // First, see if the processing was triggered by a IFormSubmittingComponent 817 if (submitter == null) 818 { 819 submitter = findSubmitter(); 820 821 if (submitter instanceof IFormSubmittingComponent) 822 { 823 IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)submitter; 824 Component component = (Component)submitter; 825 826 if (!component.isVisibleInHierarchy()) 827 { 828 throw new WicketRuntimeException("Submit Button " + 829 submittingComponent.getInputName() + " (path=" + 830 component.getPageRelativePath() + ") is not visible"); 831 } 832 833 if (!component.isEnabledInHierarchy()) 834 { 835 throw new WicketRuntimeException("Submit Button " + 836 submittingComponent.getInputName() + " (path=" + 837 component.getPageRelativePath() + ") is not enabled"); 838 } 839 } 840 } 841 842 // When processing was triggered by a Wicket IFormSubmittingComponent and that 843 // component indicates it wants to be called immediately 844 // (without processing), call the IFormSubmittingComponent.onSubmit* methods right 845 // away. 846 if (submitter != null && !submitter.getDefaultFormProcessing()) 847 { 848 submitter.onSubmit(); 849 submitter.onAfterSubmit(); 850 } 851 else 852 { 853 // the submit request might be for one of the nested forms, so let's 854 // find the right one: 855 final Form<?> formToProcess = findFormToProcess(submitter); 856 857 // process the form for this request 858 formToProcess.process(submitter); 859 } 860 } 861 // If multi part did fail check if an error is registered and call 862 // onError 863 else if (hasError()) 864 { 865 callOnError(submitter); 866 } 867 868 // update auto labels if we are inside an ajax request 869 getRequestCycle().find(AjaxRequestTarget.class).ifPresent(target -> { 870 visitChildren(FormComponent.class, new IVisitor<FormComponent<?>, Void>() 871 { 872 @Override 873 public void component(FormComponent<?> component, IVisit<Void> visit) 874 { 875 component.updateAutoLabels(target); 876 } 877 }); 878 }); 879 } 880 881 /** 882 * This method finds the correct form that should be processed based on the submitting component 883 * (if there is one) and correctly handles nested forms by also looking at 884 * {@link #wantSubmitOnNestedFormSubmit()} throughout the form hierarchy. The form that needs to 885 * be processed is: 886 * <ul> 887 * <li>if there is no submitting component (i.e. a "default submit"): this form.</li> 888 * <li>if only one form exists (this): this form.</li> 889 * <li>if nested forms exist: 890 * <ul> 891 * <li>if the submitting component points at the root form: the root form</li> 892 * <li>if the submitting component points at a nested form: 893 * <ul> 894 * <li>starting at that nested form, the outermost form that returns true for 895 * {@link #wantSubmitOnNestedFormSubmit()}</li> 896 * <li>if no outer form returns true for that, the nested form is returned.</li> 897 * </ul> 898 * </li> 899 * </ul> 900 * </li> 901 * </ul> 902 * 903 * @param submitter 904 * The submitting component, if any. May be null. 905 * @return The form that needs to be processed. 906 */ 907 private Form<?> findFormToProcess(IFormSubmitter submitter) 908 { 909 if (submitter == null) 910 { 911 // no submitting component => default form submit => so *this* is the 912 // form to process 913 return this; 914 } 915 else 916 { 917 // some button submitted this request, this is the form it belongs to: 918 final Form<?> targetedForm = submitter.getForm(); 919 if (targetedForm == null) 920 { 921 throw new IllegalStateException( 922 "submitting component must not return 'null' on getForm()"); 923 } 924 925 final Form<?> rootForm = getRootForm(); 926 if (targetedForm == rootForm) 927 { 928 // the submitting component points at the root form => so let's just go with 929 // root, everything else will be submitted with it anyway. 930 return rootForm; 931 } 932 else 933 { 934 // a different form was targeted. let's find the outermost form that wants to be 935 // submitted. 936 Form<?> formThatWantsToBeSubmitted = targetedForm; 937 Form<?> current = targetedForm.findParent(Form.class); 938 while (current != null) 939 { 940 if (current.wantSubmitOnNestedFormSubmit()) 941 { 942 formThatWantsToBeSubmitted = current; 943 } 944 current = current.findParent(Form.class); 945 } 946 return formThatWantsToBeSubmitted; 947 } 948 } 949 } 950 951 /** 952 * Whether this form wants to be submitted too if a nested form is submitted. By default, this 953 * is false, so when a nested form is submitted, this form will <em>not</em> be submitted. If 954 * this method is overridden to return true, this form <em>will</em> be submitted. 955 * 956 * @return Whether this form wants to be submitted too if a nested form is submitted. 957 */ 958 // TODO wicket-7 migration guide: changed from public to protected 959 protected boolean wantSubmitOnNestedFormSubmit() 960 { 961 return false; 962 } 963 964 /** 965 * Whether this *nested* form wants to be submitted when parent form is submitted. By default, 966 * this is true, so when a parent form is submitted, the nested form is also submitted. If this 967 * method is overridden to return false, it will not be validated, processed nor submitted. 968 * 969 * @return {@code true} by default 970 */ 971 protected boolean wantSubmitOnParentFormSubmit() 972 { 973 return true; 974 } 975 976 /** 977 * Process the form. Though you can override this method to provide your own algorithm, it is 978 * not recommended to do so. 979 * 980 * <p> 981 * See the class documentation for further details on the form processing 982 * </p> 983 * 984 * @param submittingComponent 985 * component responsible for submitting the form, or <code>null</code> if none (eg 986 * the form has been submitted via the enter key or javascript calling form.submit()) 987 * 988 * @see #delegateSubmit(IFormSubmitter) for an easy way to process submitting component in the 989 * default manner 990 */ 991 public void process(IFormSubmitter submittingComponent) 992 { 993 if (!isEnabledInHierarchy() || !isVisibleInHierarchy()) 994 { 995 // since process() can be called outside of the default form workflow, an additional 996 // check is needed 997 998 // FIXME throw listener exception 999 return; 1000 } 1001 1002 // run validation 1003 validate(); 1004 1005 // If a validation error occurred 1006 if (hasError()) 1007 { 1008 // mark all children as invalid 1009 markFormComponentsInvalid(); 1010 1011 // let subclass handle error 1012 callOnError(submittingComponent); 1013 } 1014 else 1015 { 1016 // mark all children as valid 1017 markFormComponentsValid(); 1018 1019 // before updating, call the interception method for clients 1020 beforeUpdateFormComponentModels(); 1021 1022 // Update model using form data 1023 updateFormComponentModels(); 1024 1025 // validate model objects after input values have been bound 1026 internalOnValidateModelObjects(); 1027 if (hasError()) 1028 { 1029 callOnError(submittingComponent); 1030 return; 1031 } 1032 1033 // Form has no error 1034 delegateSubmit(submittingComponent); 1035 } 1036 } 1037 1038 /** 1039 * Calls onError on this {@link Form} and any enabled and visible nested form, if the respective 1040 * {@link Form} actually has errors. 1041 * 1042 * @param submitter 1043 */ 1044 protected void callOnError(IFormSubmitter submitter) 1045 { 1046 final Form<?> processingForm = findFormToProcess(submitter); 1047 1048 if (submitter != null) 1049 { 1050 submitter.onError(); 1051 } 1052 1053 // invoke Form#onSubmit(..) going from innermost to outermost 1054 Visits.visitPostOrder(processingForm, new IVisitor<Form<?>, Void>() 1055 { 1056 @Override 1057 public void component(Form<?> form, IVisit<Void> visit) 1058 { 1059 if (!form.isEnabledInHierarchy() || !form.isVisibleInHierarchy()) 1060 { 1061 visit.dontGoDeeper(); 1062 return; 1063 } 1064 if (form.hasError()) 1065 { 1066 form.onError(); 1067 } 1068 } 1069 }, new ClassVisitFilter(Form.class)); 1070 } 1071 1072 1073 /** 1074 * Sets FLAG_SUBMITTED to true on this form and every enabled nested form. 1075 * @param submitter 1076 */ 1077 private void markFormsSubmitted(IFormSubmitter submitter) 1078 { 1079 setFlag(FLAG_SUBMITTED, true); 1080 Form<?> formToProcess = findFormToProcess(submitter); 1081 1082 visitChildren(Form.class, new IVisitor<Component, Void>() 1083 { 1084 @Override 1085 public void component(final Component component, final IVisit<Void> visit) 1086 { 1087 Form<?> form = (Form<?>)component; 1088 if ((form.wantSubmitOnParentFormSubmit() || form == formToProcess) 1089 && form.isEnabledInHierarchy() && form.isVisibleInHierarchy()) 1090 { 1091 form.setFlag(FLAG_SUBMITTED, true); 1092 return; 1093 } 1094 visit.dontGoDeeper(); 1095 } 1096 }); 1097 } 1098 1099 /** 1100 * Sets the default IFormSubmittingComponent. If set (not null), a hidden submit component will 1101 * be rendered right after the form tag, so that when users press enter in a textfield, this 1102 * submit component's action will be selected. If no default component is set (so unset by 1103 * calling this method with null), nothing additional is rendered. 1104 * <p> 1105 * WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a 1106 * form is ill defined in the standards, and of course IE has it's own way of doing things. 1107 * </p> 1108 * There can be only one default button per form hierarchy. So if you set default button on a 1109 * nested form, it will actually delegate the call to root form. </b> 1110 * 1111 * @param submittingComponent 1112 * The component to set as the default submitting component, or null when you want to 1113 * 'unset' any previously set default component 1114 */ 1115 public final void setDefaultButton(IFormSubmittingComponent submittingComponent) 1116 { 1117 if (isRootForm()) 1118 { 1119 defaultSubmittingComponent = submittingComponent; 1120 } 1121 else 1122 { 1123 getRootForm().setDefaultButton(submittingComponent); 1124 } 1125 } 1126 1127 /** 1128 * Sets the maximum size for uploads. If null, the setting 1129 * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. 1130 * 1131 * @param maxSize 1132 * The maximum size 1133 */ 1134 public void setMaxSize(final Bytes maxSize) 1135 { 1136 this.maxSize = maxSize; 1137 } 1138 1139 /** 1140 * Sets maximum size of each file in upload request. 1141 * 1142 * @param fileMaxSize 1143 */ 1144 public void setFileMaxSize(Bytes fileMaxSize) 1145 { 1146 this.fileMaxSize = fileMaxSize; 1147 } 1148 1149 /** 1150 * Sets maximum amount of files in upload request. 1151 * 1152 * @param fileCountMax 1153 */ 1154 public void setFileCountMax(long fileCountMax) 1155 { 1156 this.fileCountMax = fileCountMax; 1157 } 1158 1159 /** 1160 * Set to true to use enctype='multipart/form-data', and to process file uploads by default 1161 * multiPart = false 1162 * 1163 * @param multiPart 1164 * whether this form should behave as a multipart form 1165 */ 1166 public void setMultiPart(boolean multiPart) 1167 { 1168 if (multiPart) 1169 { 1170 this.multiPart |= MULTIPART_HARD; 1171 } 1172 else 1173 { 1174 this.multiPart &= ~MULTIPART_HARD; 1175 } 1176 } 1177 1178 /** 1179 * @see org.apache.wicket.Component#setVersioned(boolean) 1180 */ 1181 @Override 1182 public final Component setVersioned(final boolean isVersioned) 1183 { 1184 super.setVersioned(isVersioned); 1185 1186 // Search for FormComponents like TextField etc. 1187 visitFormComponents(new IVisitor<FormComponent<?>, Void>() 1188 { 1189 @Override 1190 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1191 { 1192 formComponent.setVersioned(isVersioned); 1193 } 1194 }); 1195 return this; 1196 } 1197 1198 /** 1199 * Convenient and typesafe way to visit all the form components on a form. 1200 * 1201 * @param <R> 1202 * return object type 1203 * @param visitor 1204 * The visitor interface to call 1205 * @return user provided in callback 1206 */ 1207 public final <R> R visitFormComponents(final IVisitor<FormComponent<?>, R> visitor) 1208 { 1209 return visitChildren(FormComponent.class, visitor); 1210 } 1211 1212 /** 1213 * Convenient and typesafe way to visit all the form components on a form postorder (deepest 1214 * first) 1215 * 1216 * @param <R> 1217 * Return object type 1218 * @param visitor 1219 * The visitor interface to call 1220 * @return whatever you provided 1221 */ 1222 public final <R> R visitFormComponentsPostOrder( 1223 final IVisitor<? extends FormComponent<?>, R> visitor) 1224 { 1225 return FormComponent.visitFormComponentsPostOrder(this, visitor); 1226 } 1227 1228 /** 1229 * Find out whether there is any registered error for a form component. 1230 * 1231 * @return whether there is any registered error for a form component 1232 */ 1233 private boolean anyFormComponentError() 1234 { 1235 // Check ALL children for error messages irrespective of FormComponents or not 1236 Boolean error = visitChildren(Component.class, new IVisitor<Component, Boolean>() 1237 { 1238 @Override 1239 public void component(final Component component, final IVisit<Boolean> visit) 1240 { 1241 if (component.hasErrorMessage() && component.isVisibleInHierarchy() && component.isEnabledInHierarchy()) 1242 { 1243 visit.stop(true); 1244 } 1245 } 1246 }); 1247 1248 return (error != null) && error; 1249 } 1250 1251 /** 1252 * Visits the form's children FormComponents and inform them that a new user input is available 1253 * in the Request 1254 */ 1255 private void inputChanged() 1256 { 1257 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1258 { 1259 @Override 1260 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1261 { 1262 formComponent.inputChanged(); 1263 } 1264 }); 1265 } 1266 1267 /** 1268 * If a default IFormSubmittingComponent was set on this form, this method will be called to 1269 * render an extra field with an invisible style so that pressing enter in one of the textfields 1270 * will do a form submit using this component. This method is overridable as what we do is best 1271 * effort only, and may not what you want in specific situations. So if you have specific 1272 * usability concerns, or want to follow another strategy, you may override this method. 1273 * 1274 * @see #addDefaultSubmitButtonHandler(IHeaderResponse) 1275 */ 1276 protected void appendDefaultButtonField() 1277 { 1278 AppendingStringBuffer buffer = new AppendingStringBuffer(); 1279 1280 // hidden div 1281 buffer.append(String.format("<div hidden=\"\" class=\"%s\">", 1282 getString(HIDDEN_FIELDS_CSS_CLASS_KEY))); 1283 1284 // add an empty textfield (otherwise IE doesn't work) 1285 buffer.append("<input type=\"text\" tabindex=\"-1\" autocomplete=\"off\"/>"); 1286 1287 // add the submitting component 1288 buffer 1289 .append(String.format("<input id=\"%s\" type=\"submit\" tabindex=\"-1\" name=\"%s\" />", 1290 getHiddenFieldsId(HIDDEN_FIELDS_SUBMIT_IDX), 1291 defaultSubmittingComponent.getInputName())); 1292 1293 // close div 1294 buffer.append("</div>"); 1295 1296 getResponse().write(buffer); 1297 } 1298 1299 /** 1300 * Where {@link #appendDefaultButtonField()} renders the markup for default submit button 1301 * handling, this method attaches the event handler to its 'click' event. The 'click' event on 1302 * the hidden submit button will be dispatched to the selected default submit button. As with 1303 * {@link #appendDefaultButtonField()} this method can be overridden when the generated code 1304 * needs to be adjusted for a specific usecase. 1305 * 1306 * @param headerResponse 1307 * The header response. 1308 */ 1309 protected void addDefaultSubmitButtonHandler(IHeaderResponse headerResponse) 1310 { 1311 final Component submittingComponent = (Component) defaultSubmittingComponent; 1312 AppendingStringBuffer buffer = new AppendingStringBuffer(); 1313 buffer.append("var b=document.getElementById('"); 1314 buffer.append(submittingComponent.getMarkupId()); 1315 buffer.append("'); if (b!=null && b.onclick!=null && typeof(b.onclick) != 'undefined') "); 1316 buffer.append( 1317 "{ var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } else { b.click(); }; return false;"); 1318 headerResponse.render(OnEventHeaderItem 1319 .forMarkupId(getHiddenFieldsId(HIDDEN_FIELDS_SUBMIT_IDX), "click", buffer.toString())); 1320 } 1321 1322 /** 1323 * Template method to allow clients to do any processing (like recording the current model so 1324 * that, in case onSubmit does further validation, the model can be rolled back) before the 1325 * actual updating of form component models is done. 1326 */ 1327 protected void beforeUpdateFormComponentModels() 1328 { 1329 } 1330 1331 /** 1332 * Called (by the default implementation of 'process') when all fields validated, the form was 1333 * updated and it's data was allowed to be persisted. It is meant for delegating further 1334 * processing to clients. 1335 * <p> 1336 * This implementation first finds out whether the form processing was triggered by a nested 1337 * IFormSubmittingComponent of this form. If that is the case, that component's 1338 * onSubmitBefore/AfterForm methods are called appropriately.. 1339 * </p> 1340 * <p> 1341 * Regardless of whether a submitting component was found, the form's onSubmit method is called 1342 * next. 1343 * </p> 1344 * 1345 * @param submittingComponent 1346 * the component that triggered this form processing, or null if the processing was 1347 * triggered by something else (like a non-Wicket submit button or a javascript 1348 * execution) 1349 */ 1350 protected void delegateSubmit(IFormSubmitter submittingComponent) 1351 { 1352 final Form<?> processingForm = findFormToProcess(submittingComponent); 1353 1354 // collect all forms innermost to outermost before any hierarchy is changed 1355 final List<Form<?>> forms = Generics.newArrayList(3); 1356 Visits.visitPostOrder(processingForm, new IVisitor<Form<?>, Void>() 1357 { 1358 @Override 1359 public void component(Form<?> form, IVisit<Void> visit) 1360 { 1361 if (form.isSubmitted()) 1362 { 1363 forms.add(form); 1364 } 1365 } 1366 }, new ClassVisitFilter(Form.class)); 1367 1368 // process submitting component (if specified) 1369 if (submittingComponent != null) 1370 { 1371 // invoke submit on component 1372 submittingComponent.onSubmit(); 1373 } 1374 1375 // invoke Form#onSubmit(..) 1376 for (Form<?> form : forms) 1377 { 1378 form.onSubmit(); 1379 } 1380 1381 if (submittingComponent != null) 1382 { 1383 submittingComponent.onAfterSubmit(); 1384 } 1385 } 1386 1387 /** 1388 * Returns the id which will be used for the hidden div containing all parameter fields. 1389 * 1390 * @param idx 1391 * The index of the div to keep different divs apart. 1392 * @return the id of the hidden div 1393 */ 1394 private final String getHiddenFieldsId(int idx) 1395 { 1396 return getInputNamePrefix() + getMarkupId() + "_hf_" + idx; 1397 } 1398 1399 /** 1400 * Gets the HTTP submit method that will appear in form markup. If no method is specified in the 1401 * template, "post" is the default. Note that the markup-declared HTTP method may not correspond 1402 * to the one actually used to submit the form; in an Ajax submit, for example, JavaScript event 1403 * handlers may submit the form with a "get" even when the form method is declared as "post." 1404 * Therefore this method should not be considered a guarantee of the HTTP method used, but a 1405 * value for the markup only. Override if you have a requirement to alter this behavior. 1406 * 1407 * @return the submit method specified in markup. 1408 */ 1409 protected String getMethod() 1410 { 1411 String method = getMarkupAttributes().getString("method"); 1412 return (method != null) ? method : METHOD_POST; 1413 } 1414 1415 /** 1416 * 1417 * @see org.apache.wicket.Component#getStatelessHint() 1418 */ 1419 @Override 1420 protected boolean getStatelessHint() 1421 { 1422 return false; 1423 } 1424 1425 /** 1426 * @return True if is multipart 1427 */ 1428 public boolean isMultiPart() 1429 { 1430 if (multiPart == 0) 1431 { 1432 Boolean anyEmbeddedMultipart = visitChildren(Component.class, 1433 new IVisitor<Component, Boolean>() 1434 { 1435 @Override 1436 public void component(final Component component, final IVisit<Boolean> visit) 1437 { 1438 boolean isMultiPart = false; 1439 if (component instanceof Form<?>) 1440 { 1441 Form<?> form = (Form<?>)component; 1442 if (form.isVisibleInHierarchy() && form.isEnabledInHierarchy()) 1443 { 1444 isMultiPart = (form.multiPart & MULTIPART_HARD) != 0; 1445 } 1446 } 1447 else if (component instanceof FormComponent<?>) 1448 { 1449 FormComponent<?> fc = (FormComponent<?>)component; 1450 if (fc.isVisibleInHierarchy() && fc.isEnabledInHierarchy()) 1451 { 1452 isMultiPart = fc.isMultiPart(); 1453 } 1454 } 1455 1456 if (isMultiPart) 1457 { 1458 visit.stop(true); 1459 } 1460 } 1461 1462 }); 1463 1464 if (Boolean.TRUE.equals(anyEmbeddedMultipart)) { 1465 multiPart |= MULTIPART_HINT_YES; 1466 } else { 1467 multiPart |= MULTIPART_HINT_NO; 1468 } 1469 } 1470 1471 return (multiPart & (MULTIPART_HARD | MULTIPART_HINT_YES)) != 0; 1472 } 1473 1474 /** 1475 * Handles multi-part processing of the submitted data. 1476 * <strong>WARNING</strong> If this method is overridden it can break {@link FileUploadField}s on this form 1477 * 1478 * @return false if form is multipart and upload failed 1479 */ 1480 protected boolean handleMultiPart() 1481 { 1482 if (isMultiPart()) 1483 { 1484 // Change the request to a multipart web request so parameters are 1485 // parsed out correctly 1486 try 1487 { 1488 ServletWebRequest request = (ServletWebRequest)getRequest(); 1489 final MultipartServletWebRequest multipartWebRequest = request.newMultipartWebRequest( 1490 getMaxSize(), getPage().getId()); 1491 multipartWebRequest.setFileMaxSize(getFileMaxSize()); 1492 multipartWebRequest.setFileCountMax(getFileCountMax()); 1493 multipartWebRequest.parseFileParts(); 1494 1495 // TODO: Can't this be detected from header? 1496 getRequestCycle().setRequest(multipartWebRequest); 1497 } 1498 catch (final FileUploadException fux) 1499 { 1500 // Create model with exception and maximum size values 1501 final Map<String, Object> model = new HashMap<>(); 1502 model.put("exception", fux); 1503 model.put("maxSize", getMaxSize()); 1504 model.put("fileMaxSize", getFileMaxSize()); 1505 model.put("fileCountMax", getFileCountMax()); 1506 1507 onFileUploadException(fux, model); 1508 1509 // don't process the form if there is a FileUploadException 1510 return false; 1511 } 1512 } 1513 return true; 1514 } 1515 1516 /** 1517 * The default message may look like ".. may not exceed 10240 Bytes..". Which is ok, but 1518 * sometimes you may want something like "10KB". By subclassing this method you may replace 1519 * maxSize in the model or add you own property and use that in your error message. 1520 * <p> 1521 * Don't forget to call super.onFileUploadException(e, model) at the end of your method. 1522 * 1523 * @param e 1524 * @param model 1525 */ 1526 protected void onFileUploadException(final FileUploadException e, 1527 final Map<String, Object> model) 1528 { 1529 if (e instanceof FileUploadBase.SizeLimitExceededException) 1530 { 1531 String msg = getString(UPLOAD_TOO_LARGE_RESOURCE_KEY, Model.ofMap(model)); 1532 error(msg); 1533 } 1534 else if (e instanceof FileUploadBase.FileSizeLimitExceededException) 1535 { 1536 String msg = getString(UPLOAD_SINGLE_FILE_TOO_LARGE_RESOURCE_KEY, Model.ofMap(model)); 1537 error(msg); 1538 } 1539 else if (e instanceof FileCountLimitExceededException) 1540 { 1541 String msg = getString(UPLOAD_TOO_MANY_FILES_RESOURCE_KEY, Model.ofMap(model)); 1542 error(msg); 1543 } 1544 else 1545 { 1546 String msg = getString(UPLOAD_FAILED_RESOURCE_KEY, Model.ofMap(model)); 1547 error(msg); 1548 1549 log.warn(msg, e); 1550 } 1551 } 1552 1553 /** 1554 * @see org.apache.wicket.Component#internalOnModelChanged() 1555 */ 1556 @Override 1557 protected void internalOnModelChanged() 1558 { 1559 // Visit all the form components and validate each 1560 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1561 { 1562 @Override 1563 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1564 { 1565 // If form component is using form model 1566 if (formComponent.sameInnermostModel(Form.this)) 1567 { 1568 formComponent.modelChanged(); 1569 } 1570 } 1571 }); 1572 } 1573 1574 /** 1575 * Mark each form component on this form invalid. 1576 */ 1577 protected final void markFormComponentsInvalid() 1578 { 1579 // call invalidate methods of all nested form components 1580 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1581 { 1582 @Override 1583 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1584 { 1585 if (formComponent.isVisibleInHierarchy()) 1586 { 1587 formComponent.invalid(); 1588 } 1589 } 1590 }); 1591 } 1592 1593 /** 1594 * Mark each form component on this form and on nested forms valid. 1595 */ 1596 protected final void markFormComponentsValid() 1597 { 1598 internalMarkFormComponentsValid(); 1599 markNestedFormComponentsValid(); 1600 } 1601 1602 /** 1603 * Mark each form component on nested form valid. 1604 */ 1605 private void markNestedFormComponentsValid() 1606 { 1607 visitChildren(Form.class, new IVisitor<Form<?>, Void>() 1608 { 1609 @Override 1610 public void component(final Form<?> form, final IVisit<Void> visit) 1611 { 1612 if (form.isSubmitted()) 1613 { 1614 form.internalMarkFormComponentsValid(); 1615 } 1616 else 1617 { 1618 visit.dontGoDeeper(); 1619 } 1620 } 1621 }); 1622 } 1623 1624 /** 1625 * Mark each form component on this form valid. 1626 */ 1627 private void internalMarkFormComponentsValid() 1628 { 1629 // call valid methods of all nested form components 1630 visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() 1631 { 1632 @Override 1633 public void component(final FormComponent<?> formComponent, IVisit<Void> visit) 1634 { 1635 if (formComponent.getForm() == Form.this && formComponent.isVisibleInHierarchy()) 1636 { 1637 formComponent.valid(); 1638 } 1639 } 1640 }); 1641 } 1642 1643 /** 1644 * @see org.apache.wicket.Component#onComponentTag(ComponentTag) 1645 */ 1646 @Override 1647 protected void onComponentTag(final ComponentTag tag) 1648 { 1649 super.onComponentTag(tag); 1650 1651 if (isRootForm()) 1652 { 1653 checkComponentTag(tag, "form"); 1654 1655 String method = getMethod().toLowerCase(Locale.ROOT); 1656 tag.put("method", method); 1657 String url = getActionUrl().toString(); 1658 if (encodeUrlInHiddenFields()) 1659 { 1660 int i = url.indexOf('?'); 1661 String action = (i > -1) ? url.substring(0, i) : ""; 1662 tag.put("action", action); 1663 // alternatively, we could just put an empty string here, so 1664 // that mounted paths stay in good order. I decided against this 1665 // as I'm not sure whether that could have side effects with 1666 // other encoders 1667 } 1668 else 1669 { 1670 tag.put("action", url); 1671 } 1672 1673 if (isMultiPart()) 1674 { 1675 if (METHOD_GET.equalsIgnoreCase(method)) 1676 { 1677 log.warn("Form with id '{}' is multipart. It should use method 'POST'!", 1678 getId()); 1679 tag.put("method", METHOD_POST.toLowerCase(Locale.ROOT)); 1680 } 1681 1682 tag.put("enctype", ENCTYPE_MULTIPART_FORM_DATA); 1683 // 1684 // require the application-encoding for multipart/form-data to be sure to 1685 // get multipart-uploaded characters with the proper encoding on the following 1686 // request. 1687 // 1688 // for details see: http://stackoverflow.com/questions/546365 1689 // 1690 tag.put("accept-charset", getApplication().getRequestCycleSettings() 1691 .getResponseRequestEncoding()); 1692 } 1693 else 1694 { 1695 // sanity check 1696 String enctype = (String)tag.getAttributes().get("enctype"); 1697 if (ENCTYPE_MULTIPART_FORM_DATA.equalsIgnoreCase(enctype)) 1698 { 1699 // though not set explicitly in Java, this is a multipart 1700 // form 1701 setMultiPart(true); 1702 } 1703 } 1704 } 1705 else 1706 { 1707 adjustNestedTagName(tag); 1708 tag.remove("method"); 1709 tag.remove("action"); 1710 tag.remove("enctype"); 1711 } 1712 } 1713 1714 // WICKET-6658 form is not allowed, anything else can stay as is 1715 private void adjustNestedTagName(ComponentTag tag) { 1716 if ("form".equalsIgnoreCase(tag.getName())) 1717 { 1718 tag.setName("div"); 1719 } 1720 } 1721 1722 /** 1723 * Generates the action url for the form 1724 * 1725 * @return action url 1726 */ 1727 protected CharSequence getActionUrl() 1728 { 1729 return urlForListener(new PageParameters()); 1730 } 1731 1732 /** 1733 * @see org.apache.wicket.Component#renderPlaceholderTag(org.apache.wicket.markup.ComponentTag, 1734 * org.apache.wicket.request.Response) 1735 */ 1736 @Override 1737 protected void renderPlaceholderTag(ComponentTag tag, Response response) 1738 { 1739 if (!isRootForm()) 1740 { 1741 // WICKET-2166 1742 adjustNestedTagName(tag); 1743 } 1744 1745 super.renderPlaceholderTag(tag, response); 1746 } 1747 1748 /** 1749 * Should URL query parameters be encoded in hidden fields, by default <code>true</code> 1750 * for {@link #METHOD_GET} only. 1751 * <p> 1752 * In that case, the parameters must <em>not</em> be written as query parameters, as the browser 1753 * would strip them from the action url before appending the form values. 1754 * 1755 * @return true if form's method is 'get' 1756 * 1757 * @see #getMethod() 1758 */ 1759 protected boolean encodeUrlInHiddenFields() 1760 { 1761 return METHOD_GET.equalsIgnoreCase(getMethod()); 1762 } 1763 1764 /** 1765 * Append an additional hidden input tag to support anchor tags that can submit a form. 1766 * 1767 * @param markupStream 1768 * The markup stream 1769 * @param openTag 1770 * The open tag for the body 1771 */ 1772 @Override 1773 public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) 1774 { 1775 if (isRootForm()) 1776 { 1777 // get the hidden field id 1778 writeHiddenFields(); 1779 } 1780 1781 // do the rest of the processing 1782 super.onComponentTagBody(markupStream, openTag); 1783 } 1784 1785 @Override 1786 public void renderHead(IHeaderResponse response) 1787 { 1788 super.renderHead(response); 1789 1790 if (hasDefaultSubmittingComponent()) 1791 { 1792 addDefaultSubmitButtonHandler(response); 1793 } 1794 } 1795 1796 /** 1797 * Writes the markup for the hidden input fields and default button field if applicable to the 1798 * current response. 1799 */ 1800 public final void writeHiddenFields() 1801 { 1802 getResponse().write(String.format("<div id=\"%s\" hidden=\"\" class=\"%s\">", 1803 getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX), 1804 getString(HIDDEN_FIELDS_CSS_CLASS_KEY))); 1805 // if the parameters are not in the action attribute, they have to be written as hidden fields 1806 if (encodeUrlInHiddenFields()) 1807 { 1808 AppendingStringBuffer buffer = new AppendingStringBuffer(); 1809 1810 String url = getActionUrl().toString(); 1811 int i = url.indexOf('?'); 1812 String queryString = (i > -1) ? url.substring(i + 1) : url; 1813 String[] params = Strings.split(queryString, '&'); 1814 1815 writeParamsAsHiddenFields(params, buffer); 1816 1817 getResponse().write(buffer); 1818 } 1819 getResponse().write("</div>"); 1820 1821 // if a default submitting component was set, handle the rendering of that 1822 if (hasDefaultSubmittingComponent()) 1823 { 1824 appendDefaultButtonField(); 1825 } 1826 } 1827 1828 private boolean hasDefaultSubmittingComponent() 1829 { 1830 if (defaultSubmittingComponent instanceof Component) 1831 { 1832 final Component submittingComponent = (Component) defaultSubmittingComponent; 1833 return submittingComponent.isVisibleInHierarchy() 1834 && submittingComponent.isEnabledInHierarchy(); 1835 } 1836 return false; 1837 } 1838 1839 /** 1840 * 1841 * @param params 1842 * @param buffer 1843 */ 1844 protected void writeParamsAsHiddenFields(String[] params, AppendingStringBuffer buffer) 1845 { 1846 for (String param : params) 1847 { 1848 String[] pair = Strings.split(param, '='); 1849 1850 buffer.append("<input type=\"hidden\" name=\"") 1851 .append(recode(pair[0])) 1852 .append("\" value=\"") 1853 .append(pair.length > 1 ? recode(pair[1]) : "") 1854 .append("\" />"); 1855 } 1856 } 1857 1858 /** 1859 * Take URL-encoded query string value, decode it and return HTML-escaped version 1860 * 1861 * @param s 1862 * value to decode 1863 * @return URL decoded and HTML escaped value 1864 */ 1865 private String recode(String s) 1866 { 1867 String un = UrlDecoder.QUERY_INSTANCE.decode(s, getRequest().getCharset()); 1868 return Strings.escapeMarkup(un).toString(); 1869 } 1870 1871 /** 1872 * @see org.apache.wicket.Component#onDetach() 1873 */ 1874 @Override 1875 protected void onDetach() 1876 { 1877 setFlag(FLAG_SUBMITTED, false); 1878 1879 super.onDetach(); 1880 } 1881 1882 /** 1883 * Method to override if you want to do something special when an error occurs (other than 1884 * simply displaying validation errors). 1885 */ 1886 protected void onError() 1887 { 1888 } 1889 1890 @Override 1891 public void onEvent(IEvent<?> event) { 1892 if (event.getPayload() instanceof AjaxRequestTarget) { 1893 // WICKET-6171 clear multipart hint, it might change during Ajax requests without this form being rendered 1894 this.multiPart &= MULTIPART_HARD; 1895 } 1896 } 1897 1898 @Override 1899 protected void onBeforeRender() 1900 { 1901 // clear multipart hint, it will be reevaluated by #isMultiPart() 1902 this.multiPart &= MULTIPART_HARD; 1903 1904 super.onBeforeRender(); 1905 } 1906 1907 /** 1908 * Implemented by subclasses to deal with form submits. 1909 */ 1910 protected void onSubmit() 1911 { 1912 } 1913 1914 /** 1915 * Update the model of all components on this form and nested forms using the fields that were 1916 * sent with the current request. This method only updates models when the Form.validate() is 1917 * called first that takes care of the conversion for the FormComponents. 1918 * 1919 * Normally this method will not be called when a validation error occurs in one of the form 1920 * components. 1921 * 1922 * @see org.apache.wicket.markup.html.form.FormComponent#updateModel() 1923 */ 1924 protected final void updateFormComponentModels() 1925 { 1926 internalUpdateFormComponentModels(); 1927 updateNestedFormComponentModels(); 1928 } 1929 1930 /** 1931 * Update the model of all components on nested forms. 1932 * 1933 * @see #updateFormComponentModels() 1934 */ 1935 private void updateNestedFormComponentModels() 1936 { 1937 visitChildren(Form.class, new IVisitor<Form<?>, Void>() 1938 { 1939 @Override 1940 public void component(final Form<?> form, final IVisit<Void> visit) 1941 { 1942 if (form.isSubmitted()) 1943 { 1944 form.internalUpdateFormComponentModels(); 1945 } 1946 else 1947 { 1948 visit.dontGoDeeper(); 1949 } 1950 } 1951 }); 1952 } 1953 1954 /** 1955 * Update the model of all components on this form. 1956 * 1957 * @see #updateFormComponentModels() 1958 */ 1959 private void internalUpdateFormComponentModels() 1960 { 1961 FormComponent.visitComponentsPostOrder(this, new FormModelUpdateVisitor(this)); 1962 } 1963 1964 /** 1965 * Validates the form by checking required fields, converting raw input and running validators 1966 * for every form component, and last running global form validators. This method is typically 1967 * called before updating any models. 1968 * <p> 1969 * NOTE: in most cases, custom validations on the form can be achieved using an IFormValidator 1970 * that can be added using addValidator(). 1971 * </p> 1972 */ 1973 protected final void validate() 1974 { 1975 // since this method can be called directly by users, this additional check is needed 1976 if (isEnabledInHierarchy() && isVisibleInHierarchy()) 1977 { 1978 validateNestedForms(); 1979 validateComponents(); 1980 validateFormValidators(); 1981 onValidate(); 1982 } 1983 } 1984 1985 /** 1986 * Callback during the validation stage of the form 1987 */ 1988 protected void onValidate() 1989 { 1990 1991 } 1992 1993 /** 1994 * Calls {@linkplain #onValidateModelObjects()} on this form and all nested forms that are 1995 * visible and enabled 1996 */ 1997 private void internalOnValidateModelObjects() 1998 { 1999 onValidateModelObjects(); 2000 visitChildren(Form.class, new IVisitor<Form<?>, Void>() 2001 { 2002 @Override 2003 public void component(Form<?> form, IVisit<Void> visit) 2004 { 2005 if (form.isSubmitted()) 2006 { 2007 form.onValidateModelObjects(); 2008 } 2009 else 2010 { 2011 visit.dontGoDeeper(); 2012 } 2013 } 2014 }); 2015 } 2016 2017 /** 2018 * Called after form components have updated their models. This is a late-stage validation that 2019 * allows outside frameworks to validate any beans that the form is updating. 2020 * 2021 * This validation method is not preferred because at this point any errors will not unroll any 2022 * changes to the model object, so the model object is in a modified state potentially 2023 * containing illegal values. However, with external frameworks there may not be an alternate 2024 * way to validate the model object. A good example of this is a JSR303 Bean Validator 2025 * validating the model object to check any class-level constraints, in order to check such 2026 * constraints the model object must contain the values set by the user. 2027 */ 2028 protected void onValidateModelObjects() 2029 { 2030 } 2031 2032 /** 2033 * Triggers type conversion on form components 2034 */ 2035 protected final void validateComponents() 2036 { 2037 visitFormComponentsPostOrder(new ValidationVisitor() 2038 { 2039 @Override 2040 public void validate(final FormComponent<?> formComponent) 2041 { 2042 final Form<?> form = formComponent.getForm(); 2043 if (form == Form.this && form.isEnabledInHierarchy() && form.isVisibleInHierarchy()) 2044 { 2045 formComponent.validate(); 2046 } 2047 } 2048 }); 2049 } 2050 2051 /** 2052 * Checks if the specified form component visible and is attached to a page 2053 * 2054 * @param fc 2055 * form component 2056 * 2057 * @return true if the form component and all its parents are visible and there component is in 2058 * page's hierarchy 2059 */ 2060 private boolean isFormComponentVisibleInPage(FormComponent<?> fc) 2061 { 2062 if (fc == null) 2063 { 2064 throw new IllegalArgumentException("Argument `fc` cannot be null"); 2065 } 2066 return fc.isVisibleInHierarchy(); 2067 } 2068 2069 2070 /** 2071 * Validates form with the given form validator 2072 * 2073 * @param validator 2074 */ 2075 protected final void validateFormValidator(final IFormValidator validator) 2076 { 2077 Args.notNull(validator, "validator"); 2078 2079 final FormComponent<?>[] dependents = validator.getDependentFormComponents(); 2080 2081 boolean validate = true; 2082 2083 if (dependents != null) 2084 { 2085 for (final FormComponent<?> dependent : dependents) 2086 { 2087 // check if the dependent component is valid 2088 if (!dependent.isValid()) 2089 { 2090 validate = false; 2091 break; 2092 } 2093 // check if the dependent component is visible and is attached to 2094 // the page 2095 else if (!isFormComponentVisibleInPage(dependent)) 2096 { 2097 if (log.isWarnEnabled()) 2098 { 2099 log.warn("IFormValidator in form `" + 2100 getPageRelativePath() + 2101 "` depends on a component that has been removed from the page or is no longer visible. " + 2102 "Offending component id `" + dependent.getId() + "`."); 2103 } 2104 validate = false; 2105 break; 2106 } 2107 } 2108 } 2109 2110 if (validate) 2111 { 2112 validator.validate(this); 2113 } 2114 } 2115 2116 /** 2117 * Triggers any added {@link IFormValidator}s. 2118 */ 2119 protected final void validateFormValidators() 2120 { 2121 for (Behavior behavior : getBehaviors()) 2122 { 2123 if (behavior instanceof IFormValidator) 2124 { 2125 validateFormValidator((IFormValidator)behavior); 2126 } 2127 } 2128 } 2129 2130 /** 2131 * Validates {@link FormComponent}s as well as {@link IFormValidator}s in nested {@link Form}s. 2132 * 2133 * @see #validate() 2134 */ 2135 private void validateNestedForms() 2136 { 2137 Visits.visitPostOrder(this, new IVisitor<Form<?>, Void>() 2138 { 2139 @Override 2140 public void component(final Form<?> form, final IVisit<Void> visit) 2141 { 2142 if (form == Form.this) 2143 { 2144 // skip self, only process children 2145 visit.stop(); 2146 return; 2147 } 2148 2149 if (form.isSubmitted()) 2150 { 2151 form.validateComponents(); 2152 form.validateFormValidators(); 2153 form.onValidate(); 2154 } 2155 } 2156 }, new ClassVisitFilter(Form.class)); 2157 } 2158 2159 /** 2160 * Allows to customize input names of form components inside this form. 2161 * 2162 * @return String that well be used as prefix to form component input names 2163 */ 2164 protected String getInputNamePrefix() 2165 { 2166 return ""; 2167 } 2168 2169 /** 2170 * @param component 2171 * @return The parent form for component 2172 */ 2173 public static Form<?> findForm(Component component) 2174 { 2175 return component.findParent(Form.class); 2176 } 2177 2178 /** 2179 * Utility method to assemble an id to distinct form components from different nesting levels. 2180 * Useful to generate input names attributes. 2181 * 2182 * @param component 2183 * @return form relative identification string 2184 */ 2185 public static String getRootFormRelativeId(Component component) 2186 { 2187 String id = component.getId(); 2188 final PrependingStringBuffer inputName = new PrependingStringBuffer(id.length()); 2189 Component c = component; 2190 while (true) 2191 { 2192 inputName.prepend(id); 2193 c = c.getParent(); 2194 if (c == null || (c instanceof Form<?> && ((Form<?>)c).isRootForm()) || 2195 c instanceof Page) 2196 { 2197 break; 2198 } 2199 inputName.prepend(Component.PATH_SEPARATOR); 2200 id = c.getId(); 2201 } 2202 2203 /* 2204 * Certain input names causes problems with JavaScript. If the input name would cause a 2205 * problem, we create a replacement unique name by prefixing the name with a path that would 2206 * otherwise never be used (blank id in path). 2207 * 2208 * Input names must start with [A-Za-z] according to HTML 4.01 spec. HTML 5 allows almost 2209 * anything. 2210 */ 2211 if (JavaScriptReservedNames.isNameReserved(inputName.toString())) 2212 { 2213 inputName.prepend(Component.PATH_SEPARATOR); 2214 inputName.prepend(Component.PATH_SEPARATOR); 2215 inputName.prepend("p"); 2216 } 2217 return inputName.toString(); 2218 } 2219 2220 /** 2221 * Get the request parameters for a form submit, 2222 * according to the request's method or the form's method as fallback. 2223 * 2224 * @param component any component inside the form or the form itself 2225 * @return parameters 2226 */ 2227 static IRequestParameters getRequestParameters(Component component) { 2228 String method = Form.METHOD_POST; 2229 final Request request = component.getRequest(); 2230 if (request.getContainerRequest() instanceof HttpServletRequest) 2231 { 2232 method = ((HttpServletRequest)request.getContainerRequest()).getMethod(); 2233 } 2234 else 2235 { 2236 final Form<?> form; 2237 if (component instanceof Form) { 2238 form = (Form<?>)component; 2239 } else { 2240 form = component.findParent(Form.class); 2241 } 2242 2243 if (form != null) 2244 { 2245 method = form.getMethod(); 2246 } 2247 } 2248 2249 final IRequestParameters parameters; 2250 switch (method.toLowerCase(Locale.ROOT)) 2251 { 2252 case Form.METHOD_POST: 2253 parameters = request.getPostParameters(); 2254 break; 2255 case Form.METHOD_GET: 2256 parameters = request.getQueryParameters(); 2257 break; 2258 default: 2259 parameters = EmptyRequestParameters.INSTANCE; 2260 } 2261 2262 return parameters; 2263 } 2264 2265 /** 2266 * Response when a submission method mismatch is detected 2267 * 2268 * @see Form#getMethod() 2269 * 2270 * @author igor 2271 */ 2272 public static enum MethodMismatchResponse { 2273 /** 2274 * Continue processing. 2275 */ 2276 CONTINUE, 2277 2278 /** 2279 * Abort processing. 2280 */ 2281 ABORT 2282 } 2283}