001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.validator; 018 019 import java.io.IOException; 020 import java.io.InputStream; 021 import java.io.Serializable; 022 import java.net.URL; 023 import java.util.Collections; 024 import java.util.Iterator; 025 import java.util.Locale; 026 import java.util.Map; 027 028 import org.apache.commons.collections.FastHashMap; 029 import org.apache.commons.digester.Digester; 030 import org.apache.commons.digester.Rule; 031 import org.apache.commons.digester.xmlrules.DigesterLoader; 032 import org.apache.commons.logging.Log; 033 import org.apache.commons.logging.LogFactory; 034 import org.xml.sax.SAXException; 035 import org.xml.sax.Attributes; 036 037 /** 038 * <p> 039 * General purpose class for storing <code>FormSet</code> objects based 040 * on their associated <code>Locale</code>. Instances of this class are usually 041 * configured through a validation.xml file that is parsed in a constructor. 042 * </p> 043 * 044 * <p><strong>Note</strong> - Classes that extend this class 045 * must be Serializable so that instances may be used in distributable 046 * application server environments.</p> 047 * 048 * <p> 049 * The use of FastHashMap is deprecated and will be replaced in a future 050 * release. 051 * </p> 052 * 053 * @version $Revision: 562117 $ $Date: 2007-08-02 16:17:42 +0200 (Do, 02. Aug 2007) $ 054 */ 055 public class ValidatorResources implements Serializable { 056 057 /** Name of the digester validator rules file */ 058 private static final String VALIDATOR_RULES = "digester-rules.xml"; 059 060 /** 061 * The set of public identifiers, and corresponding resource names, for 062 * the versions of the configuration file DTDs that we know about. There 063 * <strong>MUST</strong> be an even number of Strings in this list! 064 */ 065 private static final String REGISTRATIONS[] = { 066 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN", 067 "/org/apache/commons/validator/resources/validator_1_0.dtd", 068 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0.1//EN", 069 "/org/apache/commons/validator/resources/validator_1_0_1.dtd", 070 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN", 071 "/org/apache/commons/validator/resources/validator_1_1.dtd", 072 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN", 073 "/org/apache/commons/validator/resources/validator_1_1_3.dtd", 074 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.2.0//EN", 075 "/org/apache/commons/validator/resources/validator_1_2_0.dtd", 076 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.3.0//EN", 077 "/org/apache/commons/validator/resources/validator_1_3_0.dtd", 078 "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.4.0//EN", 079 "/org/apache/commons/validator/resources/validator_1_4_0.dtd" 080 }; 081 082 private transient Log log = LogFactory.getLog(ValidatorResources.class); 083 084 /** 085 * <code>Map</code> of <code>FormSet</code>s stored under 086 * a <code>Locale</code> key. 087 * @deprecated Subclasses should use getFormSets() instead. 088 */ 089 protected FastHashMap hFormSets = new FastHashMap(); 090 091 /** 092 * <code>Map</code> of global constant values with 093 * the name of the constant as the key. 094 * @deprecated Subclasses should use getConstants() instead. 095 */ 096 protected FastHashMap hConstants = new FastHashMap(); 097 098 /** 099 * <code>Map</code> of <code>ValidatorAction</code>s with 100 * the name of the <code>ValidatorAction</code> as the key. 101 * @deprecated Subclasses should use getActions() instead. 102 */ 103 protected FastHashMap hActions = new FastHashMap(); 104 105 /** 106 * The default locale on our server. 107 */ 108 protected static Locale defaultLocale = Locale.getDefault(); 109 110 /** 111 * Create an empty ValidatorResources object. 112 */ 113 public ValidatorResources() { 114 super(); 115 } 116 117 /** 118 * This is the default <code>FormSet</code> (without locale). (We probably don't need 119 * the defaultLocale anymore.) 120 */ 121 protected FormSet defaultFormSet; 122 123 /** 124 * Create a ValidatorResources object from an InputStream. 125 * 126 * @param in InputStream to a validation.xml configuration file. It's the client's 127 * responsibility to close this stream. 128 * @throws IOException 129 * @throws SAXException if the validation XML files are not valid or well 130 * formed. 131 * @throws IOException if an I/O error occurs processing the XML files 132 * @since Validator 1.1 133 */ 134 public ValidatorResources(InputStream in) throws IOException, SAXException { 135 this(new InputStream[]{in}); 136 } 137 138 /** 139 * Create a ValidatorResources object from an InputStream. 140 * 141 * @param streams An array of InputStreams to several validation.xml 142 * configuration files that will be read in order and merged into this object. 143 * It's the client's responsibility to close these streams. 144 * @throws IOException 145 * @throws SAXException if the validation XML files are not valid or well 146 * formed. 147 * @throws IOException if an I/O error occurs processing the XML files 148 * @since Validator 1.1 149 */ 150 public ValidatorResources(InputStream[] streams) 151 throws IOException, SAXException { 152 153 super(); 154 155 Digester digester = initDigester(); 156 for (int i = 0; i < streams.length; i++) { 157 if (streams[i] == null) { 158 throw new IllegalArgumentException("Stream[" + i + "] is null"); 159 } 160 digester.push(this); 161 digester.parse(streams[i]); 162 } 163 164 this.process(); 165 } 166 167 /** 168 * Create a ValidatorResources object from an uri 169 * 170 * @param uri The location of a validation.xml configuration file. 171 * @throws IOException 172 * @throws SAXException if the validation XML files are not valid or well 173 * formed. 174 * @throws IOException if an I/O error occurs processing the XML files 175 * @since Validator 1.2 176 */ 177 public ValidatorResources(String uri) throws IOException, SAXException { 178 this(new String[]{uri}); 179 } 180 181 /** 182 * Create a ValidatorResources object from several uris 183 * 184 * @param uris An array of uris to several validation.xml 185 * configuration files that will be read in order and merged into this object. 186 * @throws IOException 187 * @throws SAXException if the validation XML files are not valid or well 188 * formed. 189 * @throws IOException if an I/O error occurs processing the XML files 190 * @since Validator 1.2 191 */ 192 public ValidatorResources(String[] uris) 193 throws IOException, SAXException { 194 195 super(); 196 197 Digester digester = initDigester(); 198 for (int i = 0; i < uris.length; i++) { 199 digester.push(this); 200 digester.parse(uris[i]); 201 } 202 203 this.process(); 204 } 205 206 /** 207 * Create a ValidatorResources object from a URL. 208 * 209 * @param url The URL for the validation.xml 210 * configuration file that will be read into this object. 211 * @throws IOException 212 * @throws SAXException if the validation XML file are not valid or well 213 * formed. 214 * @throws IOException if an I/O error occurs processing the XML files 215 * @since Validator 1.3.1 216 */ 217 public ValidatorResources(URL url) 218 throws IOException, SAXException { 219 this(new URL[]{url}); 220 } 221 222 /** 223 * Create a ValidatorResources object from several URL. 224 * 225 * @param urls An array of URL to several validation.xml 226 * configuration files that will be read in order and merged into this object. 227 * @throws IOException 228 * @throws SAXException if the validation XML files are not valid or well 229 * formed. 230 * @throws IOException if an I/O error occurs processing the XML files 231 * @since Validator 1.3.1 232 */ 233 public ValidatorResources(URL[] urls) 234 throws IOException, SAXException { 235 236 super(); 237 238 Digester digester = initDigester(); 239 for (int i = 0; i < urls.length; i++) { 240 digester.push(this); 241 digester.parse(urls[i]); 242 } 243 244 this.process(); 245 } 246 247 /** 248 * Initialize the digester. 249 */ 250 private Digester initDigester() { 251 URL rulesUrl = this.getClass().getResource(VALIDATOR_RULES); 252 if (rulesUrl == null) { 253 // Fix for Issue# VALIDATOR-195 254 rulesUrl = ValidatorResources.class.getResource(VALIDATOR_RULES); 255 } 256 if (getLog().isDebugEnabled()) { 257 getLog().debug("Loading rules from '" + rulesUrl + "'"); 258 } 259 Digester digester = DigesterLoader.createDigester(rulesUrl); 260 digester.setNamespaceAware(true); 261 digester.setValidating(true); 262 digester.setUseContextClassLoader(true); 263 264 // Add rules for arg0-arg3 elements 265 addOldArgRules(digester); 266 267 // register DTDs 268 for (int i = 0; i < REGISTRATIONS.length; i += 2) { 269 URL url = this.getClass().getResource(REGISTRATIONS[i + 1]); 270 if (url != null) { 271 digester.register(REGISTRATIONS[i], url.toString()); 272 } 273 } 274 return digester; 275 } 276 277 private static final String ARGS_PATTERN 278 = "form-validation/formset/form/field/arg"; 279 280 /** 281 * Create a <code>Rule</code> to handle <code>arg0-arg3</code> 282 * elements. This will allow validation.xml files that use the 283 * versions of the DTD prior to Validator 1.2.0 to continue 284 * working. 285 */ 286 private void addOldArgRules(Digester digester) { 287 288 // Create a new rule to process args elements 289 Rule rule = new Rule() { 290 public void begin(String namespace, String name, 291 Attributes attributes) throws Exception { 292 // Create the Arg 293 Arg arg = new Arg(); 294 arg.setKey(attributes.getValue("key")); 295 arg.setName(attributes.getValue("name")); 296 if ("false".equalsIgnoreCase(attributes.getValue("resource"))) { 297 arg.setResource(false); 298 } 299 try { 300 arg.setPosition(Integer.parseInt(name.substring(3))); 301 } catch (Exception ex) { 302 getLog().error("Error parsing Arg position: " 303 + name + " " + arg + " " + ex); 304 } 305 306 // Add the arg to the parent field 307 ((Field)getDigester().peek(0)).addArg(arg); 308 } 309 }; 310 311 // Add the rule for each of the arg elements 312 digester.addRule(ARGS_PATTERN + "0", rule); 313 digester.addRule(ARGS_PATTERN + "1", rule); 314 digester.addRule(ARGS_PATTERN + "2", rule); 315 digester.addRule(ARGS_PATTERN + "3", rule); 316 317 } 318 319 /** 320 * Add a <code>FormSet</code> to this <code>ValidatorResources</code> 321 * object. It will be associated with the <code>Locale</code> of the 322 * <code>FormSet</code>. 323 * @param fs The form set to add. 324 * @since Validator 1.1 325 */ 326 public void addFormSet(FormSet fs) { 327 String key = this.buildKey(fs); 328 if (key.length() == 0) {// there can only be one default formset 329 if (getLog().isWarnEnabled() && defaultFormSet != null) { 330 // warn the user he might not get the expected results 331 getLog().warn("Overriding default FormSet definition."); 332 } 333 defaultFormSet = fs; 334 } else { 335 FormSet formset = (FormSet) hFormSets.get(key); 336 if (formset == null) {// it hasn't been included yet 337 if (getLog().isDebugEnabled()) { 338 getLog().debug("Adding FormSet '" + fs.toString() + "'."); 339 } 340 } else if (getLog().isWarnEnabled()) {// warn the user he might not 341 // get the expected results 342 getLog() 343 .warn("Overriding FormSet definition. Duplicate for locale: " 344 + key); 345 } 346 hFormSets.put(key, fs); 347 } 348 } 349 350 /** 351 * Add a global constant to the resource. 352 * @param name The constant name. 353 * @param value The constant value. 354 */ 355 public void addConstant(String name, String value) { 356 if (getLog().isDebugEnabled()) { 357 getLog().debug("Adding Global Constant: " + name + "," + value); 358 } 359 360 this.hConstants.put(name, value); 361 } 362 363 /** 364 * Add a <code>ValidatorAction</code> to the resource. It also creates an 365 * instance of the class based on the <code>ValidatorAction</code>s 366 * classname and retrieves the <code>Method</code> instance and sets them 367 * in the <code>ValidatorAction</code>. 368 * @param va The validator action. 369 */ 370 public void addValidatorAction(ValidatorAction va) { 371 va.init(); 372 373 this.hActions.put(va.getName(), va); 374 375 if (getLog().isDebugEnabled()) { 376 getLog().debug("Add ValidatorAction: " + va.getName() + "," + va.getClassname()); 377 } 378 } 379 380 /** 381 * Get a <code>ValidatorAction</code> based on it's name. 382 * @param key The validator action key. 383 * @return The validator action. 384 */ 385 public ValidatorAction getValidatorAction(String key) { 386 return (ValidatorAction) hActions.get(key); 387 } 388 389 /** 390 * Get an unmodifiable <code>Map</code> of the <code>ValidatorAction</code>s. 391 * @return Map of validator actions. 392 */ 393 public Map getValidatorActions() { 394 return Collections.unmodifiableMap(hActions); 395 } 396 397 /** 398 * Builds a key to store the <code>FormSet</code> under based on it's 399 * language, country, and variant values. 400 * @param fs The Form Set. 401 * @return generated key for a formset. 402 */ 403 protected String buildKey(FormSet fs) { 404 return 405 this.buildLocale(fs.getLanguage(), fs.getCountry(), fs.getVariant()); 406 } 407 408 /** 409 * Assembles a Locale code from the given parts. 410 */ 411 private String buildLocale(String lang, String country, String variant) { 412 String key = ((lang != null && lang.length() > 0) ? lang : ""); 413 key += ((country != null && country.length() > 0) ? "_" + country : ""); 414 key += ((variant != null && variant.length() > 0) ? "_" + variant : ""); 415 return key; 416 } 417 418 /** 419 * <p>Gets a <code>Form</code> based on the name of the form and the 420 * <code>Locale</code> that most closely matches the <code>Locale</code> 421 * passed in. The order of <code>Locale</code> matching is:</p> 422 * <ol> 423 * <li>language + country + variant</li> 424 * <li>language + country</li> 425 * <li>language</li> 426 * <li>default locale</li> 427 * </ol> 428 * @param locale The Locale. 429 * @param formKey The key for the Form. 430 * @return The validator Form. 431 * @since Validator 1.1 432 */ 433 public Form getForm(Locale locale, String formKey) { 434 return this.getForm(locale.getLanguage(), locale.getCountry(), locale 435 .getVariant(), formKey); 436 } 437 438 /** 439 * <p>Gets a <code>Form</code> based on the name of the form and the 440 * <code>Locale</code> that most closely matches the <code>Locale</code> 441 * passed in. The order of <code>Locale</code> matching is:</p> 442 * <ol> 443 * <li>language + country + variant</li> 444 * <li>language + country</li> 445 * <li>language</li> 446 * <li>default locale</li> 447 * </ol> 448 * @param language The locale's language. 449 * @param country The locale's country. 450 * @param variant The locale's language variant. 451 * @param formKey The key for the Form. 452 * @return The validator Form. 453 * @since Validator 1.1 454 */ 455 public Form getForm(String language, String country, String variant, 456 String formKey) { 457 458 Form form = null; 459 460 // Try language/country/variant 461 String key = this.buildLocale(language, country, variant); 462 if (key.length() > 0) { 463 FormSet formSet = (FormSet)hFormSets.get(key); 464 if (formSet != null) { 465 form = formSet.getForm(formKey); 466 } 467 } 468 String localeKey = key; 469 470 471 // Try language/country 472 if (form == null) { 473 key = buildLocale(language, country, null); 474 if (key.length() > 0) { 475 FormSet formSet = (FormSet)hFormSets.get(key); 476 if (formSet != null) { 477 form = formSet.getForm(formKey); 478 } 479 } 480 } 481 482 // Try language 483 if (form == null) { 484 key = buildLocale(language, null, null); 485 if (key.length() > 0) { 486 FormSet formSet = (FormSet)hFormSets.get(key); 487 if (formSet != null) { 488 form = formSet.getForm(formKey); 489 } 490 } 491 } 492 493 // Try default formset 494 if (form == null) { 495 form = defaultFormSet.getForm(formKey); 496 key = "default"; 497 } 498 499 if (form == null) { 500 if (getLog().isWarnEnabled()) { 501 getLog().warn("Form '" + formKey + "' not found for locale '" + 502 localeKey + "'"); 503 } 504 } else { 505 if (getLog().isDebugEnabled()) { 506 getLog().debug("Form '" + formKey + "' found in formset '" + 507 key + "' for locale '" + localeKey + "'"); 508 } 509 } 510 511 return form; 512 513 } 514 515 /** 516 * Process the <code>ValidatorResources</code> object. Currently sets the 517 * <code>FastHashMap</code> s to the 'fast' mode and call the processes 518 * all other resources. <strong>Note </strong>: The framework calls this 519 * automatically when ValidatorResources is created from an XML file. If you 520 * create an instance of this class by hand you <strong>must </strong> call 521 * this method when finished. 522 */ 523 public void process() { 524 hFormSets.setFast(true); 525 hConstants.setFast(true); 526 hActions.setFast(true); 527 528 this.processForms(); 529 } 530 531 /** 532 * <p>Process the <code>Form</code> objects. This clones the <code>Field</code>s 533 * that don't exist in a <code>FormSet</code> compared to its parent 534 * <code>FormSet</code>.</p> 535 */ 536 private void processForms() { 537 if (defaultFormSet == null) {// it isn't mandatory to have a 538 // default formset 539 defaultFormSet = new FormSet(); 540 } 541 defaultFormSet.process(hConstants); 542 // Loop through FormSets and merge if necessary 543 for (Iterator i = hFormSets.keySet().iterator(); i.hasNext();) { 544 String key = (String) i.next(); 545 FormSet fs = (FormSet) hFormSets.get(key); 546 fs.merge(getParent(fs)); 547 } 548 549 // Process Fully Constructed FormSets 550 for (Iterator i = hFormSets.values().iterator(); i.hasNext();) { 551 FormSet fs = (FormSet) i.next(); 552 if (!fs.isProcessed()) { 553 fs.process(hConstants); 554 } 555 } 556 } 557 558 /** 559 * Finds the given formSet's parent. ex: A formSet with locale en_UK_TEST1 560 * has a direct parent in the formSet with locale en_UK. If it doesn't 561 * exist, find the formSet with locale en, if no found get the 562 * defaultFormSet. 563 * 564 * @param fs 565 * the formSet we want to get the parent from 566 * @return fs's parent 567 */ 568 private FormSet getParent(FormSet fs) { 569 570 FormSet parent = null; 571 if (fs.getType() == FormSet.LANGUAGE_FORMSET) { 572 parent = defaultFormSet; 573 } else if (fs.getType() == FormSet.COUNTRY_FORMSET) { 574 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(), 575 null, null)); 576 if (parent == null) { 577 parent = defaultFormSet; 578 } 579 } else if (fs.getType() == FormSet.VARIANT_FORMSET) { 580 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(), fs 581 .getCountry(), null)); 582 if (parent == null) { 583 parent = (FormSet) hFormSets.get(buildLocale(fs.getLanguage(), 584 null, null)); 585 if (parent == null) { 586 parent = defaultFormSet; 587 } 588 } 589 } 590 return parent; 591 } 592 593 /** 594 * <p>Gets a <code>FormSet</code> based on the language, country 595 * and variant.</p> 596 * @param language The locale's language. 597 * @param country The locale's country. 598 * @param variant The locale's language variant. 599 * @return The FormSet for a locale. 600 * @since Validator 1.2 601 */ 602 FormSet getFormSet(String language, String country, String variant) { 603 604 String key = buildLocale(language, country, variant); 605 606 if (key.length() == 0) { 607 return defaultFormSet; 608 } 609 610 return (FormSet)hFormSets.get(key); 611 } 612 613 /** 614 * Returns a Map of String locale keys to Lists of their FormSets. 615 * @return Map of Form sets 616 * @since Validator 1.2.0 617 */ 618 protected Map getFormSets() { 619 return hFormSets; 620 } 621 622 /** 623 * Returns a Map of String constant names to their String values. 624 * @return Map of Constants 625 * @since Validator 1.2.0 626 */ 627 protected Map getConstants() { 628 return hConstants; 629 } 630 631 /** 632 * Returns a Map of String ValidatorAction names to their ValidatorAction. 633 * @return Map of Validator Actions 634 * @since Validator 1.2.0 635 */ 636 protected Map getActions() { 637 return hActions; 638 } 639 640 /** 641 * Accessor method for Log instance. 642 * 643 * The Log instance variable is transient and 644 * accessing it through this method ensures it 645 * is re-initialized when this instance is 646 * de-serialized. 647 * 648 * @return The Log instance. 649 */ 650 private Log getLog() { 651 if (log == null) { 652 log = LogFactory.getLog(ValidatorResources.class); 653 } 654 return log; 655 } 656 657 }