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 }