1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.commons.validator;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.Serializable;
27 import java.net.URL;
28 import java.util.Collections;
29 import java.util.Iterator;
30 import java.util.Locale;
31 import java.util.Map;
32
33 import org.apache.commons.collections.FastHashMap;
34 import org.apache.commons.digester.Digester;
35 import org.apache.commons.digester.Rule;
36 import org.apache.commons.digester.xmlrules.DigesterLoader;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.xml.sax.SAXException;
40 import org.xml.sax.Attributes;
41
42 /***
43 * <p>
44 * General purpose class for storing <code>FormSet</code> objects based
45 * on their associated <code>Locale</code>. Instances of this class are usually
46 * configured through a validation.xml file that is parsed in a constructor.
47 * </p>
48 *
49 * <p><strong>Note</strong> - Classes that extend this class
50 * must be Serializable so that instances may be used in distributable
51 * application server environments.</p>
52 *
53 * <p>
54 * The use of FastHashMap is deprecated and will be replaced in a future
55 * release.
56 * </p>
57 */
58 public class ValidatorResources implements Serializable {
59
60 /***
61 * The set of public identifiers, and corresponding resource names, for
62 * the versions of the configuration file DTDs that we know about. There
63 * <strong>MUST</strong> be an even number of Strings in this list!
64 */
65 private static final String registrations[] = {
66 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN",
67 "/org/apache/commons/validator/resources/validator_1_0.dtd",
68 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0.1//EN",
69 "/org/apache/commons/validator/resources/validator_1_0_1.dtd",
70 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN",
71 "/org/apache/commons/validator/resources/validator_1_1.dtd",
72 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN",
73 "/org/apache/commons/validator/resources/validator_1_1_3.dtd",
74 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.2.0//EN",
75 "/org/apache/commons/validator/resources/validator_1_2_0.dtd"
76 };
77
78 private static final Log log = LogFactory.getLog(ValidatorResources.class);
79
80 /***
81 * <code>Map</code> of <code>FormSet</code>s stored under
82 * a <code>Locale</code> key.
83 * @deprecated Subclasses should use getFormSets() instead.
84 */
85 protected FastHashMap hFormSets = new FastHashMap();
86
87 /***
88 * <code>Map</code> of global constant values with
89 * the name of the constant as the key.
90 * @deprecated Subclasses should use getConstants() instead.
91 */
92 protected FastHashMap hConstants = new FastHashMap();
93
94 /***
95 * <code>Map</code> of <code>ValidatorAction</code>s with
96 * the name of the <code>ValidatorAction</code> as the key.
97 * @deprecated Subclasses should use getActions() instead.
98 */
99 protected FastHashMap hActions = new FastHashMap();
100
101 /***
102 * The default locale on our server.
103 */
104 protected static Locale defaultLocale = Locale.getDefault();
105
106 /***
107 * Create an empty ValidatorResources object.
108 */
109 public ValidatorResources() {
110 super();
111 }
112
113 /***
114 * This is the default <code>FormSet</code> (without locale). (We probably don't need
115 * the defaultLocale anymore.)
116 */
117 protected FormSet defaultFormSet;
118
119 /***
120 * Create a ValidatorResources object from an InputStream.
121 *
122 * @param in InputStream to a validation.xml configuration file. It's the client's
123 * responsibility to close this stream.
124 * @throws IOException
125 * @throws SAXException if the validation XML files are not valid or well
126 * formed.
127 * @since Validator 1.1
128 */
129 public ValidatorResources(InputStream in) throws IOException, SAXException {
130 this(new InputStream[]{in});
131 }
132
133 /***
134 * Create a ValidatorResources object from an InputStream.
135 *
136 * @param streams An array of InputStreams to several validation.xml
137 * configuration files that will be read in order and merged into this object.
138 * It's the client's responsibility to close these streams.
139 * @throws IOException
140 * @throws SAXException if the validation XML files are not valid or well
141 * formed.
142 * @since Validator 1.1
143 */
144 public ValidatorResources(InputStream[] streams)
145 throws IOException, SAXException {
146
147 super();
148
149 Digester digester = initDigester();
150 for (int i = 0; i < streams.length; i++) {
151 digester.push(this);
152 digester.parse(streams[i]);
153 }
154
155 this.process();
156 }
157
158 /***
159 * Create a ValidatorResources object from an uri
160 *
161 * @param uri The location of a validation.xml configuration file.
162 * @throws IOException
163 * @throws SAXException if the validation XML files are not valid or well
164 * formed.
165 * @since Validator 1.2
166 */
167 public ValidatorResources(String uri) throws IOException, SAXException {
168 this(new String[]{uri});
169 }
170
171 /***
172 * Create a ValidatorResources object from several uris
173 *
174 * @param uris An array of uris to several validation.xml
175 * configuration files that will be read in order and merged into this object.
176 * @throws IOException
177 * @throws SAXException if the validation XML files are not valid or well
178 * formed.
179 * @since Validator 1.2
180 */
181 public ValidatorResources(String[] uris)
182 throws IOException, SAXException {
183
184 super();
185
186 Digester digester = initDigester();
187 for (int i = 0; i < uris.length; i++) {
188 digester.push(this);
189 digester.parse(uris[i]);
190 }
191
192 this.process();
193 }
194
195 /***
196 * Initialize the digester.
197 */
198 private Digester initDigester() {
199 URL rulesUrl = this.getClass().getResource("digester-rules.xml");
200 Digester digester = DigesterLoader.createDigester(rulesUrl);
201 digester.setNamespaceAware(true);
202 digester.setValidating(true);
203 digester.setUseContextClassLoader(true);
204
205
206 addOldArgRules(digester);
207
208
209 for (int i = 0; i < registrations.length; i += 2) {
210 URL url = this.getClass().getResource(registrations[i + 1]);
211 if (url != null) {
212 digester.register(registrations[i], url.toString());
213 }
214 }
215 return digester;
216 }
217
218 private static final String argsPattern
219 = "form-validation/formset/form/field/arg";
220
221 /***
222 * Create a <code>Rule</code> to handle <code>arg0-arg3</code>
223 * elements. This will allow validation.xml files that use the
224 * versions of the DTD prior to Validator 1.2.0 to continue
225 * working.
226 */
227 private void addOldArgRules(Digester digester) {
228
229
230 Rule rule = new Rule() {
231 public void begin(String namespace, String name,
232 Attributes attributes) throws Exception {
233
234 Arg arg = new Arg();
235 arg.setKey(attributes.getValue("key"));
236 arg.setName(attributes.getValue("name"));
237 try {
238 arg.setPosition(Integer.parseInt(name.substring(3)));
239 } catch (Exception ex) {
240 log.error("Error parsing Arg position: "
241 + name + " " + arg + " " + ex);
242 }
243
244
245 ((Field)getDigester().peek(0)).addArg(arg);
246 }
247 };
248
249
250 digester.addRule(argsPattern + "0", rule);
251 digester.addRule(argsPattern + "1", rule);
252 digester.addRule(argsPattern + "2", rule);
253 digester.addRule(argsPattern + "3", rule);
254
255 }
256
257 /***
258 * Add a <code>FormSet</code> to this <code>ValidatorResources</code>
259 * object. It will be associated with the <code>Locale</code> of the
260 * <code>FormSet</code>.
261 * @param fs The form set to add.
262 * @since Validator 1.1
263 */
264 public void addFormSet(FormSet fs) {
265 String key = this.buildKey(fs);
266 if (key.length() == 0) {
267 if (log.isWarnEnabled() && defaultFormSet != null) {
268
269 log.warn("Overriding default FormSet definition.");
270 }
271 defaultFormSet = fs;
272 } else {
273 FormSet formset = (FormSet) hFormSets.get(key);
274 if (formset == null) {
275 if (log.isDebugEnabled()) {
276 log.debug("Adding FormSet '" + fs.toString() + "'.");
277 }
278 } else if (log.isWarnEnabled()) {
279
280 log
281 .warn("Overriding FormSet definition. Duplicate for locale: "
282 + key);
283 }
284 hFormSets.put(key, fs);
285 }
286 }
287
288 /***
289 * Add a global constant to the resource.
290 * @param name The constant name.
291 * @param value The constant value.
292 */
293 public void addConstant(String name, String value) {
294 if (log.isDebugEnabled()) {
295 log.debug("Adding Global Constant: " + name + "," + value);
296 }
297
298 this.hConstants.put(name, value);
299 }
300
301 /***
302 * Add a <code>ValidatorAction</code> to the resource. It also creates an
303 * instance of the class based on the <code>ValidatorAction</code>s
304 * classname and retrieves the <code>Method</code> instance and sets them
305 * in the <code>ValidatorAction</code>.
306 * @param va The validator action.
307 */
308 public void addValidatorAction(ValidatorAction va) {
309 va.init();
310
311 this.hActions.put(va.getName(), va);
312
313 if (log.isDebugEnabled()) {
314 log.debug("Add ValidatorAction: " + va.getName() + "," + va.getClassname());
315 }
316 }
317
318 /***
319 * Get a <code>ValidatorAction</code> based on it's name.
320 * @param key The validator action key.
321 * @return The validator action.
322 */
323 public ValidatorAction getValidatorAction(String key) {
324 return (ValidatorAction) hActions.get(key);
325 }
326
327 /***
328 * Get an unmodifiable <code>Map</code> of the <code>ValidatorAction</code>s.
329 * @return Map of validator actions.
330 */
331 public Map getValidatorActions() {
332 return Collections.unmodifiableMap(hActions);
333 }
334
335 /***
336 * Builds a key to store the <code>FormSet</code> under based on it's
337 * language, country, and variant values.
338 * @param fs The Form Set.
339 * @return generated key for a formset.
340 */
341 protected String buildKey(FormSet fs) {
342 return
343 this.buildLocale(fs.getLanguage(), fs.getCountry(), fs.getVariant());
344 }
345
346 /***
347 * Assembles a Locale code from the given parts.
348 */
349 private String buildLocale(String lang, String country, String variant) {
350 String key = ((lang != null && lang.length() > 0) ? lang : "");
351 key += ((country != null && country.length() > 0) ? "_" + country : "");
352 key += ((variant != null && variant.length() > 0) ? "_" + variant : "");
353 return key;
354 }
355
356 /***
357 * <p>Gets a <code>Form</code> based on the name of the form and the
358 * <code>Locale</code> that most closely matches the <code>Locale</code>
359 * passed in. The order of <code>Locale</code> matching is:</p>
360 * <ol>
361 * <li>language + country + variant</li>
362 * <li>language + country</li>
363 * <li>language</li>
364 * <li>default locale</li>
365 * </ol>
366 * @param locale The Locale.
367 * @param formKey The key for the Form.
368 * @return The validator Form.
369 * @since Validator 1.1
370 */
371 public Form getForm(Locale locale, String formKey) {
372 return this.getForm(locale.getLanguage(), locale.getCountry(), locale
373 .getVariant(), formKey);
374 }
375
376 /***
377 * <p>Gets a <code>Form</code> based on the name of the form and the
378 * <code>Locale</code> that most closely matches the <code>Locale</code>
379 * passed in. The order of <code>Locale</code> matching is:</p>
380 * <ol>
381 * <li>language + country + variant</li>
382 * <li>language + country</li>
383 * <li>language</li>
384 * <li>default locale</li>
385 * </ol>
386 * @param language The locale's language.
387 * @param country The locale's country.
388 * @param variant The locale's language variant.
389 * @param formKey The key for the Form.
390 * @return The validator Form.
391 * @since Validator 1.1
392 */
393 public Form getForm(String language, String country, String variant,
394 String formKey) {
395
396 Form form = null;
397
398
399 String key = this.buildLocale(language, country, variant);
400 if (key.length() > 0) {
401 FormSet formSet = (FormSet)hFormSets.get(key);
402 if (formSet != null) {
403 form = formSet.getForm(formKey);
404 }
405 }
406 String localeKey = key;
407
408
409
410 if (form == null) {
411 key = buildLocale(language, country, null);
412 if (key.length() > 0) {
413 FormSet formSet = (FormSet)hFormSets.get(key);
414 if (formSet != null) {
415 form = formSet.getForm(formKey);
416 }
417 }
418 }
419
420
421 if (form == null) {
422 key = buildLocale(language, null, null);
423 if (key.length() > 0) {
424 FormSet formSet = (FormSet)hFormSets.get(key);
425 if (formSet != null) {
426 form = formSet.getForm(formKey);
427 }
428 }
429 }
430
431
432 if (form == null) {
433 form = defaultFormSet.getForm(formKey);
434 key = "default";
435 }
436
437 if (form == null) {
438 if (log.isWarnEnabled()) {
439 log.warn("Form '" + formKey + "' not found for locale '" +
440 localeKey + "'");
441 }
442 } else {
443 if (log.isDebugEnabled()) {
444 log.debug("Form '" + formKey + "' found in formset '" +
445 key + "' for locale '" + localeKey + "'");
446 }
447 }
448
449 return form;
450
451 }
452
453 /***
454 * Process the <code>ValidatorResources</code> object. Currently sets the
455 * <code>FastHashMap</code> s to the 'fast' mode and call the processes
456 * all other resources. <strong>Note </strong>: The framework calls this
457 * automatically when ValidatorResources is created from an XML file. If you
458 * create an instance of this class by hand you <strong>must </strong> call
459 * this method when finished.
460 */
461 public void process() {
462 hFormSets.setFast(true);
463 hConstants.setFast(true);
464 hActions.setFast(true);
465
466 this.processForms();
467 }
468
469 /***
470 * <p>Process the <code>Form</code> objects. This clones the <code>Field</code>s
471 * that don't exist in a <code>FormSet</code> compared to its parent
472 * <code>FormSet</code>.</p>
473 */
474 private void processForms() {
475 if (defaultFormSet == null) {
476
477 defaultFormSet = new FormSet();
478 }
479 defaultFormSet.process(hConstants);
480
481 for (Iterator i = hFormSets.keySet().iterator(); i.hasNext();) {
482 String key = (String) i.next();
483 FormSet fs = (FormSet) hFormSets.get(key);
484 fs.merge(getParent(fs));
485 }
486
487
488 for (Iterator i = hFormSets.values().iterator(); i.hasNext();) {
489 FormSet fs = (FormSet) i.next();
490 if (!fs.isProcessed()) {
491 fs.process(hConstants);
492 }
493 }
494 }
495
496 /***
497 * Finds the given formSet's parent. ex: A formSet with locale en_UK_TEST1
498 * has a direct parent in the formSet with locale en_UK. If it doesn't
499 * exist, find the formSet with locale en, if no found get the
500 * defaultFormSet.
501 *
502 * @param fs
503 * the formSet we want to get the parent from
504 * @return fs's parent
505 */
506 private FormSet getParent(FormSet fs) {
507
508 FormSet parent = null;
509 if (fs.getType() == FormSet.LANGUAGE_FORMSET) {
510 parent = defaultFormSet;
511 } else if (fs.getType() == FormSet.COUNTRY_FORMSET) {
512 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(),
513 null, null));
514 if (parent == null) {
515 parent = defaultFormSet;
516 }
517 } else if (fs.getType() == FormSet.VARIANT_FORMSET) {
518 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(), fs
519 .getCountry(), null));
520 if (parent == null) {
521 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(),
522 null, null));
523 if (parent == null) {
524 parent = defaultFormSet;
525 }
526 }
527 }
528 return parent;
529 }
530
531 /***
532 * <p>Gets a <code>FormSet</code> based on the language, country
533 * and variant.</p>
534 * @param language The locale's language.
535 * @param country The locale's country.
536 * @param variant The locale's language variant.
537 * @return The FormSet for a locale.
538 * @since Validator 1.2
539 */
540 FormSet getFormSet(String language, String country, String variant) {
541
542 String key = buildLocale(language, country, variant);
543
544 if (key.length() == 0) {
545 return defaultFormSet;
546 }
547
548 return (FormSet)hFormSets.get(key);
549 }
550
551 /***
552 * Returns a Map of String locale keys to Lists of their FormSets.
553 * @return Map of Form sets
554 * @since Validator 1.2.0
555 */
556 protected Map getFormSets() {
557 return hFormSets;
558 }
559
560 /***
561 * Returns a Map of String constant names to their String values.
562 * @return Map of Constants
563 * @since Validator 1.2.0
564 */
565 protected Map getConstants() {
566 return hConstants;
567 }
568
569 /***
570 * Returns a Map of String ValidatorAction names to their ValidatorAction.
571 * @return Map of Validator Actions
572 * @since Validator 1.2.0
573 */
574 protected Map getActions() {
575 return hActions;
576 }
577
578 }