View Javadoc

1   package org.springframework.security.intercept.method;
2   
3   import java.lang.reflect.Method;
4   import java.util.HashMap;
5   import java.util.Map;
6   
7   import org.aopalliance.intercept.MethodInvocation;
8   import org.apache.commons.logging.Log;
9   import org.apache.commons.logging.LogFactory;
10  import org.aspectj.lang.JoinPoint;
11  import org.aspectj.lang.reflect.CodeSignature;
12  import org.springframework.security.ConfigAttributeDefinition;
13  import org.springframework.util.Assert;
14  import org.springframework.util.ClassUtils;
15  import org.springframework.util.ObjectUtils;
16  
17  /**
18   * Abstract implementation of {@link MethodDefinitionSource} that supports both Spring AOP and AspectJ and
19   * caches configuration attribute resolution from: 1. specific target method; 2. target class;  3. declaring method;
20   * 4. declaring class/interface.
21   *
22   * <p>
23   * This class mimics the behaviour of Spring's AbstractFallbackTransactionAttributeSource class.
24   * </p>
25   *
26   * <p>
27   * Note that this class cannot extract security metadata where that metadata is expressed by way of
28   * a target method/class (ie #1 and #2 above) AND the target method/class is encapsulated in another
29   * proxy object. Spring Security does not walk a proxy chain to locate the concrete/final target object.
30   * Consider making Spring Security your final advisor (so it advises the final target, as opposed to
31   * another proxy), move the metadata to declared methods or interfaces the proxy implements, or provide
32   * your own replacement <tt>MethodDefinitionSource</tt>.
33   * </p>
34   *
35   * @author Ben Alex
36   * @version $Id: AbstractFallbackMethodDefinitionSource.java 3253 2008-08-16 02:31:36Z luke_t $
37   * @since 2.0
38   */
39  public abstract class AbstractFallbackMethodDefinitionSource implements MethodDefinitionSource {
40  
41      private static final Log logger = LogFactory.getLog(AbstractFallbackMethodDefinitionSource.class);
42      private final static Object NULL_CONFIG_ATTRIBUTE = new Object();
43      private final Map attributeCache = new HashMap();
44  
45      public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
46          Assert.notNull(object, "Object cannot be null");
47  
48          if (object instanceof MethodInvocation) {
49              MethodInvocation mi = (MethodInvocation) object;
50              Object target = mi.getThis();
51              return getAttributes(mi.getMethod(), target == null ? null : target.getClass());
52          }
53  
54          if (object instanceof JoinPoint) {
55              JoinPoint jp = (JoinPoint) object;
56              Class targetClass = jp.getTarget().getClass();
57              String targetMethodName = jp.getStaticPart().getSignature().getName();
58              Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
59              Class declaringType = ((CodeSignature) jp.getStaticPart().getSignature()).getDeclaringType();
60  
61              Method method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types);
62              Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'");
63  
64              return getAttributes(method, targetClass);
65          }
66  
67          throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint");
68      }
69  
70      public final boolean supports(Class clazz) {
71          return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz));
72      }
73  
74      public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) {
75          // First, see if we have a cached value.
76          Object cacheKey = new DefaultCacheKey(method, targetClass);
77          synchronized (this.attributeCache) {
78              Object cached = this.attributeCache.get(cacheKey);
79              if (cached != null) {
80                  // Value will either be canonical value indicating there is no config attribute,
81                  // or an actual config attribute.
82                  if (cached == NULL_CONFIG_ATTRIBUTE) {
83                      return null;
84                  }
85                  else {
86                      return (ConfigAttributeDefinition) cached;
87                  }
88              }
89              else {
90                  // We need to work it out.
91                  ConfigAttributeDefinition cfgAtt = computeAttributes(method, targetClass);
92                  // Put it in the cache.
93                  if (cfgAtt == null) {
94                      this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
95                  }
96                  else {
97                      if (logger.isDebugEnabled()) {
98                          logger.debug("Adding security method [" + cacheKey + "] with attribute [" + cfgAtt + "]");
99                      }
100                     this.attributeCache.put(cacheKey, cfgAtt);
101                 }
102                 return cfgAtt;
103             }
104         }
105     }
106 
107     /**
108      *
109      * @param method the method for the current invocation (never <code>null</code>)
110      * @param targetClass the target class for this invocation (may be <code>null</code>)
111      * @return
112      */
113     private ConfigAttributeDefinition computeAttributes(Method method, Class targetClass) {
114         // The method may be on an interface, but we need attributes from the target class.
115         // If the target class is null, the method will be unchanged.
116         Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
117         // First try is the method in the target class.
118         ConfigAttributeDefinition attr = findAttributes(specificMethod, targetClass);
119         if (attr != null) {
120             return attr;
121         }
122 
123         // Second try is the config attribute on the target class.
124         attr = findAttributes(specificMethod.getDeclaringClass());
125         if (attr != null) {
126             return attr;
127         }
128 
129         if (specificMethod != method || targetClass == null) {
130             // Fallback is to look at the original method.
131             attr = findAttributes(method, method.getDeclaringClass());
132             if (attr != null) {
133                 return attr;
134             }
135             // Last fallback is the class of the original method.
136             return findAttributes(method.getDeclaringClass());
137         }
138         return null;
139 
140     }
141 
142     /**
143      * Obtains the security metadata applicable to the specified method invocation.
144      *
145      * <p>
146      * Note that the {@link Method#getDeclaringClass()} may not equal the <code>targetClass</code>.
147      * Both parameters are provided to assist subclasses which may wish to provide advanced
148      * capabilities related to method metadata being "registered" against a method even if the
149      * target class does not declare the method (i.e. the subclass may only inherit the method).
150      *
151      * @param method the method for the current invocation (never <code>null</code>)
152      * @param targetClass the target class for the invocation (may be <code>null</code>)
153      * @return the security metadata (or null if no metadata applies)
154      */
155     protected abstract ConfigAttributeDefinition findAttributes(Method method, Class targetClass);
156 
157     /**
158      * Obtains the security metadata registered against the specified class.
159      *
160      * <p>
161      * Subclasses should only return metadata expressed at a class level. Subclasses should NOT
162      * aggregate metadata for each method registered against a class, as the abstract superclass
163      * will separate invoke {@link #findAttributes(Method, Class)} for individual methods as
164      * appropriate.
165      *
166      * @param clazz the target class for the invocation (never <code>null</code>)
167      * @return the security metadata (or null if no metadata applies)
168      */
169     protected abstract ConfigAttributeDefinition findAttributes(Class clazz);
170 
171     private static class DefaultCacheKey {
172 
173         private final Method method;
174         private final Class targetClass;
175 
176         public DefaultCacheKey(Method method, Class targetClass) {
177             this.method = method;
178             this.targetClass = targetClass;
179         }
180 
181         public boolean equals(Object other) {
182             if (this == other) {
183                 return true;
184             }
185             if (!(other instanceof DefaultCacheKey)) {
186                 return false;
187             }
188             DefaultCacheKey otherKey = (DefaultCacheKey) other;
189             return (this.method.equals(otherKey.method) &&
190                     ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
191         }
192 
193         public int hashCode() {
194             return this.method.hashCode() * 21 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
195         }
196 
197         public String toString() {
198             return "CacheKey[" + (targetClass == null ? "-" : targetClass.getName()) + "; " + method + "]";
199         }
200     }
201 
202 }