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 }