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

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