View Javadoc

    1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
    2    *
    3    * Licensed under the Apache License, Version 2.0 (the "License");
    4    * you may not use this file except in compliance with the License.
    5    * You may obtain a copy of the License at
    6    *
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    *
    9    * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15
    16  package org.acegisecurity.domain.validation;
    17
    18  import java.util.HashMap;
    19  import java.util.HashSet;
    20  import java.util.Iterator;
    21  import java.util.Map;
    22  import java.util.Set;
    23
    24  import org.springframework.beans.BeansException;
    25  import org.springframework.beans.factory.BeanFactory;
    26  import org.springframework.beans.factory.BeanFactoryAware;
    27  import org.springframework.beans.factory.BeanFactoryUtils;
    28  import org.springframework.beans.factory.ListableBeanFactory;
    29  import org.springframework.util.Assert;
    30  import org.springframework.validation.Validator;
    31
    32
    33  /***
    34   * A basic implementation of {@link ValidationRegistryManager}.
    35   *
    36   * <p>
    37   * Locates <code>Validator</code>s registered in bean factory.
    38   * </p>
    39   *
    40   * <p>If more than one <code>Validator</code> can support a given object, the
    41   * supporting <code>Validator</code>s will be iterated and their bean factory
    42   * defined bean names will be used to attempt to select the "best matching"
    43   * <code>Validator</code>. The lowercase version of a given object's simplified
    44   * class name will be searched within the bean names. If more than one
    45   * <code>Validator</code> contains this search criteria, an exception will be
    46   * thrown as the actual intended <code>Validator</code> is unidentifiable.
    47   *
    48   * <p>For example, say you had a PartyValidator which could validate
    49   * com.foo.Party, and also its subclass, com.foo.Person. There is also a
    50   * PersonValidator which can only validate Person. PartyValidator and
    51   * PersonValidator are registered in the bean container as "partyValidator"
    52   * and "personValidator". When <code>ValidationRegistryManagerImpl</code>
    53   * is asked to return the <code>Validator</code> for Person, it will locate
    54   * the two matching <code>Validator</code>s in the bean container. As there
    55   * are two matching, it will look at the lowercase representation of the
    56   * bean names and see if either contain the lower simplified class name of
    57   * the object being search for (com.foo.Person thus becomes simply "person").
    58   * <code>ValidationRegistryManagerImpl</code> will then correctly return the
    59   * PersonValidator for Person. If the PartyValidator had been registered with
    60   * an ambiguous bean name of say "personAndPartyValidator", both bean names
    61   * would have matched and an exception would have been thrown.
    62   *
    63   * @author Matthew E. Porter
    64   * @author Ben Alex
    65   * @version $Id: ValidationRegistryManagerImpl.java,v 1.7 2005/11/17 00:55:50 benalex Exp $
    66   */
    67  public class ValidationRegistryManagerImpl implements ValidationRegistryManager,
    68      BeanFactoryAware {
    69      //~ Static fields/initializers =============================================
    70
    71      //~ Instance fields ========================================================
    72
    73      private ListableBeanFactory bf;
    74      private Map<Class,String> validatorMap = new HashMap<Class,String>();
    75
    76      //~ Methods ================================================================
    77
    78      public void setBeanFactory(BeanFactory beanFactory)
    79          throws BeansException {
    80          Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
    81              "BeanFactory must be ListableBeanFactory");
    82          this.bf = (ListableBeanFactory) beanFactory;
    83      }
    84
    85      public Validator findValidator(Class domainClass) {
    86          Assert.notNull(domainClass, "domainClass cannot be null");
    87
    88  		if (validatorMap.containsKey(domainClass)) {
    89  			if (validatorMap.get(domainClass) == null) {
    90  				return null;
    91  			}
    92  			return (Validator) this.bf.getBean((String)validatorMap.get(domainClass), Validator.class);
    93  		}
    94
    95  		// Attempt to find Validator via introspection
    96  		Map<String,Validator> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, Validator.class, true, true);
    97
    98  		// Search all Validators for those that support the class
    99  		Set<String> candidateValidatorNames = new HashSet<String>();
    100 		Iterator<String> iter = beans.keySet().iterator();
    101 		while (iter.hasNext()) {
    102 			String beanName = iter.next();
    103 			Validator validator = beans.get(beanName);
    104 			if (validator.supports(domainClass)) {
    105 				candidateValidatorNames.add(beanName);
    106 			}
    107 		}
    108
    109 		if (candidateValidatorNames.size() == 0) {
    110 			// No Validator found
    111 			this.validatorMap.put(domainClass, null);
    112 			return null;
    113 		} else if (candidateValidatorNames.size() == 1) {
    114 			// Only one Validator found, so return it
    115 			String validator = candidateValidatorNames.iterator().next();
    116 			this.validatorMap.put(domainClass, validator);
    117 			return beans.get(validator);
    118 		} else {
    119 			// Try to locate an entry with simple class name in it
    120 			StringBuffer sb = new StringBuffer();
    121 			Iterator<String> iterCandidates = candidateValidatorNames.iterator();
    122 			String lastFound = null;
    123 			int numberFound = 0;
    124 			while (iterCandidates.hasNext()) {
    125 				String candidate = iterCandidates.next();
    126 				sb.append(candidate);
    127 				if (iterCandidates.hasNext()) {
    128 					sb.append(",");
    129 				}
    130 				if (candidate.toLowerCase().contains(domainClass.getSimpleName().toLowerCase())) {
    131 					numberFound++;
    132 					lastFound = candidate;
    133 				}
    134 			}
    135 			if (numberFound != 1) {
    136 				throw new IllegalArgumentException("More than one Validator found (" + sb.toString() + ") that supports '" + domainClass + "', but cannot determine the most specific Validator to use; give a hint by making bean name include the simple name of the target class ('" + domainClass.getSimpleName().toString() + "')");
    137 			}
    138 			this.validatorMap.put(domainClass, lastFound);
    139 			return beans.get(lastFound);
    140 		}
    141     }
    142
    143     public void registerValidator(Class domainClass, String beanName) {
    144         Assert.notNull(domainClass, "domainClass cannot be null");
    145         Assert.notNull(beanName, "beanName cannot be null");
    146 		Assert.isTrue(this.bf.containsBean(beanName), "beanName not found in context");
    147 		Assert.isInstanceOf(Validator.class, this.bf.getBean(beanName), "beanName '" + beanName + "' must be a Validator");
    148         Assert.isTrue(((Validator)this.bf.getBean(beanName)).supports(domainClass), "Validator does not support " + domainClass);
    149 		this.validatorMap.put(domainClass, beanName);
    150     }
    151 }