Make MethodDefinitionMap query interfaces defined by secure objects, to properly support MethodDefinitionSourceAdvisor.

This commit is contained in:
Ben Alex 2004-10-15 03:47:53 +00:00
parent 8ec0d89fe4
commit f123e9c333
4 changed files with 149 additions and 1 deletions

View File

@ -2,6 +2,8 @@ Changes in version 0.7 (2004-xx-xx)
-----------------------------------
* Added MethodDefinitionSourceAdvisor for performance and autoproxying
* Added MethodDefinitionMap querying of interfaces defined by secure objects
* Documentation improvements
Changes in version 0.6.1 (2004-09-25)
-------------------------------------

View File

@ -15,6 +15,7 @@
package net.sf.acegisecurity.intercept.method;
import net.sf.acegisecurity.ConfigAttribute;
import net.sf.acegisecurity.ConfigAttributeDefinition;
import org.aopalliance.intercept.MethodInvocation;
@ -35,6 +36,32 @@ import java.util.Map;
* Stores a {@link ConfigAttributeDefinition} for each method signature defined
* in a bean context.
*
* <p>
* For consistency with {@link MethodDefinitionAttributes} as well as support
* for {@link MethodDefinitionSourceAdvisor}, this implementation will return
* a <code>ConfigAttributeDefinition</code> containing all configuration
* attributes defined against:
*
* <ul>
* <li>
* The method-specific attributes defined for the intercepted method of the
* intercepted class.
* </li>
* <li>
* The method-specific attributes defined by any explicitly implemented
* interface if that interface contains a method signature matching that of
* the intercepted method.
* </li>
* </ul>
* </p>
*
* <p>
* In general you should therefore define the <b>interface method</b>s of your
* secure objects, not the implementations. For example, define
* <code>com.company.Foo.findAll=ROLE_TEST</code> but not
* <code>com.company.FooImpl.findAll=ROLE_TEST</code>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
@ -53,10 +80,28 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
//~ Methods ================================================================
/**
* Obtains the configuration attributes explicitly defined against this
* bean. This method will not return implicit configuration attributes
* that may be returned by {@link #lookupAttributes(MethodInvocation)} as
* it does not have access to a method invocation at this time.
*
* @return the attributes explicitly defined against this bean
*/
public Iterator getConfigAttributeDefinitions() {
return methodMap.values().iterator();
}
/**
* Obtains the number of configuration attributes explicitly defined
* against this bean. This method will not return implicit configuration
* attributes that may be returned by {@link
* #lookupAttributes(MethodInvocation)} as it does not have access to a
* method invocation at this time.
*
* @return the number of configuration attributes explicitly defined
* against this bean
*/
public int getMethodMapSize() {
return this.methodMap.size();
}
@ -165,7 +210,38 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
}
protected ConfigAttributeDefinition lookupAttributes(MethodInvocation mi) {
return (ConfigAttributeDefinition) this.methodMap.get(mi.getMethod());
ConfigAttributeDefinition definition = new ConfigAttributeDefinition();
// Add attributes explictly defined for this method invocation
ConfigAttributeDefinition directlyAssigned = (ConfigAttributeDefinition) this.methodMap
.get(mi.getMethod());
merge(definition, directlyAssigned);
// Add attributes explicitly defined for this method invocation's interfaces
Class[] interfaces = mi.getMethod().getDeclaringClass().getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class clazz = interfaces[i];
try {
// Look for the method on the current interface
Method interfaceMethod = clazz.getDeclaredMethod(mi.getMethod()
.getName(),
mi.getMethod().getParameterTypes());
ConfigAttributeDefinition interfaceAssigned = (ConfigAttributeDefinition) this.methodMap
.get(interfaceMethod);
merge(definition, interfaceAssigned);
} catch (Exception e) {
// skip this interface
}
}
// Return null if empty, as per abstract superclass contract
if (definition.size() == 0) {
return null;
} else {
return definition;
}
}
/**
@ -184,4 +260,17 @@ public class MethodDefinitionMap extends AbstractMethodDefinitionSource {
|| (mappedName.startsWith("*")
&& methodName.endsWith(mappedName.substring(1, mappedName.length())));
}
private void merge(ConfigAttributeDefinition definition,
ConfigAttributeDefinition toMerge) {
if (toMerge == null) {
return;
}
Iterator attribs = toMerge.getConfigAttributes();
while (attribs.hasNext()) {
definition.addConfigAttribute((ConfigAttribute) attribs.next());
}
}
}

View File

@ -91,6 +91,34 @@ public class MethodDefinitionSourceEditorTests extends TestCase {
}
}
public void testConcreteClassInvocationsAlsoReturnDefinitionsAgainstInterface()
throws Exception {
MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor();
editor.setAsText(
"net.sf.acegisecurity.ITargetObject.makeLower*=ROLE_FROM_INTERFACE\r\nnet.sf.acegisecurity.ITargetObject.makeUpper*=ROLE_FROM_INTERFACE\r\nnet.sf.acegisecurity.TargetObject.makeUpper*=ROLE_FROM_IMPLEMENTATION");
MethodDefinitionMap map = (MethodDefinitionMap) editor.getValue();
assertEquals(3, map.getMethodMapSize());
ConfigAttributeDefinition returnedMakeLower = map.getAttributes(new MockMethodInvocation(
TargetObject.class, "makeLowerCase",
new Class[] {String.class}));
ConfigAttributeDefinition expectedMakeLower = new ConfigAttributeDefinition();
expectedMakeLower.addConfigAttribute(new SecurityConfig(
"ROLE_FROM_INTERFACE"));
assertEquals(expectedMakeLower, returnedMakeLower);
ConfigAttributeDefinition returnedMakeUpper = map.getAttributes(new MockMethodInvocation(
TargetObject.class, "makeUpperCase",
new Class[] {String.class}));
ConfigAttributeDefinition expectedMakeUpper = new ConfigAttributeDefinition();
expectedMakeUpper.addConfigAttribute(new SecurityConfig(
"ROLE_FROM_IMPLEMENTATION"));
expectedMakeUpper.addConfigAttribute(new SecurityConfig(
"ROLE_FROM_INTERFACE"));
assertEquals(expectedMakeUpper, returnedMakeUpper);
}
public void testEmptyStringReturnsEmptyMap() {
MethodDefinitionSourceEditor editor = new MethodDefinitionSourceEditor();
editor.setAsText("");

29
upgrade-06-07.txt Normal file
View File

@ -0,0 +1,29 @@
===============================================================================
ACEGI SECURITY SYSTEM FOR SPRING - UPGRADING FROM 0.6 TO 0.7
===============================================================================
The following should help most casual users of the project update their
applications:
- MethodDefinitionMap, which is usually used by MethodSecurityInterceptor
for its objectDefinitionSource property, has been changed. From 0.7, when
MethodDefinitionMap is queried for configuration attributes associated with
secure MethodInvocations, it will use any method matching in the method
invocation class (as it always has) plus any method matching any interface
the MethodInvocation class directly implements. So consider a PersonManager
interface, a PersonManagerImpl class that implements it, and a definition of
PersonManager.findAll=ROLE_FOO. In this example, any query for either
PersonManager.findAll OR PersonManagerImpl.findAll will return ROLE_FOO.
As we have always encouraged definition against the interface names (as per
this example), this change should not adversely impact users. This change
was necessary because of the new MethodDefinitionSourceAdvisor (see below).
Refer to the MethodDefinitionMap JavaDocs for further clarification.
- MethodDefinitionSourceAdvisor can now be used instead of defining proxies
for secure business objects. The advisor is fully compatible with both
MethodDefinitionMap and MethodDefinitionAttributes. Using an advisor allows
caching of which methods the MethodSecurityInterceptor should handle, thus
providing a performance benefit as MethodSecurityInterceptor is not called
for public (non-secure) objects. It also simplifies configuration.
$Id$