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 }