View Javadoc

1   /*
2    * $Id: ValidatorResources.java 329836 2005-10-31 15:12:50Z niallp $
3    * $Rev: 329836 $
4    * $Date: 2005-10-31 15:12:50 +0000 (Mon, 31 Oct 2005) $
5    *
6    * ====================================================================
7    * Copyright 2001-2005 The Apache Software Foundation
8    *
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *     http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
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         // Add rules for arg0-arg3 elements
206         addOldArgRules(digester);
207 
208         // register DTDs
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         // Create a new rule to process args elements
230         Rule rule = new Rule() {
231             public void begin(String namespace, String name, 
232                                Attributes attributes) throws Exception {
233                 // Create the Arg
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                 // Add the arg to the parent field
245                 ((Field)getDigester().peek(0)).addArg(arg);
246             }
247         };
248 
249         // Add the rule for each of the arg elements
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) {// there can only be one default formset
267             if (log.isWarnEnabled() && defaultFormSet != null) {
268                 // warn the user he might not get the expected results
269                 log.warn("Overriding default FormSet definition.");
270             }
271             defaultFormSet = fs;
272         } else {
273             FormSet formset = (FormSet) hFormSets.get(key);
274             if (formset == null) {// it hasn't been included yet
275                 if (log.isDebugEnabled()) {
276                     log.debug("Adding FormSet '" + fs.toString() + "'.");
277                 }
278             } else if (log.isWarnEnabled()) {// warn the user he might not
279                                                 // get the expected results
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         // Try language/country/variant
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         // Try language/country
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         // Try language
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         // Try default formset
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) {// it isn't mandatory to have a
476             // default formset
477             defaultFormSet = new FormSet();
478         }
479         defaultFormSet.process(hConstants);
480         // Loop through FormSets and merge if necessary
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         // Process Fully Constructed FormSets
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 }