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 }