001    // Copyright 2004, 2005 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.tapestry.valid;
016    
017    import java.text.MessageFormat;
018    import java.util.HashMap;
019    import java.util.Locale;
020    import java.util.Map;
021    import java.util.ResourceBundle;
022    
023    import org.apache.hivemind.HiveMind;
024    import org.apache.hivemind.Resource;
025    import org.apache.hivemind.impl.DefaultClassResolver;
026    import org.apache.hivemind.util.ClasspathResource;
027    import org.apache.hivemind.util.PropertyUtils;
028    import org.apache.tapestry.IForm;
029    import org.apache.tapestry.IMarkupWriter;
030    import org.apache.tapestry.IRequestCycle;
031    import org.apache.tapestry.IScript;
032    import org.apache.tapestry.PageRenderSupport;
033    import org.apache.tapestry.TapestryUtils;
034    import org.apache.tapestry.engine.IScriptSource;
035    import org.apache.tapestry.form.IFormComponent;
036    
037    /**
038     * Abstract base class for {@link IValidator}. Supports a required and locale property.
039     * 
040     * @author Howard Lewis Ship
041     * @since 1.0.8
042     */
043    
044    public abstract class BaseValidator implements IValidator
045    {
046        /**
047         * Input Symbol used to represent the field being validated.
048         * 
049         * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
050         * @since 2.2
051         */
052    
053        public static final String FIELD_SYMBOL = "field";
054    
055        /**
056         * Input symbol used to represent the validator itself to the script.
057         * 
058         * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
059         * @since 2.2
060         */
061    
062        public static final String VALIDATOR_SYMBOL = "validator";
063    
064        /**
065         * Input symbol used to represent the {@link IForm}containing the field to the script.
066         * 
067         * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
068         * @since 2.2
069         */
070    
071        public static final String FORM_SYMBOL = "form";
072    
073        /**
074         * Output symbol set by the script asthe name of the validator JavaScript function. The function
075         * implemented must return true or false (true if the field is valid, false otherwise). After
076         * the script is executed, the function is added to the {@link IForm}as a
077         * {@link org.apache.tapestry.form.FormEventType#SUBMIT}.
078         * 
079         * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
080         * @since 2.2
081         */
082    
083        public static final String FUNCTION_SYMBOL = "function";
084    
085        private boolean _required;
086    
087        /** @since 3.0 */
088    
089        private String _requiredMessage;
090    
091        /**
092         * @since 2.2
093         */
094    
095        private boolean _clientScriptingEnabled = false;
096        
097        /**
098         * @since 4.1
099         */
100        private IScriptSource _scriptSource;
101        
102        /**
103         * Standard constructor. Leaves locale as system default and required as false.
104         */
105    
106        public BaseValidator()
107        {
108        }
109    
110        /**
111         * Allow the validator to be initialized with a property initialization string.
112         * 
113         * @since 4.0
114         */
115        public BaseValidator(String initializer)
116        {
117            PropertyUtils.configureProperties(this, initializer);
118        }
119    
120        protected BaseValidator(boolean required)
121        {
122            _required = required;
123        }
124        
125        public boolean isRequired()
126        {
127            return _required;
128        }
129    
130        public void setRequired(boolean required)
131        {
132            _required = required;
133        }
134        
135        public void setScriptSource(IScriptSource scriptSource)
136        {
137            _scriptSource = scriptSource;
138        }
139        
140        /**
141         * Gets a pattern, either as the default value, or as a localized key. If override is null, then
142         * the key from the <code>org.apache.tapestry.valid.ValidationStrings</code>
143         * {@link ResourceBundle}(in the specified locale) is used. The pattern can then be used with
144         * {@link #formatString(String, Object[])}.
145         * <p>
146         * Why do we not just lump these strings into TapestryStrings.properties? because
147         * TapestryStrings.properties is localized to the server's locale, which is fine for the
148         * logging, debugging and error messages it contains. For field validation, whose errors are
149         * visible to the end user normally, we want to localize to the page's locale.
150         * 
151         * @param override
152         *            The override value for the localized string from the bundle.
153         * @param key
154         *            used to lookup pattern from bundle, if override is null.
155         * @param locale
156         *            used to get right localization of bundle.
157         * @since 3.0
158         */
159    
160        protected String getPattern(String override, String key, Locale locale)
161        {
162            if (override != null)
163                return override;
164    
165            ResourceBundle strings = ResourceBundle.getBundle(
166                    "org.apache.tapestry.valid.ValidationStrings",
167                    locale);
168    
169            return strings.getString(key);
170        }
171    
172        /**
173         * Gets a string from the standard resource bundle. The string in the bundle is treated as a
174         * pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
175         * 
176         * @param pattern
177         *            string the input pattern to be used with
178         *            {@link MessageFormat#format(java.lang.String, java.lang.Object[])}. It may
179         *            contain replaceable parameters, {0}, {1}, etc.
180         * @param args
181         *            the arguments used to fill replaceable parameters {0}, {1}, etc.
182         * @since 3.0
183         */
184    
185        protected String formatString(String pattern, Object[] args)
186        {
187            return MessageFormat.format(pattern, args);
188        }
189    
190        /**
191         * Convienience method for invoking {@link #formatString(String, Object[])}.
192         * 
193         * @since 3.0
194         */
195    
196        protected String formatString(String pattern, Object arg)
197        {
198            return formatString(pattern, new Object[]
199            { arg });
200        }
201    
202        /**
203         * Convienience method for invoking {@link #formatString(String, Object[])}.
204         * 
205         * @since 3.0
206         */
207    
208        protected String formatString(String pattern, Object arg1, Object arg2)
209        {
210            return formatString(pattern, new Object[]
211            { arg1, arg2 });
212        }
213    
214        /**
215         * Invoked to check if the value is null. If the value is null (or empty), but the required flag
216         * is set, then this method throws a {@link ValidatorException}. Otherwise, returns true if the
217         * value is null.
218         */
219    
220        protected boolean checkRequired(IFormComponent field, String value) throws ValidatorException
221        {
222            boolean isEmpty = HiveMind.isBlank(value);
223    
224            if (_required && isEmpty)
225                throw new ValidatorException(buildRequiredMessage(field), ValidationConstraint.REQUIRED);
226    
227            return isEmpty;
228        }
229    
230        /**
231         * Builds an error message indicating a value for a required field was not supplied.
232         * 
233         * @since 3.0
234         */
235    
236        protected String buildRequiredMessage(IFormComponent field)
237        {
238            String pattern = getPattern(_requiredMessage, "field-is-required", field.getPage()
239                    .getLocale());
240    
241            return formatString(pattern, field.getDisplayName());
242        }
243    
244        /**
245         * This implementation does nothing. Subclasses may supply their own implementation.
246         * 
247         * @since 2.2
248         */
249    
250        public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer,
251                IRequestCycle cycle)
252        {
253        }
254    
255        /**
256         * Invoked (from sub-class implementations of
257         * {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}to process
258         * a standard validation script. This expects that:
259         * <ul>
260         * <li>The {@link IFormComponent}is (ultimately) wrapped by a {@link Body}
261         * <li>The script generates a symbol named "function" (as per {@link #FUNCTION_SYMBOL})
262         * </ul>
263         * 
264         * @param scriptPath
265         *            the resource path of the script to execute
266         * @param cycle
267         *            The active request cycle
268         * @param field
269         *            The field to be validated
270         * @param symbols
271         *            a set of input symbols needed by the script. These symbols are augmented with
272         *            symbols for the field, form and validator. symbols may be null, but will be
273         *            modified if not null.
274         * @throws ApplicationRuntimeException
275         *             if there's an error processing the script.
276         * @since 2.2
277         */
278    
279        protected void processValidatorScript(String scriptPath, IRequestCycle cycle,
280                IFormComponent field, Map symbols)
281        {
282            IForm form = field.getForm();
283            
284            Map finalSymbols = (symbols == null) ? new HashMap() : symbols;
285            
286            finalSymbols.put(FIELD_SYMBOL, field);
287            finalSymbols.put(FORM_SYMBOL, form);
288            finalSymbols.put(VALIDATOR_SYMBOL, this);
289            
290            Resource location = new ClasspathResource(new DefaultClassResolver(), scriptPath);
291            
292            IScript script = _scriptSource.getScript(location);
293    
294            // If there's an error, report it against the field (this validator object doesn't
295            // have a location).
296    
297            PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, field);
298    
299            script.execute(field, cycle, pageRenderSupport, finalSymbols);
300        }
301    
302        /**
303         * Returns true if client scripting is enabled. Some validators are capable of generating
304         * client-side scripting to perform validation when the form is submitted. By default, this flag
305         * is false and subclasses should check it (in
306         * {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}) before
307         * generating client side script.
308         * 
309         * @since 2.2
310         */
311    
312        public boolean isClientScriptingEnabled()
313        {
314            return _clientScriptingEnabled;
315        }
316    
317        public void setClientScriptingEnabled(boolean clientScriptingEnabled)
318        {
319            _clientScriptingEnabled = clientScriptingEnabled;
320        }
321    
322        public String getRequiredMessage()
323        {
324            return _requiredMessage;
325        }
326    
327        /**
328         * Overrides the <code>field-is-required</code> bundle key. Parameter {0} is the display name
329         * of the field.
330         * 
331         * @since 3.0
332         */
333    
334        public void setRequiredMessage(String string)
335        {
336            _requiredMessage = string;
337        }
338    
339    }