1. /*
  2. * $Header: /home/cvs/jakarta-commons/validator/src/share/org/apache/commons/validator/ValidatorResources.java,v 1.30.2.2 2004/06/22 02:24:38 husted Exp $
  3. * $Revision: 1.30.2.2 $
  4. * $Date: 2004/06/22 02:24:38 $
  5. *
  6. * ====================================================================
  7. * Copyright 2001-2004 The Apache Software Foundation
  8. *
  9. * Licensed under the Apache License, Version 2.0 (the "License");
  10. * you may not use this file except in compliance with the License.
  11. * You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing, software
  16. * distributed under the License is distributed on an "AS IS" BASIS,
  17. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. * See the License for the specific language governing permissions and
  19. * limitations under the License.
  20. */
  21. package org.apache.commons.validator;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.Serializable;
  25. import java.net.URL;
  26. import java.util.ArrayList;
  27. import java.util.Collections;
  28. import java.util.Iterator;
  29. import java.util.List;
  30. import java.util.Locale;
  31. import java.util.Map;
  32. import org.apache.commons.collections.FastHashMap; // DEPRECATED
  33. import org.apache.commons.digester.Digester;
  34. import org.apache.commons.digester.xmlrules.DigesterLoader;
  35. import org.apache.commons.logging.Log;
  36. import org.apache.commons.logging.LogFactory;
  37. import org.xml.sax.SAXException;
  38. /**
  39. * <p>
  40. * General purpose class for storing <code>FormSet</code> objects based
  41. * on their associated <code>Locale</code>. Instances of this class are usually
  42. * configured through a validation.xml file that is parsed in a constructor.
  43. * </p>
  44. *
  45. * <p><strong>Note</strong> - Classes that extend this class
  46. * must be Serializable so that instances may be used in distributable
  47. * application server environments.</p>
  48. *
  49. * <p>
  50. * The use of FastHashMap is deprecated and will be replaced in a future
  51. * release.
  52. * </p>
  53. */
  54. public class ValidatorResources implements Serializable {
  55. /**
  56. * The set of public identifiers, and corresponding resource names, for
  57. * the versions of the configuration file DTDs that we know about. There
  58. * <strong>MUST</strong> be an even number of Strings in this list!
  59. */
  60. private static final String registrations[] = {
  61. "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN",
  62. "/org/apache/commons/validator/resources/validator_1_0.dtd",
  63. "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0.1//EN",
  64. "/org/apache/commons/validator/resources/validator_1_0_1.dtd",
  65. "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN",
  66. "/org/apache/commons/validator/resources/validator_1_1.dtd",
  67. "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN",
  68. "/org/apache/commons/validator/resources/validator_1_1_3.dtd"
  69. };
  70. /**
  71. * Logger.
  72. * @deprecated Subclasses should use their own logging instance.
  73. */
  74. protected static Log log = LogFactory.getLog(ValidatorResources.class);
  75. /**
  76. * <code>FastHashMap</code> of <code>FormSet</code>s stored under
  77. * a <code>Locale</code> key.
  78. */
  79. protected FastHashMap hFormSets = new FastHashMap();
  80. /**
  81. * <code>FastHashMap</code> of global constant values with
  82. * the name of the constant as the key.
  83. */
  84. protected FastHashMap hConstants = new FastHashMap();
  85. /**
  86. * <code>FastHashMap</code> of <code>ValidatorAction</code>s with
  87. * the name of the <code>ValidatorAction</code> as the key.
  88. */
  89. protected FastHashMap hActions = new FastHashMap();
  90. /**
  91. * The default locale on our server.
  92. */
  93. protected static Locale defaultLocale = Locale.getDefault();
  94. /**
  95. * Create an empty ValidatorResources object.
  96. */
  97. public ValidatorResources() {
  98. super();
  99. }
  100. /**
  101. * Create a ValidatorResources object from an InputStream.
  102. *
  103. * @param in InputStream to a validation.xml configuration file. It's the client's
  104. * responsibility to close this stream.
  105. * @throws IOException
  106. * @throws SAXException if the validation XML files are not valid or well
  107. * formed.
  108. * @since Validator 1.1
  109. */
  110. public ValidatorResources(InputStream in) throws IOException, SAXException {
  111. this(new InputStream[]{in});
  112. }
  113. /**
  114. * Create a ValidatorResources object from an InputStream.
  115. *
  116. * @param streams An array of InputStreams to several validation.xml
  117. * configuration files that will be read in order and merged into this object.
  118. * It's the client's responsibility to close these streams.
  119. * @throws IOException
  120. * @throws SAXException if the validation XML files are not valid or well
  121. * formed.
  122. * @since Validator 1.1
  123. */
  124. public ValidatorResources(InputStream[] streams)
  125. throws IOException, SAXException {
  126. super();
  127. URL rulesUrl = this.getClass().getResource("digester-rules.xml");
  128. Digester digester = DigesterLoader.createDigester(rulesUrl);
  129. digester.setNamespaceAware(true);
  130. digester.setValidating(true);
  131. digester.setUseContextClassLoader(true);
  132. // register DTDs
  133. for (int i = 0; i < registrations.length; i += 2) {
  134. URL url = this.getClass().getResource(registrations[i + 1]);
  135. if (url != null) {
  136. digester.register(registrations[i], url.toString());
  137. }
  138. }
  139. for (int i = 0; i < streams.length; i++) {
  140. digester.push(this);
  141. digester.parse(streams[i]);
  142. }
  143. this.process();
  144. }
  145. /**
  146. * Add a <code>FormSet</code> to this <code>ValidatorResources</code>
  147. * object. It will be associated with the <code>Locale</code> of the
  148. * <code>FormSet</code>.
  149. * @deprecated Use addFormSet() instead.
  150. */
  151. public void put(FormSet fs) {
  152. this.addFormSet(fs);
  153. }
  154. /**
  155. * Add a <code>FormSet</code> to this <code>ValidatorResources</code>
  156. * object. It will be associated with the <code>Locale</code> of the
  157. * <code>FormSet</code>.
  158. * @since Validator 1.1
  159. */
  160. public void addFormSet(FormSet fs) {
  161. String key = this.buildKey(fs);
  162. List formsets = (List) hFormSets.get(key);
  163. if (formsets == null) {
  164. formsets = new ArrayList();
  165. hFormSets.put(key, formsets);
  166. }
  167. if (!formsets.contains(fs)) {
  168. if (log.isDebugEnabled()) {
  169. log.debug("Adding FormSet '" + fs.toString() + "'.");
  170. }
  171. formsets.add(fs);
  172. }
  173. }
  174. /**
  175. * Add a global constant to the resource.
  176. * @deprecated Use addConstant(String, String) instead.
  177. */
  178. public void addConstant(Constant c) {
  179. this.addConstantParam(c.getName(), c.getValue());
  180. }
  181. /**
  182. * Add a global constant to the resource.
  183. * @deprecated Use addConstant(String, String) instead.
  184. */
  185. public void addConstantParam(String name, String value) {
  186. if (name != null
  187. && name.length() > 0
  188. && value != null
  189. && value.length() > 0) {
  190. if (log.isDebugEnabled()) {
  191. log.debug("Adding Global Constant: " + name + "," + value);
  192. }
  193. this.hConstants.put(name, value);
  194. }
  195. }
  196. /**
  197. * Add a global constant to the resource.
  198. */
  199. public void addConstant(String name, String value) {
  200. if (log.isDebugEnabled()) {
  201. log.debug("Adding Global Constant: " + name + "," + value);
  202. }
  203. this.hConstants.put(name, value);
  204. }
  205. /**
  206. * Add a <code>ValidatorAction</code> to the resource. It also creates an
  207. * instance of the class based on the <code>ValidatorAction</code>s
  208. * classname and retrieves the <code>Method</code> instance and sets them
  209. * in the <code>ValidatorAction</code>.
  210. */
  211. public void addValidatorAction(ValidatorAction va) {
  212. va.init();
  213. this.hActions.put(va.getName(), va);
  214. if (log.isDebugEnabled()) {
  215. log.debug("Add ValidatorAction: " + va.getName() + "," + va.getClassname());
  216. }
  217. }
  218. /**
  219. * Get a <code>ValidatorAction</code> based on it's name.
  220. */
  221. public ValidatorAction getValidatorAction(String key) {
  222. return (ValidatorAction) hActions.get(key);
  223. }
  224. /**
  225. * Get an unmodifiable <code>Map</code> of the <code>ValidatorAction</code>s.
  226. */
  227. public Map getValidatorActions() {
  228. return Collections.unmodifiableMap(hActions);
  229. }
  230. /**
  231. * Builds a key to store the <code>FormSet</code> under based on it's
  232. * language, country, and variant values.
  233. */
  234. protected String buildKey(FormSet fs) {
  235. String locale =
  236. this.buildLocale(fs.getLanguage(), fs.getCountry(), fs.getVariant());
  237. if (locale.length() == 0) {
  238. locale = defaultLocale.toString();
  239. }
  240. return locale;
  241. }
  242. /**
  243. * Assembles a Locale code from the given parts.
  244. */
  245. private String buildLocale(String lang, String country, String variant) {
  246. String key = ((lang != null && lang.length() > 0) ? lang : "");
  247. key += ((country != null && country.length() > 0) ? "_" + country : "");
  248. key += ((variant != null && variant.length() > 0) ? "_" + variant : "");
  249. return key;
  250. }
  251. /**
  252. * <p>Gets a <code>Form</code> based on the name of the form and the <code>Locale</code> that
  253. * most closely matches the <code>Locale</code> passed in. The order of <code>Locale</code>
  254. * matching is:</p>
  255. * <ol>
  256. * <li>language + country + variant</li>
  257. * <li>language + country</li>
  258. * <li>language</li>
  259. * <li>default locale</li>
  260. * </ol>
  261. * @deprecated Use getForm() instead.
  262. */
  263. public Form get(Locale locale, Object formKey) {
  264. String key = (formKey == null) ? null : formKey.toString();
  265. return this.getForm(locale, key);
  266. }
  267. /**
  268. * <p>Gets a <code>Form</code> based on the name of the form and the
  269. * <code>Locale</code> that most closely matches the <code>Locale</code>
  270. * passed in. The order of <code>Locale</code> matching is:</p>
  271. * <ol>
  272. * <li>language + country + variant</li>
  273. * <li>language + country</li>
  274. * <li>language</li>
  275. * <li>default locale</li>
  276. * </ol>
  277. * @since Validator 1.1
  278. */
  279. public Form getForm(Locale locale, String formKey) {
  280. return this.getForm(
  281. locale.getLanguage(),
  282. locale.getCountry(),
  283. locale.getVariant(),
  284. formKey);
  285. }
  286. /**
  287. * <p>Gets a <code>Form</code> based on the name of the form and the
  288. * <code>Locale</code> that most closely matches the <code>Locale</code>
  289. * passed in. The order of <code>Locale</code> matching is:</p>
  290. * <ol>
  291. * <li>language + country + variant</li>
  292. * <li>language + country</li>
  293. * <li>language</li>
  294. * <li>default locale</li>
  295. * </ol>
  296. * @deprecated Use getForm() instead.
  297. */
  298. public Form get(
  299. String language,
  300. String country,
  301. String variant,
  302. Object formKey) {
  303. String key = (formKey == null) ? null : formKey.toString();
  304. return this.getForm(language, country, variant, key);
  305. }
  306. /**
  307. * <p>Gets a <code>Form</code> based on the name of the form and the
  308. * <code>Locale</code> that most closely matches the <code>Locale</code>
  309. * passed in. The order of <code>Locale</code> matching is:</p>
  310. * <ol>
  311. * <li>language + country + variant</li>
  312. * <li>language + country</li>
  313. * <li>language</li>
  314. * <li>default locale</li>
  315. * </ol>
  316. * @since Validator 1.1
  317. */
  318. public Form getForm(
  319. String language,
  320. String country,
  321. String variant,
  322. String formKey) {
  323. String key = this.buildLocale(language, country, variant);
  324. List v = (List) hFormSets.get(key);
  325. if (v == null) {
  326. key = (language != null && language.length() > 0) ? language : "";
  327. key += (country != null && country.length() > 0) ? "_" + country : "";
  328. v = (List) hFormSets.get(key);
  329. }
  330. if (v == null) {
  331. key = (language != null && language.length() > 0) ? language : "";
  332. v = (List) hFormSets.get(key);
  333. }
  334. if (v == null) {
  335. key = defaultLocale.toString();
  336. v = (List) hFormSets.get(key);
  337. }
  338. if (v == null) {
  339. return null;
  340. }
  341. Iterator formsets = v.iterator();
  342. while (formsets.hasNext()) {
  343. FormSet set = (FormSet) formsets.next();
  344. if ((set != null) && (set.getForm(formKey) != null)) {
  345. return set.getForm(formKey);
  346. }
  347. }
  348. return null;
  349. }
  350. /**
  351. * Process the <code>ValidatorResources</code> object. Currently sets the
  352. * <code>FastHashMap</code>s to the 'fast' mode and call the processes all
  353. * other resources. <strong>Note</strong>: The framework calls this automatically
  354. * when ValidatorResources is created from an XML file. If you create an instance
  355. * of this class by hand you <strong>must</strong> call this method when finished.
  356. */
  357. public void process() {
  358. hFormSets.setFast(true);
  359. hConstants.setFast(true);
  360. hActions.setFast(true);
  361. this.internalProcessForms();
  362. }
  363. /**
  364. * <p>Process the <code>Form</code> objects. This clones the <code>Field</code>s
  365. * that don't exist in a <code>FormSet</code> compared to the default
  366. * <code>FormSet</code>.</p>
  367. * @deprecated This is an internal method that client classes need not call directly.
  368. */
  369. public void processForms() {
  370. this.internalProcessForms();
  371. }
  372. /**
  373. * <p>Process the <code>Form</code> objects. This clones the <code>Field</code>s
  374. * that don't exist in a <code>FormSet</code> compared to the default
  375. * <code>FormSet</code>.</p>
  376. * TODO When processForms() is removed from the public interface, rename this
  377. * private method back to "processForms".
  378. */
  379. private void internalProcessForms() {
  380. //hFormSets.put(buildKey(fs), fs);
  381. String defaultKey = defaultLocale.toString();
  382. // Loop through FormSets
  383. for (Iterator i = hFormSets.keySet().iterator(); i.hasNext();) {
  384. String key = (String) i.next();
  385. // Skip default FormSet
  386. if (key.equals(defaultKey)) {
  387. continue;
  388. }
  389. List formsets = (List) hFormSets.get(key);
  390. Iterator formsetsIterator = formsets.iterator();
  391. while (formsetsIterator.hasNext()) {
  392. FormSet fs = (FormSet) formsetsIterator.next();
  393. // Loop through Forms and copy/clone fields from default locale
  394. for (Iterator x = fs.getForms().keySet().iterator(); x.hasNext();) {
  395. String formKey = (String) x.next();
  396. Form form = (Form) fs.getForms().get(formKey);
  397. // Create a new Form object so the order from the default is
  398. // maintained (very noticable in the JavaScript).
  399. Form newForm = new Form();
  400. newForm.setName(form.getName());
  401. // Loop through the default locale form's fields
  402. // If they don't exist in the current locale's form, then clone them.
  403. Form defaultForm = get(defaultLocale, formKey);
  404. Iterator defaultFields = defaultForm.getFields().iterator();
  405. while (defaultFields.hasNext()) {
  406. Field defaultField = (Field) defaultFields.next();
  407. String fieldKey = defaultField.getKey();
  408. if (form.containsField(fieldKey)) {
  409. newForm.addField(form.getField(fieldKey));
  410. } else {
  411. Field field =
  412. getClosestLocaleField(fs, formKey, fieldKey);
  413. newForm.addField((Field) field.clone());
  414. }
  415. }
  416. fs.addForm(newForm);
  417. }
  418. }
  419. }
  420. // Process Fully Constructed FormSets
  421. for (Iterator i = hFormSets.values().iterator(); i.hasNext();) {
  422. List formsets = (List) i.next();
  423. Iterator formsetsIterator = formsets.iterator();
  424. while (formsetsIterator.hasNext()) {
  425. FormSet fs = (FormSet) formsetsIterator.next();
  426. if (!fs.isProcessed()) {
  427. fs.process(hConstants);
  428. }
  429. }
  430. }
  431. }
  432. /**
  433. * Retrieves the closest matching <code>Field</code> based
  434. * on <code>FormSet</code>'s locale. This is used when
  435. * constructing a clone, field by field, of partial
  436. * <code>FormSet</code>.
  437. */
  438. protected Field getClosestLocaleField(
  439. FormSet fs,
  440. String formKey,
  441. String fieldKey) {
  442. Field field = null;
  443. String language = fs.getLanguage();
  444. String country = fs.getCountry();
  445. String variant = fs.getVariant();
  446. if (!GenericValidator.isBlankOrNull(language)
  447. && !GenericValidator.isBlankOrNull(country)
  448. && !GenericValidator.isBlankOrNull(variant)) {
  449. Form form = this.getForm(language, country, variant, formKey);
  450. field = form.getField(fieldKey);
  451. }
  452. if (field == null) {
  453. if (!GenericValidator.isBlankOrNull(language)
  454. && !GenericValidator.isBlankOrNull(country)) {
  455. Form form = this.getForm(language, country, null, formKey);
  456. field = form.getField(fieldKey);
  457. }
  458. }
  459. if (field == null) {
  460. if (!GenericValidator.isBlankOrNull(language)) {
  461. Form form = this.getForm(language, null, null, formKey);
  462. field = form.getField(fieldKey);
  463. }
  464. }
  465. if (field == null) {
  466. Form form = this.getForm(defaultLocale, formKey);
  467. field = form.getField(fieldKey);
  468. }
  469. return field;
  470. }
  471. }