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.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      //~ Static fields/initializers =============================================
68  
69      private static final Log logger = LogFactory.getLog(MethodDefinitionMap.class);
70  
71      //~ Instance fields ========================================================
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      //~ Methods ================================================================
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>&#42</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>&#42</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>&#42</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         // register all matching methods
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                 // no already registered method name, or more specific
192                 // method name specification now -> (re-)register method
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         // Add attributes explictly defined for this method invocation
213         ConfigAttributeDefinition directlyAssigned = (ConfigAttributeDefinition) this.methodMap
214             .get(method);
215         merge(definition, directlyAssigned);
216 
217         // Add attributes explicitly defined for this method invocation's interfaces
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                 // Look for the method on the current interface
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                 // skip this interface
232             }
233         }
234 
235         // Return null if empty, as per abstract superclass contract
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 }