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