| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.wicket.markup.html.form; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import jakarta.servlet.http.HttpServletRequest; |
| |
| import org.apache.commons.fileupload.FileUploadBase; |
| import org.apache.commons.fileupload.FileUploadException; |
| import org.apache.wicket.Component; |
| import org.apache.wicket.IGenericComponent; |
| import org.apache.wicket.IRequestListener; |
| import org.apache.wicket.Page; |
| import org.apache.wicket.WicketRuntimeException; |
| import org.apache.wicket.ajax.AjaxRequestTarget; |
| import org.apache.wicket.behavior.Behavior; |
| import org.apache.wicket.core.util.string.CssUtils; |
| import org.apache.wicket.event.IEvent; |
| import org.apache.wicket.markup.ComponentTag; |
| import org.apache.wicket.markup.MarkupStream; |
| import org.apache.wicket.markup.head.IHeaderResponse; |
| import org.apache.wicket.markup.head.OnEventHeaderItem; |
| import org.apache.wicket.markup.html.WebMarkupContainer; |
| import org.apache.wicket.markup.html.form.upload.FileUploadField; |
| import org.apache.wicket.markup.html.form.validation.FormValidatorAdapter; |
| import org.apache.wicket.markup.html.form.validation.IFormValidator; |
| import org.apache.wicket.model.IModel; |
| import org.apache.wicket.model.Model; |
| import org.apache.wicket.protocol.http.servlet.MultipartServletWebRequest; |
| import org.apache.wicket.protocol.http.servlet.ServletWebRequest; |
| import org.apache.wicket.request.IRequestParameters; |
| import org.apache.wicket.request.Request; |
| import org.apache.wicket.request.Response; |
| import org.apache.wicket.request.mapper.parameter.PageParameters; |
| import org.apache.wicket.request.parameter.EmptyRequestParameters; |
| import org.apache.wicket.util.encoding.UrlDecoder; |
| import org.apache.wicket.util.lang.Args; |
| import org.apache.wicket.util.lang.Bytes; |
| import org.apache.wicket.util.lang.Generics; |
| import org.apache.wicket.util.string.AppendingStringBuffer; |
| import org.apache.wicket.util.string.PrependingStringBuffer; |
| import org.apache.wicket.util.string.Strings; |
| import org.apache.wicket.util.string.interpolator.MapVariableInterpolator; |
| import org.apache.wicket.util.value.LongValue; |
| import org.apache.wicket.util.visit.ClassVisitFilter; |
| import org.apache.wicket.util.visit.IVisit; |
| import org.apache.wicket.util.visit.IVisitor; |
| import org.apache.wicket.util.visit.Visits; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * Container for {@link FormComponent}s (such as {@link CheckBox}es, {@link ListChoice}s or |
| * {@link TextField}s). Subclass this class to receive submit notifications through |
| * {@link #onSubmit()} or nest multiple {@link IFormSubmittingComponent}s if you want to vary submit |
| * behavior. In the former case it is not necessary to use any of Wicket's classes (such as |
| * {@link Button} or {@link SubmitLink}), just putting e.g. <input type="submit" value="go"/> |
| * suffices. |
| * <p> |
| * As a {@link IRequestListener} the form gets notified of listener requests in |
| * {@link #onRequest()}. By default, the processing of this submit works like this: |
| * <ul> |
| * <li>All nested {@link FormComponent}s are notified of new input via |
| * {@link FormComponent#inputChanged()}</li> |
| * <li>The form submitter is looked up, e.g. a {@link Button} is contained in the component |
| * hierarchy of this form and was clicked by the user: |
| * <ul> |
| * <li>If an {@link IFormSubmitter} was found which |
| * {@link IFormSubmitter#getDefaultFormProcessing()} returns {@code false} (default is {@code true} |
| * ), it's {@link IFormSubmitter#onSubmit()} method will be called right away, thus all further |
| * processing is skipped. This has the same effect as nesting a normal link in the form. <br> |
| * If needed the form submitter can continue processing however, by calling {@link #validate()} to |
| * execute form validation, {@link #hasError()} to find out whether validate() resulted in |
| * validation errors, and {@link #updateFormComponentModels()} to update the models of nested form |
| * components.</li> |
| * <li>Otherwise this form is further processed via {@link #process(IFormSubmitter)}, resulting in |
| * all nested components being validated via {@link FormComponent#validate()}. <br> |
| * <ul> |
| * <li>If form validation failed, all nested form components will be marked invalid, and |
| * {@link #onError()} is called to allow clients to provide custom error handling code.</li> |
| * <li>Otherwise the nested components will be asked to update their models via |
| * {@link FormComponent#updateModel()}. After that submit notification is delegated to the |
| * {@link IFormSubmitter#onSubmit()} (if just found) before calling {@link #onSubmit()} on this |
| * form. Subclasses may override {@link #delegateSubmit(IFormSubmitter)} if they want a different |
| * behavior.</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * </li> |
| * </ul> |
| * |
| * A Form can be configured for handling uploads with multipart requests (e.g. files) by calling |
| * {@link #setMultiPart(boolean)} (although Wicket will try to automatically detect this for you). |
| * Use this with {@link FileUploadField} components. You can attach multiple {@link FileUploadField} |
| * components for multiple file uploads. |
| * <p> |
| * In case of an upload error two resource keys are available to specify error messages: |
| * {@code uploadTooLarge} and {@code uploadFailed}, i.e. for a form with id {@code myform} in |
| * {@code MyPage.properties}: |
| * |
| * <pre> |
| * myform.uploadTooLarge=You have uploaded a file that is over the allowed limit of 2Mb |
| * </pre> |
| * |
| * Forms can be nested. You can put a form in another form. Since HTML doesn't allow nested |
| * <form> tags, the inner forms will be rendered using the <div> tag. You have to submit |
| * the inner forms using explicit components (like {@link Button} or {@link SubmitLink}), you can't |
| * rely on implicit submit behavior (by using just <input type="submit"> that is not attached |
| * to a component). |
| * <p> |
| * When a nested form is submitted, the user entered values in outer (parent) forms are preserved |
| * and only the fields in the submitted form are validated. </b> |
| * |
| * @author Jonathan Locke |
| * @author Juergen Donnerstag |
| * @author Eelco Hillenius |
| * @author Cameron Braid |
| * @author Johan Compagner |
| * @author Igor Vaynberg (ivaynberg) |
| * @author David Leangen |
| * |
| * @param <T> |
| * The model object type |
| */ |
| public class Form<T> extends WebMarkupContainer |
| implements |
| IRequestListener, |
| IGenericComponent<T, Form<T>> |
| { |
| public static final String ENCTYPE_MULTIPART_FORM_DATA = "multipart/form-data"; |
| |
| public static final String HIDDEN_FIELDS_CSS_CLASS_KEY = CssUtils |
| .key(Form.class, "hidden-fields"); |
| |
| /** |
| * Visitor used for validation |
| * |
| * @author Igor Vaynberg (ivaynberg) |
| */ |
| public abstract static class ValidationVisitor implements IVisitor<FormComponent<?>, Void> |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, final IVisit<Void> visit) |
| { |
| |
| Form<?> form = formComponent.getForm(); |
| if (!form.isVisibleInHierarchy() || !form.isEnabledInHierarchy()) |
| { |
| // do not validate formComponent or any of formComponent's children |
| visit.dontGoDeeper(); |
| return; |
| } |
| |
| if (formComponent.isVisibleInHierarchy() && formComponent.isEnabledInHierarchy()) |
| { |
| validate(formComponent); |
| } |
| if (formComponent.processChildren() == false) |
| { |
| visit.dontGoDeeper(); |
| } |
| } |
| |
| /** |
| * Callback that should be used to validate form component |
| * |
| * @param formComponent |
| */ |
| public abstract void validate(FormComponent<?> formComponent); |
| } |
| |
| |
| /** |
| * Visitor used to update component models |
| * |
| * @author Igor Vaynberg (ivaynberg) |
| */ |
| private static class FormModelUpdateVisitor implements IVisitor<Component, Void> |
| { |
| private final Form<?> formFilter; |
| |
| /** |
| * Constructor |
| * |
| * @param formFilter |
| */ |
| public FormModelUpdateVisitor(Form<?> formFilter) |
| { |
| this.formFilter = formFilter; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void component(final Component component, final IVisit<Void> visit) |
| { |
| if (component instanceof IFormModelUpdateListener) |
| { |
| final Form<?> form = Form.findForm(component); |
| if (form != null) |
| { |
| if (this.formFilter == null || this.formFilter == form) |
| { |
| if (form.isEnabledInHierarchy()) |
| { |
| if (component.isVisibleInHierarchy() && |
| component.isEnabledInHierarchy()) |
| { |
| ((IFormModelUpdateListener)component).updateModel(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Constant for specifying how a form is submitted, in this case using get. |
| */ |
| public static final String METHOD_GET = "get"; |
| |
| /** |
| * Constant for specifying how a form is submitted, in this case using post. |
| */ |
| public static final String METHOD_POST = "post"; |
| |
| /** Flag that indicates this form has been submitted during this request */ |
| private static final short FLAG_SUBMITTED = FLAG_RESERVED1; |
| |
| /** Log. */ |
| private static final Logger log = LoggerFactory.getLogger(Form.class); |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed"; |
| |
| private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge"; |
| private static final String UPLOAD_SINGLE_FILE_TOO_LARGE_RESOURCE_KEY = "uploadSingleFileTooLarge"; |
| |
| /** |
| * Any default IFormSubmittingComponent. If set, a hidden submit component will be rendered |
| * right after the form tag, so that when users press enter in a textfield, this submit |
| * component's action will be selected. If no default IFormSubmittingComponent is set, nothing |
| * additional is rendered. |
| * <p> |
| * WARNING: note that this is a best effort only. Unfortunately having a 'default' |
| * IFormSubmittingComponent in a form is ill defined in the standards, and of course IE has it's |
| * own way of doing things. |
| * </p> |
| */ |
| private IFormSubmittingComponent defaultSubmittingComponent; |
| |
| /** |
| * Maximum size of an upload in bytes. If null, the setting |
| * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. |
| */ |
| private Bytes maxSize = null; |
| |
| /** |
| * Maximum size of file of upload in bytes (if there are more than one) in request. |
| */ |
| private Bytes fileMaxSize; |
| |
| /** True if the form has enctype of multipart/form-data */ |
| private short multiPart = 0; |
| |
| /** |
| * A user has explicitly called {@link #setMultiPart(boolean)} with value {@code true} forcing |
| * it to be true |
| */ |
| private static final short MULTIPART_HARD = 0x01; |
| |
| /** |
| * The form has discovered a multipart component before rendering and is marking itself as |
| * multipart until next render |
| */ |
| private static final short MULTIPART_HINT_YES = 0x02; |
| |
| /** |
| * The form has discovered no multipart component before rendering and is marking itself as |
| * not multipart until next render |
| */ |
| private static final short MULTIPART_HINT_NO = 0x04; |
| |
| /** |
| * The index of the hidden fields used to pass parameters. |
| */ |
| private static final int HIDDEN_FIELDS_PARAMS_IDX = 0; |
| |
| /** |
| * The index of the hidden fields used for the default submit button. |
| */ |
| private static final int HIDDEN_FIELDS_SUBMIT_IDX = 1; |
| |
| /** |
| * Constructs a form with no validation. |
| * |
| * @param id |
| * See Component |
| */ |
| public Form(final String id) |
| { |
| this(id, null); |
| } |
| |
| /** |
| * @param id |
| * See Component |
| * @param model |
| * See Component |
| * @see org.apache.wicket.Component#Component(String, IModel) |
| */ |
| public Form(final String id, final IModel<T> model) |
| { |
| super(id, model); |
| setOutputMarkupId(true); |
| } |
| |
| /** |
| * Adds a form validator to the form. |
| * |
| * @param validator |
| * validator |
| * @throws IllegalArgumentException |
| * if validator is null |
| * @see IFormValidator |
| */ |
| public void add(final IFormValidator validator) |
| { |
| Args.notNull(validator, "validator"); |
| |
| if (validator instanceof Behavior) |
| { |
| add((Behavior)validator); |
| } |
| else |
| { |
| add(new FormValidatorAdapter(validator)); |
| } |
| } |
| |
| /** |
| * Removes a form validator from the form. |
| * |
| * @param validator |
| * validator |
| * @throws IllegalArgumentException |
| * if validator is null |
| * @see IFormValidator |
| */ |
| public void remove(final IFormValidator validator) |
| { |
| Args.notNull(validator, "validator"); |
| |
| Behavior match = null; |
| for (Behavior behavior : getBehaviors()) |
| { |
| if (behavior.equals(validator)) |
| { |
| match = behavior; |
| break; |
| } |
| else if (behavior instanceof FormValidatorAdapter) |
| { |
| if (((FormValidatorAdapter)behavior).getValidator().equals(validator)) |
| { |
| match = behavior; |
| break; |
| } |
| } |
| } |
| |
| if (match != null) |
| { |
| remove(match); |
| } |
| else |
| { |
| |
| throw new IllegalStateException( |
| "Tried to remove form validator that was not previously added. " |
| + "Make sure your validator's equals() implementation is sufficient"); |
| } |
| } |
| |
| /** |
| * Clears the input from the form's nested children of type {@link FormComponent}. This method |
| * is typically called when a form needs to be reset. |
| */ |
| public final void clearInput() |
| { |
| // Visit all the (visible) form components and clear the input on each. |
| visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, IVisit<Void> visit) |
| { |
| if (formComponent.isVisibleInHierarchy()) |
| { |
| // Clear input from form component |
| formComponent.clearInput(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Registers an error feedback message for this component |
| * |
| * @param error |
| * error message |
| * @param args |
| * argument replacement map for ${key} variables |
| */ |
| public final void error(String error, Map<String, Object> args) |
| { |
| error(new MapVariableInterpolator(error, args).toString()); |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! |
| * <p> |
| * Gets the IFormSubmittingComponent which submitted this form. |
| * |
| * @return The component which submitted this form, or null if the processing was not triggered |
| * by a registered IFormSubmittingComponent |
| */ |
| public final IFormSubmittingComponent findSubmitter() |
| { |
| final IRequestParameters parameters = getRequestParameters(this); |
| |
| IFormSubmittingComponent submittingComponent = getPage().visitChildren( |
| IFormSubmittingComponent.class, new IVisitor<Component, IFormSubmittingComponent>() |
| { |
| @Override |
| public void component(final Component component, |
| final IVisit<IFormSubmittingComponent> visit) |
| { |
| // Get submitting component |
| final IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)component; |
| final Form<?> form = submittingComponent.getForm(); |
| |
| // Check for component-name or component-name.x request string |
| if ((form != null) && (form.getRootForm() == Form.this)) |
| { |
| String name = submittingComponent.getInputName(); |
| if ((!parameters.getParameterValue(name).isNull()) || |
| !parameters.getParameterValue(name + ".x").isNull()) |
| { |
| visit.stop(submittingComponent); |
| } |
| } |
| } |
| }); |
| |
| return submittingComponent; |
| } |
| |
| /** |
| * Gets the default IFormSubmittingComponent. If set (not null), a hidden submit component will |
| * be rendered right after the form tag, so that when users press enter in a textfield, this |
| * submit component's action will be selected. If no default component is set (it is null), |
| * nothing additional is rendered. |
| * <p> |
| * WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a |
| * form is ill defined in the standards, and of course IE has it's own way of doing things. |
| * </p> |
| * There can be only one default submit component per form hierarchy. So if you want to get the |
| * default component on a nested form, it will actually delegate the call to root form. </b> |
| * |
| * @return The submit component to set as the default IFormSubmittingComponent, or null when you |
| * want to 'unset' any previously set default IFormSubmittingComponent |
| */ |
| public final IFormSubmittingComponent getDefaultButton() |
| { |
| if (isRootForm()) |
| { |
| return defaultSubmittingComponent; |
| } |
| else |
| { |
| return getRootForm().getDefaultButton(); |
| } |
| } |
| |
| /** |
| * Gets all {@link IFormValidator}s added to this form |
| * |
| * @return unmodifiable collection of {@link IFormValidator}s |
| */ |
| public final Collection<IFormValidator> getFormValidators() |
| { |
| List<IFormValidator> validators = new ArrayList<>(); |
| |
| for (Behavior behavior : getBehaviors()) |
| { |
| if (behavior instanceof IFormValidator) |
| { |
| validators.add((IFormValidator)behavior); |
| } |
| } |
| |
| return Collections.unmodifiableCollection(validators); |
| } |
| |
| /** |
| * Generate a piece of JavaScript that submits the form to the given URL of an {@link IRequestListener}. |
| * |
| * Warning: This code should only be called in the rendering phase for form components inside |
| * the form because it uses the css/javascript id of the form which can be stored in the markup. |
| * |
| * @param url |
| * The listener url to be submitted to |
| * @return the javascript code that submits the form. |
| */ |
| public final CharSequence getJsForListenerUrl(CharSequence url) |
| { |
| Form<?> root = getRootForm(); |
| |
| AppendingStringBuffer buffer = new AppendingStringBuffer(); |
| |
| String action = url.toString(); |
| if (root.encodeUrlInHiddenFields()) { |
| buffer.append(String.format("document.getElementById('%s').innerHTML = '", |
| root.getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX))); |
| |
| // parameter must be sent as hidden field, as it would be ignored in the action URL |
| int i = action.indexOf('?'); |
| if (i != -1) |
| { |
| writeParamsAsHiddenFields(Strings.split(action.substring(i + 1), '&'), buffer); |
| |
| action = action.substring(0, i); |
| } |
| |
| buffer.append("';"); |
| } |
| buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId())); |
| buffer.append(String.format("f.action='%s';", action)); |
| buffer.append("Wicket.Event.fire(f, 'submit');"); |
| return buffer; |
| } |
| |
| /** |
| * Generate a piece of JavaScript that submits the form with the given |
| * {@link IFormSubmittingComponent}. |
| * |
| * @param submitter |
| * the submitter |
| * @param triggerEvent |
| * When true, the form will be submited via a javascript submit event, when false via |
| * the {@code submit()} method. |
| * @return the javascript code that submits the form. |
| * |
| * @see #findSubmitter() |
| */ |
| public final CharSequence getJsForSubmitter(IFormSubmittingComponent submitter, boolean triggerEvent) |
| { |
| Form<?> root = getRootForm(); |
| |
| String param = submitter.getInputName() + "=x"; |
| |
| AppendingStringBuffer buffer = new AppendingStringBuffer(); |
| buffer.append(String.format("var f = document.getElementById('%s');", root.getMarkupId())); |
| buffer.append(String.format("document.getElementById('%s').innerHTML += '", |
| root.getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX))); |
| writeParamsAsHiddenFields(new String[] {param}, buffer); |
| buffer.append("';"); |
| |
| if (triggerEvent) |
| { |
| buffer.append("Wicket.Event.fire(f, 'submit');"); |
| } |
| else |
| { |
| buffer.append("f.submit();"); |
| } |
| return buffer; |
| } |
| |
| /** |
| * Gets the maximum size for uploads. If null, the setting |
| * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. |
| * |
| * |
| * @return the maximum size |
| */ |
| public final Bytes getMaxSize() |
| { |
| /* |
| * NOTE: This method should remain final otherwise it will be impossible to set a default |
| * max size smaller then the one specified in applications settings because the inner form |
| * will return the default unless it is specifically set in the traversal. With this method |
| * remaining final we can tell when the value is explicitly set by the user. |
| * |
| * If the value needs to be dynamic it can be set in oncofigure() instead of overriding this |
| * method. |
| */ |
| |
| final Bytes[] maxSize = { this.maxSize }; |
| if (maxSize[0] == null) |
| { |
| visitChildren(Form.class, new IVisitor<Form<?>, Bytes>() |
| { |
| @Override |
| public void component(Form<?> component, IVisit<Bytes> visit) |
| { |
| maxSize[0] = LongValue.maxNullSafe(maxSize[0], component.maxSize); |
| } |
| }); |
| } |
| if (maxSize[0] == null) |
| { |
| return getApplication().getApplicationSettings().getDefaultMaximumUploadSize(); |
| } |
| return maxSize[0]; |
| } |
| |
| /** |
| * Gets maximum size for each file of an upload. |
| * |
| * @return |
| */ |
| public Bytes getFileMaxSize() |
| { |
| return fileMaxSize; |
| } |
| |
| /** |
| * Returns the root form or this, if this is the root form. |
| * |
| * @return root form or this form |
| */ |
| public Form<?> getRootForm() |
| { |
| Form<?> form; |
| Form<?> parent = this; |
| do |
| { |
| form = parent; |
| parent = form.findParent(Form.class); |
| } |
| while (parent != null); |
| |
| return form; |
| } |
| |
| /** |
| * Returns the prefix used when building validator keys. This allows a form to use a separate |
| * "set" of keys. For example if prefix "short" is returned, validator key short.Required will |
| * be tried instead of Required key. |
| * <p> |
| * This can be useful when different designs are used for a form. In a form where error messages |
| * are displayed next to their respective form components as opposed to at the top of the form, |
| * the ${label} attribute is of little use and only causes redundant information to appear in |
| * the message. Forms like these can return the "short" (or any other string) validator prefix |
| * and declare key: short.Required=required to override the longer message which is usually |
| * declared like this: Required=${label} is a required field |
| * <p> |
| * Returned prefix will be used for all form components. The prefix can also be overridden on |
| * form component level by overriding {@link FormComponent#getValidatorKeyPrefix()} |
| * |
| * @return prefix prepended to validator keys |
| */ |
| public String getValidatorKeyPrefix() |
| { |
| return null; |
| } |
| |
| /** |
| * Gets whether the current form has any error registered. |
| * |
| * @return True if this form has at least one error. |
| */ |
| public final boolean hasError() |
| { |
| // if this form itself has an error message |
| if (hasErrorMessage()) |
| { |
| return true; |
| } |
| |
| // the form doesn't have any errors, now check any nested form |
| // components |
| return anyFormComponentError(); |
| } |
| |
| /** |
| * Returns whether the form is a root form, which means that there's no other form in it's |
| * parent hierarchy. |
| * |
| * @return true if form is a root form, false otherwise |
| */ |
| public boolean isRootForm() |
| { |
| return findParent(Form.class) == null; |
| } |
| |
| /** |
| * Checks if this form has been submitted during the current request |
| * |
| * @return true if the form has been submitted during this request, false otherwise |
| */ |
| public final boolean isSubmitted() |
| { |
| return getFlag(FLAG_SUBMITTED); |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. |
| * |
| * Handles form submissions. |
| * |
| * @see #onFormSubmitted(IFormSubmitter) |
| */ |
| @Override |
| public final void onRequest() |
| { |
| onFormSubmitted(null); |
| } |
| |
| /** |
| * Called when a form has been submitted using a method differing from return value of |
| * {@link #getMethod()}. For example, someone can copy and paste the action url and invoke the |
| * form using a {@code GET} instead of the desired {@code POST}. This method allows the user to |
| * react to this situation. |
| * |
| * @return response that can either abort or continue the processing of the form |
| */ |
| protected MethodMismatchResponse onMethodMismatch() |
| { |
| return MethodMismatchResponse.CONTINUE; |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. |
| * |
| * Handles form submissions. |
| * |
| * @param submitter |
| * listener that will receive form processing events, if {@code null} the form will |
| * attempt to locate one |
| * |
| * @see Form#validate() |
| */ |
| public final void onFormSubmitted(IFormSubmitter submitter) |
| { |
| // check methods match |
| if (getRequest().getContainerRequest() instanceof HttpServletRequest) |
| { |
| String desiredMethod = getMethod(); |
| String actualMethod = ((HttpServletRequest)getRequest().getContainerRequest()).getMethod(); |
| if (!actualMethod.equalsIgnoreCase(desiredMethod)) |
| { |
| MethodMismatchResponse response = onMethodMismatch(); |
| switch (response) |
| { |
| case ABORT : |
| return; |
| case CONTINUE : |
| break; |
| default : |
| throw new IllegalStateException("Invalid " + |
| MethodMismatchResponse.class.getName() + " value: " + response); |
| } |
| } |
| } |
| |
| markFormsSubmitted(submitter); |
| |
| if (handleMultiPart()) |
| { |
| // Tells FormComponents that a new user input has come |
| inputChanged(); |
| |
| // First, see if the processing was triggered by a IFormSubmittingComponent |
| if (submitter == null) |
| { |
| submitter = findSubmitter(); |
| |
| if (submitter instanceof IFormSubmittingComponent) |
| { |
| IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)submitter; |
| Component component = (Component)submitter; |
| |
| if (!component.isVisibleInHierarchy()) |
| { |
| throw new WicketRuntimeException("Submit Button " + |
| submittingComponent.getInputName() + " (path=" + |
| component.getPageRelativePath() + ") is not visible"); |
| } |
| |
| if (!component.isEnabledInHierarchy()) |
| { |
| throw new WicketRuntimeException("Submit Button " + |
| submittingComponent.getInputName() + " (path=" + |
| component.getPageRelativePath() + ") is not enabled"); |
| } |
| } |
| } |
| |
| // When processing was triggered by a Wicket IFormSubmittingComponent and that |
| // component indicates it wants to be called immediately |
| // (without processing), call the IFormSubmittingComponent.onSubmit* methods right |
| // away. |
| if (submitter != null && !submitter.getDefaultFormProcessing()) |
| { |
| submitter.onSubmit(); |
| submitter.onAfterSubmit(); |
| } |
| else |
| { |
| // the submit request might be for one of the nested forms, so let's |
| // find the right one: |
| final Form<?> formToProcess = findFormToProcess(submitter); |
| |
| // process the form for this request |
| formToProcess.process(submitter); |
| } |
| } |
| // If multi part did fail check if an error is registered and call |
| // onError |
| else if (hasError()) |
| { |
| callOnError(submitter); |
| } |
| |
| // update auto labels if we are inside an ajax request |
| getRequestCycle().find(AjaxRequestTarget.class).ifPresent(target -> { |
| visitChildren(FormComponent.class, new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(FormComponent<?> component, IVisit<Void> visit) |
| { |
| component.updateAutoLabels(target); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * This method finds the correct form that should be processed based on the submitting component |
| * (if there is one) and correctly handles nested forms by also looking at |
| * {@link #wantSubmitOnNestedFormSubmit()} throughout the form hierarchy. The form that needs to |
| * be processed is: |
| * <ul> |
| * <li>if there is no submitting component (i.e. a "default submit"): this form.</li> |
| * <li>if only one form exists (this): this form.</li> |
| * <li>if nested forms exist: |
| * <ul> |
| * <li>if the submitting component points at the root form: the root form</li> |
| * <li>if the submitting component points at a nested form: |
| * <ul> |
| * <li>starting at that nested form, the outermost form that returns true for |
| * {@link #wantSubmitOnNestedFormSubmit()}</li> |
| * <li>if no outer form returns true for that, the nested form is returned.</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * </li> |
| * </ul> |
| * |
| * @param submitter |
| * The submitting component, if any. May be null. |
| * @return The form that needs to be processed. |
| */ |
| private Form<?> findFormToProcess(IFormSubmitter submitter) |
| { |
| if (submitter == null) |
| { |
| // no submitting component => default form submit => so *this* is the |
| // form to process |
| return this; |
| } |
| else |
| { |
| // some button submitted this request, this is the form it belongs to: |
| final Form<?> targetedForm = submitter.getForm(); |
| if (targetedForm == null) |
| { |
| throw new IllegalStateException( |
| "submitting component must not return 'null' on getForm()"); |
| } |
| |
| final Form<?> rootForm = getRootForm(); |
| if (targetedForm == rootForm) |
| { |
| // the submitting component points at the root form => so let's just go with |
| // root, everything else will be submitted with it anyway. |
| return rootForm; |
| } |
| else |
| { |
| // a different form was targeted. let's find the outermost form that wants to be |
| // submitted. |
| Form<?> formThatWantsToBeSubmitted = targetedForm; |
| Form<?> current = targetedForm.findParent(Form.class); |
| while (current != null) |
| { |
| if (current.wantSubmitOnNestedFormSubmit()) |
| { |
| formThatWantsToBeSubmitted = current; |
| } |
| current = current.findParent(Form.class); |
| } |
| return formThatWantsToBeSubmitted; |
| } |
| } |
| } |
| |
| /** |
| * Whether this form wants to be submitted too if a nested form is submitted. By default, this |
| * is false, so when a nested form is submitted, this form will <em>not</em> be submitted. If |
| * this method is overridden to return true, this form <em>will</em> be submitted. |
| * |
| * @return Whether this form wants to be submitted too if a nested form is submitted. |
| */ |
| // TODO wicket-7 migration guide: changed from public to protected |
| protected boolean wantSubmitOnNestedFormSubmit() |
| { |
| return false; |
| } |
| |
| /** |
| * Whether this *nested* form wants to be submitted when parent form is submitted. By default, |
| * this is true, so when a parent form is submitted, the nested form is also submitted. If this |
| * method is overridden to return false, it will not be validated, processed nor submitted. |
| * |
| * @return {@code true} by default |
| */ |
| protected boolean wantSubmitOnParentFormSubmit() |
| { |
| return true; |
| } |
| |
| /** |
| * Process the form. Though you can override this method to provide your own algorithm, it is |
| * not recommended to do so. |
| * |
| * <p> |
| * See the class documentation for further details on the form processing |
| * </p> |
| * |
| * @param submittingComponent |
| * component responsible for submitting the form, or <code>null</code> if none (eg |
| * the form has been submitted via the enter key or javascript calling form.submit()) |
| * |
| * @see #delegateSubmit(IFormSubmitter) for an easy way to process submitting component in the |
| * default manner |
| */ |
| public void process(IFormSubmitter submittingComponent) |
| { |
| if (!isEnabledInHierarchy() || !isVisibleInHierarchy()) |
| { |
| // since process() can be called outside of the default form workflow, an additional |
| // check is needed |
| |
| // FIXME throw listener exception |
| return; |
| } |
| |
| // run validation |
| validate(); |
| |
| // If a validation error occurred |
| if (hasError()) |
| { |
| // mark all children as invalid |
| markFormComponentsInvalid(); |
| |
| // let subclass handle error |
| callOnError(submittingComponent); |
| } |
| else |
| { |
| // mark all children as valid |
| markFormComponentsValid(); |
| |
| // before updating, call the interception method for clients |
| beforeUpdateFormComponentModels(); |
| |
| // Update model using form data |
| updateFormComponentModels(); |
| |
| // validate model objects after input values have been bound |
| internalOnValidateModelObjects(); |
| if (hasError()) |
| { |
| callOnError(submittingComponent); |
| return; |
| } |
| |
| // Form has no error |
| delegateSubmit(submittingComponent); |
| } |
| } |
| |
| /** |
| * Calls onError on this {@link Form} and any enabled and visible nested form, if the respective |
| * {@link Form} actually has errors. |
| * |
| * @param submitter |
| */ |
| protected void callOnError(IFormSubmitter submitter) |
| { |
| final Form<?> processingForm = findFormToProcess(submitter); |
| |
| if (submitter != null) |
| { |
| submitter.onError(); |
| } |
| |
| // invoke Form#onSubmit(..) going from innermost to outermost |
| Visits.visitPostOrder(processingForm, new IVisitor<Form<?>, Void>() |
| { |
| @Override |
| public void component(Form<?> form, IVisit<Void> visit) |
| { |
| if (!form.isEnabledInHierarchy() || !form.isVisibleInHierarchy()) |
| { |
| visit.dontGoDeeper(); |
| return; |
| } |
| if (form.hasError()) |
| { |
| form.onError(); |
| } |
| } |
| }, new ClassVisitFilter(Form.class)); |
| } |
| |
| |
| /** |
| * Sets FLAG_SUBMITTED to true on this form and every enabled nested form. |
| * @param submitter |
| */ |
| private void markFormsSubmitted(IFormSubmitter submitter) |
| { |
| setFlag(FLAG_SUBMITTED, true); |
| Form<?> formToProcess = findFormToProcess(submitter); |
| |
| visitChildren(Form.class, new IVisitor<Component, Void>() |
| { |
| @Override |
| public void component(final Component component, final IVisit<Void> visit) |
| { |
| Form<?> form = (Form<?>)component; |
| if ((form.wantSubmitOnParentFormSubmit() || form == formToProcess) |
| && form.isEnabledInHierarchy() && form.isVisibleInHierarchy()) |
| { |
| form.setFlag(FLAG_SUBMITTED, true); |
| return; |
| } |
| visit.dontGoDeeper(); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the default IFormSubmittingComponent. If set (not null), a hidden submit component will |
| * be rendered right after the form tag, so that when users press enter in a textfield, this |
| * submit component's action will be selected. If no default component is set (so unset by |
| * calling this method with null), nothing additional is rendered. |
| * <p> |
| * WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a |
| * form is ill defined in the standards, and of course IE has it's own way of doing things. |
| * </p> |
| * There can be only one default button per form hierarchy. So if you set default button on a |
| * nested form, it will actually delegate the call to root form. </b> |
| * |
| * @param submittingComponent |
| * The component to set as the default submitting component, or null when you want to |
| * 'unset' any previously set default component |
| */ |
| public final void setDefaultButton(IFormSubmittingComponent submittingComponent) |
| { |
| if (isRootForm()) |
| { |
| defaultSubmittingComponent = submittingComponent; |
| } |
| else |
| { |
| getRootForm().setDefaultButton(submittingComponent); |
| } |
| } |
| |
| /** |
| * Sets the maximum size for uploads. If null, the setting |
| * {@link org.apache.wicket.settings.ApplicationSettings#getDefaultMaximumUploadSize()} is used. |
| * |
| * @param maxSize |
| * The maximum size |
| */ |
| public void setMaxSize(final Bytes maxSize) |
| { |
| this.maxSize = maxSize; |
| } |
| |
| /** |
| * Sets maximum size of each file in upload request. |
| * |
| * @param fileMaxSize |
| */ |
| public void setFileMaxSize(Bytes fileMaxSize) |
| { |
| this.fileMaxSize = fileMaxSize; |
| } |
| |
| /** |
| * Set to true to use enctype='multipart/form-data', and to process file uploads by default |
| * multiPart = false |
| * |
| * @param multiPart |
| * whether this form should behave as a multipart form |
| */ |
| public void setMultiPart(boolean multiPart) |
| { |
| if (multiPart) |
| { |
| this.multiPart |= MULTIPART_HARD; |
| } |
| else |
| { |
| this.multiPart &= ~MULTIPART_HARD; |
| } |
| } |
| |
| /** |
| * @see org.apache.wicket.Component#setVersioned(boolean) |
| */ |
| @Override |
| public final Component setVersioned(final boolean isVersioned) |
| { |
| super.setVersioned(isVersioned); |
| |
| // Search for FormComponents like TextField etc. |
| visitFormComponents(new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, IVisit<Void> visit) |
| { |
| formComponent.setVersioned(isVersioned); |
| } |
| }); |
| return this; |
| } |
| |
| /** |
| * Convenient and typesafe way to visit all the form components on a form. |
| * |
| * @param <R> |
| * return object type |
| * @param visitor |
| * The visitor interface to call |
| * @return user provided in callback |
| */ |
| public final <R> R visitFormComponents(final IVisitor<FormComponent<?>, R> visitor) |
| { |
| return visitChildren(FormComponent.class, visitor); |
| } |
| |
| /** |
| * Convenient and typesafe way to visit all the form components on a form postorder (deepest |
| * first) |
| * |
| * @param <R> |
| * Return object type |
| * @param visitor |
| * The visitor interface to call |
| * @return whatever you provided |
| */ |
| public final <R> R visitFormComponentsPostOrder( |
| final IVisitor<? extends FormComponent<?>, R> visitor) |
| { |
| return FormComponent.visitFormComponentsPostOrder(this, visitor); |
| } |
| |
| /** |
| * Find out whether there is any registered error for a form component. |
| * |
| * @return whether there is any registered error for a form component |
| */ |
| private boolean anyFormComponentError() |
| { |
| // Check ALL children for error messages irrespective of FormComponents or not |
| Boolean error = visitChildren(Component.class, new IVisitor<Component, Boolean>() |
| { |
| @Override |
| public void component(final Component component, final IVisit<Boolean> visit) |
| { |
| if (component.hasErrorMessage() && component.isVisibleInHierarchy() && component.isEnabledInHierarchy()) |
| { |
| visit.stop(true); |
| } |
| } |
| }); |
| |
| return (error != null) && error; |
| } |
| |
| /** |
| * Visits the form's children FormComponents and inform them that a new user input is available |
| * in the Request |
| */ |
| private void inputChanged() |
| { |
| visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, IVisit<Void> visit) |
| { |
| formComponent.inputChanged(); |
| } |
| }); |
| } |
| |
| /** |
| * If a default IFormSubmittingComponent was set on this form, this method will be called to |
| * render an extra field with an invisible style so that pressing enter in one of the textfields |
| * will do a form submit using this component. This method is overridable as what we do is best |
| * effort only, and may not what you want in specific situations. So if you have specific |
| * usability concerns, or want to follow another strategy, you may override this method. |
| * |
| * @see #addDefaultSubmitButtonHandler(IHeaderResponse) |
| */ |
| protected void appendDefaultButtonField() |
| { |
| AppendingStringBuffer buffer = new AppendingStringBuffer(); |
| |
| // hidden div |
| buffer.append(String.format("<div hidden=\"\" class=\"%s\">", |
| getString(HIDDEN_FIELDS_CSS_CLASS_KEY))); |
| |
| // add an empty textfield (otherwise IE doesn't work) |
| buffer.append("<input type=\"text\" tabindex=\"-1\" autocomplete=\"off\"/>"); |
| |
| // add the submitting component |
| buffer |
| .append(String.format("<input id=\"%s\" type=\"submit\" tabindex=\"-1\" name=\"%s\" />", |
| getHiddenFieldsId(HIDDEN_FIELDS_SUBMIT_IDX), |
| defaultSubmittingComponent.getInputName())); |
| |
| // close div |
| buffer.append("</div>"); |
| |
| getResponse().write(buffer); |
| } |
| |
| /** |
| * Where {@link #appendDefaultButtonField()} renders the markup for default submit button |
| * handling, this method attaches the event handler to its 'click' event. The 'click' event on |
| * the hidden submit button will be dispatched to the selected default submit button. As with |
| * {@link #appendDefaultButtonField()} this method can be overridden when the generated code |
| * needs to be adjusted for a specific usecase. |
| * |
| * @param headerResponse |
| * The header response. |
| */ |
| protected void addDefaultSubmitButtonHandler(IHeaderResponse headerResponse) |
| { |
| final Component submittingComponent = (Component) defaultSubmittingComponent; |
| AppendingStringBuffer buffer = new AppendingStringBuffer(); |
| buffer.append("var b=document.getElementById('"); |
| buffer.append(submittingComponent.getMarkupId()); |
| buffer.append("'); if (b!=null && b.onclick!=null && typeof(b.onclick) != 'undefined') "); |
| buffer.append( |
| "{ var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } else { b.click(); }; return false;"); |
| headerResponse.render(OnEventHeaderItem |
| .forMarkupId(getHiddenFieldsId(HIDDEN_FIELDS_SUBMIT_IDX), "click", buffer.toString())); |
| } |
| |
| /** |
| * Template method to allow clients to do any processing (like recording the current model so |
| * that, in case onSubmit does further validation, the model can be rolled back) before the |
| * actual updating of form component models is done. |
| */ |
| protected void beforeUpdateFormComponentModels() |
| { |
| } |
| |
| /** |
| * Called (by the default implementation of 'process') when all fields validated, the form was |
| * updated and it's data was allowed to be persisted. It is meant for delegating further |
| * processing to clients. |
| * <p> |
| * This implementation first finds out whether the form processing was triggered by a nested |
| * IFormSubmittingComponent of this form. If that is the case, that component's |
| * onSubmitBefore/AfterForm methods are called appropriately.. |
| * </p> |
| * <p> |
| * Regardless of whether a submitting component was found, the form's onSubmit method is called |
| * next. |
| * </p> |
| * |
| * @param submittingComponent |
| * the component that triggered this form processing, or null if the processing was |
| * triggered by something else (like a non-Wicket submit button or a javascript |
| * execution) |
| */ |
| protected void delegateSubmit(IFormSubmitter submittingComponent) |
| { |
| final Form<?> processingForm = findFormToProcess(submittingComponent); |
| |
| // collect all forms innermost to outermost before any hierarchy is changed |
| final List<Form<?>> forms = Generics.newArrayList(3); |
| Visits.visitPostOrder(processingForm, new IVisitor<Form<?>, Void>() |
| { |
| @Override |
| public void component(Form<?> form, IVisit<Void> visit) |
| { |
| if (form.isSubmitted()) |
| { |
| forms.add(form); |
| } |
| } |
| }, new ClassVisitFilter(Form.class)); |
| |
| // process submitting component (if specified) |
| if (submittingComponent != null) |
| { |
| // invoke submit on component |
| submittingComponent.onSubmit(); |
| } |
| |
| // invoke Form#onSubmit(..) |
| for (Form<?> form : forms) |
| { |
| form.onSubmit(); |
| } |
| |
| if (submittingComponent != null) |
| { |
| submittingComponent.onAfterSubmit(); |
| } |
| } |
| |
| /** |
| * Returns the id which will be used for the hidden div containing all parameter fields. |
| * |
| * @param idx |
| * The index of the div to keep different divs apart. |
| * @return the id of the hidden div |
| */ |
| private final String getHiddenFieldsId(int idx) |
| { |
| return getInputNamePrefix() + getMarkupId() + "_hf_" + idx; |
| } |
| |
| /** |
| * Gets the HTTP submit method that will appear in form markup. If no method is specified in the |
| * template, "post" is the default. Note that the markup-declared HTTP method may not correspond |
| * to the one actually used to submit the form; in an Ajax submit, for example, JavaScript event |
| * handlers may submit the form with a "get" even when the form method is declared as "post." |
| * Therefore this method should not be considered a guarantee of the HTTP method used, but a |
| * value for the markup only. Override if you have a requirement to alter this behavior. |
| * |
| * @return the submit method specified in markup. |
| */ |
| protected String getMethod() |
| { |
| String method = getMarkupAttributes().getString("method"); |
| return (method != null) ? method : METHOD_POST; |
| } |
| |
| /** |
| * |
| * @see org.apache.wicket.Component#getStatelessHint() |
| */ |
| @Override |
| protected boolean getStatelessHint() |
| { |
| return false; |
| } |
| |
| /** |
| * @return True if is multipart |
| */ |
| public boolean isMultiPart() |
| { |
| if (multiPart == 0) |
| { |
| Boolean anyEmbeddedMultipart = visitChildren(Component.class, |
| new IVisitor<Component, Boolean>() |
| { |
| @Override |
| public void component(final Component component, final IVisit<Boolean> visit) |
| { |
| boolean isMultiPart = false; |
| if (component instanceof Form<?>) |
| { |
| Form<?> form = (Form<?>)component; |
| if (form.isVisibleInHierarchy() && form.isEnabledInHierarchy()) |
| { |
| isMultiPart = (form.multiPart & MULTIPART_HARD) != 0; |
| } |
| } |
| else if (component instanceof FormComponent<?>) |
| { |
| FormComponent<?> fc = (FormComponent<?>)component; |
| if (fc.isVisibleInHierarchy() && fc.isEnabledInHierarchy()) |
| { |
| isMultiPart = fc.isMultiPart(); |
| } |
| } |
| |
| if (isMultiPart) |
| { |
| visit.stop(true); |
| } |
| } |
| |
| }); |
| |
| if (Boolean.TRUE.equals(anyEmbeddedMultipart)) { |
| multiPart |= MULTIPART_HINT_YES; |
| } else { |
| multiPart |= MULTIPART_HINT_NO; |
| } |
| } |
| |
| return (multiPart & (MULTIPART_HARD | MULTIPART_HINT_YES)) != 0; |
| } |
| |
| /** |
| * Handles multi-part processing of the submitted data. <h3> |
| * WARNING</h3> If this method is overridden it can break {@link FileUploadField}s on this form |
| * |
| * @return false if form is multipart and upload failed |
| */ |
| protected boolean handleMultiPart() |
| { |
| if (isMultiPart()) |
| { |
| // Change the request to a multipart web request so parameters are |
| // parsed out correctly |
| try |
| { |
| ServletWebRequest request = (ServletWebRequest)getRequest(); |
| final MultipartServletWebRequest multipartWebRequest = request.newMultipartWebRequest( |
| getMaxSize(), getPage().getId()); |
| multipartWebRequest.setFileMaxSize(getFileMaxSize()); |
| multipartWebRequest.parseFileParts(); |
| |
| // TODO: Can't this be detected from header? |
| getRequestCycle().setRequest(multipartWebRequest); |
| } |
| catch (final FileUploadException fux) |
| { |
| // Create model with exception and maximum size values |
| final Map<String, Object> model = new HashMap<>(); |
| model.put("exception", fux); |
| model.put("maxSize", getMaxSize()); |
| model.put("fileMaxSize", getFileMaxSize()); |
| |
| onFileUploadException(fux, model); |
| |
| // don't process the form if there is a FileUploadException |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * The default message may look like ".. may not exceed 10240 Bytes..". Which is ok, but |
| * sometimes you may want something like "10KB". By subclassing this method you may replace |
| * maxSize in the model or add you own property and use that in your error message. |
| * <p> |
| * Don't forget to call super.onFileUploadException(e, model) at the end of your method. |
| * |
| * @param e |
| * @param model |
| */ |
| protected void onFileUploadException(final FileUploadException e, |
| final Map<String, Object> model) |
| { |
| if (e instanceof FileUploadBase.SizeLimitExceededException) |
| { |
| String msg = getString(UPLOAD_TOO_LARGE_RESOURCE_KEY, Model.ofMap(model)); |
| error(msg); |
| } |
| else if (e instanceof FileUploadBase.FileSizeLimitExceededException) |
| { |
| String msg = getString(UPLOAD_SINGLE_FILE_TOO_LARGE_RESOURCE_KEY, Model.ofMap(model)); |
| error(msg); |
| } |
| else |
| { |
| String msg = getString(UPLOAD_FAILED_RESOURCE_KEY, Model.ofMap(model)); |
| error(msg); |
| |
| log.warn(msg, e); |
| } |
| } |
| |
| /** |
| * @see org.apache.wicket.Component#internalOnModelChanged() |
| */ |
| @Override |
| protected void internalOnModelChanged() |
| { |
| // Visit all the form components and validate each |
| visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, IVisit<Void> visit) |
| { |
| // If form component is using form model |
| if (formComponent.sameInnermostModel(Form.this)) |
| { |
| formComponent.modelChanged(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Mark each form component on this form invalid. |
| */ |
| protected final void markFormComponentsInvalid() |
| { |
| // call invalidate methods of all nested form components |
| visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, IVisit<Void> visit) |
| { |
| if (formComponent.isVisibleInHierarchy()) |
| { |
| formComponent.invalid(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Mark each form component on this form and on nested forms valid. |
| */ |
| protected final void markFormComponentsValid() |
| { |
| internalMarkFormComponentsValid(); |
| markNestedFormComponentsValid(); |
| } |
| |
| /** |
| * Mark each form component on nested form valid. |
| */ |
| private void markNestedFormComponentsValid() |
| { |
| visitChildren(Form.class, new IVisitor<Form<?>, Void>() |
| { |
| @Override |
| public void component(final Form<?> form, final IVisit<Void> visit) |
| { |
| if (form.isSubmitted()) |
| { |
| form.internalMarkFormComponentsValid(); |
| } |
| else |
| { |
| visit.dontGoDeeper(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Mark each form component on this form valid. |
| */ |
| private void internalMarkFormComponentsValid() |
| { |
| // call valid methods of all nested form components |
| visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() |
| { |
| @Override |
| public void component(final FormComponent<?> formComponent, IVisit<Void> visit) |
| { |
| if (formComponent.getForm() == Form.this && formComponent.isVisibleInHierarchy()) |
| { |
| formComponent.valid(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * @see org.apache.wicket.Component#onComponentTag(ComponentTag) |
| */ |
| @Override |
| protected void onComponentTag(final ComponentTag tag) |
| { |
| super.onComponentTag(tag); |
| |
| if (isRootForm()) |
| { |
| checkComponentTag(tag, "form"); |
| |
| String method = getMethod().toLowerCase(Locale.ROOT); |
| tag.put("method", method); |
| String url = getActionUrl().toString(); |
| if (encodeUrlInHiddenFields()) |
| { |
| int i = url.indexOf('?'); |
| String action = (i > -1) ? url.substring(0, i) : ""; |
| tag.put("action", action); |
| // alternatively, we could just put an empty string here, so |
| // that mounted paths stay in good order. I decided against this |
| // as I'm not sure whether that could have side effects with |
| // other encoders |
| } |
| else |
| { |
| tag.put("action", url); |
| } |
| |
| if (isMultiPart()) |
| { |
| if (METHOD_GET.equalsIgnoreCase(method)) |
| { |
| log.warn("Form with id '{}' is multipart. It should use method 'POST'!", |
| getId()); |
| tag.put("method", METHOD_POST.toLowerCase(Locale.ROOT)); |
| } |
| |
| tag.put("enctype", ENCTYPE_MULTIPART_FORM_DATA); |
| // |
| // require the application-encoding for multipart/form-data to be sure to |
| // get multipart-uploaded characters with the proper encoding on the following |
| // request. |
| // |
| // for details see: http://stackoverflow.com/questions/546365 |
| // |
| tag.put("accept-charset", getApplication().getRequestCycleSettings() |
| .getResponseRequestEncoding()); |
| } |
| else |
| { |
| // sanity check |
| String enctype = (String)tag.getAttributes().get("enctype"); |
| if (ENCTYPE_MULTIPART_FORM_DATA.equalsIgnoreCase(enctype)) |
| { |
| // though not set explicitly in Java, this is a multipart |
| // form |
| setMultiPart(true); |
| } |
| } |
| } |
| else |
| { |
| adjustNestedTagName(tag); |
| tag.remove("method"); |
| tag.remove("action"); |
| tag.remove("enctype"); |
| } |
| } |
| |
| // WICKET-6658 form is not allowed, anything else can stay as is |
| private void adjustNestedTagName(ComponentTag tag) { |
| if ("form".equalsIgnoreCase(tag.getName())) |
| { |
| tag.setName("div"); |
| } |
| } |
| |
| /** |
| * Generates the action url for the form |
| * |
| * @return action url |
| */ |
| protected CharSequence getActionUrl() |
| { |
| return urlForListener(new PageParameters()); |
| } |
| |
| /** |
| * @see org.apache.wicket.Component#renderPlaceholderTag(org.apache.wicket.markup.ComponentTag, |
| * org.apache.wicket.request.Response) |
| */ |
| @Override |
| protected void renderPlaceholderTag(ComponentTag tag, Response response) |
| { |
| if (!isRootForm()) |
| { |
| // WICKET-2166 |
| adjustNestedTagName(tag); |
| } |
| |
| super.renderPlaceholderTag(tag, response); |
| } |
| |
| /** |
| * Should URL query parameters be encoded in hidden fields, by default <code>true</code> |
| * for {@link #METHOD_GET} only. |
| * <p> |
| * In that case, the parameters must <em>not</em> be written as query parameters, as the browser |
| * would strip them from the action url before appending the form values. |
| * |
| * @return true if form's method is 'get' |
| * |
| * @see #getMethod() |
| */ |
| protected boolean encodeUrlInHiddenFields() |
| { |
| return METHOD_GET.equalsIgnoreCase(getMethod()); |
| } |
| |
| /** |
| * Append an additional hidden input tag to support anchor tags that can submit a form. |
| * |
| * @param markupStream |
| * The markup stream |
| * @param openTag |
| * The open tag for the body |
| */ |
| @Override |
| public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) |
| { |
| if (isRootForm()) |
| { |
| // get the hidden field id |
| writeHiddenFields(); |
| } |
| |
| // do the rest of the processing |
| super.onComponentTagBody(markupStream, openTag); |
| } |
| |
| @Override |
| public void renderHead(IHeaderResponse response) |
| { |
| super.renderHead(response); |
| |
| if (hasDefaultSubmittingComponent()) |
| { |
| addDefaultSubmitButtonHandler(response); |
| } |
| } |
| |
| /** |
| * Writes the markup for the hidden input fields and default button field if applicable to the |
| * current response. |
| */ |
| public final void writeHiddenFields() |
| { |
| getResponse().write(String.format("<div id=\"%s\" hidden=\"\" class=\"%s\">", |
| getHiddenFieldsId(HIDDEN_FIELDS_PARAMS_IDX), |
| getString(HIDDEN_FIELDS_CSS_CLASS_KEY))); |
| // if the parameters are not in the action attribute, they have to be written as hidden fields |
| if (encodeUrlInHiddenFields()) |
| { |
| AppendingStringBuffer buffer = new AppendingStringBuffer(); |
| |
| String url = getActionUrl().toString(); |
| int i = url.indexOf('?'); |
| String queryString = (i > -1) ? url.substring(i + 1) : url; |
| String[] params = Strings.split(queryString, '&'); |
| |
| writeParamsAsHiddenFields(params, buffer); |
| |
| getResponse().write(buffer); |
| } |
| getResponse().write("</div>"); |
| |
| // if a default submitting component was set, handle the rendering of that |
| if (hasDefaultSubmittingComponent()) |
| { |
| appendDefaultButtonField(); |
| } |
| } |
| |
| private boolean hasDefaultSubmittingComponent() |
| { |
| if (defaultSubmittingComponent instanceof Component) |
| { |
| final Component submittingComponent = (Component) defaultSubmittingComponent; |
| return submittingComponent.isVisibleInHierarchy() |
| && submittingComponent.isEnabledInHierarchy(); |
| } |
| return false; |
| } |
| |
| /** |
| * |
| * @param params |
| * @param buffer |
| */ |
| protected void writeParamsAsHiddenFields(String[] params, AppendingStringBuffer buffer) |
| { |
| for (String param : params) |
| { |
| String[] pair = Strings.split(param, '='); |
| |
| buffer.append("<input type=\"hidden\" name=\"") |
| .append(recode(pair[0])) |
| .append("\" value=\"") |
| .append(pair.length > 1 ? recode(pair[1]) : "") |
| .append("\" />"); |
| } |
| } |
| |
| /** |
| * Take URL-encoded query string value, decode it and return HTML-escaped version |
| * |
| * @param s |
| * value to decode |
| * @return URL decoded and HTML escaped value |
| */ |
| private String recode(String s) |
| { |
| String un = UrlDecoder.QUERY_INSTANCE.decode(s, getRequest().getCharset()); |
| return Strings.escapeMarkup(un).toString(); |
| } |
| |
| /** |
| * @see org.apache.wicket.Component#onDetach() |
| */ |
| @Override |
| protected void onDetach() |
| { |
| setFlag(FLAG_SUBMITTED, false); |
| |
| super.onDetach(); |
| } |
| |
| /** |
| * Method to override if you want to do something special when an error occurs (other than |
| * simply displaying validation errors). |
| */ |
| protected void onError() |
| { |
| } |
| |
| @Override |
| public void onEvent(IEvent<?> event) { |
| if (event.getPayload() instanceof AjaxRequestTarget) { |
| // WICKET-6171 clear multipart hint, it might change during Ajax requests without this form being rendered |
| this.multiPart &= MULTIPART_HARD; |
| } |
| } |
| |
| @Override |
| protected void onBeforeRender() |
| { |
| // clear multipart hint, it will be reevaluated by #isMultiPart() |
| this.multiPart &= MULTIPART_HARD; |
| |
| super.onBeforeRender(); |
| } |
| |
| /** |
| * Implemented by subclasses to deal with form submits. |
| */ |
| protected void onSubmit() |
| { |
| } |
| |
| /** |
| * Update the model of all components on this form and nested forms using the fields that were |
| * sent with the current request. This method only updates models when the Form.validate() is |
| * called first that takes care of the conversion for the FormComponents. |
| * |
| * Normally this method will not be called when a validation error occurs in one of the form |
| * components. |
| * |
| * @see org.apache.wicket.markup.html.form.FormComponent#updateModel() |
| */ |
| protected final void updateFormComponentModels() |
| { |
| internalUpdateFormComponentModels(); |
| updateNestedFormComponentModels(); |
| } |
| |
| /** |
| * Update the model of all components on nested forms. |
| * |
| * @see #updateFormComponentModels() |
| */ |
| private void updateNestedFormComponentModels() |
| { |
| visitChildren(Form.class, new IVisitor<Form<?>, Void>() |
| { |
| @Override |
| public void component(final Form<?> form, final IVisit<Void> visit) |
| { |
| if (form.isSubmitted()) |
| { |
| form.internalUpdateFormComponentModels(); |
| } |
| else |
| { |
| visit.dontGoDeeper(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Update the model of all components on this form. |
| * |
| * @see #updateFormComponentModels() |
| */ |
| private void internalUpdateFormComponentModels() |
| { |
| FormComponent.visitComponentsPostOrder(this, new FormModelUpdateVisitor(this)); |
| } |
| |
| /** |
| * Validates the form by checking required fields, converting raw input and running validators |
| * for every form component, and last running global form validators. This method is typically |
| * called before updating any models. |
| * <p> |
| * NOTE: in most cases, custom validations on the form can be achieved using an IFormValidator |
| * that can be added using addValidator(). |
| * </p> |
| */ |
| protected final void validate() |
| { |
| // since this method can be called directly by users, this additional check is needed |
| if (isEnabledInHierarchy() && isVisibleInHierarchy()) |
| { |
| validateNestedForms(); |
| validateComponents(); |
| validateFormValidators(); |
| onValidate(); |
| } |
| } |
| |
| /** |
| * Callback during the validation stage of the form |
| */ |
| protected void onValidate() |
| { |
| |
| } |
| |
| /** |
| * Calls {@linkplain #onValidateModelObjects()} on this form and all nested forms that are |
| * visible and enabled |
| */ |
| private void internalOnValidateModelObjects() |
| { |
| onValidateModelObjects(); |
| visitChildren(Form.class, new IVisitor<Form<?>, Void>() |
| { |
| @Override |
| public void component(Form<?> form, IVisit<Void> visit) |
| { |
| if (form.isSubmitted()) |
| { |
| form.onValidateModelObjects(); |
| } |
| else |
| { |
| visit.dontGoDeeper(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Called after form components have updated their models. This is a late-stage validation that |
| * allows outside frameworks to validate any beans that the form is updating. |
| * |
| * This validation method is not preferred because at this point any errors will not unroll any |
| * changes to the model object, so the model object is in a modified state potentially |
| * containing illegal values. However, with external frameworks there may not be an alternate |
| * way to validate the model object. A good example of this is a JSR303 Bean Validator |
| * validating the model object to check any class-level constraints, in order to check such |
| * constraints the model object must contain the values set by the user. |
| */ |
| protected void onValidateModelObjects() |
| { |
| } |
| |
| /** |
| * Triggers type conversion on form components |
| */ |
| protected final void validateComponents() |
| { |
| visitFormComponentsPostOrder(new ValidationVisitor() |
| { |
| @Override |
| public void validate(final FormComponent<?> formComponent) |
| { |
| final Form<?> form = formComponent.getForm(); |
| if (form == Form.this && form.isEnabledInHierarchy() && form.isVisibleInHierarchy()) |
| { |
| formComponent.validate(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Checks if the specified form component visible and is attached to a page |
| * |
| * @param fc |
| * form component |
| * |
| * @return true if the form component and all its parents are visible and there component is in |
| * page's hierarchy |
| */ |
| private boolean isFormComponentVisibleInPage(FormComponent<?> fc) |
| { |
| if (fc == null) |
| { |
| throw new IllegalArgumentException("Argument `fc` cannot be null"); |
| } |
| return fc.isVisibleInHierarchy(); |
| } |
| |
| |
| /** |
| * Validates form with the given form validator |
| * |
| * @param validator |
| */ |
| protected final void validateFormValidator(final IFormValidator validator) |
| { |
| Args.notNull(validator, "validator"); |
| |
| final FormComponent<?>[] dependents = validator.getDependentFormComponents(); |
| |
| boolean validate = true; |
| |
| if (dependents != null) |
| { |
| for (final FormComponent<?> dependent : dependents) |
| { |
| // check if the dependent component is valid |
| if (!dependent.isValid()) |
| { |
| validate = false; |
| break; |
| } |
| // check if the dependent component is visible and is attached to |
| // the page |
| else if (!isFormComponentVisibleInPage(dependent)) |
| { |
| if (log.isWarnEnabled()) |
| { |
| log.warn("IFormValidator in form `" + |
| getPageRelativePath() + |
| "` depends on a component that has been removed from the page or is no longer visible. " + |
| "Offending component id `" + dependent.getId() + "`."); |
| } |
| validate = false; |
| break; |
| } |
| } |
| } |
| |
| if (validate) |
| { |
| validator.validate(this); |
| } |
| } |
| |
| /** |
| * Triggers any added {@link IFormValidator}s. |
| */ |
| protected final void validateFormValidators() |
| { |
| for (Behavior behavior : getBehaviors()) |
| { |
| if (behavior instanceof IFormValidator) |
| { |
| validateFormValidator((IFormValidator)behavior); |
| } |
| } |
| } |
| |
| /** |
| * Validates {@link FormComponent}s as well as {@link IFormValidator}s in nested {@link Form}s. |
| * |
| * @see #validate() |
| */ |
| private void validateNestedForms() |
| { |
| Visits.visitPostOrder(this, new IVisitor<Form<?>, Void>() |
| { |
| @Override |
| public void component(final Form<?> form, final IVisit<Void> visit) |
| { |
| if (form == Form.this) |
| { |
| // skip self, only process children |
| visit.stop(); |
| return; |
| } |
| |
| if (form.isSubmitted()) |
| { |
| form.validateComponents(); |
| form.validateFormValidators(); |
| form.onValidate(); |
| } |
| } |
| }, new ClassVisitFilter(Form.class)); |
| } |
| |
| /** |
| * Allows to customize input names of form components inside this form. |
| * |
| * @return String that well be used as prefix to form component input names |
| */ |
| protected String getInputNamePrefix() |
| { |
| return ""; |
| } |
| |
| /** |
| * @param component |
| * @return The parent form for component |
| */ |
| public static Form<?> findForm(Component component) |
| { |
| return component.findParent(Form.class); |
| } |
| |
| /** |
| * Utility method to assemble an id to distinct form components from different nesting levels. |
| * Useful to generate input names attributes. |
| * |
| * @param component |
| * @return form relative identification string |
| */ |
| public static String getRootFormRelativeId(Component component) |
| { |
| String id = component.getId(); |
| final PrependingStringBuffer inputName = new PrependingStringBuffer(id.length()); |
| Component c = component; |
| while (true) |
| { |
| inputName.prepend(id); |
| c = c.getParent(); |
| if (c == null || (c instanceof Form<?> && ((Form<?>)c).isRootForm()) || |
| c instanceof Page) |
| { |
| break; |
| } |
| inputName.prepend(Component.PATH_SEPARATOR); |
| id = c.getId(); |
| } |
| |
| /* |
| * Certain input names causes problems with JavaScript. If the input name would cause a |
| * problem, we create a replacement unique name by prefixing the name with a path that would |
| * otherwise never be used (blank id in path). |
| * |
| * Input names must start with [A-Za-z] according to HTML 4.01 spec. HTML 5 allows almost |
| * anything. |
| */ |
| if (JavaScriptReservedNames.isNameReserved(inputName.toString())) |
| { |
| inputName.prepend(Component.PATH_SEPARATOR); |
| inputName.prepend(Component.PATH_SEPARATOR); |
| inputName.prepend("p"); |
| } |
| return inputName.toString(); |
| } |
| |
| /** |
| * Get the request parameters for a form submit, |
| * according to the request's method or the form's method as fallback. |
| * |
| * @param component any component inside the form or the form itself |
| * @return parameters |
| */ |
| static IRequestParameters getRequestParameters(Component component) { |
| String method = Form.METHOD_POST; |
| final Request request = component.getRequest(); |
| if (request.getContainerRequest() instanceof HttpServletRequest) |
| { |
| method = ((HttpServletRequest)request.getContainerRequest()).getMethod(); |
| } |
| else |
| { |
| final Form<?> form; |
| if (component instanceof Form) { |
| form = (Form<?>)component; |
| } else { |
| form = component.findParent(Form.class); |
| } |
| |
| if (form != null) |
| { |
| method = form.getMethod(); |
| } |
| } |
| |
| final IRequestParameters parameters; |
| switch (method.toLowerCase(Locale.ROOT)) |
| { |
| case Form.METHOD_POST: |
| parameters = request.getPostParameters(); |
| break; |
| case Form.METHOD_GET: |
| parameters = request.getQueryParameters(); |
| break; |
| default: |
| parameters = EmptyRequestParameters.INSTANCE; |
| } |
| |
| return parameters; |
| } |
| |
| /** |
| * Response when a submission method mismatch is detected |
| * |
| * @see Form#getMethod() |
| * |
| * @author igor |
| */ |
| public static enum MethodMismatchResponse { |
| /** |
| * Continue processing. |
| */ |
| CONTINUE, |
| |
| /** |
| * Abort processing. |
| */ |
| ABORT |
| } |
| } |