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 }