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
015package org.apache.tapestry.valid;
016
017import java.text.MessageFormat;
018import java.util.HashMap;
019import java.util.Locale;
020import java.util.Map;
021import java.util.ResourceBundle;
022
023import org.apache.hivemind.HiveMind;
024import org.apache.hivemind.Resource;
025import org.apache.hivemind.util.ClasspathResource;
026import org.apache.hivemind.util.PropertyUtils;
027import org.apache.tapestry.IEngine;
028import org.apache.tapestry.IForm;
029import org.apache.tapestry.IMarkupWriter;
030import org.apache.tapestry.IRequestCycle;
031import org.apache.tapestry.IScript;
032import org.apache.tapestry.PageRenderSupport;
033import org.apache.tapestry.TapestryUtils;
034import org.apache.tapestry.engine.IScriptSource;
035import 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
044public 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}