1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.intercept.method;
17
18 import org.acegisecurity.ConfigAttribute;
19 import org.acegisecurity.ConfigAttributeDefinition;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23
24 import java.lang.reflect.Method;
25
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31
32
33 /***
34 * Stores a {@link ConfigAttributeDefinition} for each method signature defined
35 * in a bean context.
36 *
37 * <p>
38 * For consistency with {@link MethodDefinitionAttributes} as well as support
39 * for <code>MethodDefinitionSourceAdvisor</code>, this implementation will
40 * return a <code>ConfigAttributeDefinition</code> containing all
41 * configuration attributes defined against:
42 *
43 * <ul>
44 * <li>
45 * The method-specific attributes defined for the intercepted method of the
46 * intercepted class.
47 * </li>
48 * <li>
49 * The method-specific attributes defined by any explicitly implemented
50 * interface if that interface contains a method signature matching that of
51 * the intercepted method.
52 * </li>
53 * </ul>
54 * </p>
55 *
56 * <p>
57 * In general you should therefore define the <b>interface method</b>s of your
58 * secure objects, not the implementations. For example, define
59 * <code>com.company.Foo.findAll=ROLE_TEST</code> but not
60 * <code>com.company.FooImpl.findAll=ROLE_TEST</code>.
61 * </p>
62 *
63 * @author Ben Alex
64 * @version $Id: MethodDefinitionMap.java,v 1.6 2005/11/17 00:56:09 benalex Exp $
65 */
66 public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
67
68
69 private static final Log logger = LogFactory.getLog(MethodDefinitionMap.class);
70
71
72
73 /*** Map from Method to ApplicationDefinition */
74 protected Map methodMap = new HashMap();
75
76 /*** Map from Method to name pattern used for registration */
77 private Map nameMap = new HashMap();
78
79
80
81 /***
82 * Obtains the configuration attributes explicitly defined against this
83 * bean. This method will not return implicit configuration attributes
84 * that may be returned by {@link #lookupAttributes(Method)} as it does
85 * not have access to a method invocation at this time.
86 *
87 * @return the attributes explicitly defined against this bean
88 */
89 public Iterator getConfigAttributeDefinitions() {
90 return methodMap.values().iterator();
91 }
92
93 /***
94 * Obtains the number of configuration attributes explicitly defined
95 * against this bean. This method will not return implicit configuration
96 * attributes that may be returned by {@link #lookupAttributes(Method)} as
97 * it does not have access to a method invocation at this time.
98 *
99 * @return the number of configuration attributes explicitly defined
100 * against this bean
101 */
102 public int getMethodMapSize() {
103 return this.methodMap.size();
104 }
105
106 /***
107 * Add configuration attributes for a secure method. Method names can end
108 * or start with <code>*</code> for matching multiple methods.
109 *
110 * @param method the method to be secured
111 * @param attr required authorities associated with the method
112 */
113 public void addSecureMethod(Method method, ConfigAttributeDefinition attr) {
114 logger.info("Adding secure method [" + method + "] with attributes ["
115 + attr + "]");
116 this.methodMap.put(method, attr);
117 }
118
119 /***
120 * Add configuration attributes for a secure method. Method names can end
121 * or start with <code>*</code> for matching multiple methods.
122 *
123 * @param name class and method name, separated by a dot
124 * @param attr required authorities associated with the method
125 *
126 * @throws IllegalArgumentException DOCUMENT ME!
127 */
128 public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
129 int lastDotIndex = name.lastIndexOf(".");
130
131 if (lastDotIndex == -1) {
132 throw new IllegalArgumentException("'" + name
133 + "' is not a valid method name: format is FQN.methodName");
134 }
135
136 String className = name.substring(0, lastDotIndex);
137 String methodName = name.substring(lastDotIndex + 1);
138
139 try {
140 Class clazz = Class.forName(className, true,
141 Thread.currentThread().getContextClassLoader());
142 addSecureMethod(clazz, methodName, attr);
143 } catch (ClassNotFoundException ex) {
144 throw new IllegalArgumentException("Class '" + className
145 + "' not found");
146 }
147 }
148
149 /***
150 * Add configuration attributes for a secure method. Method names can end
151 * or start with <code>*</code> for matching multiple methods.
152 *
153 * @param clazz target interface or class
154 * @param mappedName mapped method name
155 * @param attr required authorities associated with the method
156 *
157 * @throws IllegalArgumentException DOCUMENT ME!
158 */
159 public void addSecureMethod(Class clazz, String mappedName,
160 ConfigAttributeDefinition attr) {
161 String name = clazz.getName() + '.' + mappedName;
162
163 if (logger.isDebugEnabled()) {
164 logger.debug("Adding secure method [" + name
165 + "] with attributes [" + attr + "]");
166 }
167
168 Method[] methods = clazz.getDeclaredMethods();
169 List matchingMethods = new ArrayList();
170
171 for (int i = 0; i < methods.length; i++) {
172 if (methods[i].getName().equals(mappedName)
173 || isMatch(methods[i].getName(), mappedName)) {
174 matchingMethods.add(methods[i]);
175 }
176 }
177
178 if (matchingMethods.isEmpty()) {
179 throw new IllegalArgumentException("Couldn't find method '"
180 + mappedName + "' on " + clazz);
181 }
182
183
184 for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
185 Method method = (Method) it.next();
186 String regMethodName = (String) this.nameMap.get(method);
187
188 if ((regMethodName == null)
189 || (!regMethodName.equals(name)
190 && (regMethodName.length() <= name.length()))) {
191
192
193 if (regMethodName != null) {
194 logger.debug("Replacing attributes for secure method ["
195 + method + "]: current name [" + name
196 + "] is more specific than [" + regMethodName + "]");
197 }
198
199 this.nameMap.put(method, name);
200 addSecureMethod(method, attr);
201 } else {
202 logger.debug("Keeping attributes for secure method [" + method
203 + "]: current name [" + name
204 + "] is not more specific than [" + regMethodName + "]");
205 }
206 }
207 }
208
209 protected ConfigAttributeDefinition lookupAttributes(Method method) {
210 ConfigAttributeDefinition definition = new ConfigAttributeDefinition();
211
212
213 ConfigAttributeDefinition directlyAssigned = (ConfigAttributeDefinition) this.methodMap
214 .get(method);
215 merge(definition, directlyAssigned);
216
217
218 Class[] interfaces = method.getDeclaringClass().getInterfaces();
219
220 for (int i = 0; i < interfaces.length; i++) {
221 Class clazz = interfaces[i];
222
223 try {
224
225 Method interfaceMethod = clazz.getDeclaredMethod(method.getName(),
226 (Class[]) method.getParameterTypes());
227 ConfigAttributeDefinition interfaceAssigned = (ConfigAttributeDefinition) this.methodMap
228 .get(interfaceMethod);
229 merge(definition, interfaceAssigned);
230 } catch (Exception e) {
231
232 }
233 }
234
235
236 if (definition.size() == 0) {
237 return null;
238 } else {
239 return definition;
240 }
241 }
242
243 /***
244 * Return if the given method name matches the mapped name. The default
245 * implementation checks for "xxx" and "xxx" matches.
246 *
247 * @param methodName the method name of the class
248 * @param mappedName the name in the descriptor
249 *
250 * @return if the names match
251 */
252 private boolean isMatch(String methodName, String mappedName) {
253 return (mappedName.endsWith("*")
254 && methodName.startsWith(mappedName.substring(0, mappedName.length()
255 - 1)))
256 || (mappedName.startsWith("*")
257 && methodName.endsWith(mappedName.substring(1, mappedName.length())));
258 }
259
260 private void merge(ConfigAttributeDefinition definition,
261 ConfigAttributeDefinition toMerge) {
262 if (toMerge == null) {
263 return;
264 }
265
266 Iterator attribs = toMerge.getConfigAttributes();
267
268 while (attribs.hasNext()) {
269 definition.addConfigAttribute((ConfigAttribute) attribs.next());
270 }
271 }
272 }