001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.intercept.method;
017:
018: import org.acegisecurity.ConfigAttribute;
019: import org.acegisecurity.ConfigAttributeDefinition;
020: import org.acegisecurity.SecurityConfig;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024:
025: import java.lang.reflect.Method;
026:
027: import java.util.ArrayList;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032:
033: /**
034: * Stores a {@link ConfigAttributeDefinition} for each method signature defined in a bean context.<p>For
035: * consistency with {@link MethodDefinitionAttributes} as well as support for
036: * <code>MethodDefinitionSourceAdvisor</code>, this implementation will return a
037: * <code>ConfigAttributeDefinition</code> containing all configuration attributes defined against:
038: * <ul>
039: * <li>The method-specific attributes defined for the intercepted method of the intercepted class.</li>
040: * <li>The method-specific attributes defined by any explicitly implemented interface if that interface
041: * contains a method signature matching that of the intercepted method.</li>
042: * </ul>
043: * </p>
044: * <p>In general you should therefore define the <b>interface method</b>s of your secure objects, not the
045: * implementations. For example, define <code>com.company.Foo.findAll=ROLE_TEST</code> but not
046: * <code>com.company.FooImpl.findAll=ROLE_TEST</code>.</p>
047: *
048: * @author Ben Alex
049: * @version $Id: MethodDefinitionMap.java 1784 2007-02-24 21:00:24Z luke_t $
050: */
051: public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
052: //~ Static fields/initializers =====================================================================================
053:
054: private static final Log logger = LogFactory
055: .getLog(MethodDefinitionMap.class);
056:
057: //~ Instance fields ================================================================================================
058:
059: /** Map from Method to ApplicationDefinition */
060: protected Map methodMap = new HashMap();
061:
062: /** Map from Method to name pattern used for registration */
063: private Map nameMap = new HashMap();
064:
065: //~ Methods ========================================================================================================
066:
067: /**
068: * Add configuration attributes for a secure method. Method names can end or start with <code>*</code>
069: * for matching multiple methods.
070: *
071: * @param method the method to be secured
072: * @param attr required authorities associated with the method
073: */
074: public void addSecureMethod(Method method,
075: ConfigAttributeDefinition attr) {
076: logger.info("Adding secure method [" + method
077: + "] with attributes [" + attr + "]");
078: this .methodMap.put(method, attr);
079: }
080:
081: /**
082: * Add configuration attributes for a secure method. Method names can end or start with <code>*</code>
083: * for matching multiple methods.
084: *
085: * @param name class and method name, separated by a dot
086: * @param attr required authorities associated with the method
087: *
088: * @throws IllegalArgumentException DOCUMENT ME!
089: */
090: public void addSecureMethod(String name,
091: ConfigAttributeDefinition attr) {
092: int lastDotIndex = name.lastIndexOf(".");
093:
094: if (lastDotIndex == -1) {
095: throw new IllegalArgumentException(
096: "'"
097: + name
098: + "' is not a valid method name: format is FQN.methodName");
099: }
100:
101: String className = name.substring(0, lastDotIndex);
102: String methodName = name.substring(lastDotIndex + 1);
103:
104: try {
105: Class clazz = Class.forName(className, true, Thread
106: .currentThread().getContextClassLoader());
107: addSecureMethod(clazz, methodName, attr);
108: } catch (ClassNotFoundException ex) {
109: throw new IllegalArgumentException("Class '" + className
110: + "' not found");
111: }
112: }
113:
114: /**
115: * Add configuration attributes for a secure method. Method names can end or start with <code>*</code>
116: * for matching multiple methods.
117: *
118: * @param clazz target interface or class
119: * @param mappedName mapped method name
120: * @param attr required authorities associated with the method
121: *
122: * @throws IllegalArgumentException DOCUMENT ME!
123: */
124: public void addSecureMethod(Class clazz, String mappedName,
125: ConfigAttributeDefinition attr) {
126: String name = clazz.getName() + '.' + mappedName;
127:
128: if (logger.isDebugEnabled()) {
129: logger.debug("Adding secure method [" + name
130: + "] with attributes [" + attr + "]");
131: }
132:
133: Method[] methods = clazz.getDeclaredMethods();
134: List matchingMethods = new ArrayList();
135:
136: for (int i = 0; i < methods.length; i++) {
137: if (methods[i].getName().equals(mappedName)
138: || isMatch(methods[i].getName(), mappedName)) {
139: matchingMethods.add(methods[i]);
140: }
141: }
142:
143: if (matchingMethods.isEmpty()) {
144: throw new IllegalArgumentException("Couldn't find method '"
145: + mappedName + "' on " + clazz);
146: }
147:
148: // register all matching methods
149: for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
150: Method method = (Method) it.next();
151: String regMethodName = (String) this .nameMap.get(method);
152:
153: if ((regMethodName == null)
154: || (!regMethodName.equals(name) && (regMethodName
155: .length() <= name.length()))) {
156: // no already registered method name, or more specific
157: // method name specification now -> (re-)register method
158: if (regMethodName != null) {
159: logger
160: .debug("Replacing attributes for secure method ["
161: + method
162: + "]: current name ["
163: + name
164: + "] is more specific than ["
165: + regMethodName + "]");
166: }
167:
168: this .nameMap.put(method, name);
169: addSecureMethod(method, attr);
170: } else {
171: logger.debug("Keeping attributes for secure method ["
172: + method + "]: current name [" + name
173: + "] is not more specific than ["
174: + regMethodName + "]");
175: }
176: }
177: }
178:
179: /**
180: * Obtains the configuration attributes explicitly defined against this bean. This method will not return
181: * implicit configuration attributes that may be returned by {@link #lookupAttributes(Method)} as it does not have
182: * access to a method invocation at this time.
183: *
184: * @return the attributes explicitly defined against this bean
185: */
186: public Iterator getConfigAttributeDefinitions() {
187: return methodMap.values().iterator();
188: }
189:
190: /**
191: * Obtains the number of configuration attributes explicitly defined against this bean. This method will
192: * not return implicit configuration attributes that may be returned by {@link #lookupAttributes(Method)} as it
193: * does not have access to a method invocation at this time.
194: *
195: * @return the number of configuration attributes explicitly defined against this bean
196: */
197: public int getMethodMapSize() {
198: return this .methodMap.size();
199: }
200:
201: /**
202: * Return if the given method name matches the mapped name. The default implementation checks for "xxx" and
203: * "xxx" matches.
204: *
205: * @param methodName the method name of the class
206: * @param mappedName the name in the descriptor
207: *
208: * @return if the names match
209: */
210: private boolean isMatch(String methodName, String mappedName) {
211: return (mappedName.endsWith("*") && methodName
212: .startsWith(mappedName.substring(0,
213: mappedName.length() - 1)))
214: || (mappedName.startsWith("*") && methodName
215: .endsWith(mappedName.substring(1, mappedName
216: .length())));
217: }
218:
219: protected ConfigAttributeDefinition lookupAttributes(Method method) {
220: ConfigAttributeDefinition definition = new ConfigAttributeDefinition();
221:
222: // Add attributes explictly defined for this method invocation
223: ConfigAttributeDefinition directlyAssigned = (ConfigAttributeDefinition) this .methodMap
224: .get(method);
225: merge(definition, directlyAssigned);
226:
227: // Add attributes explicitly defined for this method invocation's interfaces
228: Class[] interfaces = method.getDeclaringClass().getInterfaces();
229:
230: for (int i = 0; i < interfaces.length; i++) {
231: Class clazz = interfaces[i];
232:
233: try {
234: // Look for the method on the current interface
235: Method interfaceMethod = clazz.getDeclaredMethod(method
236: .getName(), (Class[]) method
237: .getParameterTypes());
238: ConfigAttributeDefinition interfaceAssigned = (ConfigAttributeDefinition) this .methodMap
239: .get(interfaceMethod);
240: merge(definition, interfaceAssigned);
241: } catch (Exception e) {
242: // skip this interface
243: }
244: }
245:
246: // Return null if empty, as per abstract superclass contract
247: if (definition.size() == 0) {
248: return null;
249: } else {
250: return definition;
251: }
252: }
253:
254: private void merge(ConfigAttributeDefinition definition,
255: ConfigAttributeDefinition toMerge) {
256: if (toMerge == null) {
257: return;
258: }
259:
260: Iterator attribs = toMerge.getConfigAttributes();
261:
262: while (attribs.hasNext()) {
263: definition.addConfigAttribute((ConfigAttribute) attribs
264: .next());
265: }
266: }
267:
268: /**
269: * Easier configuration of the instance, using {@link MethodDefinitionSourceMapping}.
270: *
271: * @param mappings {@link List} of {@link MethodDefinitionSourceMapping} objects.
272: */
273: public void setMappings(List mappings) {
274: Iterator it = mappings.iterator();
275: while (it.hasNext()) {
276: MethodDefinitionSourceMapping mapping = (MethodDefinitionSourceMapping) it
277: .next();
278: ConfigAttributeDefinition configDefinition = new ConfigAttributeDefinition();
279:
280: Iterator configAttributesIt = mapping.getConfigAttributes()
281: .iterator();
282: while (configAttributesIt.hasNext()) {
283: String s = (String) configAttributesIt.next();
284: configDefinition.addConfigAttribute(new SecurityConfig(
285: s));
286: }
287:
288: addSecureMethod(mapping.getMethodName(), configDefinition);
289: }
290: }
291: }
|