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