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