/*
* 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.Map;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
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.ServletWebRequest;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.UrlDecoder;
import org.apache.wicket.request.UrlRenderer;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.settings.IApplicationSettings;
import org.apache.wicket.util.lang.Bytes;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.string.interpolator.MapVariableInterpolator;
import org.apache.wicket.util.upload.FileUploadBase.SizeLimitExceededException;
import org.apache.wicket.util.upload.FileUploadException;
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.apache.wicket.validation.IValidatorAddListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for forms. To implement a form, subclass this class, add FormComponents (such as
* CheckBoxes, ListChoices or TextFields) to the form. You can nest multiple
* IFormSubmittingComponents if you want to vary submit behavior. However, it is not necessary to
* use any of Wicket's classes (such as Button or SubmitLink), just putting e.g. <input
* type="submit" value="go"> suffices.
* <p>
* By default, the processing of a form works like this:
* <li>The submitting component is looked up. An submitting IFormSubmittingComponent (such as a
* button) is nested in this form (is a child component) and was clicked by the user. If an
* IFormSubmittingComponent was found, and it has the defaultFormProcessing field set to false
* (default is true), it's onSubmit method will be called right away, thus no validition is done,
* and things like updating form component models that would normally be done are skipped. In that
* respect, nesting an IFormSubmittingComponent with the defaultFormProcessing field set to false
* has the same effect as nesting a normal link. If you want you can call validate() to execute form
* validation, hasError() to find out whether validate() resulted in validation errors, and
* updateFormComponentModels() to update the models of nested form components.</li>
* <li>When no submitting IFormSubmittingComponent with defaultFormProcessing set to false was
* found, this form is processed (method process()). Now, two possible paths exist:
* <ul>
* <li>Form validation failed. All nested form components will be marked invalid, and onError() is
* called to allow clients to provide custom error handling code.</li>
* <li>Form validation succeeded. The nested components will be asked to update their models and
* persist their data is applicable. After that, method delegateSubmit with optionally the
* submitting IFormSubmittingComponent is called. The default when there is a submitting
* IFormSubmittingComponent is to first call onSubmit on that Component, and after that call
* onSubmit on this form. Clients may override delegateSubmit if they want different behavior.</li>
* </ul>
* </li>
* </li>
* </p>
*
* Form for handling (file) uploads with multipart requests is supported by calling
* setMultiPart(true) ( although wicket will try to automatically detect this for you ). Use this
* with {@link org.apache.wicket.markup.html.form.upload.FileUploadField} components. You can attach
* multiple FileUploadField components for multiple file uploads.
* <p>
* In case of an upload error two resource keys are available to specify error messages:
* uploadTooLarge and uploadFailed
*
* ie in [page].properties
*
* [form-id].uploadTooLarge=You have uploaded a file that is over the allowed limit of 2Mb
*
* <p>
* If you want to have multiple IFormSubmittingComponents which submit the same form, simply put two
* or more IFormSubmittingComponents somewhere in the hierarchy of components that are children of
* the form.
* </p>
* <p>
* To get form components to persist their values for users via cookies, simply call
* setPersistent(true) on each component.
* </p>
* <p>
* 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 Button or SubmitLink), you can't rely on implicit
* submit behavior (by using just <input type="submit"> that is not attached to a component).
* </p>
* <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 IFormSubmitListener, IHeaderContributor
{
private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
/**
* Visitor used for validation
*
* @author Igor Vaynberg (ivaynberg)
*/
public static abstract class ValidationVisitor implements IVisitor<FormComponent<?>, Void>
{
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.isValid() &&
formComponent.isEnabledInHierarchy())
{
validate(formComponent);
}
if (formComponent.processChildren())
{
return;
}
else
{
visit.dontGoDeeper();
return;
}
}
/**
* 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} */
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";
/**
* 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;
/** multi-validators assigned to this form */
private Object formValidators = null;
/**
* Maximum size of an upload in bytes. If null, the setting
* {@link IApplicationSettings#getDefaultMaximumUploadSize()} is used.
*/
private Bytes maxSize = null;
/** 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 = 0x02;
/**
* Constructs a form with no validation.
*
* @param id
* See Component
*/
public Form(final String id)
{
super(id);
setOutputMarkupId(true);
}
/**
* @param id
* See Component
* @param model
* See Component
* @see org.apache.wicket.Component#Component(String, IModel)
*/
public Form(final String id, 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
* @see IValidatorAddListener
*/
public void add(IFormValidator validator)
{
if (validator == null)
{
throw new IllegalArgumentException("Argument `validator` cannot be null");
}
// add the validator
formValidators_add(validator);
// see whether the validator listens for add events
if (validator instanceof IValidatorAddListener)
{
((IValidatorAddListener)validator).onAdded(this);
}
}
/**
* Removes a form validator from the form.
*
* @param validator
* validator
* @throws IllegalArgumentException
* if validator is null
* @see IFormValidator
*/
public void remove(IFormValidator validator)
{
if (validator == null)
{
throw new IllegalArgumentException("Argument `validator` cannot be null");
}
IFormValidator removed = formValidators_remove(validator);
if (removed == null)
{
throw new IllegalStateException(
"Tried to remove form validator that was not previously added. "
+ "Make sure your validator's equals() implementation is sufficient");
}
addStateChange();
}
private final int formValidators_indexOf(IFormValidator validator)
{
if (formValidators != null)
{
if (formValidators instanceof IFormValidator)
{
final IFormValidator v = (IFormValidator)formValidators;
if (v == validator || v.equals(validator))
{
return 0;
}
}
else
{
final IFormValidator[] validators = (IFormValidator[])formValidators;
for (int i = 0; i < validators.length; i++)
{
final IFormValidator v = validators[i];
if (v == validator || v.equals(validator))
{
return i;
}
}
}
}
return -1;
}
private final IFormValidator formValidators_remove(IFormValidator validator)
{
int index = formValidators_indexOf(validator);
if (index != -1)
{
return formValidators_remove(index);
}
return null;
}
private final IFormValidator formValidators_remove(int index)
{
if (formValidators instanceof IFormValidator)
{
if (index == 0)
{
final IFormValidator removed = (IFormValidator)formValidators;
formValidators = null;
return removed;
}
else
{
throw new IndexOutOfBoundsException();
}
}
else
{
final IFormValidator[] validators = (IFormValidator[])formValidators;
final IFormValidator removed = validators[index];
// check if we can collapse array of 1 element into a single object
if (validators.length == 2)
{
formValidators = validators[1 - index];
}
else
{
IFormValidator[] newValidators = new IFormValidator[validators.length - 1];
int j = 0;
for (int i = 0; i < validators.length; i++)
{
if (i != index)
{
newValidators[j++] = validators[i];
}
}
formValidators = newValidators;
}
return removed;
}
}
/**
* 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>()
{
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());
}
/**
* 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 findSubmittingButton()
{
IFormSubmittingComponent submittingComponent = getPage().visitChildren(
IFormSubmittingComponent.class, new IVisitor<Component, IFormSubmittingComponent>()
{
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();
IRequestParameters parameters = getRequest().getRequestParameters();
if ((!parameters.getParameterValue(name).isNull()) ||
!parameters.getParameterValue(name + ".x").isNull())
{
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");
}
visit.stop(submittingComponent);
return;
}
}
}
});
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()
{
final int size = formValidators_size();
List<IFormValidator> validators = null;
if (size == 0)
{
// form has no validators, use empty collection
validators = Collections.emptyList();
}
else
{
// form has validators, copy all into collection
validators = new ArrayList<IFormValidator>(size);
for (int i = 0; i < size; i++)
{
validators.add(formValidators_get(i));
}
}
return Collections.unmodifiableCollection(validators);
}
/**
* This generates a piece of javascript code that sets the url in the special hidden field and
* submits the form.
*
* 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 interface url that has to be stored in the hidden field and submitted
* @return The javascript code that submits the form.
*/
public final CharSequence getJsForInterfaceUrl(CharSequence url)
{
/*
* since the passed in url is handled when the current url is form's action url and not the
* current request's url we rerender the passed in url to be relative to the form's action
* url
*/
UrlRenderer renderer = getRequestCycle().getUrlRenderer();
Url oldBase = renderer.getBaseUrl();
Url action = Url.parse(getActionUrl().toString());
renderer.setBaseUrl(action);
url = renderer.renderUrl(Url.parse(url.toString()));
renderer.setBaseUrl(oldBase);
Form<?> root = getRootForm();
return new AppendingStringBuffer("document.getElementById('").append(
root.getHiddenFieldId())
.append("').value='")
.append(url)
.append("';document.getElementById('")
.append(root.getMarkupId())
.append("').submit();");
}
/**
* Gets the maximum size for uploads. If null, the setting
* {@link IApplicationSettings#getDefaultMaximumUploadSize()} is used.
*
* @return the maximum size
*/
public Bytes getMaxSize()
{
Bytes maxSize = this.maxSize;
if (maxSize == null)
{
maxSize = visitChildren(Form.class, new IVisitor<Form<?>, Bytes>()
{
public void component(Form<?> component, IVisit<Bytes> visit)
{
Bytes maxSize = component.getMaxSize();
if (maxSize != null)
{
visit.stop(maxSize);
}
}
});
}
if (maxSize == null)
{
return getApplication().getApplicationSettings().getDefaultMaximumUploadSize();
}
return maxSize;
}
/**
* 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);
}
/**
* Method made final because we want to ensure users call setVersioned.
*
* @see org.apache.wicket.Component#isVersioned()
*/
@Override
public boolean isVersioned()
{
return super.isVersioned();
}
/**
* THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT.
*
* Handles form submissions.
*
* @see Form#validate()
*/
public final void onFormSubmitted()
{
markFormsSubmitted();
if (handleMultiPart())
{
// Tells FormComponents that a new user input has come
inputChanged();
String url = getRequest().getRequestParameters()
.getParameterValue(getHiddenFieldId())
.toString();
if (!Strings.isEmpty(url))
{
dispatchEvent(getPage(), url);
}
else
{
// First, see if the processing was triggered by a Wicket IFormSubmittingComponent
final IFormSubmittingComponent submittingComponent = findSubmittingButton();
// When processing was triggered by a Wicket IFormSubmittingComponent and that
// component indicates it wants to be called immediately
// (without processing), call IFormSubmittingComponent.onSubmit() right away.
if (submittingComponent != null && !submittingComponent.getDefaultFormProcessing())
{
submittingComponent.onSubmit();
}
else
{
// this is the root form
Form<?> formToProcess = this;
// find out whether it was a nested form that was submitted
if (submittingComponent != null)
{
formToProcess = submittingComponent.getForm();
}
// process the form for this request
formToProcess.process(submittingComponent);
}
}
}
// If multi part did fail check if an error is registered and call
// onError
else if (hasError())
{
callOnError();
}
}
/**
* 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.onsubmit())
*
* @see #delegateSubmit(IFormSubmittingComponent) for an easy way to process submitting
* component in the default manner
*/
public void process(IFormSubmittingComponent submittingComponent)
{
// save the page in case the component is removed during submit
final Page page = getPage();
// process the form for this request
if (process())
{
// let clients handle further processing
delegateSubmit(submittingComponent);
}
// WICKET-1912
// If the form is stateless page parameters contain all form component
// values. We need to remove those otherwise they get appended to action URL
final PageParameters parameters = page.getPageParameters();
if (parameters != null)
{
visitFormComponents(new IVisitor<FormComponent<?>, Void>()
{
public void component(final FormComponent<?> formComponent, final IVisit<Void> visit)
{
parameters.remove(formComponent.getInputName());
}
});
parameters.remove(getHiddenFieldId());
}
}
/**
* Process the form. Though you can override this method to provide your whole own algorithm, it
* is not recommended to do so.
* <p>
* See the class documentation for further details on the form processing
* </p>
*
* @return False if the form had an error
*/
private boolean process()
{
if (!isEnabledInHierarchy() || !isVisibleInHierarchy())
{
// since process() can be called outside of the default form workflow, an additional
// check is needed
return false;
}
// run validation
validate();
// If a validation error occurred
if (hasError())
{
// mark all children as invalid
markFormComponentsInvalid();
// let subclass handle error
callOnError();
// Form has an error
return false;
}
else
{
// mark all children as valid
markFormComponentsValid();
// before updating, call the interception method for clients
beforeUpdateFormComponentModels();
// Update model using form data
updateFormComponentModels();
// Form has no error
return true;
}
}
/**
* Calls onError on this {@link Form} and any enabled and visible nested form, if the respective
* {@link Form} actually has errors.
*/
private void callOnError()
{
onError();
// call onError on nested forms
visitChildren(Form.class, new IVisitor<Component, Void>()
{
public void component(final Component component, final IVisit<Void> visit)
{
final Form<?> form = (Form<?>)component;
if (!form.isEnabledInHierarchy() || !form.isVisibleInHierarchy())
{
visit.dontGoDeeper();
return;
}
if (form.hasError())
{
form.onError();
}
}
});
}
/**
* Sets FLAG_SUBMITTED to true on this form and every enabled nested form.
*/
private void markFormsSubmitted()
{
setFlag(FLAG_SUBMITTED, true);
visitChildren(Form.class, new IVisitor<Component, Void>()
{
public void component(final Component component, final IVisit<Void> visit)
{
Form<?> form = (Form<?>)component;
if (form.isEnabledInHierarchy() && 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 IApplicationSettings#getDefaultMaximumUploadSize()} is used.
*
* @param maxSize
* The maximum size
*/
public void setMaxSize(final Bytes maxSize)
{
this.maxSize = maxSize;
}
/**
* 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>()
{
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 visitor
* The visitor interface to call
*/
public final <R> R visitFormComponents(final IVisitor<? extends 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 visitor
* The visitor interface to call
*/
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>()
{
public void component(final Component component, final IVisit<Boolean> visit)
{
if (component.hasErrorMessage())
{
visit.stop(true);
}
}
});
return (error != null) && error;
}
/**
* Method for dispatching/calling a interface on a page from the given url. Used by
* {@link org.apache.wicket.markup.html.form.Form#onFormSubmitted()} for dispatching events
*
* @param page
* The page where the event should be called on.
* @param url
* The url which describes the component path and the interface to be called.
*/
private void dispatchEvent(final Page page, final String url)
{
// the current requst's url is most likely wicket/page?x-y.IFormSubmitListener-path-to-form
// while the passed in url is most likely page?x.y.IOnChangeListener-path-to-component
// we transform the passed in url into wicket/page?x-y.IOnChangeListener-path-to-component
// so the system mapper can interpret it
Url resolved = new Url(getRequest().getUrl());
resolved.resolveRelative(Url.parse(url));
IRequestMapper mapper = getApplication().getRootRequestMapper();
Request request = getRequest().cloneWithUrl(resolved);
IRequestHandler handler = mapper.mapRequest(request);
if (handler != null)
{
getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
}
}
/**
* @param validator
* The form validator to add to the formValidators Object (which may be an array of
* IFormValidators or a single instance, for efficiency)
*/
private void formValidators_add(final IFormValidator validator)
{
if (formValidators == null)
{
formValidators = validator;
}
else
{
// Get current list size
final int size = formValidators_size();
// Create array that holds size + 1 elements
final IFormValidator[] validators = new IFormValidator[size + 1];
// Loop through existing validators copying them
for (int i = 0; i < size; i++)
{
validators[i] = formValidators_get(i);
}
// Add new validator to the end
validators[size] = validator;
// Save new validator list
formValidators = validators;
}
}
/**
* Gets form validator from formValidators Object (which may be an array of IFormValidators or a
* single instance, for efficiency) at the given index
*
* @param index
* The index of the validator to get
* @return The form validator
*/
private IFormValidator formValidators_get(int index)
{
if (formValidators == null)
{
throw new IndexOutOfBoundsException();
}
if (formValidators instanceof IFormValidator[])
{
return ((IFormValidator[])formValidators)[index];
}
return (IFormValidator)formValidators;
}
/**
* @return The number of form validators in the formValidators Object (which may be an array of
* IFormValidators or a single instance, for efficiency)
*/
private int formValidators_size()
{
if (formValidators == null)
{
return 0;
}
if (formValidators instanceof IFormValidator[])
{
return ((IFormValidator[])formValidators).length;
}
return 1;
}
/**
* 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>()
{
public void component(final FormComponent<?> formComponent, IVisit<Void> visit)
{
if (formComponent.isVisibleInHierarchy())
{
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.
*
* @param markupStream
* The markup stream
* @param openTag
* The open tag for the body
*/
protected void appendDefaultButtonField(final MarkupStream markupStream,
final ComponentTag openTag)
{
AppendingStringBuffer buffer = new AppendingStringBuffer();
// div that is not visible (but not display:none either)
buffer.append(HIDDEN_DIV_START);
// add an empty textfield (otherwise IE doesn't work)
buffer.append("<input type=\"text\" autocomplete=\"false\"/>");
// add the submitting component
final Component submittingComponent = (Component)defaultSubmittingComponent;
buffer.append("<input type=\"submit\" name=\"");
buffer.append(defaultSubmittingComponent.getInputName());
buffer.append("\" onclick=\" var b=document.getElementById('");
buffer.append(submittingComponent.getMarkupId());
buffer.append("'); if (b!=null&&b.onclick!=null&&typeof(b.onclick) != 'undefined') { var r = b.onclick.bind(b)(); if (r != false) b.click(); } else { b.click(); }; return false;\" ");
buffer.append(" />");
// close div
buffer.append("</div>");
getResponse().write(buffer);
}
/**
* 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 onSubmit is
* called first.
* </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(IFormSubmittingComponent submittingComponent)
{
// when the given submitting component is not null, it means that it was the
// submitting component
Form<?> formToProcess = this;
if (submittingComponent != null)
{
// use the form which the submittingComponent has submitted for further processing
formToProcess = submittingComponent.getForm();
submittingComponent.onSubmit();
}
// Model was successfully updated with valid data
Visits.visitPostOrder(this, new IVisitor<Form<?>, Void>()
{
public void component(Form<?> form, IVisit<Void> visit)
{
if (form.isEnabledInHierarchy() && form.isVisibleInHierarchy())
{
form.onSubmit();
}
}
}, new ClassVisitFilter(Form.class));
}
/**
* Returns the HiddenFieldId which will be used as the name and id property of the hiddenfield
* that is generated for event dispatches.
*
* @return The name and id of the hidden field.
*/
public final String getHiddenFieldId()
{
return getInputNamePrefix() + getMarkupId() + "_hf_0";
}
/**
* 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;
}
private boolean isMultiPart()
{
if (multiPart != 0)
{
return true;
}
Boolean anyEmbeddedMultipart = visitChildren(Component.class,
new IVisitor<Component, Boolean>()
{
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 != 0);
}
}
else if (component instanceof FormComponent<?>)
{
FormComponent<?> fc = (FormComponent<?>)component;
if (fc.isVisibleInHierarchy() && fc.isEnabledInHierarchy())
{
isMultiPart = fc.isMultiPart();
}
}
if (isMultiPart)
{
visit.stop(true);
}
}
});
boolean mp = Boolean.TRUE.equals(anyEmbeddedMultipart);
if (mp)
{
multiPart |= MULTIPART_HINT;
}
return mp;
}
/**
* Handles multi-part processing of the submitted data.
*
* WARNING
*
* 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 WebRequest multipartWebRequest = request.newMultipartWebRequest(getMaxSize());
// 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<String, Object>();
model.put("exception", fux);
model.put("maxSize", getMaxSize());
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 SizeLimitExceededException)
{
// Resource key should be <form-id>.uploadTooLarge to
// override default message
final String defaultValue = "Upload must be less than " + getMaxSize();
String msg = getString(getId() + "." + UPLOAD_TOO_LARGE_RESOURCE_KEY,
Model.ofMap(model), defaultValue);
error(msg);
}
else
{
// Resource key should be <form-id>.uploadFailed to override
// default message
final String defaultValue = "Upload failed: " + e.getLocalizedMessage();
String msg = getString(getId() + "." + UPLOAD_FAILED_RESOURCE_KEY, Model.ofMap(model),
defaultValue);
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>()
{
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>()
{
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>()
{
public void component(final Form<?> component, final IVisit<Void> visit)
{
Form<?> form = component;
if (form.isEnabledInHierarchy() && form.isVisibleInHierarchy())
{
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>()
{
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);
checkComponentTag(tag, "form");
if (isRootForm())
{
String method = getMethod().toLowerCase();
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", Strings.escapeMarkup(url));
}
if (isMultiPart())
{
tag.put("enctype", "multipart/form-data");
}
else
{
// sanity check
String enctype = (String)tag.getAttributes().get("enctype");
if ("multipart/form-data".equalsIgnoreCase(enctype))
{
// though not set explicitly in Java, this is a multipart
// form
setMultiPart(true);
}
}
}
else
{
tag.setName("div");
tag.remove("method");
tag.remove("action");
tag.remove("enctype");
}
}
/**
* Generates the action url for the form
*
* @return action url
*/
protected CharSequence getActionUrl()
{
return urlFor(IFormSubmitListener.INTERFACE);
}
/**
* @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())
{
super.renderPlaceholderTag(tag, response);
}
else
{
// rewrite inner form tag as div
response.write("<div style=\"display:none\"");
if (getOutputMarkupId())
{
response.write(" id=\"");
response.write(getMarkupId());
response.write("\"");
}
response.write("></div>");
}
}
/**
*
* @return true if form's method is 'get'
*/
protected boolean encodeUrlInHiddenFields()
{
String method = getMethod().toLowerCase();
return method.equals("get");
}
/**
* 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
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
{
if (isRootForm())
{
// get the hidden field id
String nameAndId = getHiddenFieldId();
// render the hidden field
AppendingStringBuffer buffer = new AppendingStringBuffer(HIDDEN_DIV_START).append(
"<input type=\"hidden\" name=\"")
.append(nameAndId)
.append("\" id=\"")
.append(nameAndId)
.append("\" />");
// if it's a get, did put the parameters in the action attribute,
// and have to write the url parameters as hidden fields
if (encodeUrlInHiddenFields())
{
String url = getActionUrl().toString();
int i = url.indexOf('?');
String[] params = ((i > -1) ? url.substring(i + 1) : url).split("&");
writeParamsAsHiddenFields(params, buffer);
}
buffer.append("</div>");
getResponse().write(buffer);
// if a default submitting component was set, handle the rendering of that
if (defaultSubmittingComponent instanceof Component)
{
final Component submittingComponent = (Component)defaultSubmittingComponent;
if (submittingComponent.isVisibleInHierarchy() &&
submittingComponent.isEnabledInHierarchy())
{
appendDefaultButtonField(markupStream, openTag);
}
}
}
// do the rest of the processing
super.onComponentTagBody(markupStream, openTag);
}
/**
*
* @param params
* @param buffer
*/
protected void writeParamsAsHiddenFields(String[] params, AppendingStringBuffer buffer)
{
for (int j = 0; j < params.length; j++)
{
String[] pair = params[j].split("=");
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, unencode it and return HTML-escaped version
*
* @param s
* value to reencode
* @return reencoded 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
protected void onBeforeRender()
{
// clear multipart hint, it will be set if necessary by the visitor
this.multiPart &= ~MULTIPART_HINT;
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 final void updateNestedFormComponentModels()
{
visitChildren(Form.class, new IVisitor<Form<?>, Void>()
{
public void component(final Form<?> form, final IVisit<Void> visit)
{
if (form.isEnabledInHierarchy() && form.isVisibleInHierarchy())
{
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()
{
if (isEnabledInHierarchy() && isVisibleInHierarchy())
{
// since this method can be called directly by users, this additional check is needed
validateComponents();
validateFormValidators();
onValidate();
validateNestedForms();
}
}
/**
* Callback during the validation stage of the form
*/
protected void onValidate()
{
}
/**
* 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)
{
if (validator == null)
{
throw new IllegalArgumentException("Argument [[validator]] cannot be null");
}
final FormComponent<?>[] dependents = validator.getDependentFormComponents();
boolean validate = true;
if (dependents != null)
{
for (int j = 0; j < dependents.length; j++)
{
final FormComponent<?> dependent = dependents[j];
// 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()
{
final int count = formValidators_size();
for (int i = 0; i < count; i++)
{
validateFormValidator(formValidators_get(i));
}
}
/**
* Validates {@link FormComponent}s as well as {@link IFormValidator}s in nested {@link Form}s.
*
* @see #validate()
*/
private void validateNestedForms()
{
visitChildren(Form.class, new IVisitor<Form<?>, Void>()
{
public void component(final Form<?> form, final IVisit<Void> visit)
{
if (form.isEnabledInHierarchy() && form.isVisibleInHierarchy())
{
form.validateComponents();
form.validateFormValidators();
form.onValidate();
}
else
{
visit.dontGoDeeper();
}
}
});
}
/**
* 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 "";
}
/**
* Gets model
*
* @return model
*/
@SuppressWarnings("unchecked")
public final IModel<T> getModel()
{
return (IModel<T>)getDefaultModel();
}
/**
* Sets model
*
* @param model
*/
public final void setModel(IModel<T> model)
{
setDefaultModel(model);
}
/**
* Gets model object
*
* @return model object
*/
@SuppressWarnings("unchecked")
public final T getModelObject()
{
return (T)getDefaultModelObject();
}
/**
* Sets model object
*
* @param object
*/
public final void setModelObject(T object)
{
setDefaultModelObject(object);
}
/**
* @param component
* @return The parent form for component
*/
public static Form<?> findForm(Component component)
{
return component.findParent(Form.class);
}
/** {@inheritDoc} */
@Override
public void renderHead(IHeaderResponse response)
{
if (!isRootForm() && isMultiPart())
{
// register some metadata so we can later properly handle multipart ajax posts for
// embedded forms
registerJavascriptNamespaces(response);
response.renderJavascript("Wicket.Forms[\"" + getMarkupId() + "\"]={multipart:true};",
Form.class.getName() + "." + getMarkupId() + ".metadata");
}
}
/**
* Produces javascript that registereds Wicket.Forms namespaces
*
* @param response
*/
protected void registerJavascriptNamespaces(IHeaderResponse response)
{
response.renderJavascript(
"if (typeof(Wicket)=='undefined') { Wicket={}; } if (typeof(Wicket.Forms)=='undefined') { Wicket.Forms={}; }",
Form.class.getName());
}
}