View Javadoc

1   /*
2    * $Id: ValidatorResources.java 386637 2006-03-17 13:22:26Z niallp $
3    * $Rev: 386637 $
4    * $Date: 2006-03-17 13:22:26 +0000 (Fri, 17 Mar 2006) $
5    *
6    * ====================================================================
7    * Copyright 2001-2006 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          "-//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         // Add rules for arg0-arg3 elements
212         addOldArgRules(digester);
213 
214         // register DTDs
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         // Create a new rule to process args elements
236         Rule rule = new Rule() {
237             public void begin(String namespace, String name, 
238                                Attributes attributes) throws Exception {
239                 // Create the Arg
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                 // Add the arg to the parent field
254                 ((Field)getDigester().peek(0)).addArg(arg);
255             }
256         };
257 
258         // Add the rule for each of the arg elements
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) {// there can only be one default formset
276             if (getLog().isWarnEnabled() && defaultFormSet != null) {
277                 // warn the user he might not get the expected results
278                 getLog().warn("Overriding default FormSet definition.");
279             }
280             defaultFormSet = fs;
281         } else {
282             FormSet formset = (FormSet) hFormSets.get(key);
283             if (formset == null) {// it hasn't been included yet
284                 if (getLog().isDebugEnabled()) {
285                     getLog().debug("Adding FormSet '" + fs.toString() + "'.");
286                 }
287             } else if (getLog().isWarnEnabled()) {// warn the user he might not
288                                                 // get the expected results
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         // Try language/country/variant
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         // Try language/country
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         // Try language
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         // Try default formset
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) {// it isn't mandatory to have a
485             // default formset
486             defaultFormSet = new FormSet();
487         }
488         defaultFormSet.process(hConstants);
489         // Loop through FormSets and merge if necessary
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         // Process Fully Constructed FormSets
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 }