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