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 "-//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 * Initialize the digester.
203 */
204 private Digester initDigester() {
205 URL rulesUrl = this.getClass().getResource("digester-rules.xml");
206 Digester digester = DigesterLoader.createDigester(rulesUrl);
207 digester.setNamespaceAware(true);
208 digester.setValidating(true);
209 digester.setUseContextClassLoader(true);
210
211
212 addOldArgRules(digester);
213
214
215 for (int i = 0; i < REGISTRATIONS.length; i += 2) {
216 URL url = this.getClass().getResource(REGISTRATIONS[i + 1]);
217 if (url != null) {
218 digester.register(REGISTRATIONS[i], url.toString());
219 }
220 }
221 return digester;
222 }
223
224 private static final String ARGS_PATTERN
225 = "form-validation/formset/form/field/arg";
226
227 /***
228 * Create a <code>Rule</code> to handle <code>arg0-arg3</code>
229 * elements. This will allow validation.xml files that use the
230 * versions of the DTD prior to Validator 1.2.0 to continue
231 * working.
232 */
233 private void addOldArgRules(Digester digester) {
234
235
236 Rule rule = new Rule() {
237 public void begin(String namespace, String name,
238 Attributes attributes) throws Exception {
239
240 Arg arg = new Arg();
241 arg.setKey(attributes.getValue("key"));
242 arg.setName(attributes.getValue("name"));
243 if ("false".equalsIgnoreCase(attributes.getValue("resource"))) {
244 arg.setResource(false);
245 }
246 try {
247 arg.setPosition(Integer.parseInt(name.substring(3)));
248 } catch (Exception ex) {
249 getLog().error("Error parsing Arg position: "
250 + name + " " + arg + " " + ex);
251 }
252
253
254 ((Field)getDigester().peek(0)).addArg(arg);
255 }
256 };
257
258
259 digester.addRule(ARGS_PATTERN + "0", rule);
260 digester.addRule(ARGS_PATTERN + "1", rule);
261 digester.addRule(ARGS_PATTERN + "2", rule);
262 digester.addRule(ARGS_PATTERN + "3", rule);
263
264 }
265
266 /***
267 * Add a <code>FormSet</code> to this <code>ValidatorResources</code>
268 * object. It will be associated with the <code>Locale</code> of the
269 * <code>FormSet</code>.
270 * @param fs The form set to add.
271 * @since Validator 1.1
272 */
273 public void addFormSet(FormSet fs) {
274 String key = this.buildKey(fs);
275 if (key.length() == 0) {
276 if (getLog().isWarnEnabled() && defaultFormSet != null) {
277
278 getLog().warn("Overriding default FormSet definition.");
279 }
280 defaultFormSet = fs;
281 } else {
282 FormSet formset = (FormSet) hFormSets.get(key);
283 if (formset == null) {
284 if (getLog().isDebugEnabled()) {
285 getLog().debug("Adding FormSet '" + fs.toString() + "'.");
286 }
287 } else if (getLog().isWarnEnabled()) {
288
289 getLog()
290 .warn("Overriding FormSet definition. Duplicate for locale: "
291 + key);
292 }
293 hFormSets.put(key, fs);
294 }
295 }
296
297 /***
298 * Add a global constant to the resource.
299 * @param name The constant name.
300 * @param value The constant value.
301 */
302 public void addConstant(String name, String value) {
303 if (getLog().isDebugEnabled()) {
304 getLog().debug("Adding Global Constant: " + name + "," + value);
305 }
306
307 this.hConstants.put(name, value);
308 }
309
310 /***
311 * Add a <code>ValidatorAction</code> to the resource. It also creates an
312 * instance of the class based on the <code>ValidatorAction</code>s
313 * classname and retrieves the <code>Method</code> instance and sets them
314 * in the <code>ValidatorAction</code>.
315 * @param va The validator action.
316 */
317 public void addValidatorAction(ValidatorAction va) {
318 va.init();
319
320 this.hActions.put(va.getName(), va);
321
322 if (getLog().isDebugEnabled()) {
323 getLog().debug("Add ValidatorAction: " + va.getName() + "," + va.getClassname());
324 }
325 }
326
327 /***
328 * Get a <code>ValidatorAction</code> based on it's name.
329 * @param key The validator action key.
330 * @return The validator action.
331 */
332 public ValidatorAction getValidatorAction(String key) {
333 return (ValidatorAction) hActions.get(key);
334 }
335
336 /***
337 * Get an unmodifiable <code>Map</code> of the <code>ValidatorAction</code>s.
338 * @return Map of validator actions.
339 */
340 public Map getValidatorActions() {
341 return Collections.unmodifiableMap(hActions);
342 }
343
344 /***
345 * Builds a key to store the <code>FormSet</code> under based on it's
346 * language, country, and variant values.
347 * @param fs The Form Set.
348 * @return generated key for a formset.
349 */
350 protected String buildKey(FormSet fs) {
351 return
352 this.buildLocale(fs.getLanguage(), fs.getCountry(), fs.getVariant());
353 }
354
355 /***
356 * Assembles a Locale code from the given parts.
357 */
358 private String buildLocale(String lang, String country, String variant) {
359 String key = ((lang != null && lang.length() > 0) ? lang : "");
360 key += ((country != null && country.length() > 0) ? "_" + country : "");
361 key += ((variant != null && variant.length() > 0) ? "_" + variant : "");
362 return key;
363 }
364
365 /***
366 * <p>Gets a <code>Form</code> based on the name of the form and the
367 * <code>Locale</code> that most closely matches the <code>Locale</code>
368 * passed in. The order of <code>Locale</code> matching is:</p>
369 * <ol>
370 * <li>language + country + variant</li>
371 * <li>language + country</li>
372 * <li>language</li>
373 * <li>default locale</li>
374 * </ol>
375 * @param locale The Locale.
376 * @param formKey The key for the Form.
377 * @return The validator Form.
378 * @since Validator 1.1
379 */
380 public Form getForm(Locale locale, String formKey) {
381 return this.getForm(locale.getLanguage(), locale.getCountry(), locale
382 .getVariant(), formKey);
383 }
384
385 /***
386 * <p>Gets a <code>Form</code> based on the name of the form and the
387 * <code>Locale</code> that most closely matches the <code>Locale</code>
388 * passed in. The order of <code>Locale</code> matching is:</p>
389 * <ol>
390 * <li>language + country + variant</li>
391 * <li>language + country</li>
392 * <li>language</li>
393 * <li>default locale</li>
394 * </ol>
395 * @param language The locale's language.
396 * @param country The locale's country.
397 * @param variant The locale's language variant.
398 * @param formKey The key for the Form.
399 * @return The validator Form.
400 * @since Validator 1.1
401 */
402 public Form getForm(String language, String country, String variant,
403 String formKey) {
404
405 Form form = null;
406
407
408 String key = this.buildLocale(language, country, variant);
409 if (key.length() > 0) {
410 FormSet formSet = (FormSet)hFormSets.get(key);
411 if (formSet != null) {
412 form = formSet.getForm(formKey);
413 }
414 }
415 String localeKey = key;
416
417
418
419 if (form == null) {
420 key = buildLocale(language, country, null);
421 if (key.length() > 0) {
422 FormSet formSet = (FormSet)hFormSets.get(key);
423 if (formSet != null) {
424 form = formSet.getForm(formKey);
425 }
426 }
427 }
428
429
430 if (form == null) {
431 key = buildLocale(language, null, null);
432 if (key.length() > 0) {
433 FormSet formSet = (FormSet)hFormSets.get(key);
434 if (formSet != null) {
435 form = formSet.getForm(formKey);
436 }
437 }
438 }
439
440
441 if (form == null) {
442 form = defaultFormSet.getForm(formKey);
443 key = "default";
444 }
445
446 if (form == null) {
447 if (getLog().isWarnEnabled()) {
448 getLog().warn("Form '" + formKey + "' not found for locale '" +
449 localeKey + "'");
450 }
451 } else {
452 if (getLog().isDebugEnabled()) {
453 getLog().debug("Form '" + formKey + "' found in formset '" +
454 key + "' for locale '" + localeKey + "'");
455 }
456 }
457
458 return form;
459
460 }
461
462 /***
463 * Process the <code>ValidatorResources</code> object. Currently sets the
464 * <code>FastHashMap</code> s to the 'fast' mode and call the processes
465 * all other resources. <strong>Note </strong>: The framework calls this
466 * automatically when ValidatorResources is created from an XML file. If you
467 * create an instance of this class by hand you <strong>must </strong> call
468 * this method when finished.
469 */
470 public void process() {
471 hFormSets.setFast(true);
472 hConstants.setFast(true);
473 hActions.setFast(true);
474
475 this.processForms();
476 }
477
478 /***
479 * <p>Process the <code>Form</code> objects. This clones the <code>Field</code>s
480 * that don't exist in a <code>FormSet</code> compared to its parent
481 * <code>FormSet</code>.</p>
482 */
483 private void processForms() {
484 if (defaultFormSet == null) {
485
486 defaultFormSet = new FormSet();
487 }
488 defaultFormSet.process(hConstants);
489
490 for (Iterator i = hFormSets.keySet().iterator(); i.hasNext();) {
491 String key = (String) i.next();
492 FormSet fs = (FormSet) hFormSets.get(key);
493 fs.merge(getParent(fs));
494 }
495
496
497 for (Iterator i = hFormSets.values().iterator(); i.hasNext();) {
498 FormSet fs = (FormSet) i.next();
499 if (!fs.isProcessed()) {
500 fs.process(hConstants);
501 }
502 }
503 }
504
505 /***
506 * Finds the given formSet's parent. ex: A formSet with locale en_UK_TEST1
507 * has a direct parent in the formSet with locale en_UK. If it doesn't
508 * exist, find the formSet with locale en, if no found get the
509 * defaultFormSet.
510 *
511 * @param fs
512 * the formSet we want to get the parent from
513 * @return fs's parent
514 */
515 private FormSet getParent(FormSet fs) {
516
517 FormSet parent = null;
518 if (fs.getType() == FormSet.LANGUAGE_FORMSET) {
519 parent = defaultFormSet;
520 } else if (fs.getType() == FormSet.COUNTRY_FORMSET) {
521 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(),
522 null, null));
523 if (parent == null) {
524 parent = defaultFormSet;
525 }
526 } else if (fs.getType() == FormSet.VARIANT_FORMSET) {
527 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(), fs
528 .getCountry(), null));
529 if (parent == null) {
530 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(),
531 null, null));
532 if (parent == null) {
533 parent = defaultFormSet;
534 }
535 }
536 }
537 return parent;
538 }
539
540 /***
541 * <p>Gets a <code>FormSet</code> based on the language, country
542 * and variant.</p>
543 * @param language The locale's language.
544 * @param country The locale's country.
545 * @param variant The locale's language variant.
546 * @return The FormSet for a locale.
547 * @since Validator 1.2
548 */
549 FormSet getFormSet(String language, String country, String variant) {
550
551 String key = buildLocale(language, country, variant);
552
553 if (key.length() == 0) {
554 return defaultFormSet;
555 }
556
557 return (FormSet)hFormSets.get(key);
558 }
559
560 /***
561 * Returns a Map of String locale keys to Lists of their FormSets.
562 * @return Map of Form sets
563 * @since Validator 1.2.0
564 */
565 protected Map getFormSets() {
566 return hFormSets;
567 }
568
569 /***
570 * Returns a Map of String constant names to their String values.
571 * @return Map of Constants
572 * @since Validator 1.2.0
573 */
574 protected Map getConstants() {
575 return hConstants;
576 }
577
578 /***
579 * Returns a Map of String ValidatorAction names to their ValidatorAction.
580 * @return Map of Validator Actions
581 * @since Validator 1.2.0
582 */
583 protected Map getActions() {
584 return hActions;
585 }
586
587 /***
588 * Accessor method for Log instance.
589 *
590 * The Log instance variable is transient and
591 * accessing it through this method ensures it
592 * is re-initialized when this instance is
593 * de-serialized.
594 *
595 * @return The Log instance.
596 */
597 private Log getLog() {
598 if (log == null) {
599 log = LogFactory.getLog(ValidatorResources.class);
600 }
601 return log;
602 }
603
604 }