diff --git a/changelog.txt b/changelog.txt
index 902b08b125..b51e64f483 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -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)
-------------------------------------
diff --git a/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java b/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java
index 206139014e..51e66a1df9 100644
--- a/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java
+++ b/core/src/main/java/org/acegisecurity/intercept/method/MethodDefinitionMap.java
@@ -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;
@@ -34,6 +35,32 @@ import java.util.Map;
/**
* Stores a {@link ConfigAttributeDefinition} for each method signature defined
* in a bean context.
+ *
+ *
+ * For consistency with {@link MethodDefinitionAttributes} as well as support
+ * for {@link MethodDefinitionSourceAdvisor}, this implementation will return
+ * a ConfigAttributeDefinition
containing all configuration
+ * attributes defined against:
+ *
+ *
+ * -
+ * The method-specific attributes defined for the intercepted method of the
+ * intercepted class.
+ *
+ * -
+ * The method-specific attributes defined by any explicitly implemented
+ * interface if that interface contains a method signature matching that of
+ * the intercepted method.
+ *
+ *
+ *
+ *
+ *
+ * In general you should therefore define the interface methods of your
+ * secure objects, not the implementations. For example, define
+ * com.company.Foo.findAll=ROLE_TEST
but not
+ * com.company.FooImpl.findAll=ROLE_TEST
.
+ *
*
* @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());
+ }
+ }
}
diff --git a/core/src/test/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditorTests.java b/core/src/test/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditorTests.java
index ca57e2e0fb..3da03fde5f 100644
--- a/core/src/test/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditorTests.java
+++ b/core/src/test/java/org/acegisecurity/intercept/method/MethodDefinitionSourceEditorTests.java
@@ -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("");
diff --git a/upgrade-06-07.txt b/upgrade-06-07.txt
new file mode 100644
index 0000000000..c9e9e9a2ba
--- /dev/null
+++ b/upgrade-06-07.txt
@@ -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$