Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 272   Methods: 8
NCLOC: 123   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
MethodDefinitionMap.java 95.8% 98% 100% 97.6%
coverage coverage
 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  12 public Iterator getConfigAttributeDefinitions() {
 90  12 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  5 public int getMethodMapSize() {
 103  5 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  51 public void addSecureMethod(Method method, ConfigAttributeDefinition attr) {
 114  51 logger.info("Adding secure method [" + method + "] with attributes ["
 115    + attr + "]");
 116  51 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  50 public void addSecureMethod(String name, ConfigAttributeDefinition attr) {
 129  50 int lastDotIndex = name.lastIndexOf(".");
 130   
 131  50 if (lastDotIndex == -1) {
 132  1 throw new IllegalArgumentException("'" + name
 133    + "' is not a valid method name: format is FQN.methodName");
 134    }
 135   
 136  49 String className = name.substring(0, lastDotIndex);
 137  49 String methodName = name.substring(lastDotIndex + 1);
 138   
 139  49 try {
 140  49 Class clazz = Class.forName(className, true,
 141    Thread.currentThread().getContextClassLoader());
 142  48 addSecureMethod(clazz, methodName, attr);
 143    } catch (ClassNotFoundException ex) {
 144  1 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  48 public void addSecureMethod(Class clazz, String mappedName,
 160    ConfigAttributeDefinition attr) {
 161  48 String name = clazz.getName() + '.' + mappedName;
 162   
 163  48 if (logger.isDebugEnabled()) {
 164  0 logger.debug("Adding secure method [" + name
 165    + "] with attributes [" + attr + "]");
 166    }
 167   
 168  48 Method[] methods = clazz.getDeclaredMethods();
 169  48 List matchingMethods = new ArrayList();
 170   
 171  48 for (int i = 0; i < methods.length; i++) {
 172  240 if (methods[i].getName().equals(mappedName)
 173    || isMatch(methods[i].getName(), mappedName)) {
 174  54 matchingMethods.add(methods[i]);
 175    }
 176    }
 177   
 178  48 if (matchingMethods.isEmpty()) {
 179  1 throw new IllegalArgumentException("Couldn't find method '"
 180    + mappedName + "' on " + clazz);
 181    }
 182   
 183    // register all matching methods
 184  47 for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
 185  54 Method method = (Method) it.next();
 186  54 String regMethodName = (String) this.nameMap.get(method);
 187   
 188  54 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  51 if (regMethodName != null) {
 194  1 logger.debug("Replacing attributes for secure method ["
 195    + method + "]: current name [" + name
 196    + "] is more specific than [" + regMethodName + "]");
 197    }
 198   
 199  51 this.nameMap.put(method, name);
 200  51 addSecureMethod(method, attr);
 201    } else {
 202  3 logger.debug("Keeping attributes for secure method [" + method
 203    + "]: current name [" + name
 204    + "] is not more specific than [" + regMethodName + "]");
 205    }
 206    }
 207    }
 208   
 209  21 protected ConfigAttributeDefinition lookupAttributes(Method method) {
 210  21 ConfigAttributeDefinition definition = new ConfigAttributeDefinition();
 211   
 212    // Add attributes explictly defined for this method invocation
 213  21 ConfigAttributeDefinition directlyAssigned = (ConfigAttributeDefinition) this.methodMap
 214    .get(method);
 215  21 merge(definition, directlyAssigned);
 216   
 217    // Add attributes explicitly defined for this method invocation's interfaces
 218  21 Class[] interfaces = method.getDeclaringClass().getInterfaces();
 219   
 220  21 for (int i = 0; i < interfaces.length; i++) {
 221  12 Class clazz = interfaces[i];
 222   
 223  12 try {
 224    // Look for the method on the current interface
 225  12 Method interfaceMethod = clazz.getDeclaredMethod(method.getName(),
 226    (Class[]) method.getParameterTypes());
 227  12 ConfigAttributeDefinition interfaceAssigned = (ConfigAttributeDefinition) this.methodMap
 228    .get(interfaceMethod);
 229  12 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  21 if (definition.size() == 0) {
 237  4 return null;
 238    } else {
 239  17 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  229 private boolean isMatch(String methodName, String mappedName) {
 253  229 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  33 private void merge(ConfigAttributeDefinition definition,
 261    ConfigAttributeDefinition toMerge) {
 262  33 if (toMerge == null) {
 263  15 return;
 264    }
 265   
 266  18 Iterator attribs = toMerge.getConfigAttributes();
 267   
 268  18 while (attribs.hasNext()) {
 269  27 definition.addConfigAttribute((ConfigAttribute) attribs.next());
 270    }
 271    }
 272    }