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: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $
054 */
055 public 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 }