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.util.ClasspathResource;
026    import org.apache.hivemind.util.PropertyUtils;
027    import org.apache.tapestry.IEngine;
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         * Standard constructor. Leaves locale as system default and required as false.
099         */
100    
101        public BaseValidator()
102        {
103        }
104    
105        /**
106         * Allow the validator to be initialized with a property initialization string.
107         * 
108         * @since 4.0
109         */
110        public BaseValidator(String initializer)
111        {
112            PropertyUtils.configureProperties(this, initializer);
113        }
114    
115        protected BaseValidator(boolean required)
116        {
117            _required = required;
118        }
119    
120        public boolean isRequired()
121        {
122            return _required;
123        }
124    
125        public void setRequired(boolean required)
126        {
127            _required = required;
128        }
129    
130        /**
131         * Gets a pattern, either as the default value, or as a localized key. If override is null, then
132         * the key from the <code>org.apache.tapestry.valid.ValidationStrings</code>
133         * {@link ResourceBundle}(in the specified locale) is used. The pattern can then be used with
134         * {@link #formatString(String, Object[])}.
135         * <p>
136         * Why do we not just lump these strings into TapestryStrings.properties? because
137         * TapestryStrings.properties is localized to the server's locale, which is fine for the
138         * logging, debugging and error messages it contains. For field validation, whose errors are
139         * visible to the end user normally, we want to localize to the page's locale.
140         * 
141         * @param override
142         *            The override value for the localized string from the bundle.
143         * @param key
144         *            used to lookup pattern from bundle, if override is null.
145         * @param locale
146         *            used to get right localization of bundle.
147         * @since 3.0
148         */
149    
150        protected String getPattern(String override, String key, Locale locale)
151        {
152            if (override != null)
153                return override;
154    
155            ResourceBundle strings = ResourceBundle.getBundle(
156                    "org.apache.tapestry.valid.ValidationStrings",
157                    locale);
158    
159            return strings.getString(key);
160        }
161    
162        /**
163         * Gets a string from the standard resource bundle. The string in the bundle is treated as a
164         * pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
165         * 
166         * @param pattern
167         *            string the input pattern to be used with
168         *            {@link MessageFormat#format(java.lang.String, java.lang.Object[])}. It may
169         *            contain replaceable parameters, {0}, {1}, etc.
170         * @param args
171         *            the arguments used to fill replaceable parameters {0}, {1}, etc.
172         * @since 3.0
173         */
174    
175        protected String formatString(String pattern, Object[] args)
176        {
177            return MessageFormat.format(pattern, args);
178        }
179    
180        /**
181         * Convienience method for invoking {@link #formatString(String, Object[])}.
182         * 
183         * @since 3.0
184         */
185    
186        protected String formatString(String pattern, Object arg)
187        {
188            return formatString(pattern, new Object[]
189            { arg });
190        }
191    
192        /**
193         * Convienience method for invoking {@link #formatString(String, Object[])}.
194         * 
195         * @since 3.0
196         */
197    
198        protected String formatString(String pattern, Object arg1, Object arg2)
199        {
200            return formatString(pattern, new Object[]
201            { arg1, arg2 });
202        }
203    
204        /**
205         * Invoked to check if the value is null. If the value is null (or empty), but the required flag
206         * is set, then this method throws a {@link ValidatorException}. Otherwise, returns true if the
207         * value is null.
208         */
209    
210        protected boolean checkRequired(IFormComponent field, String value) throws ValidatorException
211        {
212            boolean isEmpty = HiveMind.isBlank(value);
213    
214            if (_required && isEmpty)
215                throw new ValidatorException(buildRequiredMessage(field), ValidationConstraint.REQUIRED);
216    
217            return isEmpty;
218        }
219    
220        /**
221         * Builds an error message indicating a value for a required field was not supplied.
222         * 
223         * @since 3.0
224         */
225    
226        protected String buildRequiredMessage(IFormComponent field)
227        {
228            String pattern = getPattern(_requiredMessage, "field-is-required", field.getPage()
229                    .getLocale());
230    
231            return formatString(pattern, field.getDisplayName());
232        }
233    
234        /**
235         * This implementation does nothing. Subclasses may supply their own implementation.
236         * 
237         * @since 2.2
238         */
239    
240        public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer,
241                IRequestCycle cycle)
242        {
243        }
244    
245        /**
246         * Invoked (from sub-class implementations of
247         * {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}to process
248         * a standard validation script. This expects that:
249         * <ul>
250         * <li>The {@link IFormComponent}is (ultimately) wrapped by a {@link Body}
251         * <li>The script generates a symbol named "function" (as per {@link #FUNCTION_SYMBOL})
252         * </ul>
253         * 
254         * @param scriptPath
255         *            the resource path of the script to execute
256         * @param cycle
257         *            The active request cycle
258         * @param field
259         *            The field to be validated
260         * @param symbols
261         *            a set of input symbols needed by the script. These symbols are augmented with
262         *            symbols for the field, form and validator. symbols may be null, but will be
263         *            modified if not null.
264         * @throws ApplicationRuntimeException
265         *             if there's an error processing the script.
266         * @since 2.2
267         */
268    
269        protected void processValidatorScript(String scriptPath, IRequestCycle cycle,
270                IFormComponent field, Map symbols)
271        {
272            IEngine engine = field.getPage().getEngine();
273            IScriptSource source = engine.getScriptSource();
274            IForm form = field.getForm();
275    
276            Map finalSymbols = (symbols == null) ? new HashMap() : symbols;
277    
278            finalSymbols.put(FIELD_SYMBOL, field);
279            finalSymbols.put(FORM_SYMBOL, form);
280            finalSymbols.put(VALIDATOR_SYMBOL, this);
281    
282            Resource location = new ClasspathResource(engine.getClassResolver(), scriptPath);
283    
284            IScript script = source.getScript(location);
285    
286            // If there's an error, report it against the field (this validator object doesn't
287            // have a location).
288    
289            PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, field);
290    
291            script.execute(cycle, pageRenderSupport, finalSymbols);
292        }
293    
294        /**
295         * Returns true if client scripting is enabled. Some validators are capable of generating
296         * client-side scripting to perform validation when the form is submitted. By default, this flag
297         * is false and subclasses should check it (in
298         * {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}) before
299         * generating client side script.
300         * 
301         * @since 2.2
302         */
303    
304        public boolean isClientScriptingEnabled()
305        {
306            return _clientScriptingEnabled;
307        }
308    
309        public void setClientScriptingEnabled(boolean clientScriptingEnabled)
310        {
311            _clientScriptingEnabled = clientScriptingEnabled;
312        }
313    
314        public String getRequiredMessage()
315        {
316            return _requiredMessage;
317        }
318    
319        /**
320         * Overrides the <code>field-is-required</code> bundle key. Parameter {0} is the display name
321         * of the field.
322         * 
323         * @since 3.0
324         */
325    
326        public void setRequiredMessage(String string)
327        {
328            _requiredMessage = string;
329        }
330    
331    }