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 }