001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005:
006: package com.opensymphony.xwork.validator;
007:
008: import com.opensymphony.util.FileManager;
009:
010: import org.apache.commons.logging.Log;
011: import org.apache.commons.logging.LogFactory;
012:
013: import java.io.IOException;
014: import java.io.InputStream;
015:
016: import java.util.*;
017:
018: /**
019: * This is the entry point into XWork's rule-based validation framework. Validation rules are
020: * specified in XML configuration files named "className-contextName-validation.xml" where
021: * className is the name of the class the configuration is for and -contextName is optional
022: * (contextName is an arbitrary key that is used to look up additional validation rules for a
023: * specific context).
024: *
025: * @author Jason Carreira
026: * @author Mark Woon
027: * @author James House
028: * @author Rainer Hermanns
029: * @author tmjee
030: */
031: public class DefaultActionValidatorManager implements
032: ActionValidatorManager {
033:
034: /** The file suffix for any validation file. */
035: protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
036:
037: private static final Map validatorCache = Collections
038: .synchronizedMap(new HashMap());
039: private static final Map validatorFileCache = Collections
040: .synchronizedMap(new HashMap());
041: private static final Log LOG = LogFactory
042: .getLog(DefaultActionValidatorManager.class);
043:
044: /**
045: * Returns a list of validators for the given class and context. This is the primary
046: * lookup method for validators.
047: *
048: * @param clazz the class to lookup.
049: * @param context the context of the action class - can be <tt>null</tt>.
050: * @return a list of all validators for the given class and context.
051: */
052: public synchronized List getValidators(Class clazz, String context) {
053: final String validatorKey = buildValidatorKey(clazz, context);
054:
055: if (validatorCache.containsKey(validatorKey)) {
056: if (FileManager.isReloadingConfigs()) {
057: validatorCache.put(validatorKey, buildValidatorConfigs(
058: clazz, context, true, null));
059: }
060: } else {
061: validatorCache.put(validatorKey, buildValidatorConfigs(
062: clazz, context, false, null));
063: }
064:
065: // get the set of validator configs
066: List cfgs = (List) validatorCache.get(validatorKey);
067:
068: // create clean instances of the validators for the caller's use
069: ArrayList validators = new ArrayList(cfgs.size());
070: for (Iterator iterator = cfgs.iterator(); iterator.hasNext();) {
071: ValidatorConfig cfg = (ValidatorConfig) iterator.next();
072: Validator validator = ValidatorFactory.getValidator(cfg);
073: validator.setValidatorType(cfg.getType());
074: validators.add(validator);
075: }
076:
077: return validators;
078: }
079:
080: /**
081: * Validates the given object using action and its context.
082: *
083: * @param object the action to validate.
084: * @param context the action's context.
085: * @throws ValidationException if an error happens when validating the action.
086: */
087: public void validate(Object object, String context)
088: throws ValidationException {
089: ValidatorContext validatorContext = new DelegatingValidatorContext(
090: object);
091: validate(object, context, validatorContext);
092: }
093:
094: /**
095: * Validates an action give its context and a validation context.
096: *
097: * @param object the action to validate.
098: * @param context the action's context.
099: * @param validatorContext
100: * @throws ValidationException if an error happens when validating the action.
101: */
102: public void validate(Object object, String context,
103: ValidatorContext validatorContext)
104: throws ValidationException {
105: List validators = getValidators(object.getClass(), context);
106: validate(object, validators, validatorContext);
107: }
108:
109: /**
110: * Validates an action through a series of <code>validators</code> with
111: * the given <code>validatorContext</code>
112: *
113: * @param object
114: * @param validators
115: * @param validatorContext
116: * @throws ValidationException
117: */
118: public void validate(Object object, List validators,
119: ValidatorContext validatorContext)
120: throws ValidationException {
121: Set shortcircuitedFields = null;
122:
123: for (Iterator iterator = validators.iterator(); iterator
124: .hasNext();) {
125: final Validator validator = (Validator) iterator.next();
126: try {
127: validator.setValidatorContext(validatorContext);
128:
129: if (LOG.isDebugEnabled()) {
130: LOG.debug("Running validator: " + validator
131: + " for object " + object);
132: }
133:
134: FieldValidator fValidator = null;
135: String fullFieldName = null;
136:
137: if (validator instanceof FieldValidator) {
138: fValidator = (FieldValidator) validator;
139: fullFieldName = fValidator
140: .getValidatorContext()
141: .getFullFieldName(fValidator.getFieldName());
142:
143: if ((shortcircuitedFields != null)
144: && shortcircuitedFields
145: .contains(fullFieldName)) {
146: if (LOG.isDebugEnabled()) {
147: LOG.debug("Short-circuited, skipping");
148: }
149:
150: continue;
151: }
152: }
153:
154: if (validator instanceof ShortCircuitableValidator
155: && ((ShortCircuitableValidator) validator)
156: .isShortCircuit()) {
157: // get number of existing errors
158: List errs = null;
159:
160: if (fValidator != null) {
161: if (validatorContext.hasFieldErrors()) {
162: Collection fieldErrors = (Collection) validatorContext
163: .getFieldErrors()
164: .get(fullFieldName);
165:
166: if (fieldErrors != null) {
167: errs = new ArrayList(fieldErrors);
168: }
169: }
170: } else if (validatorContext.hasActionErrors()) {
171: Collection actionErrors = validatorContext
172: .getActionErrors();
173:
174: if (actionErrors != null) {
175: errs = new ArrayList(actionErrors);
176: }
177: }
178:
179: validator.validate(object);
180:
181: if (fValidator != null) {
182: if (validatorContext.hasFieldErrors()) {
183: Collection errCol = (Collection) validatorContext
184: .getFieldErrors()
185: .get(fullFieldName);
186:
187: if ((errCol != null)
188: && !errCol.equals(errs)) {
189: if (LOG.isDebugEnabled()) {
190: LOG
191: .debug("Short-circuiting on field validation");
192: }
193:
194: if (shortcircuitedFields == null) {
195: shortcircuitedFields = new TreeSet();
196: }
197:
198: shortcircuitedFields.add(fullFieldName);
199: }
200: }
201: } else if (validatorContext.hasActionErrors()) {
202: Collection errCol = validatorContext
203: .getActionErrors();
204:
205: if ((errCol != null) && !errCol.equals(errs)) {
206: if (LOG.isDebugEnabled()) {
207: LOG.debug("Short-circuiting");
208: }
209:
210: break;
211: }
212: }
213:
214: continue;
215: }
216:
217: validator.validate(object);
218: } finally {
219: validator.setValidatorContext(null);
220: }
221: }
222: }
223:
224: /**
225: * Builds a key for validators - used when caching validators.
226: *
227: * @param clazz the action.
228: * @param context the action's context.
229: * @return a validator key which is the class name plus context.
230: */
231: protected static String buildValidatorKey(Class clazz,
232: String context) {
233: StringBuffer sb = new StringBuffer(clazz.getName());
234: sb.append("/");
235: sb.append(context);
236: return sb.toString();
237: }
238:
239: private List buildAliasValidatorConfigs(Class aClass,
240: String context, boolean checkFile) {
241: String fileName = aClass.getName().replace('.', '/') + "-"
242: + context + VALIDATION_CONFIG_SUFFIX;
243:
244: return loadFile(fileName, aClass, checkFile);
245: }
246:
247: private List buildClassValidatorConfigs(Class aClass,
248: boolean checkFile) {
249: String fileName = aClass.getName().replace('.', '/')
250: + VALIDATION_CONFIG_SUFFIX;
251:
252: return loadFile(fileName, aClass, checkFile);
253: }
254:
255: /**
256: * <p>This method 'collects' all the validator configurations for a given
257: * action invocation.</p>
258: *
259: * <p>It will traverse up the class hierarchy looking for validators for every super class
260: * and directly implemented interface of the current action, as well as adding validators for
261: * any alias of this invocation. Nifty!</p>
262: *
263: * <p>Given the following class structure:
264: * <pre>
265: * interface Thing;
266: * interface Animal extends Thing;
267: * interface Quadraped extends Animal;
268: * class AnimalImpl implements Animal;
269: * class QuadrapedImpl extends AnimalImpl implements Quadraped;
270: * class Dog extends QuadrapedImpl;
271: * </pre></p>
272: *
273: * <p>This method will look for the following config files for Dog:
274: * <pre>
275: * Animal
276: * Animal-context
277: * AnimalImpl
278: * AnimalImpl-context
279: * Quadraped
280: * Quadraped-context
281: * QuadrapedImpl
282: * QuadrapedImpl-context
283: * Dog
284: * Dog-context
285: * </pre></p>
286: *
287: * <p>Note that the validation rules for Thing is never looked for because no class in the
288: * hierarchy directly implements Thing.</p>
289: *
290: * @param clazz the Class to look up validators for.
291: * @param context the context to use when looking up validators.
292: * @param checkFile true if the validation config file should be checked to see if it has been
293: * updated.
294: * @param checked the set of previously checked class-contexts, null if none have been checked
295: * @return a list of validator configs for the given class and context.
296: */
297: private List buildValidatorConfigs(Class clazz, String context,
298: boolean checkFile, Set checked) {
299: List validatorConfigs = new ArrayList();
300:
301: if (checked == null) {
302: checked = new TreeSet();
303: } else if (checked.contains(clazz.getName())) {
304: return validatorConfigs;
305: }
306:
307: if (clazz.isInterface()) {
308: Class[] interfaces = clazz.getInterfaces();
309:
310: for (int x = 0; x < interfaces.length; x++) {
311: validatorConfigs.addAll(buildValidatorConfigs(
312: interfaces[x], context, checkFile, checked));
313: }
314: } else {
315: if (!clazz.equals(Object.class)) {
316: validatorConfigs.addAll(buildValidatorConfigs(clazz
317: .getSuperclass(), context, checkFile, checked));
318: }
319: }
320:
321: // look for validators for implemented interfaces
322: Class[] interfaces = clazz.getInterfaces();
323:
324: for (int x = 0; x < interfaces.length; x++) {
325: if (checked.contains(interfaces[x].getName())) {
326: continue;
327: }
328:
329: validatorConfigs.addAll(buildClassValidatorConfigs(
330: interfaces[x], checkFile));
331:
332: if (context != null) {
333: validatorConfigs.addAll(buildAliasValidatorConfigs(
334: interfaces[x], context, checkFile));
335: }
336:
337: checked.add(interfaces[x].getName());
338: }
339:
340: validatorConfigs.addAll(buildClassValidatorConfigs(clazz,
341: checkFile));
342:
343: if (context != null) {
344: validatorConfigs.addAll(buildAliasValidatorConfigs(clazz,
345: context, checkFile));
346: }
347:
348: checked.add(clazz.getName());
349:
350: return validatorConfigs;
351: }
352:
353: private List loadFile(String fileName, Class clazz,
354: boolean checkFile) {
355: List retList = Collections.EMPTY_LIST;
356:
357: if ((checkFile && FileManager.fileNeedsReloading(fileName))
358: || !validatorFileCache.containsKey(fileName)) {
359: InputStream is = null;
360:
361: try {
362: is = FileManager.loadFile(fileName, clazz);
363:
364: if (is != null) {
365: retList = new ArrayList(ValidatorFileParser
366: .parseActionValidatorConfigs(is, fileName));
367: }
368: } finally {
369: if (is != null) {
370: try {
371: is.close();
372: } catch (IOException e) {
373: LOG.error("Unable to close input stream for "
374: + fileName, e);
375: }
376: }
377: }
378:
379: validatorFileCache.put(fileName, retList);
380: } else {
381: retList = (List) validatorFileCache.get(fileName);
382: }
383:
384: return retList;
385: }
386: }
|