All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.markup.html.form.FormComponent Maven / Gradle / Ivy

Go to download

Wicket is a Java web application framework that takes simplicity, separation of concerns and ease of development to a whole new level. Wicket pages can be mocked up, previewed and later revised using standard WYSIWYG HTML design tools. Dynamic content processing and form handling is all handled in Java code using a first-class component model backed by POJO data beans that can easily be persisted using your favorite technology.

There is a newer version: 10.0.0
Show newest version
/*
 * 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.io.Serializable;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IConverterLocator;
import org.apache.wicket.IGenericComponent;
import org.apache.wicket.Localizer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.AutoLabelResolver.AutoLabelMarker;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.IPropertyReflectionAwareModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.convert.IConverter;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.string.StringList;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.string.interpolator.VariableInterpolator;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitFilter;
import org.apache.wicket.util.visit.IVisitor;
import org.apache.wicket.util.visit.Visits;
import org.apache.wicket.validation.IErrorMessageSource;
import org.apache.wicket.validation.INullAcceptingValidator;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidationError;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;
import org.apache.wicket.validation.ValidatorAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * An HTML form component knows how to validate itself. Validators that implement IValidator can be
 * added to the component. They will be evaluated in the order they were added and the first
 * Validator that returns an error message determines the error message returned by the component.
 * 

* FormComponents are not versioned by default. If you need versioning for your FormComponents, you * will need to call Form.setVersioned(true), which will set versioning on for the form and all form * component children. *

* If this component is required and that fails, the error key that is used is the "Required"; if * the type conversion fails, it will use the key "IConverter" if the conversion failed in a * converter, or "ConversionError" if type was explicitly specified via {@link #setType(Class)} or a * {@link IPropertyReflectionAwareModel} was used. Notice that both "IConverter" and * "ConversionError" have a more specific variant of "key.classname" where classname is the type * that we failed to convert to. Classname is not full qualified, so only the actual name of the * class is used. * * Property expressions that can be used in error messages are: *

    *
  • ${input}: the input the user did give
  • *
  • ${name}: the name of the component that failed
  • *
  • ${label}: the label of the component
  • *
* * @author Jonathan Locke * @author Eelco Hillenius * @author Johan Compagner * @author Igor Vaynberg (ivaynberg) * * @param * The model object type * */ public abstract class FormComponent extends LabeledWebMarkupContainer implements IFormVisitorParticipant, IFormModelUpdateListener, IGenericComponent { private static final Logger logger = LoggerFactory.getLogger(FormComponent.class); /** * {@link IErrorMessageSource} used for error messages against this form components. * * @author ivaynberg */ private class MessageSource implements IErrorMessageSource { private final Set triedKeys = new LinkedHashSet(); /** * @see org.apache.wicket.validation.IErrorMessageSource#getMessage(String, java.util.Map) */ @Override public String getMessage(String key, Map vars) { final FormComponent formComponent = FormComponent.this; // Use the following log4j config for detailed logging on the property resolution // process // log4j.logger.org.apache.wicket.resource.loader=DEBUG // log4j.logger.org.apache.wicket.Localizer=DEBUG final Localizer localizer = formComponent.getLocalizer(); // retrieve prefix that will be used to construct message keys String prefix = formComponent.getValidatorKeyPrefix(); String message = null; // first try the full form of key [form-component-id].[prefix].[key] String resource = getId() + "." + prefix(prefix, key); message = getString(localizer, resource, formComponent); // if not found, try a more general form (without prefix) // [form-component-id].[key] if (Strings.isEmpty(message) && Strings.isEmpty(prefix)) { resource = getId() + "." + key; message = getString(localizer, resource, formComponent); } // If not found try a more general form [prefix].[key] if (Strings.isEmpty(message)) { resource = prefix(prefix, key); message = getString(localizer, resource, formComponent); } // If not found try the most general form [key] if (Strings.isEmpty(message)) { // Try a variation of the resource key message = getString(localizer, key, formComponent); } // convert empty string to null in case our default value of "" was // returned from localizer if (Strings.isEmpty(message)) { message = null; } else { message = substitute(message, addDefaultVars(vars)); } return message; } private String prefix(String prefix, String key) { if (!Strings.isEmpty(prefix)) { return prefix + "." + key; } else { return key; } } /** * * @param localizer * @param key * @param component * @return string */ private String getString(Localizer localizer, String key, Component component) { triedKeys.add(key); // Note: It is important that the default value of "" is // provided to getString() not to throw a MissingResourceException or to // return a default string like "[Warning: String ..." return localizer.getString(key, component, ""); } private String substitute(String string, final Map vars) throws IllegalStateException { return new VariableInterpolator(string, Application.get() .getResourceSettings() .getThrowExceptionOnMissingResource()) { private static final long serialVersionUID = 1L; @SuppressWarnings({ "rawtypes", "unchecked" }) @Override protected String getValue(String variableName) { Object value = vars.get(variableName); if (value == null ||value instanceof String) { return String.valueOf(value); } IConverter converter = getConverter(value.getClass()); if (converter == null) { return Strings.toString(value); } else { return converter.convertToString(value, getLocale()); } } }.toString(); } /** * Creates a new params map that additionally contains the default input, name, label * parameters * * @param params * original params map * @return new params map */ private Map addDefaultVars(Map params) { // create and fill the new params map final HashMap fullParams; if (params == null) { fullParams = new HashMap(6); } else { fullParams = new HashMap(params.size() + 6); fullParams.putAll(params); } // add the input param if not already present if (!fullParams.containsKey("input")) { fullParams.put("input", getInput()); } // add the name param if not already present if (!fullParams.containsKey("name")) { fullParams.put("name", getId()); } // add the label param if not already present if (!fullParams.containsKey("label")) { fullParams.put("label", getLabel()); } return fullParams; } /** * @return value of label param for this form component */ private String getLabel() { final FormComponent fc = FormComponent.this; String label = null; // first try the label model ... if (fc.getLabel() != null) { label = fc.getLabel().getObject(); } // ... then try a resource of format [form-component-id] with // default of '[form-component-id]' if (label == null) { label = fc.getDefaultLabel(); } return label; } } /** * Adapter that makes this component appear as {@link IValidatable} * * @author ivaynberg */ private class ValidatableAdapter implements IValidatable { /** * @see org.apache.wicket.validation.IValidatable#error(org.apache.wicket.validation.IValidationError) */ @Override public void error(IValidationError error) { FormComponent.this.error(error); } /** * @see org.apache.wicket.validation.IValidatable#getValue() */ @Override public T getValue() { return getConvertedInput(); } /** * @see org.apache.wicket.validation.IValidatable#isValid() */ @Override public boolean isValid() { return FormComponent.this.isValid(); } @Override public IModel getModel() { return FormComponent.this.getModel(); } } /** * The value separator */ public static final String VALUE_SEPARATOR = ";"; private static final String[] EMPTY_STRING_ARRAY = new String[] { "" }; /** Whether or not this component's value is required (non-empty) */ private static final short FLAG_REQUIRED = FLAG_RESERVED3; private static final String NO_RAW_INPUT = "[-NO-RAW-INPUT-]"; private static final long serialVersionUID = 1L; /** * Make empty strings null values boolean. Used by AbstractTextComponent subclass. */ protected static final short FLAG_CONVERT_EMPTY_INPUT_STRING_TO_NULL = FLAG_RESERVED1; /** * Visits any form components inside component if it is a container, or component itself if it * is itself a form component * * @param * the type of the visitor's result * * @param component * starting point of the traversal * * @param visitor * The visitor to call * @return the visitor's result */ public static final R visitFormComponentsPostOrder(Component component, final IVisitor, R> visitor) { return Visits.visitPostOrder(component, visitor, new IVisitFilter() { @Override public boolean visitChildren(Object object) { if (object instanceof IFormVisitorParticipant) { return ((IFormVisitorParticipant)object).processChildren(); } return true; } @Override public boolean visitObject(Object object) { return (object instanceof FormComponent); } }); } /** * Visits any form components inside component if it is a container, or component itself if it * is itself a form component * * @param * the type of the visitor's result * @param component * starting point of the traversal * * @param visitor * The visitor to call * @return the visitor's result */ public static final R visitComponentsPostOrder(Component component, final org.apache.wicket.util.visit.IVisitor visitor) { Args.notNull(visitor, "visitor"); return Visits.visitPostOrder(component, visitor, new IVisitFilter() { @Override public boolean visitObject(Object object) { return true; } @Override public boolean visitChildren(Object object) { if (object instanceof IFormVisitorParticipant) { return ((IFormVisitorParticipant)object).processChildren(); } return true; } }); } private transient T convertedInput; /** * Raw Input entered by the user or NO_RAW_INPUT if nothing is filled in. */ private String rawInput = NO_RAW_INPUT; /** * Type that the raw input string will be converted to */ private String typeName; /** * @see org.apache.wicket.Component#Component(String) */ public FormComponent(final String id) { this(id, null); } /** * @param id * @param model * @see org.apache.wicket.Component#Component(String, IModel) */ public FormComponent(final String id, IModel model) { super(id, model); // the form decides whether form components are versioned or not // see Form.setVersioned setVersioned(false); } /** * Gets the string the component would use as a label when one was requested but no label model * was set via {@link #getLabel()}. The value of this string is usually set in a property file; * if the value is not set the default value equivalent to component id will be returned. * * @return localized label */ public final String getDefaultLabel() { return getDefaultLabel(getId()); } /** * Gets the string the component would use as a label when one was requested but no label model * was set via {@link #getLabel()}. The value of this string is usually set in a property file; * if the value is not set the {@code defaultValue} will be returned. * * @param defaultValue * * @return localized label */ public final String getDefaultLabel(String defaultValue) { return getLocalizer().getString(getId(), getParent(), defaultValue); } /** * Adds a validator to this form component * * @param validator * validator to be added * @return this for chaining * @throws IllegalArgumentException * if validator is null * @see IValidator */ @SuppressWarnings({ "rawtypes", "unchecked" }) public final FormComponent add(final IValidator validator) { Args.notNull(validator, "validator"); if (validator instanceof Behavior) { add((Behavior)validator); } else { add((Behavior)new ValidatorAdapter(validator)); } return this; } /** * Removes a validator from the form component. * * @param validator * validator * @throws IllegalArgumentException * if validator is null or not found * @see IValidator * @see #add(IValidator) * @return form component for chaining */ public final FormComponent remove(final IValidator validator) { Args.notNull(validator, "validator"); Behavior match = null; for (Behavior behavior : getBehaviors()) { if (behavior.equals(validator)) { match = behavior; break; } else if (behavior instanceof ValidatorAdapter) { if (((ValidatorAdapter)behavior).getValidator().equals(validator)) { match = behavior; break; } } } if (match != null) { remove(match); } else { throw new IllegalStateException( "Tried to remove validator that was not previously added. " + "Make sure your validator's equals() implementation is sufficient"); } return this; } /** * Adds a validator to this form component. * * @param validators * The validator(s) to be added * @return This * @throws IllegalArgumentException * if validator is null * @see IValidator */ public final FormComponent add(final IValidator... validators) { Args.notNull(validators, "validators"); for (IValidator validator : validators) { add(validator); } // return this for chaining return this; } /** * Checks if the form component's 'required' requirement is met by first checking * {@link #isRequired()} to see if it has to check for requirement. If that is true then by * default it checks if the input is null or an empty String * {@link Strings#isEmpty(CharSequence)} *

* Subclasses that overwrite this method should also call {@link #isRequired()} first. *

* * @return true if the 'required' requirement is met, false otherwise * * @see Strings#isEmpty(CharSequence) * @see #isInputNullable() */ public boolean checkRequired() { if (isRequired()) { final String input = getInput(); // when null, check whether this is natural for that component, or // whether - as is the case with text fields - this can only happen // when the component was disabled if (input == null && !isInputNullable() && !isEnabledInHierarchy()) { // this value must have come from a disabled field // do not perform validation return true; } // perform validation by looking whether the value is null or empty return !Strings.isEmpty(input); } return true; } /** * Clears the user input. */ public final void clearInput() { rawInput = NO_RAW_INPUT; } /** * Reports a validation error against this form component. * * The actual error is reported by creating a {@link ValidationErrorFeedback} object that holds * both the validation error and the generated error message - so a custom feedback panel can * have access to both. * * @param error * validation error */ public void error(IValidationError error) { Args.notNull(error, "error"); MessageSource source = new MessageSource(); Serializable message = error.getErrorMessage(source); if (message == null) { StringBuilder buffer = new StringBuilder(); buffer.append("Could not locate error message for component: "); buffer.append(Classes.simpleName(getClass())); buffer.append("@"); buffer.append(getPageRelativePath()); buffer.append(" and error: "); buffer.append(error.toString()); buffer.append(". Tried keys: "); Iterator keys = source.triedKeys.iterator(); while (keys.hasNext()) { buffer.append(keys.next()); if (keys.hasNext()) { buffer.append(", "); } } buffer.append("."); message = buffer.toString(); logger.warn(message.toString()); } error(message); } /** * Gets the converted input. The converted input is set earlier though the implementation of * {@link #convertInput()}. * * {@link FormComponentPanel} often access this method when constructing their converted input * value which is often the combination of converted values of the embedded FormComponents * * To access the model object resulted by the full form processing, use * {@link #getModelObject()} instead, that is an generified version of * {@link #getDefaultModelObject()} * * @return value of input possibly converted into an appropriate type */ public final T getConvertedInput() { return convertedInput; } /** * Sets the converted input. This method is typically not called by clients, unless they * override {@link #convertInput()}, in which case they should call this method to update the * input for this component instance. * * @param convertedInput * the converted input */ public final void setConvertedInput(T convertedInput) { this.convertedInput = convertedInput; } /** * @return The parent form for this form component */ public Form getForm() { Form form = Form.findForm(this); if (form == null) { throw new WicketRuntimeException("Could not find Form parent for " + this); } return form; } /** * Gets the request parameter for this component as a string. * * @return The value in the request for this component */ public String getInput() { String[] input = getInputAsArray(); if (input == null || input.length == 0) { return null; } else { return trim(input[0]); } } /** * Gets the request parameters for this component as strings. * * @return The values in the request for this component */ public String[] getInputAsArray() { List list = getRequest().getRequestParameters().getParameterValues( getInputName()); String[] values = null; if (list != null) { values = new String[list.size()]; for (int i = 0; i < list.size(); ++i) { values[i] = list.get(i).toString(); } } if (!isInputNullable()) { if (values != null && values.length == 1 && values[0] == null) { // we the key got passed in (otherwise values would be null), // but the value was set to null. // As the servlet spec isn't clear on what to do with 'empty' // request values - most return an empty string, but some null - // we have to workaround here and deliberately set to an empty // string if the the component is not nullable (text components) return EMPTY_STRING_ARRAY; } } return values; } /** * Gets the string to be used for the name attribute of the form element. Generated * using the path from the form to the component, excluding the form itself. Override it if you * want even a smaller name. E.g. if you know for sure that the id is unique within a form. * * @return The string to use as the form element's name attribute */ public String getInputName() { String inputName = Form.getRootFormRelativeId(this); Form form = findParent(Form.class); if (form != null) { return form.getInputNamePrefix() + inputName; } else { return inputName; } } /** * Use hasRawInput() to check if this component has raw input because null can mean 2 things: It * doesn't have rawinput or the rawinput is really null. * * @return The raw form input that is stored for this formcomponent */ public final String getRawInput() { return NO_RAW_INPUT.equals(rawInput) ? null : rawInput; } /** * @return the type to use when updating the model for this form component */ @SuppressWarnings("unchecked") public final Class getType() { return typeName == null ? null : (Class)WicketObjects.resolveClass(typeName); } /** * @see Form#getValidatorKeyPrefix() * @return prefix used when constructing validator key messages */ public String getValidatorKeyPrefix() { Form form = findParent(Form.class); if (form != null) { return getForm().getValidatorKeyPrefix(); } return null; } /** * Gets an unmodifiable list of validators for this FormComponent. * * @return List of validators */ @SuppressWarnings("unchecked") public final List> getValidators() { final List> list = new ArrayList>(); for (Behavior behavior : getBehaviors()) { if (behavior instanceof IValidator) { list.add((IValidator)behavior); } } return Collections.unmodifiableList(list); } /** * Gets current value for a form component, which can be either input data entered by the user, * or the component's model object if no input was provided. * * @return The value */ public final String getValue() { if (NO_RAW_INPUT.equals(rawInput)) { return getModelValue(); } else { if (getEscapeModelStrings() && rawInput != null) { return Strings.escapeMarkup(rawInput).toString(); } return rawInput; } } /** * Returns whether this component has raw input. Raw input is unconverted input straight from * the client. * * @return boolean whether this component has raw input. */ public final boolean hasRawInput() { return !NO_RAW_INPUT.equals(rawInput); } /** * Used by Form to tell the FormComponent that a new user input is available */ public final void inputChanged() { if (isVisibleInHierarchy() && isEnabledInHierarchy()) { // Get input as String array final String[] input = getInputAsArray(); // If there is any input if (input != null && input.length > 0 && input[0] != null) { // join the values together with ";", for example, "id1;id2;id3" rawInput = StringList.valueOf(input).join(VALUE_SEPARATOR); } else if (isInputNullable()) { // no input rawInput = null; } else { rawInput = NO_RAW_INPUT; } } } /** * Indicate that validation of this form component failed. */ public final void invalid() { onInvalid(); } /** * Gets whether this component's input can be null. By default, components that do not get input * will have null values passed in for input. However, component TextField is an example * (possibly the only one) that never gets a null passed in, even if the field is left empty * UNLESS it had attribute disabled="disabled" set. * * @return True if this component's input can be null. Returns true by default. */ public boolean isInputNullable() { return true; } /** * @return True if this component encodes data in a multipart form submit */ public boolean isMultiPart() { return false; } /** * @return whether or not this component's value is required */ public boolean isRequired() { return getFlag(FLAG_REQUIRED); } /** * Gets whether this component is 'valid'. Valid in this context means that no validation errors * were reported the last time the form component was processed. This variable not only is * convenient for 'business' use, but is also necessary as we don't want the form component * models updated with invalid input. * * @return valid whether this component is 'valid' */ public final boolean isValid() { class IsValidVisitor implements IVisitor, Boolean> { @Override public void component(final FormComponent formComponent, final IVisit visit) { if (formComponent.hasErrorMessage()) { visit.stop(Boolean.FALSE); } } } IsValidVisitor tmp = new IsValidVisitor(); final Object result = visitFormComponentsPostOrder(this, tmp); return (Boolean.FALSE != result); } /** * @see IFormVisitorParticipant#processChildren() */ @Override public boolean processChildren() { return true; } /** * This method will retrieve the request parameter, validate it, and if valid update the model. * These are the same steps as would be performed by the form. * * This is useful when a formcomponent is used outside a form. * */ public final void processInput() { inputChanged(); validate(); if (hasErrorMessage()) { invalid(); } else { valid(); updateModel(); } } /** * The value will be made available to the validator property by means of ${label}. It does not * have any specific meaning to FormComponent itself. * * @param labelModel * @return this for chaining */ @Override public FormComponent setLabel(IModel labelModel) { super.setLabel(labelModel); return this; } /** * Sets the value for a form component. * * @param value * The value */ public void setModelValue(final String[] value) { convertedInput = convertValue(value); updateModel(); } /** * Sets the required flag * * @param required * @return this for chaining */ public final FormComponent setRequired(final boolean required) { if (!required && getType() != null && getType().isPrimitive()) { throw new WicketRuntimeException( "FormComponent can't be required when the type is primitive class: " + this); } if (required != isRequired()) { addStateChange(); } setFlag(FLAG_REQUIRED, required); return this; } /** * Sets the type that will be used when updating the model for this component. If no type is * specified String type is assumed. * * @param type * @return this for chaining */ public FormComponent setType(Class type) { typeName = type == null ? null : type.getName(); if (type != null && type.isPrimitive()) { setRequired(true); } return this; } /** * Updates this components model from the request, it expects that the object is already * converted through the convertInput() call that is called by the validate() method when a form * is being processed. * * By default it just does this: * *
	 * setModelObject(getConvertedInput());
	 * 
* * DO NOT CALL THIS METHOD DIRECTLY UNLESS YOU ARE SURE WHAT YOU ARE DOING. USUALLY UPDATING * YOUR MODEL IS HANDLED BY THE FORM, NOT DIRECTLY BY YOU. */ @Override public void updateModel() { setModelObject(getConvertedInput()); } /** * Called to indicate that the user input is valid. */ public final void valid() { clearInput(); onValid(); } /** * Performs full validation of the form component, which consists of calling validateRequired(), * convertInput(), and validateValidators(). This method should only be used if the form * component needs to be fully validated outside the form process. */ public void validate() { // clear any previous feedback messages if (hasFeedbackMessage()) { getFeedbackMessages().clear(); } // validate validateRequired(); if (isValid()) { convertInput(); if (isValid()) { if (isRequired() && getConvertedInput() == null && isInputNullable()) { reportRequiredError(); } else { validateValidators(); } } } } /** * Converts and validates the conversion of the raw input string into the object specified by * {@link FormComponent#getType()} and records any thrown {@link ConversionException}s. * Converted value is available through {@link FormComponent#getConvertedInput()}. * *

* Usually the user should do custom conversions by specifying an {@link IConverter} by * registering it with the application by overriding {@link Application#getConverterLocator()}, * or at the component level by overriding {@link #getConverter(Class)} . *

* * @see IConverterLocator * @see Application#newConverterLocator() * @see IConverter#convertToObject(String, Locale) * @see #newValidationError(ConversionException) */ protected void convertInput() { if (typeName == null) { try { convertedInput = convertValue(getInputAsArray()); } catch (ConversionException e) { error(newValidationError(e)); } } else { final IConverter converter = getConverter(getType()); try { convertedInput = converter.convertToObject(getInput(), getLocale()); } catch (ConversionException e) { error(newValidationError(e)); } } } /** * This method is called, when the validation triggered by {@link FormComponent#convertInput()} * failed with a {@link ConversionException}, to construct a {@link ValidationError} based on * the exception. *

* Override this method to modify the ValidationError object, e.g. add a custom variable for * message substitution: *

* *

	 * new FormComponent<T>(id)
	 * {
	 * 	protected ValidationError newValidationError(ConversionException cause)
	 * 	{
	 * 		return super.newValidationError(cause).setVariable("foo", foovalue);
	 * 	}
	 * };
	 * 
* * @param cause * the original cause * @return {@link ValidationError} */ protected ValidationError newValidationError(ConversionException cause) { ValidationError error = new ValidationError(cause.getMessage()); if (cause.getResourceKey() != null) { error.addKey(cause.getResourceKey()); } if (typeName == null) { if (cause.getTargetType() != null) { error.addKey("ConversionError." + Classes.simpleName(cause.getTargetType())); } error.addKey("ConversionError"); } else { String simpleName = Classes.simpleName(getType()); error.addKey("IConverter." + simpleName); error.addKey("IConverter"); error.setVariable("type", simpleName); } final Locale locale = cause.getLocale(); if (locale != null) { error.setVariable("locale", locale); } error.setVariable("exception", cause); Format format = cause.getFormat(); if (format instanceof SimpleDateFormat) { error.setVariable("format", ((SimpleDateFormat)format).toLocalizedPattern()); } Map variables = cause.getVariables(); if (variables != null) { error.getVariables().putAll(variables); } return error; } /** * Subclasses should overwrite this if the conversion is not done through the type field and the * {@link IConverter}. WARNING: this method may be removed in future versions. * * If conversion fails then a ConversionException should be thrown * * @param value * The value can be the getInput() or through a cookie * * @return The converted value. default returns just the given value * @throws ConversionException * If input can't be converted */ @SuppressWarnings("unchecked") protected T convertValue(String[] value) throws ConversionException { return (T)(value != null && value.length > 0 && value[0] != null ? trim(value[0]) : null); } /** * @return Value to return when model value is needed */ protected String getModelValue() { return getDefaultModelObjectAsString(); } /** * Gets the request parameter for this component as an int. * * @return The value in the request for this component */ protected final int inputAsInt() { final String string = getInput(); try { return Integer.parseInt(string); } catch (NumberFormatException e) { throw new IllegalArgumentException( exceptionMessage("Internal error. Request string '" + string + "' not a valid integer")); } } /** * Gets the request parameter for this component as an int, using the given default in case no * corresponding request parameter was found. * * @param defaultValue * Default value to return if request does not have an integer for this component * @return The value in the request for this component */ protected final int inputAsInt(final int defaultValue) { final String string = getInput(); if (string != null) { try { return Integer.parseInt(string); } catch (NumberFormatException e) { throw new IllegalArgumentException(exceptionMessage("Request string '" + string + "' is not a valid integer")); } } else { return defaultValue; } } /** * Gets the request parameters for this component as ints. * * @return The values in the request for this component */ protected final int[] inputAsIntArray() { final String[] strings = getInputAsArray(); if (strings != null) { final int[] ints = new int[strings.length]; for (int i = 0; i < strings.length; i++) { ints[i] = Integer.parseInt(strings[i]); } return ints; } return null; } /** * @see org.apache.wicket.Component#internalOnModelChanged() */ @Override protected void internalOnModelChanged() { // If the model for this form component changed, we should make it // valid again because there can't be any invalid input for it anymore. valid(); } /** * Processes the component tag. * * @param tag * Tag to modify * @see org.apache.wicket.Component#onComponentTag(ComponentTag) */ @Override protected void onComponentTag(final ComponentTag tag) { tag.put("name", getInputName()); if (!isEnabledInHierarchy()) { onDisabled(tag); } if (isRequired()) { onRequired(tag); } super.onComponentTag(tag); } /** * Sets the temporary converted input value to null. * * @see org.apache.wicket.Component#onDetach() */ @Override protected void onDetach() { super.onDetach(); convertedInput = null; } /** * Called by {@link #onComponentTag(ComponentTag)} when the component is disabled. By default, * this method will add a disabled="disabled" attribute to the tag. Components may override this * method to tweak the tag as they think is fit. * * @param tag * the tag that is being rendered */ protected void onDisabled(final ComponentTag tag) { tag.put("disabled", "disabled"); } /** * Called by {@link #onComponentTag(ComponentTag)} when the component is required. * * @param tag * the tag that is being rendered */ @Deprecated protected void onRequired(final ComponentTag tag) { } /** * Handle invalidation */ protected void onInvalid() { } /** * Handle validation */ protected void onValid() { } /** * Determines whether or not this component should trim its input prior to processing it. The * default value is true * * @return True if the input should be trimmed. */ protected boolean shouldTrimInput() { return true; } /** * Trims the input according to {@link #shouldTrimInput()} * * @param string * @return trimmed input if {@link #shouldTrimInput()} returns true, unchanged input otherwise */ protected String trim(String string) { String trimmed = string; if (trimmed != null && shouldTrimInput()) { trimmed = trimmed.trim(); } return trimmed; } /** * Checks if the raw input value is not null if this component is required. */ protected final void validateRequired() { if (!checkRequired()) { reportRequiredError(); } } /** * Reports required error against this component */ protected void reportRequiredError() { error(new ValidationError().addKey("Required")); } /** * Validates this component using the component's validators. */ @SuppressWarnings("unchecked") protected final void validateValidators() { final IValidatable validatable = newValidatable(); boolean isNull = getConvertedInput() == null; IValidator validator = null; try { for (Behavior behavior : getBehaviors()) { if (isBehaviorAccepted(behavior) == false) { continue; } validator = null; if (behavior instanceof ValidatorAdapter) { validator = ((ValidatorAdapter)behavior).getValidator(); } else if (behavior instanceof IValidator) { validator = (IValidator)behavior; } if (validator != null) { if (isNull == false || validator instanceof INullAcceptingValidator) { validator.validate(validatable); } if (!isValid()) { break; } } } } catch (Exception e) { throw new WicketRuntimeException("Exception '" + e.getMessage() + "' occurred during validation " + validator.getClass().getName() + " on component " + getPath(), e); } } /** * Creates an IValidatable that can be used to validate this form component. This validatable * incorporates error key lookups that correspond to this form component. * * This method is useful when validation needs to happen outside the regular validation workflow * but error messages should still be properly reported against the form component. * * @return IValidatable for this form component */ public final IValidatable newValidatable() { return new ValidatableAdapter(); } @Override @SuppressWarnings("unchecked") public final IModel getModel() { return (IModel)getDefaultModel(); } @Override public final void setModel(IModel model) { setDefaultModel(model); } @SuppressWarnings("unchecked") @Override public final T getModelObject() { return (T)getDefaultModelObject(); } @Override public final void setModelObject(T object) { setDefaultModelObject(object); } /** * Updates auto label css classes such as error/required during ajax updates when the labels may * not be directly repainted in the response. * * @param target */ public final void updateAutoLabels(AjaxRequestTarget target) { if (!((WebApplication)getApplication()).getUpdateAutoLabelsOnAjaxRequests()) { return; } AutoLabelMarker marker = getMetaData(AutoLabelResolver.MARKER_KEY); if (marker == null) { // this component does not have an auto label return; } marker.updateFrom(this, target); } /** * Update the model of a {@link FormComponent} containing a {@link Collection}. * * If the model object does not yet exists, a new {@link ArrayList} is filled with the converted * input and used as the new model object. Otherwise the existing collection is modified * in-place, then {@link Model#setObject(Object)} is called with the same instance: it allows * the Model to be notified of changes even when {@link Model#getObject()} returns a different * {@link Collection} at every invocation. * * @param * collection type * @param formComponent * the form component to update * @see FormComponent#updateModel() * @throws WicketRuntimeException * if the existing model object collection is unmodifiable and no setter exists */ public static void updateCollectionModel(FormComponent> formComponent) { Collection convertedInput = formComponent.getConvertedInput(); if (convertedInput == null) { convertedInput = Collections.emptyList(); } Collection collection = formComponent.getModelObject(); if (collection == null) { collection = new ArrayList(convertedInput); formComponent.setModelObject(collection); } else { boolean modified = false; formComponent.modelChanging(); try { collection.clear(); collection.addAll(convertedInput); modified = true; } catch (UnsupportedOperationException unmodifiable) { if (logger.isDebugEnabled()) { logger.debug("An error occurred while trying to modify the collection attached to " + formComponent, unmodifiable); } collection = new ArrayList(convertedInput); } try { formComponent.getModel().setObject(collection); } catch (Exception noSetter) { if (!modified) { throw new WicketRuntimeException("An error occurred while trying to set the collection attached to " + formComponent, noSetter); } else if (logger.isDebugEnabled()) { logger.debug("An error occurred while trying to set the collection attached to " + formComponent, noSetter); } } formComponent.modelChanged(); } } }