Save This Page
Home » xwork-2.1.5 » com.opensymphony » xwork2 » validator » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002-2006 by OpenSymphony
    3    * All rights reserved.
    4    */
    5   package com.opensymphony.xwork2.validator;
    6   
    7   
    8   import com.opensymphony.xwork2.ActionContext;
    9   import com.opensymphony.xwork2.ActionInvocation;
   10   import com.opensymphony.xwork2.ActionProxy;
   11   import com.opensymphony.xwork2.inject.Inject;
   12   import com.opensymphony.xwork2.util.FileManager;
   13   import com.opensymphony.xwork2.util.ValueStack;
   14   import com.opensymphony.xwork2.util.logging.Logger;
   15   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   16   import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator;
   17   
   18   import java.io.IOException;
   19   import java.io.InputStream;
   20   import java.util;
   21   
   22   /**
   23    * AnnotationActionValidatorManager is the entry point into XWork's annotations-based validator framework.
   24    * Validation rules are specified as annotations within the source files.
   25    *
   26    * @author Rainer Hermanns
   27    * @author jepjep
   28    */
   29   public class AnnotationActionValidatorManager implements ActionValidatorManager {
   30   
   31       /**
   32        * The file suffix for any validation file.
   33        */
   34       protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
   35   
   36       private final Map<String, List<ValidatorConfig>> validatorCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
   37       private final Map<String, List<ValidatorConfig>> validatorFileCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
   38       private static final Logger LOG = LoggerFactory.getLogger(AnnotationActionValidatorManager.class);
   39   
   40       private ValidatorFactory validatorFactory;
   41       private ValidatorFileParser validatorFileParser;
   42   
   43       @Inject
   44       public void setValidatorFactory(ValidatorFactory fac) {
   45           this.validatorFactory = fac;
   46       }
   47   
   48       @Inject
   49       public void setValidatorFileParser(ValidatorFileParser parser) {
   50           this.validatorFileParser = parser;
   51       }
   52   
   53       public synchronized List<Validator> getValidators(Class clazz, String context) {
   54           return getValidators(clazz, context, null);
   55       }
   56   
   57       public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
   58           final String validatorKey = buildValidatorKey(clazz);
   59   
   60           if (validatorCache.containsKey(validatorKey)) {
   61               if (FileManager.isReloadingConfigs()) {
   62                   validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
   63               }
   64           } else {
   65               validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
   66           }
   67   
   68           // get the set of validator configs
   69           List<ValidatorConfig> cfgs = validatorCache.get(validatorKey);
   70   
   71           ValueStack stack = ActionContext.getContext().getValueStack();
   72   
   73           // create clean instances of the validators for the caller's use
   74           ArrayList<Validator> validators = new ArrayList<Validator>(cfgs.size());
   75           for (ValidatorConfig cfg : cfgs) {
   76               if (method == null || method.equals(cfg.getParams().get("methodName"))) {
   77                   Validator validator = validatorFactory.getValidator(
   78                           new ValidatorConfig.Builder(cfg)
   79                               .removeParam("methodName")
   80                               .build());
   81                   validator.setValidatorType(cfg.getType());
   82                   validator.setValueStack(stack);
   83                   validators.add(validator);
   84               }
   85           }
   86   
   87           return validators;
   88       }
   89   
   90       public void validate(Object object, String context) throws ValidationException {
   91           validate(object, context, (String) null);
   92       }
   93   
   94       public void validate(Object object, String context, String method) throws ValidationException {
   95           ValidatorContext validatorContext = new DelegatingValidatorContext(object);
   96           validate(object, context, validatorContext, method);
   97       }
   98   
   99       public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
  100           validate(object, context, validatorContext, null);
  101       }
  102   
  103       public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
  104           List<Validator> validators = getValidators(object.getClass(), context, method);
  105           Set<String> shortcircuitedFields = null;
  106   
  107           for (final Validator validator: validators) {
  108               try {
  109                   validator.setValidatorContext(validatorContext);
  110   
  111                   if (LOG.isDebugEnabled()) {
  112                       LOG.debug("Running validator: " + validator + " for object " + object + " and method " + method);
  113                   }
  114   
  115                   FieldValidator fValidator = null;
  116                   String fullFieldName = null;
  117   
  118                   if (validator instanceof FieldValidator) {
  119                       fValidator = (FieldValidator) validator;
  120                       fullFieldName = new InternalValidatorContextWrapper(fValidator.getValidatorContext()).getFullFieldName(fValidator.getFieldName());
  121   
  122                       if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
  123                           if (LOG.isDebugEnabled()) {
  124                               LOG.debug("Short-circuited, skipping");
  125                           }
  126   
  127                           continue;
  128                       }
  129                   }
  130   
  131                   if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit())
  132                   {
  133                       // get number of existing errors
  134                       List<String> errs = null;
  135   
  136                       if (fValidator != null) {
  137                           if (validatorContext.hasFieldErrors()) {
  138                               Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
  139   
  140                               if (fieldErrors != null) {
  141                                   errs = new ArrayList<String>(fieldErrors);
  142                               }
  143                           }
  144                       } else if (validatorContext.hasActionErrors()) {
  145                           Collection<String> actionErrors = validatorContext.getActionErrors();
  146   
  147                           if (actionErrors != null) {
  148                               errs = new ArrayList<String>(actionErrors);
  149                           }
  150                       }
  151   
  152                       validator.validate(object);
  153   
  154                       if (fValidator != null) {
  155                           if (validatorContext.hasFieldErrors()) {
  156                               Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
  157   
  158                               if ((errCol != null) && !errCol.equals(errs)) {
  159                                   if (LOG.isDebugEnabled()) {
  160                                       LOG.debug("Short-circuiting on field validation");
  161                                   }
  162   
  163                                   if (shortcircuitedFields == null) {
  164                                       shortcircuitedFields = new TreeSet<String>();
  165                                   }
  166   
  167                                   shortcircuitedFields.add(fullFieldName);
  168                               }
  169                           }
  170                       } else if (validatorContext.hasActionErrors()) {
  171                           Collection<String> errCol = validatorContext.getActionErrors();
  172   
  173                           if ((errCol != null) && !errCol.equals(errs)) {
  174                               if (LOG.isDebugEnabled()) {
  175                                   LOG.debug("Short-circuiting");
  176                               }
  177   
  178                               break;
  179                           }
  180                       }
  181   
  182                       continue;
  183                   }
  184   
  185                   validator.validate(object);
  186               } finally {
  187                   validator.setValidatorContext( null );
  188               }
  189   
  190           }
  191       }
  192   
  193       /**
  194        * Builds a key for validators - used when caching validators.
  195        *
  196        * @param clazz   the action.
  197        * @return a validator key which is the class name plus context.
  198        */
  199       protected static String buildValidatorKey(Class clazz) {
  200           ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
  201           ActionProxy proxy = invocation.getProxy();
  202   
  203           //the key needs to use the name of the action from the config file,
  204           //instead of the url, so wild card actions will have the same validator
  205           //see WW-2996
  206           StringBuilder sb = new StringBuilder(clazz.getName());
  207           sb.append("/");
  208           sb.append(proxy.getConfig().getName());
  209           sb.append("|");
  210           sb.append(proxy.getMethod());
  211           return sb.toString();
  212       }
  213   
  214       private  List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
  215           String fileName = aClass.getName().replace('.', '/') + "-" + context.replace('/', '-') + VALIDATION_CONFIG_SUFFIX;
  216   
  217           return loadFile(fileName, aClass, checkFile);
  218       }
  219   
  220   
  221       protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
  222   
  223           String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
  224   
  225           List<ValidatorConfig> result = new ArrayList<ValidatorConfig>(loadFile(fileName, aClass, checkFile));
  226   
  227           AnnotationValidationConfigurationBuilder builder = new AnnotationValidationConfigurationBuilder(validatorFactory);
  228   
  229           List<ValidatorConfig> annotationResult = new ArrayList<ValidatorConfig>(builder.buildAnnotationClassValidatorConfigs(aClass));
  230   
  231           result.addAll(annotationResult);
  232   
  233           return result;
  234   
  235       }
  236   
  237       /**
  238        * <p>This method 'collects' all the validator configurations for a given
  239        * action invocation.</p>
  240        * <p/>
  241        * <p>It will traverse up the class hierarchy looking for validators for every super class
  242        * and directly implemented interface of the current action, as well as adding validators for
  243        * any alias of this invocation. Nifty!</p>
  244        * <p/>
  245        * <p>Given the following class structure:
  246        * <pre>
  247        *   interface Thing;
  248        *   interface Animal extends Thing;
  249        *   interface Quadraped extends Animal;
  250        *   class AnimalImpl implements Animal;
  251        *   class QuadrapedImpl extends AnimalImpl implements Quadraped;
  252        *   class Dog extends QuadrapedImpl;
  253        * </pre></p>
  254        * <p/>
  255        * <p>This method will look for the following config files for Dog:
  256        * <pre>
  257        *   Animal
  258        *   Animal-context
  259        *   AnimalImpl
  260        *   AnimalImpl-context
  261        *   Quadraped
  262        *   Quadraped-context
  263        *   QuadrapedImpl
  264        *   QuadrapedImpl-context
  265        *   Dog
  266        *   Dog-context
  267        * </pre></p>
  268        * <p/>
  269        * <p>Note that the validation rules for Thing is never looked for because no class in the
  270        * hierarchy directly implements Thing.</p>
  271        *
  272        * @param clazz     the Class to look up validators for.
  273        * @param context   the context to use when looking up validators.
  274        * @param checkFile true if the validation config file should be checked to see if it has been
  275        *                  updated.
  276        * @param checked   the set of previously checked class-contexts, null if none have been checked
  277        * @return a list of validator configs for the given class and context.
  278        */
  279       private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
  280           List<ValidatorConfig> validatorConfigs = new ArrayList<ValidatorConfig>();
  281   
  282           if (checked == null) {
  283               checked = new TreeSet<String>();
  284           } else if (checked.contains(clazz.getName())) {
  285               return validatorConfigs;
  286           }
  287   
  288           if (clazz.isInterface()) {
  289               Class[] interfaces = clazz.getInterfaces();
  290   
  291               for (Class anInterface : interfaces) {
  292                   validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
  293               }
  294           } else {
  295               if (!clazz.equals(Object.class)) {
  296                   validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
  297               }
  298           }
  299   
  300           // look for validators for implemented interfaces
  301           Class[] interfaces = clazz.getInterfaces();
  302   
  303           for (Class anInterface1 : interfaces) {
  304               if (checked.contains(anInterface1.getName())) {
  305                   continue;
  306               }
  307   
  308               validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
  309   
  310               if (context != null) {
  311                   validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
  312               }
  313   
  314               checked.add(anInterface1.getName());
  315           }
  316   
  317           validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
  318   
  319           if (context != null) {
  320               validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
  321           }
  322   
  323           checked.add(clazz.getName());
  324   
  325           return validatorConfigs;
  326       }
  327   
  328       private List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
  329           List<ValidatorConfig> retList = Collections.emptyList();
  330   
  331           if ((checkFile && FileManager.fileNeedsReloading(fileName, clazz)) || !validatorFileCache.containsKey(fileName)) {
  332               InputStream is = null;
  333   
  334               try {
  335                   is = FileManager.loadFile(fileName, clazz);
  336   
  337                   if (is != null) {
  338                       retList = new ArrayList<ValidatorConfig>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
  339                   }
  340               } catch (Exception e) {
  341                   LOG.error("Caught exception while loading file " + fileName, e);
  342               } finally {
  343                   if (is != null) {
  344                       try {
  345                           is.close();
  346                       } catch (IOException e) {
  347                           LOG.error("Unable to close input stream for " + fileName, e);
  348                       }
  349                   }
  350               }
  351   
  352               validatorFileCache.put(fileName, retList);
  353           } else {
  354               retList = validatorFileCache.get(fileName);
  355           }
  356   
  357           return retList;
  358       }
  359   
  360   
  361   
  362       /**
  363        * An {@link com.opensymphony.xwork2.validator.ValidatorContext} wrapper that
  364        * returns the full field name
  365        * {@link com.opensymphony.xwork2.validator.AbstractActionValidatorManager.InternalValidatorContextWrapper#getFullFieldName(String)}
  366        * by consulting it's parent if its an {@link com.opensymphony.xwork2.validator.validators.VisitorFieldValidator.AppendingValidatorContext}.
  367        * <p/>
  368        * Eg. if we have nested Visitor
  369        * AddressVisitor nested inside PersonVisitor, when using the normal #getFullFieldName, we will get
  370        * "address.somefield", we lost the parent, with this wrapper, we will get "person.address.somefield".
  371        * This is so that the key is used to register errors, so that we don't screw up short-curcuit feature
  372        * when using nested visitor. See XW-571 (nested visitor validators break short-circuit functionality)
  373        * at http://jira.opensymphony.com/browse/XW-571
  374        */
  375       protected class InternalValidatorContextWrapper {
  376           private ValidatorContext validatorContext = null;
  377   
  378           InternalValidatorContextWrapper(ValidatorContext validatorContext) {
  379               this.validatorContext = validatorContext;
  380           }
  381   
  382           /**
  383            * Get the full field name by consulting the parent, so that when we are using nested visitors (
  384            * visitor nested inside visitor etc.) we still get the full field name including its parents.
  385            * See XW-571 for more details.
  386            * @param field
  387            * @return String
  388            */
  389           public String getFullFieldName(String field) {
  390               if (validatorContext instanceof VisitorFieldValidator.AppendingValidatorContext) {
  391                   VisitorFieldValidator.AppendingValidatorContext appendingValidatorContext =
  392                           (VisitorFieldValidator.AppendingValidatorContext) validatorContext;
  393                   return appendingValidatorContext.getFullFieldNameFromParent(field);
  394               }
  395               return validatorContext.getFullFieldName(field);
  396           }
  397   
  398       }    
  399   }

Save This Page
Home » xwork-2.1.5 » com.opensymphony » xwork2 » validator » [javadoc | source]