001    // Copyright 2006, 2007, 2008, 2009, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.corelib.base;
016    
017    import org.apache.tapestry5.*;
018    import org.apache.tapestry5.annotations.*;
019    import org.apache.tapestry5.beaneditor.Width;
020    import org.apache.tapestry5.corelib.mixins.RenderDisabled;
021    import org.apache.tapestry5.ioc.AnnotationProvider;
022    import org.apache.tapestry5.ioc.annotations.Inject;
023    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024    import org.apache.tapestry5.services.ComponentDefaultProvider;
025    import org.apache.tapestry5.services.Request;
026    
027    import java.lang.annotation.Annotation;
028    import java.util.Locale;
029    
030    /**
031     * Abstract class for a variety of components that render some variation of a text field. Most of the hooks for user
032     * input validation are in this class.
033     * <p/>
034     * In particular, all subclasses support the "toclient" and "parseclient" events. These two events allow the normal
035     * {@link Translator} (specified by the translate parameter, but often automatically derived by Tapestry) to be
036     * augmented.
037     * <p/>
038     * If the component container (i.e., the page) provides an event handler method for the "toclient" event, and that
039     * handler returns a non-null string, that will be the string value sent to the client. The context passed to the event
040     * handler method is t he current value of the value parameter.
041     * <p/>
042     * Likewise, on a form submit, the "parseclient" event handler method will be passed the string provided by the client,
043     * and may provide a non-null value as the parsed value. Returning null allows the normal translator to operate. The
044     * event handler may also throw {@link org.apache.tapestry5.ValidationException}.
045     */
046    @Events(
047            {EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT})
048    public abstract class AbstractTextField extends AbstractField
049    {
050        /**
051         * The value to be read and updated. This is not necessarily a string, a translator may be provided to convert
052         * between client side and server side representations. If not bound, a default binding is made to a property of the
053         * container matching the component's id. If no such property exists, then you will see a runtime exception due to
054         * the unbound value parameter.
055         */
056        @Parameter(required = true, principal = true, autoconnect = true)
057        private Object value;
058    
059        /**
060         * The object which will perform translation between server-side and client-side representations. If not specified,
061         * a value will usually be generated based on the type of the value parameter.
062         */
063        @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.TRANSLATE)
064        private FieldTranslator<Object> translate;
065    
066        /**
067         * The object that will perform input validation (which occurs after translation). The validate binding prefix is
068         * generally used to provide this object in a declarative fashion.
069         */
070        @Parameter(defaultPrefix = BindingConstants.VALIDATE)
071        @SuppressWarnings("unchecked")
072        private FieldValidator<Object> validate;
073    
074        /**
075         * Provider of annotations used for some defaults. Annotation are usually provided in terms of the value parameter
076         * (i.e., from the getter and/or setter bound to the value parameter).
077         *
078         * @see org.apache.tapestry5.beaneditor.Width
079         */
080        @Parameter
081        private AnnotationProvider annotationProvider;
082    
083        /**
084         * Defines how nulls on the server side, or sent from the client side, are treated. The selected strategy may
085         * replace the nulls with some other value. The default strategy leaves nulls alone. Another built-in strategy,
086         * zero, replaces nulls with the value 0.
087         */
088        @Parameter(defaultPrefix = BindingConstants.NULLFIELDSTRATEGY, value = "default")
089        private NullFieldStrategy nulls;
090    
091        @Environmental
092        private ValidationTracker tracker;
093    
094        @Inject
095        private ComponentResources resources;
096    
097        @Inject
098        private Locale locale;
099    
100        @Inject
101        private Request request;
102    
103        @Inject
104        private FieldValidationSupport fieldValidationSupport;
105    
106        @SuppressWarnings("unused")
107        @Mixin
108        private RenderDisabled renderDisabled;
109    
110        @Inject
111        private ComponentDefaultProvider defaultProvider;
112    
113        /**
114         * Computes a default value for the "translate" parameter using
115         * {@link org.apache.tapestry5.services.ComponentDefaultProvider#defaultTranslator(String, org.apache.tapestry5.ComponentResources)}
116         * .
117         */
118        final Binding defaultTranslate()
119        {
120            return defaultProvider.defaultTranslatorBinding("value", resources);
121        }
122    
123        final AnnotationProvider defaultAnnotationProvider()
124        {
125            return new AnnotationProvider()
126            {
127                public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
128                {
129                    return resources.getParameterAnnotation("value", annotationClass);
130                }
131            };
132        }
133    
134        /**
135         * Computes a default value for the "validate" parameter using
136         * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}.
137         */
138        final Binding defaultValidate()
139        {
140            return defaultProvider.defaultValidatorBinding("value", resources);
141        }
142    
143        @SuppressWarnings(
144                {"unchecked"})
145        @BeginRender
146        void begin(MarkupWriter writer)
147        {
148            String value = tracker.getInput(this);
149    
150            // If this is a response to a form submission, and the user provided a value.
151            // then send that exact value back at them.
152    
153            if (value == null)
154            {
155                // Otherwise, get the value from the parameter ...
156                // Then let the translator and or various triggered events get it into
157                // a format ready to be sent to the client.
158    
159                value = fieldValidationSupport.toClient(this.value, resources, translate, nulls);
160            }
161    
162            writeFieldTag(writer, value);
163    
164            putPropertyNameIntoBeanValidationContext("value");
165    
166            translate.render(writer);
167            validate.render(writer);
168    
169            removePropertyNameFromBeanValidationContext();
170    
171            resources.renderInformalParameters(writer);
172    
173            decorateInsideField();
174        }
175    
176        /**
177         * Invoked from {@link #begin(MarkupWriter)} to write out the element and attributes (typically, &lt;input&gt;). The
178         * {@linkplain AbstractField#getControlName() controlName} and {@linkplain AbstractField#getClientId() clientId}
179         * properties will already have been set or updated.
180         * <p/>
181         * Generally, the subclass will invoke {@link MarkupWriter#element(String, Object[])}, and will be responsible for
182         * including an {@link AfterRender} phase method to invoke {@link MarkupWriter#end()}.
183         *
184         * @param writer markup write to send output to
185         * @param value  the value (either obtained and translated from the value parameter, or obtained from the tracker)
186         */
187        protected abstract void writeFieldTag(MarkupWriter writer, String value);
188    
189        @SuppressWarnings(
190                {"unchecked"})
191        @Override
192        protected void processSubmission(String controlName)
193        {
194            String rawValue = request.getParameter(controlName);
195    
196            tracker.recordInput(this, rawValue);
197    
198            try
199            {
200                Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls);
201    
202                putPropertyNameIntoBeanValidationContext("value");
203    
204                fieldValidationSupport.validate(translated, resources, validate);
205    
206                // If the value provided is blank and we're ignoring blank input (i.e. PasswordField),
207                // then don't update the value parameter.
208    
209                if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue)))
210                    value = translated;
211            } catch (ValidationException ex)
212            {
213                tracker.recordError(this, ex.getMessage());
214            }
215    
216            removePropertyNameFromBeanValidationContext();
217        }
218    
219        /**
220         * Should blank input be ignored (after validation)? This will be true for
221         * {@link org.apache.tapestry5.corelib.components.PasswordField}.
222         */
223        protected boolean ignoreBlankInput()
224        {
225            return false;
226        }
227    
228        @Override
229        public boolean isRequired()
230        {
231            return validate.isRequired();
232        }
233    
234        /**
235         * Looks for a {@link org.apache.tapestry5.beaneditor.Width} annotation and, if present, returns its value as a
236         * string.
237         *
238         * @return the indicated width, or null if the annotation is not present
239         */
240        protected final String getWidth()
241        {
242            Width width = annotationProvider.getAnnotation(Width.class);
243    
244            if (width == null)
245                return null;
246    
247            return Integer.toString(width.value());
248        }
249    }