View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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                         // ignore problem closing
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             // Fix for Issue# VALIDATOR-195
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         // Add rules for arg0-arg3 elements
275         addOldArgRules(digester);
276 
277         // register DTDs
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         // Create a new rule to process args elements
299         Rule rule = new Rule() {
300             public void begin(String namespace, String name, 
301                                Attributes attributes) throws Exception {
302                 // Create the Arg
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                 // Add the arg to the parent field
317                 ((Field)getDigester().peek(0)).addArg(arg);
318             }
319         };
320 
321         // Add the rule for each of the arg elements
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) {// there can only be one default formset
339             if (getLog().isWarnEnabled() && defaultFormSet != null) {
340                 // warn the user he might not get the expected results
341                 getLog().warn("Overriding default FormSet definition.");
342             }
343             defaultFormSet = fs;
344         } else {
345             FormSet formset = (FormSet) hFormSets.get(key);
346             if (formset == null) {// it hasn't been included yet
347                 if (getLog().isDebugEnabled()) {
348                     getLog().debug("Adding FormSet '" + fs.toString() + "'.");
349                 }
350             } else if (getLog().isWarnEnabled()) {// warn the user he might not
351                                                 // get the expected results
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         // Try language/country/variant
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         // Try language/country
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         // Try language
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         // Try default formset
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) {// it isn't mandatory to have a
548             // default formset
549             defaultFormSet = new FormSet();
550         }
551         defaultFormSet.process(hConstants);
552         // Loop through FormSets and merge if necessary
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         // Process Fully Constructed FormSets
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 }