com.opensymphony.xwork2.validator.DefaultActionValidatorManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwork Show documentation
Show all versions of xwork Show documentation
XWork is an command-pattern framework that is used to power WebWork
as well as other applications. XWork provides an Inversion of Control
container, a powerful expression language, data type conversion,
validation, and pluggable configuration.
/*
* Copyright (c) 2002-2006 by OpenSymphony
* All rights reserved.
*/
package com.opensymphony.xwork2.validator;
import com.opensymphony.xwork2.util.FileManager;
import com.opensymphony.xwork2.ObjectFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* This is the entry point into XWork's rule-based validation framework. Validation rules are
* specified in XML configuration files named "className-contextName-validation.xml" where
* className is the name of the class the configuration is for and -contextName is optional
* (contextName is an arbitrary key that is used to look up additional validation rules for a
* specific context).
*
* @author Jason Carreira
* @author Mark Woon
* @author James House
* @author Rainer Hermanns
*/
public class DefaultActionValidatorManager implements ActionValidatorManager {
/** The file suffix for any validation file. */
protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
private static final Map> validatorCache = Collections.synchronizedMap(new HashMap>());
private static final Map> validatorFileCache = Collections.synchronizedMap(new HashMap>());
private static final Log LOG = LogFactory.getLog(DefaultActionValidatorManager.class);
/**
* Returns a list of validators for the given class and context. This is the primary
* lookup method for validators.
*
* @param clazz the class to lookup.
* @param context the context of the action class - can be null.
* @return a list of all validators for the given class and context.
*/
public synchronized List getValidators(Class clazz, String context) {
return getValidators(clazz, context, null);
}
/**
* Returns a list of validators for the given class, context, and method name. This is the primary
* lookup method for validators.
*
* @param clazz the class to lookup.
* @param context the context of the action class - can be null.
* @param method the name of the method being invoked on the action - can be null.
* @return a list of all validators for the given class and context.
*/
public synchronized List getValidators(Class clazz, String context, String method) {
final String validatorKey = buildValidatorKey(clazz, context);
if (validatorCache.containsKey(validatorKey)) {
if (FileManager.isReloadingConfigs()) {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
}
} else {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
}
// get the set of validator configs
List cfgs = validatorCache.get(validatorKey);
// create clean instances of the validators for the caller's use
ArrayList validators = new ArrayList(cfgs.size());
for (ValidatorConfig cfg : cfgs) {
if (method == null || method.equals(cfg.getParams().get("methodName"))) {
Validator validator = ValidatorFactory.getValidator(cfg, ObjectFactory.getObjectFactory());
validator.setValidatorType(cfg.getType());
validators.add(validator);
}
}
return validators;
}
/**
* Validates the given object using action and its context.
*
* @param object the action to validate.
* @param context the action's context.
* @throws ValidationException if an error happens when validating the action.
*/
public void validate(Object object, String context) throws ValidationException {
validate(object, context, (String) null);
}
/**
* Validates the given object using action, its context, and the name of the method being invoked on the action.
*
* @param object the action to validate.
* @param context the action's context.
* @param method the name of the method being invoked on the action.
* @throws ValidationException if an error happens when validating the action.
*/
public void validate(Object object, String context, String method) throws ValidationException {
ValidatorContext validatorContext = new DelegatingValidatorContext(object);
validate(object, context, validatorContext, method);
}
/**
* Validates an action give its context and a validation context.
*
* @param object the action to validate.
* @param context the action's context.
* @param validatorContext
* @throws ValidationException if an error happens when validating the action.
*/
public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
validate(object, context, validatorContext, null);
}
/**
* Validates an action give its context, a validation context, and the name of the method being invoked on the action.
*
* @param object the action to validate.
* @param context the action's context.
* @param validatorContext
* @param method the name of the method being invoked on the action.
* @throws ValidationException if an error happens when validating the action.
*/
public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
List validators = getValidators(object.getClass(), context, method);
Set shortcircuitedFields = null;
for (final Validator validator : validators) {
try {
validator.setValidatorContext(validatorContext);
if (LOG.isDebugEnabled()) {
LOG.debug("Running validator: " + validator + " for object " + object + " and method " + method);
}
FieldValidator fValidator = null;
String fullFieldName = null;
if (validator instanceof FieldValidator) {
fValidator = (FieldValidator) validator;
fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName());
if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Short-circuited, skipping");
}
continue;
}
}
if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
// get number of existing errors
List errs = null;
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection fieldErrors = (Collection) validatorContext.getFieldErrors().get(fullFieldName);
if (fieldErrors != null) {
errs = new ArrayList(fieldErrors);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection actionErrors = validatorContext.getActionErrors();
if (actionErrors != null) {
errs = new ArrayList(actionErrors);
}
}
validator.validate(object);
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection errCol = (Collection) validatorContext.getFieldErrors().get(fullFieldName);
if ((errCol != null) && !errCol.equals(errs)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Short-circuiting on field validation");
}
if (shortcircuitedFields == null) {
shortcircuitedFields = new TreeSet();
}
shortcircuitedFields.add(fullFieldName);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection errCol = validatorContext.getActionErrors();
if ((errCol != null) && !errCol.equals(errs)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Short-circuiting");
}
break;
}
}
continue;
}
validator.validate(object);
}
finally {
validator.setValidatorContext(null);
}
}
}
/**
* Builds a key for validators - used when caching validators.
*
* @param clazz the action.
* @param context the action's context.
* @return a validator key which is the class name plus context.
*/
protected static String buildValidatorKey(Class clazz, String context) {
StringBuffer sb = new StringBuffer(clazz.getName());
sb.append("/");
sb.append(context);
return sb.toString();
}
private List buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
private List buildClassValidatorConfigs(Class aClass, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
/**
* This method 'collects' all the validator configurations for a given
* action invocation.
*
* It will traverse up the class hierarchy looking for validators for every super class
* and directly implemented interface of the current action, as well as adding validators for
* any alias of this invocation. Nifty!
*
* Given the following class structure:
*
* interface Thing;
* interface Animal extends Thing;
* interface Quadraped extends Animal;
* class AnimalImpl implements Animal;
* class QuadrapedImpl extends AnimalImpl implements Quadraped;
* class Dog extends QuadrapedImpl;
*
*
* This method will look for the following config files for Dog:
*
* Animal
* Animal-context
* AnimalImpl
* AnimalImpl-context
* Quadraped
* Quadraped-context
* QuadrapedImpl
* QuadrapedImpl-context
* Dog
* Dog-context
*
*
* Note that the validation rules for Thing is never looked for because no class in the
* hierarchy directly implements Thing.
*
* @param clazz the Class to look up validators for.
* @param context the context to use when looking up validators.
* @param checkFile true if the validation config file should be checked to see if it has been
* updated.
* @param checked the set of previously checked class-contexts, null if none have been checked
* @return a list of validator configs for the given class and context.
*/
private List buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set checked) {
List validatorConfigs = new ArrayList();
if (checked == null) {
checked = new TreeSet();
} else if (checked.contains(clazz.getName())) {
return validatorConfigs;
}
if (clazz.isInterface()) {
for (Class anInterface : clazz.getInterfaces()) {
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
}
} else {
if (!clazz.equals(Object.class)) {
validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
}
}
// look for validators for implemented interfaces
for (Class anInterface1 : clazz.getInterfaces()) {
if (checked.contains(anInterface1.getName())) {
continue;
}
validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
if (context != null) {
validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
}
checked.add(anInterface1.getName());
}
validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
if (context != null) {
validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
}
checked.add(clazz.getName());
return validatorConfigs;
}
private List loadFile(String fileName, Class clazz, boolean checkFile) {
List retList = Collections.emptyList();
if ((checkFile && FileManager.fileNeedsReloading(fileName)) || !validatorFileCache.containsKey(fileName)) {
InputStream is = null;
try {
is = FileManager.loadFile(fileName, clazz);
if (is != null) {
retList = new ArrayList(ValidatorFileParser.parseActionValidatorConfigs(is, fileName));
}
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
LOG.error("Unable to close input stream for " + fileName, e);
}
}
}
validatorFileCache.put(fileName, retList);
} else {
retList = validatorFileCache.get(fileName);
}
return retList;
}
}