001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.html.form;
018
019import java.io.Serializable;
020import java.text.Format;
021import java.text.SimpleDateFormat;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.LinkedHashSet;
029import java.util.List;
030import java.util.Locale;
031import java.util.Map;
032import java.util.Set;
033
034import org.apache.wicket.Application;
035import org.apache.wicket.Component;
036import org.apache.wicket.IConverterLocator;
037import org.apache.wicket.IGenericComponent;
038import org.apache.wicket.Localizer;
039import org.apache.wicket.WicketRuntimeException;
040import org.apache.wicket.ajax.AjaxRequestTarget;
041import org.apache.wicket.behavior.Behavior;
042import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
043import org.apache.wicket.core.util.lang.WicketObjects;
044import org.apache.wicket.markup.ComponentTag;
045import org.apache.wicket.markup.html.form.AutoLabelResolver.AutoLabelMarker;
046import org.apache.wicket.model.IModel;
047import org.apache.wicket.model.IObjectClassAwareModel;
048import org.apache.wicket.model.IPropertyReflectionAwareModel;
049import org.apache.wicket.model.Model;
050import org.apache.wicket.request.IRequestParameters;
051import org.apache.wicket.util.convert.ConversionException;
052import org.apache.wicket.util.convert.IConverter;
053import org.apache.wicket.util.lang.Args;
054import org.apache.wicket.util.lang.Classes;
055import org.apache.wicket.util.string.StringList;
056import org.apache.wicket.util.string.StringValue;
057import org.apache.wicket.util.string.Strings;
058import org.apache.wicket.util.string.interpolator.VariableInterpolator;
059import org.apache.wicket.util.visit.IVisit;
060import org.apache.wicket.util.visit.IVisitFilter;
061import org.apache.wicket.util.visit.IVisitor;
062import org.apache.wicket.util.visit.Visits;
063import org.apache.wicket.validation.IErrorMessageSource;
064import org.apache.wicket.validation.INullAcceptingValidator;
065import org.apache.wicket.validation.IValidatable;
066import org.apache.wicket.validation.IValidationError;
067import org.apache.wicket.validation.IValidator;
068import org.apache.wicket.validation.ValidationError;
069import org.apache.wicket.validation.ValidatorAdapter;
070import org.slf4j.Logger;
071import org.slf4j.LoggerFactory;
072
073
074/**
075 * An HTML form component knows how to validate itself. Validators that implement IValidator can be
076 * added to the component. They will be evaluated in the order they were added and the first
077 * Validator that returns an error message determines the error message returned by the component.
078 * <p>
079 * FormComponents are not versioned by default. If you need versioning for your FormComponents, you
080 * will need to call Form.setVersioned(true), which will set versioning on for the form and all form
081 * component children.
082 * <p>
083 * If this component is required and that fails, the error key that is used is the "Required"; if
084 * the type conversion fails, it will use the key "IConverter" if the conversion failed in a
085 * converter, or "ConversionError" if type was explicitly specified via {@link #setType(Class)} or a
086 * {@link IPropertyReflectionAwareModel} was used. Notice that both "IConverter" and
087 * "ConversionError" have a more specific variant of "key.classname" where classname is the type
088 * that we failed to convert to. Classname is not full qualified, so only the actual name of the
089 * class is used.
090 * 
091 * Property expressions that can be used in error messages are:
092 * <ul>
093 * <li>${input}: the input the user did give</li>
094 * <li>${name}: the name of the component that failed</li>
095 * <li>${label}: the label of the component</li>
096 * </ul>
097 * 
098 * @author Jonathan Locke
099 * @author Eelco Hillenius
100 * @author Johan Compagner
101 * @author Igor Vaynberg (ivaynberg)
102 * 
103 * @param <T>
104 *            The model object type
105 * 
106 */
107public abstract class FormComponent<T> extends LabeledWebMarkupContainer implements
108        IFormVisitorParticipant, IFormModelUpdateListener, IGenericComponent<T, FormComponent<T>>
109{
110        private static final Logger logger = LoggerFactory.getLogger(FormComponent.class);
111
112        /**
113         * {@link IErrorMessageSource} used for error messages against this form components.
114         * 
115         * @author ivaynberg
116         */
117        private class MessageSource implements IErrorMessageSource
118        {
119                private final Set<String> triedKeys = new LinkedHashSet<>();
120
121                @Override
122                public String getMessage(String key, Map<String, Object> vars)
123                {
124                        final FormComponent<T> formComponent = FormComponent.this;
125
126                        // Use the following slf4j-simple config for detailed logging
127                        // on the property resolution process
128                        // org.slf4j.simpleLogger.log.org.apache.wicket.resource.loader=DEBUG
129                        // org.slf4j.simpleLogger.log.org.apache.wicket.Localizer=DEBUG
130
131                        final Localizer localizer = formComponent.getLocalizer();
132
133                        // retrieve prefix that will be used to construct message keys
134                        String prefix = formComponent.getValidatorKeyPrefix();
135                        String message;
136
137                        // first try the full form of key [form-component-id].[prefix].[key]
138                        String resource = getId() + "." + prefix(prefix, key);
139                        message = getString(localizer, resource, formComponent);
140
141                        // if not found, try a more general form (without prefix)
142                        // [form-component-id].[key]
143                        if (Strings.isEmpty(message) && Strings.isEmpty(prefix))
144                        {
145                                resource = getId() + "." + key;
146                                message = getString(localizer, resource, formComponent);
147                        }
148
149                        // If not found try a more general form [prefix].[key]
150                        if (Strings.isEmpty(message))
151                        {
152                                resource = prefix(prefix, key);
153                                message = getString(localizer, resource, formComponent);
154                        }
155
156                        // If not found try the most general form [key]
157                        if (Strings.isEmpty(message))
158                        {
159                                // Try a variation of the resource key
160                                message = getString(localizer, key, formComponent);
161                        }
162
163                        // convert empty string to null in case our default value of "" was
164                        // returned from localizer
165                        if (Strings.isEmpty(message))
166                        {
167                                message = null;
168                        }
169                        else
170                        {
171                                message = substitute(message, addDefaultVars(vars));
172                        }
173                        return message;
174                }
175
176                private String prefix(String prefix, String key)
177                {
178                        if (!Strings.isEmpty(prefix))
179                        {
180                                return prefix + "." + key;
181                        }
182                        else
183                        {
184                                return key;
185                        }
186                }
187
188                /**
189                 * 
190                 * @param localizer
191                 * @param key
192                 * @param component
193                 * @return string
194                 */
195                private String getString(Localizer localizer, String key, Component component)
196                {
197                        triedKeys.add(key);
198
199                        // Note: It is important that the default value of "" is
200                        // provided to getString() not to throw a MissingResourceException or to
201                        // return a default string like "[Warning: String ..."
202                        return localizer.getString(key, component, "");
203                }
204
205                private String substitute(String string, final Map<String, Object> vars)
206                        throws IllegalStateException
207                {
208                        return new VariableInterpolator(string, Application.get()
209                                .getResourceSettings()
210                                .getThrowExceptionOnMissingResource())
211                        {
212                                private static final long serialVersionUID = 1L;
213
214                                @SuppressWarnings({ "rawtypes", "unchecked" })
215                                @Override
216                                protected String getValue(String variableName)
217                                {
218                                        Object value = vars.get(variableName);
219                                        
220                                        if (value == null ||value instanceof String)
221                                        {
222                                                return String.valueOf(value);
223                                        }
224                                        
225                                        IConverter converter = getConverter(value.getClass());
226                                        
227                                        if (converter == null)
228                                        {
229                                                return Strings.toString(value);
230                                        }
231                                        else
232                                        {
233                                                return converter.convertToString(value, getLocale());
234                                        }
235                                }
236                        }.toString();
237                }
238
239                /**
240                 * Creates a new params map that additionally contains the default input, name, label
241                 * parameters
242                 * 
243                 * @param params
244                 *            original params map
245                 * @return new params map
246                 */
247                private Map<String, Object> addDefaultVars(Map<String, Object> params)
248                {
249                        // create and fill the new params map
250                        final HashMap<String, Object> fullParams;
251                        if (params == null)
252                        {
253                                fullParams = new HashMap<>(6);
254                        }
255                        else
256                        {
257                                fullParams = new HashMap<>(params.size() + 6);
258                                fullParams.putAll(params);
259                        }
260
261                        // add the input param if not already present
262                        if (!fullParams.containsKey("input"))
263                        {
264                                fullParams.put("input", getInput());
265                        }
266
267                        // add the name param if not already present
268                        if (!fullParams.containsKey("name"))
269                        {
270                                fullParams.put("name", getId());
271                        }
272
273                        // add the label param if not already present
274                        if (!fullParams.containsKey("label"))
275                        {
276                                fullParams.put("label", getLabel());
277                        }
278                        return fullParams;
279                }
280
281                /**
282                 * @return value of label param for this form component
283                 */
284                private String getLabel()
285                {
286                        final FormComponent<T> fc = FormComponent.this;
287                        String label = null;
288
289                        // first try the label model ...
290                        if (fc.getLabel() != null)
291                        {
292                                label = fc.getLabel().getObject();
293                        }
294                        // ... then try a resource of format [form-component-id] with
295                        // default of '[form-component-id]'
296                        if (label == null)
297                        {
298
299                                label = fc.getDefaultLabel();
300                        }
301                        return label;
302                }
303        }
304
305        @Override
306        protected void onBeforeRender()
307        {
308                // WICKET-7101 update related auto-label
309                getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(handler -> updateAutoLabels(handler, true));
310                super.onBeforeRender();
311        }
312
313        /**
314         * Adapter that makes this component appear as {@link IValidatable}
315         * 
316         * @author ivaynberg
317         */
318        private class ValidatableAdapter implements IValidatable<T>
319        {
320                /**
321                 * @see org.apache.wicket.validation.IValidatable#error(org.apache.wicket.validation.IValidationError)
322                 */
323                @Override
324                public void error(IValidationError error)
325                {
326                        FormComponent.this.error(error);
327                }
328
329                /**
330                 * @see org.apache.wicket.validation.IValidatable#getValue()
331                 */
332                @Override
333                public T getValue()
334                {
335                        return getConvertedInput();
336                }
337
338                /**
339                 * @see org.apache.wicket.validation.IValidatable#isValid()
340                 */
341                @Override
342                public boolean isValid()
343                {
344                        return FormComponent.this.isValid();
345                }
346
347                @Override
348                public IModel<T> getModel()
349                {
350                        return FormComponent.this.getModel();
351                }
352        }
353
354        /**
355         * The value separator
356         */
357        public static final String VALUE_SEPARATOR = ";";
358
359        private static final String[] EMPTY_STRING_ARRAY = new String[] { "" };
360
361        /** Whether or not this component's value is required (non-empty) */
362        private static final short FLAG_REQUIRED = FLAG_RESERVED3;
363
364        private static final String NO_RAW_INPUT = "[-NO-RAW-INPUT-]";
365
366        private static final long serialVersionUID = 1L;
367
368        /**
369         * Make empty strings null values boolean. Used by AbstractTextComponent subclass.
370         */
371        protected static final short FLAG_CONVERT_EMPTY_INPUT_STRING_TO_NULL = FLAG_RESERVED1;
372
373        /**
374         * Visits any form components inside component if it is a container, or component itself if it
375         * is itself a form component
376         * 
377         * @param <R>
378         *            the type of the visitor's result
379         * 
380         * @param component
381         *            starting point of the traversal
382         * 
383         * @param visitor
384         *            The visitor to call
385         * @return the visitor's result
386         */
387        public static <R> R visitFormComponentsPostOrder(Component component,
388                final IVisitor<? extends FormComponent<?>, R> visitor)
389        {
390                return Visits.visitPostOrder(component, visitor, new IVisitFilter()
391                {
392
393                        @Override
394                        public boolean visitChildren(Object object)
395                        {
396                                if (object instanceof IFormVisitorParticipant)
397                                {
398                                        return ((IFormVisitorParticipant)object).processChildren();
399                                }
400                                return true;
401                        }
402
403                        @Override
404                        public boolean visitObject(Object object)
405                        {
406                                return (object instanceof FormComponent<?>);
407                        }
408
409                });
410
411        }
412
413        /**
414         * Visits any form components inside component if it is a container, or component itself if it
415         * is itself a form component
416         * 
417         * @param <R>
418         *            the type of the visitor's result
419         * @param component
420         *            starting point of the traversal
421         * 
422         * @param visitor
423         *            The visitor to call
424         * @return the visitor's result
425         */
426        public static <R> R visitComponentsPostOrder(Component component,
427                final org.apache.wicket.util.visit.IVisitor<Component, R> visitor)
428        {
429                Args.notNull(visitor, "visitor");
430
431                return Visits.visitPostOrder(component, visitor, new IVisitFilter()
432                {
433                        @Override
434                        public boolean visitObject(Object object)
435                        {
436                                return true;
437                        }
438
439                        @Override
440                        public boolean visitChildren(Object object)
441                        {
442                                if (object instanceof IFormVisitorParticipant)
443                                {
444                                        return ((IFormVisitorParticipant)object).processChildren();
445                                }
446                                return true;
447                        }
448
449                });
450        }
451
452        private transient T convertedInput;
453
454        /**
455         * Raw Input entered by the user or NO_RAW_INPUT if nothing is filled in.
456         */
457        private String rawInput = NO_RAW_INPUT;
458
459        /**
460         * Type that the raw input string will be converted to
461         */
462        private String typeName;
463
464        /**
465         * @see org.apache.wicket.Component#Component(String)
466         */
467        public FormComponent(final String id)
468        {
469                this(id, null);
470        }
471
472        /**
473         * @param id
474         * @param model
475         * @see org.apache.wicket.Component#Component(String, IModel)
476         */
477        public FormComponent(final String id, IModel<T> model)
478        {
479                super(id, model);
480                // the form decides whether form components are versioned or not
481                // see Form.setVersioned
482                setVersioned(false);
483        }
484
485        /**
486         * Gets the string the component would use as a label when one was requested but no label model
487         * was set via {@link #getLabel()}. The value of this string is usually set in a property file;
488         * if the value is not set the default value equivalent to component id will be returned.
489         * 
490         * @return localized label
491         */
492        public final String getDefaultLabel()
493        {
494                return getDefaultLabel(getId());
495        }
496
497        /**
498         * Gets the string the component would use as a label when one was requested but no label model
499         * was set via {@link #getLabel()}. The value of this string is usually set in a property file;
500         * if the value is not set the {@code defaultValue} will be returned.
501         * 
502         * @param defaultValue
503         * 
504         * @return localized label
505         */
506        public final String getDefaultLabel(String defaultValue)
507        {
508                return getLocalizer().getString(getId(), getParent(), defaultValue);
509        }
510
511
512        /**
513         * Adds a validator to this form component
514         * 
515         * @param validator
516         *            validator to be added
517         * @return <code>this</code> for chaining
518         * @throws IllegalArgumentException
519         *             if validator is null
520         * @see IValidator
521         */
522        @SuppressWarnings({ "rawtypes", "unchecked" })
523        public final FormComponent<T> add(final IValidator<? super T> validator)
524        {
525                Args.notNull(validator, "validator");
526
527                if (validator instanceof Behavior)
528                {
529                        add((Behavior)validator);
530                }
531                else
532                {
533                        add((Behavior)new ValidatorAdapter(validator));
534                }
535                return this;
536        }
537
538        /**
539         * Removes a validator from the form component.
540         * 
541         * @param validator
542         *            validator
543         * @throws IllegalArgumentException
544         *             if validator is null or not found
545         * @see IValidator
546         * @see #add(IValidator)
547         * @return form component for chaining
548         */
549        public final FormComponent<T> remove(final IValidator<? super T> validator)
550        {
551                Args.notNull(validator, "validator");
552                Behavior match = null;
553                for (Behavior behavior : getBehaviors())
554                {
555                        if (behavior.equals(validator))
556                        {
557                                match = behavior;
558                                break;
559                        }
560                        else if (behavior instanceof ValidatorAdapter)
561                        {
562                                if (((ValidatorAdapter<?>)behavior).getValidator().equals(validator))
563                                {
564                                        match = behavior;
565                                        break;
566                                }
567                        }
568                }
569
570                if (match != null)
571                {
572                        remove(match);
573                }
574                else
575                {
576                        throw new IllegalStateException(
577                                "Tried to remove validator that was not previously added. "
578                                        + "Make sure your validator's equals() implementation is sufficient");
579                }
580                return this;
581        }
582
583        /**
584         * Adds a validator to this form component.
585         * 
586         * @param validators
587         *            The validator(s) to be added
588         * @return This
589         * @throws IllegalArgumentException
590         *             if validator is null
591         * @see IValidator
592         */
593        public final FormComponent<T> add(final IValidator<? super T>... validators)
594        {
595                Args.notNull(validators, "validators");
596
597                for (IValidator<? super T> validator : validators)
598                {
599                        add(validator);
600                }
601
602                // return this for chaining
603                return this;
604        }
605
606        /**
607         * Checks if the form component's 'required' requirement is met by first checking
608         * {@link #isRequired()} to see if it has to check for requirement. If that is true then by
609         * default it checks if the input is null or an empty String
610         * {@link Strings#isEmpty(CharSequence)}
611         * <p>
612         * Subclasses that overwrite this method should also call {@link #isRequired()} first.
613         * </p>
614         * 
615         * @return true if the 'required' requirement is met, false otherwise
616         * 
617         * @see Strings#isEmpty(CharSequence)
618         * @see #isInputNullable()
619         */
620        public boolean checkRequired()
621        {
622                if (isRequired())
623                {
624                        final String input = getInput();
625
626                        // when null, check whether this is natural for that component, or
627                        // whether - as is the case with text fields - this can only happen
628                        // when the component was disabled
629                        if (input == null && !isInputNullable() && !isEnabledInHierarchy())
630                        {
631                                // this value must have come from a disabled field
632                                // do not perform validation
633                                return true;
634                        }
635
636                        // perform validation by looking whether the value is null or empty
637                        return !Strings.isEmpty(input);
638                }
639                return true;
640        }
641
642        /**
643         * Clears the user input.
644         */
645        public void clearInput()
646        {
647                rawInput = NO_RAW_INPUT;
648        }
649
650        /**
651         * Reports a validation error against this form component.
652         * 
653         * The actual error is reported by creating a {@link ValidationErrorFeedback} object that holds
654         * both the validation error and the generated error message - so a custom feedback panel can
655         * have access to both.
656         * 
657         * @param error
658         *            validation error
659         */
660        public void error(IValidationError error)
661        {
662                Args.notNull(error, "error");
663
664                MessageSource source = new MessageSource();
665                Serializable message = error.getErrorMessage(source);
666
667                if (message == null)
668                {
669                        StringBuilder buffer = new StringBuilder();
670                        buffer.append("Could not locate error message for component: ");
671                        buffer.append(Classes.simpleName(getClass()));
672                        buffer.append("@");
673                        buffer.append(getPageRelativePath());
674                        buffer.append(" and error: ");
675                        buffer.append(error.toString());
676                        buffer.append(". Tried keys: ");
677                        Iterator<String> keys = source.triedKeys.iterator();
678                        while (keys.hasNext())
679                        {
680                                buffer.append(keys.next());
681                                if (keys.hasNext())
682                                {
683                                        buffer.append(", ");
684                                }
685                        }
686                        buffer.append('.');
687                        message = buffer.toString();
688                        logger.warn(message.toString());
689                }
690                error(message);
691        }
692
693        /**
694         * Gets the converted input. The converted input is set earlier though the implementation of
695         * {@link #convertInput()}.
696         * 
697         * {@link FormComponentPanel} often access this method when constructing their converted input
698         * value which is often the combination of converted values of the embedded FormComponents
699         * 
700         * To access the model object resulted by the full form processing, use
701         * {@link #getModelObject()} instead, that is an generified version of
702         * {@link #getDefaultModelObject()}
703         * 
704         * @return value of input possibly converted into an appropriate type
705         */
706        public final T getConvertedInput()
707        {
708                return convertedInput;
709        }
710
711        /**
712         * Sets the converted input. This method is typically not called by clients, unless they
713         * override {@link #convertInput()}, in which case they should call this method to update the
714         * input for this component instance.
715         * 
716         * @param convertedInput
717         *            the converted input
718         */
719        public final void setConvertedInput(T convertedInput)
720        {
721                this.convertedInput = convertedInput;
722        }
723
724        /**
725         * @return The parent form for this form component
726         */
727        public Form<?> getForm()
728        {
729                Form<?> form = Form.findForm(this);
730                if (form == null)
731                {
732                        throw new WicketRuntimeException("Could not find Form parent for " + this);
733                }
734                return form;
735        }
736
737
738        /**
739         * Gets the request parameter for this component as a string.
740         * 
741         * @return The value in the request for this component
742         */
743        public String getInput()
744        {
745                String[] input = getInputAsArray();
746                if (input == null || input.length == 0)
747                {
748                        return null;
749                }
750                else
751                {
752                        return trim(input[0]);
753                }
754        }
755
756        /**
757         * Gets the request parameters for this component as strings.
758         * 
759         * @return The values in the request for this component
760         */
761        public String[] getInputAsArray()
762        {
763                List<StringValue> list = getParameterValues(getInputName());
764
765                String[] values = null;
766                if (list != null)
767                {
768                        values = new String[list.size()];
769                        for (int i = 0; i < list.size(); ++i)
770                        {
771                                values[i] = list.get(i).toString();
772                        }
773                }
774
775                if (!isInputNullable())
776                {
777                        if (values != null && values.length == 1 && values[0] == null)
778                        {
779                                // the key got passed in (otherwise values would be null),
780                                // but the value was set to null.
781                                // As the servlet spec isn't clear on what to do with 'empty'
782                                // request values - most return an empty string, but some null -
783                                // we have to workaround here and deliberately set to an empty
784                                // string if the component is not nullable (text components)
785                                return EMPTY_STRING_ARRAY;
786                        }
787                }
788                return values;
789        }
790
791        /**
792         * Reads the value(s) of the request parameter with name <em>inputName</em>
793         * from either the query parameters for <em>GET</em> method or the request body
794         * for <em>POST</em> method.
795         *
796         * @param inputName
797         *      The name of the request parameter
798         * @return The parameter's value(s)
799         */
800        protected List<StringValue> getParameterValues(String inputName)
801        {
802                final IRequestParameters parameters = Form.getRequestParameters(this);
803
804                return parameters.getParameterValues(inputName);
805        }
806
807        /**
808         * Gets the string to be used for the <tt>name</tt> attribute of the form element. Generated
809         * using the path from the form to the component, excluding the form itself. Override it if you
810         * want even a smaller name. E.g. if you know for sure that the id is unique within a form.
811         * 
812         * @return The string to use as the form element's name attribute
813         */
814        public String getInputName()
815        {
816                String inputName = Form.getRootFormRelativeId(this);
817                Form<?> form = findParent(Form.class);
818
819                if (form != null)
820                {
821                        return form.getInputNamePrefix() + inputName;
822                }
823                else
824                {
825                        return inputName;
826                }
827        }
828
829        /**
830         * Use hasRawInput() to check if this component has raw input because null can mean 2 things: It
831         * doesn't have rawinput or the rawinput is really null.
832         * 
833         * @return The raw form input that is stored for this formcomponent
834         */
835        public final String getRawInput()
836        {
837                return NO_RAW_INPUT.equals(rawInput) ? null : rawInput;
838        }
839
840        /**
841         * @return the type to use when updating the model for this form component
842         */
843        @SuppressWarnings("unchecked")
844        public final Class<T> getType()
845        {
846                return typeName == null ? null : (Class<T>)WicketObjects.resolveClass(typeName);
847        }
848
849        /**
850         * @see Form#getValidatorKeyPrefix()
851         * @return prefix used when constructing validator key messages
852         */
853        public String getValidatorKeyPrefix()
854        {
855                Form<?> form = findParent(Form.class);
856                if (form != null)
857                {
858                        return getForm().getValidatorKeyPrefix();
859                }
860                return null;
861        }
862
863        /**
864         * Gets an unmodifiable list of validators for this FormComponent.
865         * 
866         * @return List of validators
867         */
868        @SuppressWarnings("unchecked")
869        public final List<IValidator<? super T>> getValidators()
870        {
871                final List<IValidator<? super T>> list = new ArrayList<>();
872
873                for (Behavior behavior : getBehaviors())
874                {
875                        if (behavior instanceof IValidator)
876                        {
877                                list.add((IValidator<? super T>)behavior);
878                        }
879                }
880
881                return Collections.unmodifiableList(list);
882        }
883
884        /**
885         * Gets current value for a form component, which can be either input data entered by the user,
886         * or the component's model object if no input was provided.
887         * 
888         * @return The value
889         */
890        public final String getValue()
891        {
892                if (NO_RAW_INPUT.equals(rawInput))
893                {
894                        return getModelValue();
895                }
896                else
897                {
898                        if (getEscapeModelStrings() && rawInput != null)
899                        {
900                                return Strings.escapeMarkup(rawInput).toString();
901                        }
902                        return rawInput;
903                }
904        }
905
906        /**
907         * Returns whether this component has raw input. Raw input is unconverted input straight from
908         * the client.
909         * 
910         * @return boolean whether this component has raw input.
911         */
912        public final boolean hasRawInput()
913        {
914                return !NO_RAW_INPUT.equals(rawInput);
915        }
916
917        /**
918         * Used by Form to tell the FormComponent that a new user input is available
919         */
920        public final void inputChanged()
921        {
922                if (isVisibleInHierarchy() && isEnabledInHierarchy())
923                {
924                        // Get input as String array
925                        final String[] input = getInputAsArray();
926
927                        // If there is any input
928                        if (input != null && input.length > 0 && input[0] != null)
929                        {
930                                // join the values together with ";", for example, "id1;id2;id3"
931                                rawInput = StringList.valueOf(input).join(VALUE_SEPARATOR);
932                        }
933                        else if (isInputNullable())
934                        {
935                                // no input
936                                rawInput = null;
937                        }
938                        else
939                        {
940                                rawInput = NO_RAW_INPUT;
941                        }
942                }
943        }
944
945        /**
946         * Indicate that validation of this form component failed.
947         */
948        public final void invalid()
949        {
950                onInvalid();
951        }
952
953        /**
954         * Gets whether this component's input can be null. By default, components that do not get input
955         * will have null values passed in for input. However, component TextField is an example
956         * (possibly the only one) that never gets a null passed in, even if the field is left empty
957         * UNLESS it has attribute <code>disabled="disabled"</code> set.
958         * 
959         * @return True if this component's input can be null. Returns true by default.
960         */
961        public boolean isInputNullable()
962        {
963                return true;
964        }
965
966        /**
967         * @return True if this component encodes data in a multipart form submit
968         */
969        public boolean isMultiPart()
970        {
971                return false;
972        }
973
974        /**
975         * @return whether or not this component's value is required
976         */
977        public boolean isRequired()
978        {
979                return getFlag(FLAG_REQUIRED);
980        }
981
982        /**
983         * Gets whether this component is 'valid'. Valid in this context means that no validation errors
984         * were reported the last time the form component was processed. This variable not only is
985         * convenient for 'business' use, but is also necessary as we don't want the form component
986         * models updated with invalid input.
987         * 
988         * @return valid whether this component is 'valid'
989         */
990        public final boolean isValid()
991        {
992                class IsValidVisitor implements IVisitor<FormComponent<?>, Boolean>
993                {
994                        @Override
995                        public void component(final FormComponent<?> formComponent, final IVisit<Boolean> visit)
996                        {
997                                if (formComponent.hasErrorMessage())
998                                {
999                                        visit.stop(Boolean.FALSE);
1000                                }
1001                        }
1002                }
1003                IsValidVisitor tmp = new IsValidVisitor();
1004                final Object result = visitFormComponentsPostOrder(this, tmp);
1005                return (Boolean.FALSE != result);
1006        }
1007
1008        /**
1009         * @see IFormVisitorParticipant#processChildren()
1010         */
1011        @Override
1012        public boolean processChildren()
1013        {
1014                return true;
1015        }
1016
1017        /**
1018         * This method will retrieve the request parameter, validate it, and if valid update the model.
1019         * These are the same steps as would be performed by the form.
1020         * 
1021         * This is useful when a formcomponent is used outside a form.
1022         * 
1023         */
1024        public final void processInput()
1025        {
1026                inputChanged();
1027                validate();
1028                if (hasErrorMessage())
1029                {
1030                        invalid();
1031                }
1032                else
1033                {
1034                        valid();
1035                        updateModel();
1036                }
1037        }
1038
1039        /**
1040         * The value will be made available to the validator property by means of ${label}. It does not
1041         * have any specific meaning to FormComponent itself.
1042         * 
1043         * @param labelModel
1044         * @return this for chaining
1045         */
1046        @Override
1047        public FormComponent<T> setLabel(IModel<String> labelModel)
1048        {
1049                super.setLabel(labelModel);
1050                return this;
1051        }
1052
1053        /**
1054         * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
1055         *
1056         * Sets the value for a form component.
1057         * 
1058         * @param value
1059         *            The value
1060         */
1061        public void setModelValue(final String[] value)
1062        {
1063                convertedInput = convertValue(value);
1064                updateModel();
1065        }
1066
1067        /**
1068         * Sets the required flag
1069         * 
1070         * @param required
1071         * @return this for chaining
1072         */
1073        public final FormComponent<T> setRequired(final boolean required)
1074        {
1075                if (!required && getType() != null && getType().isPrimitive())
1076                {
1077                        throw new WicketRuntimeException(
1078                                "FormComponent has to be required when the type is primitive class: " + this);
1079                }
1080                if (required != isRequired())
1081                {
1082                        addStateChange();
1083                }
1084                setFlag(FLAG_REQUIRED, required);
1085                return this;
1086        }
1087
1088        /**
1089         * Sets the type that will be used when updating the model for this component. If no type is
1090         * specified String type is assumed.
1091         * 
1092         * @param type
1093         * @return this for chaining
1094         */
1095        public FormComponent<T> setType(Class<?> type)
1096        {
1097                typeName = type == null ? null : type.getName();
1098                if (type != null && type.isPrimitive())
1099                {
1100                        setRequired(true);
1101                }
1102                return this;
1103        }
1104
1105        /**
1106         * Updates this components model from the request, it expects that the object is already
1107         * converted through the convertInput() call that is called by the validate() method when a form
1108         * is being processed.
1109         * 
1110         * By default it just does this:
1111         * 
1112         * <pre>
1113         * setModelObject(getConvertedInput());
1114         * </pre>
1115         * 
1116         * <strong>DO NOT CALL THIS METHOD DIRECTLY UNLESS YOU ARE SURE WHAT YOU ARE DOING. USUALLY UPDATING
1117         * YOUR MODEL IS HANDLED BY THE FORM, NOT DIRECTLY BY YOU.</strong>
1118         */
1119        @Override
1120        public void updateModel()
1121        {
1122                setModelObject(getConvertedInput());
1123        }
1124
1125
1126        /**
1127         * Called to indicate that the user input is valid.
1128         */
1129        public final void valid()
1130        {
1131                clearInput();
1132
1133                onValid();
1134        }
1135
1136        /**
1137         * Performs full validation of the form component, which consists of calling validateRequired(),
1138         * convertInput(), and validateValidators(). This method should only be used if the form
1139         * component needs to be fully validated outside the form process.
1140         */
1141        public void validate()
1142        {
1143                // clear any previous feedback messages
1144
1145                if (hasFeedbackMessage())
1146                {
1147                        getFeedbackMessages().clear();
1148                }
1149
1150                // validate
1151
1152                validateRequired();
1153                if (isValid())
1154                {
1155                        convertInput();
1156                        if (isValid())
1157                        {
1158                                if (isRequired() && getConvertedInput() == null && isInputNullable())
1159                                {
1160                                        reportRequiredError();
1161                                }
1162                                else
1163                                {
1164                                        validateValidators();
1165                                }
1166                        }
1167                }
1168        }
1169
1170        /**
1171         * Converts and validates the conversion of the raw input string into the object specified by
1172         * {@link FormComponent#getType()} and records any thrown {@link ConversionException}s.
1173         * Converted value is available through {@link FormComponent#getConvertedInput()}.
1174         * 
1175         * <p>
1176         * Usually the user should do custom conversions by specifying an {@link IConverter} by
1177         * registering it with the application by overriding {@link Application#getConverterLocator()},
1178         * or at the component level by overriding {@link #getConverter(Class)} .
1179         * </p>
1180         *
1181         * <strong>DO NOT CALL THIS METHOD DIRECTLY UNLESS YOU ARE SURE WHAT YOU ARE DOING. USUALLY UPDATING
1182         * YOUR MODEL IS HANDLED BY THE FORM, NOT DIRECTLY BY YOU.</strong>
1183         *
1184         * @see IConverterLocator
1185         * @see Application#newConverterLocator()
1186         * @see IConverter#convertToObject(String, Locale)
1187         * @see #newValidationError(ConversionException)
1188         */
1189        public void convertInput()
1190        {
1191                if (typeName == null)
1192                {
1193                        try
1194                        {
1195                                convertedInput = convertValue(getInputAsArray());
1196                        }
1197                        catch (ConversionException e)
1198                        {
1199                                error(newValidationError(e));
1200                        }
1201                }
1202                else
1203                {
1204                        final IConverter<T> converter = getConverter(getType());
1205
1206                        try
1207                        {
1208                                convertedInput = converter.convertToObject(getInput(), getLocale());
1209                        }
1210                        catch (ConversionException e)
1211                        {
1212                                error(newValidationError(e));
1213                        }
1214                }
1215        }
1216
1217        /**
1218         * This method is called, when the validation triggered by {@link FormComponent#convertInput()}
1219         * failed with a {@link ConversionException}, to construct a {@link ValidationError} based on
1220         * the exception.
1221         * <p>
1222         * Override this method to modify the ValidationError object, e.g. add a custom variable for
1223         * message substitution:
1224         * <p>
1225         * 
1226         * <pre>
1227         * new FormComponent&lt;T&gt;(id)
1228         * {
1229         *      protected ValidationError newValidationError(ConversionException cause)
1230         *      {
1231         *              return super.newValidationError(cause).setVariable(&quot;foo&quot;, foovalue);
1232         *      }
1233         * };
1234         * </pre>
1235         * 
1236         * @param cause
1237         *            the original cause
1238         * @return {@link ValidationError}
1239         */
1240        protected ValidationError newValidationError(ConversionException cause)
1241        {
1242                ValidationError error = new ValidationError(cause.getMessage());
1243
1244                if (cause.getResourceKey() != null)
1245                {
1246                        error.addKey(cause.getResourceKey());
1247                }
1248
1249                if (typeName == null)
1250                {
1251                        if (cause.getTargetType() != null)
1252                        {
1253                                error.addKey("ConversionError." + Classes.simpleName(cause.getTargetType()));
1254                        }
1255                        error.addKey("ConversionError");
1256                }
1257                else
1258                {
1259                        String simpleName = Classes.simpleName(getType());
1260                        error.addKey("IConverter." + simpleName);
1261                        error.addKey("IConverter");
1262                        error.setVariable("type", simpleName);
1263                }
1264
1265                final Locale locale = cause.getLocale();
1266                if (locale != null)
1267                {
1268                        error.setVariable("locale", locale);
1269                }
1270
1271                error.setVariable("exception", cause);
1272
1273                Format format = cause.getFormat();
1274                if (format instanceof SimpleDateFormat)
1275                {
1276                        error.setVariable("format", ((SimpleDateFormat)format).toLocalizedPattern());
1277                }
1278
1279                Map<String, Object> variables = cause.getVariables();
1280                if (variables != null)
1281                {
1282                        error.getVariables().putAll(variables);
1283                }
1284
1285                return error;
1286        }
1287
1288        /**
1289         * Subclasses should overwrite this if the conversion is not done through the type field and the
1290         * {@link IConverter}. <strong>WARNING: this method may be removed in future versions.</strong>
1291         * 
1292         * If conversion fails then a ConversionException should be thrown
1293         * 
1294         * @param value
1295         *            The value can be the getInput() or through a cookie
1296         * 
1297         * @return The converted value. default returns just the given value
1298         * @throws ConversionException
1299         *             If input can't be converted
1300         */
1301        @SuppressWarnings("unchecked")
1302        protected T convertValue(String[] value) throws ConversionException
1303        {
1304                return (T)(value != null && value.length > 0 && value[0] != null ? trim(value[0]) : null);
1305        }
1306
1307        /**
1308         * @return Value to return when model value is needed
1309         */
1310        protected String getModelValue()
1311        {
1312                return getDefaultModelObjectAsString();
1313        }
1314
1315        /**
1316         * Gets the request parameter for this component as an int.
1317         * 
1318         * @return The value in the request for this component
1319         */
1320        protected final int inputAsInt()
1321        {
1322                final String string = getInput();
1323                try
1324                {
1325                        return Integer.parseInt(string);
1326                }
1327                catch (NumberFormatException e)
1328                {
1329                        throw new IllegalArgumentException(
1330                                exceptionMessage("Internal error.  Request string '" + string +
1331                                        "' not a valid integer"));
1332                }
1333        }
1334
1335        /**
1336         * Gets the request parameter for this component as an int, using the given default in case no
1337         * corresponding request parameter was found.
1338         * 
1339         * @param defaultValue
1340         *            Default value to return if request does not have an integer for this component
1341         * @return The value in the request for this component
1342         */
1343        protected final int inputAsInt(final int defaultValue)
1344        {
1345                final String string = getInput();
1346                if (string != null)
1347                {
1348                        try
1349                        {
1350                                return Integer.parseInt(string);
1351                        }
1352                        catch (NumberFormatException e)
1353                        {
1354                                throw new IllegalArgumentException(exceptionMessage("Request string '" + string +
1355                                        "' is not a valid integer"));
1356                        }
1357                }
1358                else
1359                {
1360                        return defaultValue;
1361                }
1362        }
1363
1364        /**
1365         * Gets the request parameters for this component as ints.
1366         * 
1367         * @return The values in the request for this component
1368         */
1369        protected final int[] inputAsIntArray()
1370        {
1371                final String[] strings = getInputAsArray();
1372                if (strings != null)
1373                {
1374                        final int[] ints = new int[strings.length];
1375                        for (int i = 0; i < strings.length; i++)
1376                        {
1377                                ints[i] = Integer.parseInt(strings[i]);
1378                        }
1379                        return ints;
1380                }
1381                return null;
1382        }
1383
1384        /**
1385         * @see org.apache.wicket.Component#internalOnModelChanged()
1386         */
1387        @Override
1388        protected void internalOnModelChanged()
1389        {
1390                // If the model for this form component changed, we should make it
1391                // valid again because there can't be any invalid input for it anymore.
1392                valid();
1393        }
1394
1395        /**
1396         * Processes the component tag.
1397         * 
1398         * @param tag
1399         *            Tag to modify
1400         * @see org.apache.wicket.Component#onComponentTag(ComponentTag)
1401         */
1402        @Override
1403        protected void onComponentTag(final ComponentTag tag)
1404        {
1405                tag.put("name", getInputName());
1406
1407                if (!isEnabledInHierarchy())
1408                {
1409                        onDisabled(tag);
1410                }
1411
1412                super.onComponentTag(tag);
1413        }
1414
1415        /**
1416         * Sets the temporary converted input value to null.
1417         * 
1418         * @see org.apache.wicket.Component#onDetach()
1419         */
1420        @Override
1421        protected void onDetach()
1422        {
1423                super.onDetach();
1424                convertedInput = null;
1425        }
1426
1427        /**
1428         * Called by {@link #onComponentTag(ComponentTag)} when the component is disabled. By default,
1429         * this method will add a disabled="disabled" attribute to the tag. Components may override this
1430         * method to tweak the tag as they think is fit.
1431         * 
1432         * @param tag
1433         *            the tag that is being rendered
1434         */
1435        protected void onDisabled(final ComponentTag tag)
1436        {
1437                tag.put("disabled", "disabled");
1438        }
1439
1440        /**
1441         * Handle invalidation
1442         */
1443        protected void onInvalid()
1444        {
1445        }
1446
1447        /**
1448         * Handle validation
1449         */
1450        protected void onValid()
1451        {
1452        }
1453
1454        /**
1455         * Determines whether or not this component should trim its input prior to processing it. The
1456         * default value is <code>true</code>
1457         * 
1458         * @return True if the input should be trimmed.
1459         */
1460        protected boolean shouldTrimInput()
1461        {
1462                return true;
1463        }
1464
1465        /**
1466         * Trims the input according to {@link #shouldTrimInput()}
1467         * 
1468         * @param string
1469         * @return trimmed input if {@link #shouldTrimInput()} returns true, unchanged input otherwise
1470         */
1471        protected String trim(String string)
1472        {
1473                String trimmed = string;
1474                if (trimmed != null && shouldTrimInput())
1475                {
1476                        trimmed = trimmed.trim();
1477                }
1478                return trimmed;
1479        }
1480
1481        /**
1482         * Checks if the raw input value is not null if this component is required.
1483         */
1484        protected final void validateRequired()
1485        {
1486                if (!checkRequired())
1487                {
1488                        reportRequiredError();
1489                }
1490        }
1491
1492        /**
1493         * Reports required error against this component
1494         */
1495        protected void reportRequiredError()
1496        {
1497                error(new ValidationError().addKey("Required"));
1498        }
1499
1500        /**
1501         * Validates this component using the component's validators.
1502         */
1503        @SuppressWarnings("unchecked")
1504        protected final void validateValidators()
1505        {
1506                final IValidatable<T> validatable = newValidatable();
1507
1508                boolean isNull = getConvertedInput() == null;
1509
1510                IValidator<T> validator;
1511
1512                for (Behavior behavior : getBehaviors())
1513                {
1514                        if (isBehaviorAccepted(behavior) == false)
1515                        {
1516                                continue;
1517                        }
1518
1519                        validator = null;
1520                        if (behavior instanceof ValidatorAdapter)
1521                        {
1522                                validator = ((ValidatorAdapter<T>)behavior).getValidator();
1523                        }
1524                        else if (behavior instanceof IValidator)
1525                        {
1526                                validator = (IValidator<T>)behavior;
1527                        }
1528                        if (validator != null)
1529                        {
1530                                if (isNull == false || validator instanceof INullAcceptingValidator<?>)
1531                                {
1532                                        try
1533                                        {
1534                                                validator.validate(validatable);
1535                                        }
1536                                        catch (Exception e)
1537                                        {
1538                                                throw new WicketRuntimeException("Exception '" + e.getMessage() +
1539                                                                "' occurred during validation " + validator.getClass().getName() +
1540                                                                " on component " + getPath(), e);
1541                                        }
1542                                }
1543                                if (!isValid())
1544                                {
1545                                        break;
1546                                }
1547                        }
1548                }
1549        }
1550
1551        /**
1552         * Creates an IValidatable that can be used to validate this form component. This validatable
1553         * incorporates error key lookups that correspond to this form component.
1554         * 
1555         * This method is useful when validation needs to happen outside the regular validation workflow
1556         * but error messages should still be properly reported against the form component.
1557         * 
1558         * @return IValidatable<T> for this form component
1559         */
1560        public final IValidatable<T> newValidatable()
1561        {
1562                return new ValidatableAdapter();
1563        }
1564
1565        /**
1566         * Updates auto label css classes such as error/required during ajax updates when the labels may
1567         * not be directly repainted in the response.
1568         * 
1569         * @param target The {@link IPartialPageRequestHandler}
1570         * @param checkAuto if true then we check is the related auto-label is marked as auto before updating it.
1571         */
1572        public final void updateAutoLabels(IPartialPageRequestHandler target, boolean checkAuto)
1573        {
1574                AutoLabelMarker marker = getMetaData(AutoLabelResolver.MARKER_KEY);
1575        
1576                if (marker == null)
1577                {
1578                        // this component does not have an auto label
1579                        return;
1580                }
1581
1582                if (checkAuto)
1583                {
1584                        if (!marker.isAuto())
1585                        {
1586                                return;
1587                        }
1588                        marker.updateFrom(this, target);
1589                        return;
1590                }
1591
1592                marker.updateFrom(this, target);
1593        }
1594
1595
1596        /**
1597         * @deprecated method in favor of the one receiving {@link IPartialPageRequestHandler}
1598         */
1599        @Deprecated(since = "9.17.0, 10.0.0", forRemoval = true)
1600        public final void updateAutoLabels(AjaxRequestTarget target)
1601        {
1602                updateAutoLabels(target, false);
1603        }
1604
1605        /**
1606         * Update the model of a {@link FormComponent} containing a {@link Collection}.
1607         * 
1608         * If the model object does not yet exists, a new suitable collection is filled with the converted
1609         * input and used as the new model object. Otherwise the existing collection is modified
1610         * in-place, then {@link IModel#setObject(Object)} is called with the same instance: it allows
1611         * the Model to be notified of changes even when {@link Model#getObject()} returns a different
1612         * {@link Collection} at every invocation.
1613         * 
1614         * @param <S>
1615         *            collection type
1616         * @param formComponent
1617         *            the form component to update
1618         * @see FormComponent#updateModel()
1619         * @throws WicketRuntimeException
1620         *             if the existing model object collection is unmodifiable and no setter exists
1621         */
1622        public static <S> void updateCollectionModel(FormComponent<Collection<S>> formComponent)
1623        {
1624                Collection<S> convertedInput = formComponent.getConvertedInput();
1625                if (convertedInput == null) {
1626                        convertedInput = Collections.emptyList();
1627                }
1628
1629                Collection<S> collection = formComponent.getModelObject();
1630                if (collection == null)
1631                {
1632                        Class<?> hint = null;
1633                        if (formComponent.getModel() instanceof IObjectClassAwareModel) {
1634                                hint = ((IObjectClassAwareModel)formComponent.getModel()).getObjectClass();
1635                        }
1636                        if (hint == null) {
1637                                hint = List.class;
1638                        }
1639                        collection = newCollection(hint, convertedInput);
1640                        formComponent.setModelObject(collection);
1641                }
1642                else
1643                {
1644                        boolean modified = false;
1645
1646                        formComponent.modelChanging();
1647
1648                        try
1649                        {
1650                                collection.clear();
1651                                collection.addAll(convertedInput);
1652                                modified = true;
1653                        }
1654                        catch (UnsupportedOperationException unmodifiable)
1655                        {
1656                                if (logger.isDebugEnabled())
1657                                {
1658                                        logger.debug("An error occurred while trying to modify the collection attached to "
1659                                                        + formComponent, unmodifiable);
1660                                }
1661                                collection = newCollection(collection.getClass(), convertedInput);
1662                        }
1663                        
1664                        try
1665                        {
1666                                formComponent.getModel().setObject(collection);
1667                        }
1668                        catch (Exception noSetter)
1669                        {
1670                                if (!modified)
1671                                {
1672                                        throw new WicketRuntimeException("An error occurred while trying to set the collection attached to "
1673                                                        + formComponent, noSetter);
1674                                }
1675                                else if (logger.isDebugEnabled())
1676                                {
1677                                        logger.debug("An error occurred while trying to set the collection attached to "
1678                                                        + formComponent, noSetter);
1679                                }
1680                        }
1681                        
1682                        formComponent.modelChanged();
1683                }
1684        }
1685        
1686        /**
1687         * Creates a new collection. 
1688         * 
1689         * @param hint type deciding the type of the returned collection
1690         * @param  elements elements for the new collection
1691         * @return collection
1692         * @throws IllegalArgumentException if type is not supported
1693         */
1694        private static <S> Collection<S> newCollection(Class<?> hint, Collection<S> elements)
1695        {
1696                if (Set.class.isAssignableFrom(hint)) {
1697                        return new HashSet<>(elements);
1698                } else {
1699                        return new ArrayList<>(elements);
1700                }
1701        }
1702}