Browse Source

SEC-239: New ACL module.

Ben Alex 19 years ago
parent
commit
9f512c384e
94 changed files with 5762 additions and 3679 deletions
  1. 2 1
      .classpath
  2. 1 1
      core/pom.xml
  3. 46 47
      core/src/main/java/org/acegisecurity/acls/AccessControlEntry.java
  4. 133 125
      core/src/main/java/org/acegisecurity/acls/Acl.java
  5. 105 106
      core/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java
  6. 95 61
      core/src/main/java/org/acegisecurity/acls/AclService.java
  7. 38 39
      core/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java
  8. 24 25
      core/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java
  9. 25 23
      core/src/main/java/org/acegisecurity/acls/AuditableAcl.java
  10. 38 39
      core/src/main/java/org/acegisecurity/acls/ChildrenExistException.java
  11. 38 39
      core/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java
  12. 65 50
      core/src/main/java/org/acegisecurity/acls/MutableAcl.java
  13. 60 69
      core/src/main/java/org/acegisecurity/acls/MutableAclService.java
  14. 38 39
      core/src/main/java/org/acegisecurity/acls/NotFoundException.java
  15. 25 28
      core/src/main/java/org/acegisecurity/acls/OwnershipAcl.java
  16. 51 52
      core/src/main/java/org/acegisecurity/acls/Permission.java
  17. 39 40
      core/src/main/java/org/acegisecurity/acls/UnloadedSidException.java
  18. 133 149
      core/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java
  19. 38 0
      core/src/main/java/org/acegisecurity/acls/domain/AclAuthorizationStrategy.java
  20. 125 0
      core/src/main/java/org/acegisecurity/acls/domain/AclAuthorizationStrategyImpl.java
  21. 55 148
      core/src/main/java/org/acegisecurity/acls/domain/AclImpl.java
  22. 25 26
      core/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java
  23. 164 0
      core/src/main/java/org/acegisecurity/acls/domain/BasePermission.java
  24. 45 46
      core/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java
  25. 78 79
      core/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java
  26. 5 0
      core/src/main/java/org/acegisecurity/acls/domain/package.html
  27. 36 33
      core/src/main/java/org/acegisecurity/acls/jdbc/AclCache.java
  28. 518 516
      core/src/main/java/org/acegisecurity/acls/jdbc/BasicLookupStrategy.java
  29. 115 93
      core/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java
  30. 114 0
      core/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java
  31. 368 0
      core/src/main/java/org/acegisecurity/acls/jdbc/JdbcMutableAclService.java
  32. 38 39
      core/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java
  33. 5 0
      core/src/main/java/org/acegisecurity/acls/jdbc/package.html
  34. 56 57
      core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java
  35. 141 140
      core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java
  36. 30 0
      core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityRetrievalStrategy.java
  37. 31 0
      core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityRetrievalStrategyImpl.java
  38. 5 0
      core/src/main/java/org/acegisecurity/acls/objectidentity/package.html
  39. 5 0
      core/src/main/java/org/acegisecurity/acls/package.html
  40. 67 68
      core/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java
  41. 72 73
      core/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java
  42. 36 37
      core/src/main/java/org/acegisecurity/acls/sid/Sid.java
  43. 32 0
      core/src/main/java/org/acegisecurity/acls/sid/SidRetrievalStrategy.java
  44. 48 0
      core/src/main/java/org/acegisecurity/acls/sid/SidRetrievalStrategyImpl.java
  45. 5 0
      core/src/main/java/org/acegisecurity/acls/sid/package.html
  46. 130 0
      core/src/main/java/org/acegisecurity/afterinvocation/AbstractAclProvider.java
  47. 133 0
      core/src/main/java/org/acegisecurity/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java
  48. 119 0
      core/src/main/java/org/acegisecurity/afterinvocation/AclEntryAfterInvocationProvider.java
  49. 101 0
      core/src/main/java/org/acegisecurity/afterinvocation/ArrayFilterer.java
  50. 26 208
      core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java
  51. 104 0
      core/src/main/java/org/acegisecurity/afterinvocation/CollectionFilterer.java
  52. 51 0
      core/src/main/java/org/acegisecurity/afterinvocation/Filterer.java
  53. 223 0
      core/src/main/java/org/acegisecurity/taglibs/authz/AccessControlListTag.java
  54. 1 5
      core/src/main/java/org/acegisecurity/vote/AbstractAclVoter.java
  55. 251 0
      core/src/main/java/org/acegisecurity/vote/AclEntryVoter.java
  56. 30 0
      core/src/main/resources/org/acegisecurity/taglibs/authz.tld
  57. 14 2
      core/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java
  58. 47 48
      core/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java
  59. 222 0
      core/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java
  60. 29 17
      core/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml
  61. 12 8
      core/src/test/java/org/acegisecurity/acls/jdbc/select.sql
  62. 45 0
      core/src/test/java/org/acegisecurity/acls/jdbc/testData.sql
  63. 291 15
      doc/docbook/acegi.xml
  64. 1 1
      project.xml
  65. 50 37
      samples/contacts-tiger/src/main/java/sample/contact/annotation/ContactManagerBackend.java
  66. 0 163
      samples/contacts-tiger/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml
  67. 0 72
      samples/contacts-tiger/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml
  68. 32 2
      samples/contacts-tiger/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml
  69. 114 71
      samples/contacts-tiger/src/main/webapp/filter/WEB-INF/applicationContext-annotations.xml
  70. 4 2
      samples/contacts-tiger/src/main/webapp/filter/WEB-INF/applicationContext-business.xml
  71. 3 3
      samples/contacts-tiger/src/main/webapp/filter/WEB-INF/web.xml
  72. 16 0
      samples/contacts-tiger/src/main/webapp/filter/accessDenied.jsp
  73. 0 5
      samples/contacts-tiger/src/main/webapp/filter/error.html
  74. 1 2
      samples/contacts/src/main/java/sample/contact/AddPermission.java
  75. 12 18
      samples/contacts/src/main/java/sample/contact/AddPermissionController.java
  76. 3 5
      samples/contacts/src/main/java/sample/contact/AddPermissionValidator.java
  77. 9 17
      samples/contacts/src/main/java/sample/contact/AdminPermissionController.java
  78. 5 3
      samples/contacts/src/main/java/sample/contact/ContactManager.java
  79. 47 36
      samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java
  80. 147 80
      samples/contacts/src/main/java/sample/contact/DataSourcePopulator.java
  81. 18 17
      samples/contacts/src/main/java/sample/contact/DeletePermissionController.java
  82. 107 64
      samples/contacts/src/main/resources/applicationContext-common-authorization.xml
  83. 6 3
      samples/contacts/src/main/resources/applicationContext-common-business.xml
  84. 2 2
      samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml
  85. 3 14
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/adminPermission.jsp
  86. 4 2
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/deletePermission.jsp
  87. 4 4
      samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp
  88. 0 115
      samples/contacts/src/test/java/sample/contact/AbstractContactsSampleTest.java
  89. 132 26
      samples/contacts/src/test/java/sample/contact/GetAllContactsTests.java
  90. 0 106
      sandbox/other/src/main/java/org/acegisecurity/acls/domain/BasePermission.java
  91. 0 78
      sandbox/other/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java
  92. 0 5
      sandbox/other/src/main/java/org/acegisecurity/acls/package.html
  93. 0 65
      sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java
  94. 0 70
      sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/testData.sql

+ 2 - 1
.classpath

@@ -5,6 +5,7 @@
 	<classpathentry kind="src" path="core/src/test/java"/>
 	<classpathentry kind="src" path="core/src/test/java"/>
 	<classpathentry kind="src" path="core/src/test/resources"/>
 	<classpathentry kind="src" path="core/src/test/resources"/>
 	<classpathentry kind="src" path="sandbox/other/src/main/java"/>
 	<classpathentry kind="src" path="sandbox/other/src/main/java"/>
+	<classpathentry kind="src" path="sandbox/other/src/test/java"/>
 	<classpathentry kind="src" path="samples/contacts/src/main/java"/>
 	<classpathentry kind="src" path="samples/contacts/src/main/java"/>
 	<classpathentry kind="src" path="samples/contacts/src/main/resources"/>
 	<classpathentry kind="src" path="samples/contacts/src/main/resources"/>
 	<classpathentry kind="src" path="samples/contacts/src/test/java"/>
 	<classpathentry kind="src" path="samples/contacts/src/test/java"/>
@@ -40,7 +41,7 @@
 	<classpathentry kind="var" path="MAVEN_REPO/commons-codec/jars/commons-codec-1.3.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/commons-codec/jars/commons-codec-1.3.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/commons-collections/jars/commons-collections-3.1.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/commons-collections/jars/commons-collections-3.1.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/commons-logging/jars/commons-logging-1.0.4.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/commons-logging/jars/commons-logging-1.0.4.jar"/>
-	<classpathentry kind="var" path="MAVEN_REPO/hsqldb/jars/hsqldb-1.7.3.0.jar"/>
+	<classpathentry kind="var" path="MAVEN_REPO/hsqldb/jars/hsqldb-1.8.0.4.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/oro/jars/oro-2.0.8.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/oro/jars/oro-2.0.8.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/javax.servlet/jars/servlet-api-2.4.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/javax.servlet/jars/servlet-api-2.4.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/tomcat/jars/catalina-4.1.9.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/tomcat/jars/catalina-4.1.9.jar"/>

+ 1 - 1
core/pom.xml

@@ -93,7 +93,7 @@
     <dependency>
     <dependency>
       <groupId>hsqldb</groupId>
       <groupId>hsqldb</groupId>
       <artifactId>hsqldb</artifactId>
       <artifactId>hsqldb</artifactId>
-      <version>1.7.3.0</version>
+      <version>1.8.0.4</version>
       <scope>test</scope>
       <scope>test</scope>
     </dependency>
     </dependency>
     <dependency>
     <dependency>

+ 46 - 47
sandbox/other/src/main/java/org/acegisecurity/acls/AccessControlEntry.java → core/src/main/java/org/acegisecurity/acls/AccessControlEntry.java

@@ -1,25 +1,24 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.sid.Sid;
-
-import java.io.Serializable;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.sid.Sid;
+
+import java.io.Serializable;
+
+
 /**
 /**
  * Represents an individual permission assignment within an {@link Acl}. 
  * Represents an individual permission assignment within an {@link Acl}. 
  * 
  * 
@@ -30,28 +29,28 @@ import java.io.Serializable;
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
  *
  *
- */
-public interface AccessControlEntry {
-    //~ Methods ========================================================================================================
-
-    public Acl getAcl();
-
-    /**
-     * Obtains an identifier that represents this ACE.
-     *
-     * @return the identifier, or <code>null</code> if unsaved
-     */
-    public Serializable getId();
-
-    public Permission getPermission();
-
-    public Sid getSid();
-
-    /**
-     * Indicates the a Permission is being granted to the relevant Sid. If false,
-     * indicates the permission is being revoked/blocked.
-     * 
-     * @return true if being granted, false otherwise
-     */
-    public boolean isGranting();
-}
+ */
+public interface AccessControlEntry {
+    //~ Methods ========================================================================================================
+
+    public Acl getAcl();
+
+    /**
+     * Obtains an identifier that represents this ACE.
+     *
+     * @return the identifier, or <code>null</code> if unsaved
+     */
+    public Serializable getId();
+
+    public Permission getPermission();
+
+    public Sid getSid();
+
+    /**
+     * Indicates the a Permission is being granted to the relevant Sid. If false, indicates the permission is
+     * being revoked/blocked.
+     *
+     * @return true if being granted, false otherwise
+     */
+    public boolean isGranting();
+}

+ 133 - 125
sandbox/other/src/main/java/org/acegisecurity/acls/Acl.java → core/src/main/java/org/acegisecurity/acls/Acl.java

@@ -1,26 +1,25 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.sid.Sid;
-
-import java.io.Serializable;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+
+import java.io.Serializable;
+
+
 /**
 /**
  * Represents an access control list (ACL) for a domain object.
  * Represents an access control list (ACL) for a domain object.
  * 
  * 
@@ -40,105 +39,114 @@ import java.io.Serializable;
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface Acl extends Serializable {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Returns all of the entries represented by the present <code>Acl</code> (not parents).<p>This method is
-     * typically used for administrative purposes.</p>
-     *  <p>The order that entries appear in the array is unspecified. However, if implementations use
-     * particular ordering logic in authorization decisions, the entries returned by this method <em>MUST</em> be
-     * ordered in that manner.</p>
-     *  <p>Do <em>NOT</em> use this method for making authorization decisions. Instead use {@link
-     * #isGranted(Permission[], Sid[])}.</p>
-     *  <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
-     * <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
-     * <code>Sid</code>s is represented.</p>
-     *
-     * @return the list of entries represented by the <code>Acl</code>
-     */
-    public AccessControlEntry[] getEntries();
-
-    /**
-     * Obtains the domain object this <code>Acl</code> provides entries for. This is immutable once an
-     * <code>Acl</code> is created.
-     *
-     * @return the object identity
-     */
-    public ObjectIdentity getObjectIdentity();
-
-    /**
-     * A domain object may have a parent for the purpose of ACL inheritance. If there is a parent, its ACL can
-     * be accessed via this method. In turn, the parent's parent (grandparent) can be accessed and so on.<p>This
-     * method solely represents the presence of a navigation hierarchy between the parent <code>Acl</code> and this
-     * <code>Acl</code>. For actual inheritance to take place, the {@link #isEntriesInheriting()} must also be
-     * <code>true</code>.</p>
-     *  <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
-     * <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
-     * <code>Sid</code>s is represented.</p>
-     *
-     * @return the parent <code>Acl</code>
-     */
-    public Acl getParentAcl();
-
-    /**
-     * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down into the current
-     * <code>Acl</code>.<p>The mere link between an <code>Acl</code> and a parent <code>Acl</code> on its own
-     * is insufficient to cause ACL entries to inherit down. This is because a domain object may wish to have entirely
-     * independent entries, but maintain the link with the parent for navigation purposes. Thus, this method denotes
-     * whether or not the navigation relationship also extends to the actual inheritence of entries.</p>
-     *
-     * @return <code>true</code> if parent ACL entries inherit into the current <code>Acl</code>
-     */
-    public boolean isEntriesInheriting();
-
-    /**
-     * This is the actual authorization logic method, and must be used whenever ACL authorization decisions are
-     * required.<p>An array of <code>Sid</code>s are presented, representing security identifies of the current
-     * principal. In addition, an array of <code>Permission</code>s is presented which will have one or more bits set
-     * in order to indicate the permissions needed for an affirmative authorization decision. An array is presented
-     * because holding <em>any</em> of the <code>Permission</code>s inside the array will be sufficient for an
-     * affirmative authorization.</p>
-     *  <p>The actual approach used to make authorization decisions is left to the implementation and is not
-     * specified by this interface. For example, an implementation <em>MAY</em> search the current ACL in the order
-     * the ACL entries have been stored. If a single entry is found that has the same active bits as are shown in a
-     * passed <code>Permission</code>, that entry's grant or deny state may determine the authorization decision. If
-     * the case of a deny state, the deny decision will only be relevant if all other <code>Permission</code>s passed
-     * in the array have also been unsuccessfully searched. If no entry is found that match the bits in the current
-     * ACL, provided that {@link #isEntriesInheriting()} is <code>true</code>, the authorization decision may be
-     * passed to the parent ACL. If there is no matching entry, the implementation MAY throw an exception, or make a
-     * predefined authorization decision.</p>
-     *  <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
-     * <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
-     * <code>Sid</code>s is represented.</p>
-     *
-     * @param permission the permission or permissions required
-     * @param sids the security identities held by the principal
-     * @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no logger or
-     *        auditing (if supported by the implementation) should be undertaken
-     *
-     * @return <code>true</code> is authorization is granted
-     *
-     * @throws NotFoundException MAY be thrown if an implementation cannot make an authoritative authorization decision
-     * @throws UnloadedSidException thrown if the <code>Acl</code> does not have details for one or more of the
-     *         <code>Sid</code>s passed as arguments
-     */
-    public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
-        throws NotFoundException, UnloadedSidException;
-
-    /**
-     * For efficiency reasons an <code>Acl</code> may be loaded and <em>not</em> contain entries for every
-     * <code>Sid</code> in the system. If an <code>Acl</code> has been loaded and does not represent every
-     * <code>Sid</code>, all methods of the <code>Sid</code> can only be used within the limited scope of the
-     * <code>Sid</code> instances it actually represents.<p>It is normal to load an <code>Acl</code> for only
-     * particular <code>Sid</code>s if read-only authorization decisions are being made. However, if user interface
-     * reporting or modification of <code>Acl</code>s are desired, an <code>Acl</code> should be loaded with all
-     * <code>Sid</code>s. This method denotes whether or not the specified <code>Sid</code>s have been loaded or not.</p>
-     *
-     * @param sids DOCUMENT ME!
-     *
-     * @return <code>true</code> if every passed <code>Sid</code> is represented by this <code>Acl</code> instance
-     */
-    public boolean isSidLoaded(Sid[] sids);
-}
+ */
+public interface Acl extends Serializable {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Returns all of the entries represented by the present <code>Acl</code> (not parents).<p>This method is
+     * typically used for administrative purposes.</p>
+     *  <p>The order that entries appear in the array is unspecified. However, if implementations use
+     * particular ordering logic in authorization decisions, the entries returned by this method <em>MUST</em> be
+     * ordered in that manner.</p>
+     *  <p>Do <em>NOT</em> use this method for making authorization decisions. Instead use {@link
+     * #isGranted(Permission[], Sid[])}.</p>
+     *  <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
+     * <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
+     * <code>Sid</code>s is represented.</p>
+     *
+     * @return the list of entries represented by the <code>Acl</code>
+     */
+    public AccessControlEntry[] getEntries();
+
+    /**
+     * Obtains the domain object this <code>Acl</code> provides entries for. This is immutable once an
+     * <code>Acl</code> is created.
+     *
+     * @return the object identity
+     */
+    public ObjectIdentity getObjectIdentity();
+
+    /**
+     * Determines the owner of the <code>Acl</code>. The meaning of ownership varies by implementation and is
+     * unspecified.
+     *
+     * @return the owner (may be null if the implementation does not use ownership concepts)
+     */
+    public Sid getOwner();
+
+    /**
+     * A domain object may have a parent for the purpose of ACL inheritance. If there is a parent, its ACL can
+     * be accessed via this method. In turn, the parent's parent (grandparent) can be accessed and so on.<p>This
+     * method solely represents the presence of a navigation hierarchy between the parent <code>Acl</code> and this
+     * <code>Acl</code>. For actual inheritance to take place, the {@link #isEntriesInheriting()} must also be
+     * <code>true</code>.</p>
+     *  <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
+     * <code>Sid</code>s. The caller is responsible for correctly handling the result if only a subset of
+     * <code>Sid</code>s is represented.</p>
+     *
+     * @return the parent <code>Acl</code>
+     */
+    public Acl getParentAcl();
+
+    /**
+     * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down into the current
+     * <code>Acl</code>.<p>The mere link between an <code>Acl</code> and a parent <code>Acl</code> on its own
+     * is insufficient to cause ACL entries to inherit down. This is because a domain object may wish to have entirely
+     * independent entries, but maintain the link with the parent for navigation purposes. Thus, this method denotes
+     * whether or not the navigation relationship also extends to the actual inheritence of entries.</p>
+     *
+     * @return <code>true</code> if parent ACL entries inherit into the current <code>Acl</code>
+     */
+    public boolean isEntriesInheriting();
+
+    /**
+     * This is the actual authorization logic method, and must be used whenever ACL authorization decisions are
+     * required.<p>An array of <code>Sid</code>s are presented, representing security identifies of the current
+     * principal. In addition, an array of <code>Permission</code>s is presented which will have one or more bits set
+     * in order to indicate the permissions needed for an affirmative authorization decision. An array is presented
+     * because holding <em>any</em> of the <code>Permission</code>s inside the array will be sufficient for an
+     * affirmative authorization.</p>
+     *  <p>The actual approach used to make authorization decisions is left to the implementation and is not
+     * specified by this interface. For example, an implementation <em>MAY</em> search the current ACL in the order
+     * the ACL entries have been stored. If a single entry is found that has the same active bits as are shown in a
+     * passed <code>Permission</code>, that entry's grant or deny state may determine the authorization decision. If
+     * the case of a deny state, the deny decision will only be relevant if all other <code>Permission</code>s passed
+     * in the array have also been unsuccessfully searched. If no entry is found that match the bits in the current
+     * ACL, provided that {@link #isEntriesInheriting()} is <code>true</code>, the authorization decision may be
+     * passed to the parent ACL. If there is no matching entry, the implementation MAY throw an exception, or make a
+     * predefined authorization decision.</p>
+     *  <p>This method must operate correctly even if the <code>Acl</code> only represents a subset of
+     * <code>Sid</code>s.</p>
+     *
+     * @param permission the permission or permissions required
+     * @param sids the security identities held by the principal
+     * @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no logging
+     *        or auditing (if supported by the implementation) should be undertaken
+     *
+     * @return <code>true</code> is authorization is granted
+     *
+     * @throws NotFoundException MUST be thrown if an implementation cannot make an authoritative authorization
+     *         decision, usually because there is no ACL information for this particular permission and/or SID
+     * @throws UnloadedSidException thrown if the <code>Acl</code> does not have details for one or more of the
+     *         <code>Sid</code>s passed as arguments
+     */
+    public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
+        throws NotFoundException, UnloadedSidException;
+
+    /**
+     * For efficiency reasons an <code>Acl</code> may be loaded and <em>not</em> contain entries for every
+     * <code>Sid</code> in the system. If an <code>Acl</code> has been loaded and does not represent every
+     * <code>Sid</code>, all methods of the <code>Sid</code> can only be used within the limited scope of the
+     * <code>Sid</code> instances it actually represents.<p>It is normal to load an <code>Acl</code> for only
+     * particular <code>Sid</code>s if read-only authorization decisions are being made. However, if user interface
+     * reporting or modification of <code>Acl</code>s are desired, an <code>Acl</code> should be loaded with all
+     * <code>Sid</code>s. This method denotes whether or not the specified <code>Sid</code>s have been loaded or not.</p>
+     *
+     * @param sids one or more security identities the caller is interest in knowing whether this <code>Sid</code>
+     *        supports
+     *
+     * @return <code>true</code> if every passed <code>Sid</code> is represented by this <code>Acl</code> instance
+     */
+    public boolean isSidLoaded(Sid[] sids);
+}

+ 105 - 106
sandbox/other/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java → core/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java

@@ -1,106 +1,105 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.springframework.util.Assert;
-
-
-/**
- * Utility methods for displaying ACL information.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class AclFormattingUtils {
-    //~ Methods ========================================================================================================
-
-    public static String demergePatterns(String original, String removeBits) {
-        Assert.notNull(original, "Original string required");
-        Assert.notNull(removeBits, "Bits To Remove string required");
-        Assert.isTrue(original.length() == removeBits.length(),
-            "Original and Bits To Remove strings must be identical length");
-
-        char[] replacement = new char[original.length()];
-
-        for (int i = 0; i < original.length(); i++) {
-            if (removeBits.charAt(i) == Permission.RESERVED_OFF) {
-                replacement[i] = original.charAt(i);
-            } else {
-                replacement[i] = Permission.RESERVED_OFF;
-            }
-        }
-
-        return new String(replacement);
-    }
-
-    public static String mergePatterns(String original, String extraBits) {
-        Assert.notNull(original, "Original string required");
-        Assert.notNull(extraBits, "Extra Bits string required");
-        Assert.isTrue(original.length() == extraBits.length(),
-            "Original and Extra Bits strings must be identical length");
-
-        char[] replacement = new char[extraBits.length()];
-
-        for (int i = 0; i < extraBits.length(); i++) {
-            if (extraBits.charAt(i) == Permission.RESERVED_OFF) {
-                replacement[i] = original.charAt(i);
-            } else {
-                replacement[i] = extraBits.charAt(i);
-            }
-        }
-
-        return new String(replacement);
-    }
-
-    private static String printBinary(int i, char on, char off) {
-        String s = Integer.toString(i, 2);
-        String pattern = Permission.THIRTY_TWO_RESERVED_OFF;
-        String temp2 = pattern.substring(0, pattern.length() - s.length()) + s;
-
-        return temp2.replace('0', off).replace('1', on);
-    }
-
-    /**
-     * Returns a representation of the active bits in the presented mask, with each active bit being denoted by
-     * character "".<p>Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.</p>
-     *
-     * @param i the integer bit mask to print the active bits for
-     *
-     * @return a 32-character representation of the bit mask
-     */
-    public static String printBinary(int i) {
-        return AclFormattingUtils.printBinary(i, '*', Permission.RESERVED_OFF);
-    }
-
-    /**
-     * Returns a representation of the active bits in the presented mask, with each active bit being denoted by
-     * the passed character.<p>Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.</p>
-     *
-     * @param mask the integer bit mask to print the active bits for
-     * @param code the character to print when an active bit is detected
-     *
-     * @return a 32-character representation of the bit mask
-     */
-    public static String printBinary(int mask, char code) {
-        Assert.doesNotContain(new Character(code).toString(), new Character(Permission.RESERVED_ON).toString(),
-            Permission.RESERVED_ON + " is a reserved character code");
-        Assert.doesNotContain(new Character(code).toString(), new Character(Permission.RESERVED_OFF).toString(),
-            Permission.RESERVED_OFF + " is a reserved character code");
-
-        return AclFormattingUtils.printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF)
-                                 .replace(Permission.RESERVED_ON, code);
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Utility methods for displaying ACL information.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AclFormattingUtils {
+    //~ Methods ========================================================================================================
+
+    public static String demergePatterns(String original, String removeBits) {
+        Assert.notNull(original, "Original string required");
+        Assert.notNull(removeBits, "Bits To Remove string required");
+        Assert.isTrue(original.length() == removeBits.length(),
+            "Original and Bits To Remove strings must be identical length");
+
+        char[] replacement = new char[original.length()];
+
+        for (int i = 0; i < original.length(); i++) {
+            if (removeBits.charAt(i) == Permission.RESERVED_OFF) {
+                replacement[i] = original.charAt(i);
+            } else {
+                replacement[i] = Permission.RESERVED_OFF;
+            }
+        }
+
+        return new String(replacement);
+    }
+
+    public static String mergePatterns(String original, String extraBits) {
+        Assert.notNull(original, "Original string required");
+        Assert.notNull(extraBits, "Extra Bits string required");
+        Assert.isTrue(original.length() == extraBits.length(),
+            "Original and Extra Bits strings must be identical length");
+
+        char[] replacement = new char[extraBits.length()];
+
+        for (int i = 0; i < extraBits.length(); i++) {
+            if (extraBits.charAt(i) == Permission.RESERVED_OFF) {
+                replacement[i] = original.charAt(i);
+            } else {
+                replacement[i] = extraBits.charAt(i);
+            }
+        }
+
+        return new String(replacement);
+    }
+
+    private static String printBinary(int i, char on, char off) {
+        String s = Integer.toString(i, 2);
+        String pattern = Permission.THIRTY_TWO_RESERVED_OFF;
+        String temp2 = pattern.substring(0, pattern.length() - s.length()) + s;
+
+        return temp2.replace('0', off).replace('1', on);
+    }
+
+    /**
+     * Returns a representation of the active bits in the presented mask, with each active bit being denoted by
+     * character "".<p>Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.</p>
+     *
+     * @param i the integer bit mask to print the active bits for
+     *
+     * @return a 32-character representation of the bit mask
+     */
+    public static String printBinary(int i) {
+        return AclFormattingUtils.printBinary(i, '*', Permission.RESERVED_OFF);
+    }
+
+    /**
+     * Returns a representation of the active bits in the presented mask, with each active bit being denoted by
+     * the passed character.<p>Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.</p>
+     *
+     * @param mask the integer bit mask to print the active bits for
+     * @param code the character to print when an active bit is detected
+     *
+     * @return a 32-character representation of the bit mask
+     */
+    public static String printBinary(int mask, char code) {
+        Assert.doesNotContain(new Character(code).toString(), new Character(Permission.RESERVED_ON).toString(),
+            Permission.RESERVED_ON + " is a reserved character code");
+        Assert.doesNotContain(new Character(code).toString(), new Character(Permission.RESERVED_OFF).toString(),
+            Permission.RESERVED_OFF + " is a reserved character code");
+
+        return AclFormattingUtils.printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF)
+                                 .replace(Permission.RESERVED_ON, code);
+    }
+}

+ 95 - 61
sandbox/other/src/main/java/org/acegisecurity/acls/AclService.java → core/src/main/java/org/acegisecurity/acls/AclService.java

@@ -1,66 +1,100 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.sid.Sid;
-
-import java.util.Map;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+
+import java.util.Map;
+
+
 /**
 /**
  * Provides retrieval of {@link Acl} instances.
  * Provides retrieval of {@link Acl} instances.
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface AclService {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Obtains all the <code>Acl</code>s that apply for the passed <code>Object</code>s.<p>The returned map is
-     * keyed on the passed objects, with the values being the <code>Acl</code> instances. Any unknown objects will not
-     * have a map key.</p>
-     *
-     * @param objects the objects to find ACL information for
-     *
-     * @return a map with zero or more elements (never <code>null</code>)
-     *
-     * @throws NotFoundException DOCUMENT ME!
-     */
-    public Map readAclsById(ObjectIdentity[] objects) throws NotFoundException;
-
-    /**
-     * Obtains all the <code>Acl</code>s that apply for the passed <code>Object</code>s, but only for the
-     * security identifies passed.<p>Implementations <em>MAY</em> provide a subset of the ACLs via this method
-     * although this is NOT a requirement. This is intended to allow performance optimisations within implementations.
-     * Callers should therefore use this method in preference to the alternative overloaded version which does not
-     * have performance optimisation opportunities.</p>
-     *  <p>The returned map is keyed on the passed objects, with the values being the <code>Acl</code>
-     * instances. Any unknown objects (or objects for which the interested <code>Sid</code>s do not have entries) will
-     * not have a map key.</p>
-     *
-     * @param objects the objects to find ACL information for
-     * @param sids the security identities for which ACL information is required (may be <code>null</code> to denote
-     *        all entries)
-     *
-     * @return a map with zero or more elements (never <code>null</code>)
-     *
-     * @throws NotFoundException DOCUMENT ME!
-     */
-    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
-        throws NotFoundException;
-}
+ */
+public interface AclService {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Locates all object identities that use the specified parent.  This is useful for administration tools.
+     *
+     * @param parentIdentity to locate children of
+     *
+     * @return the children (or <code>null</code> if none were found)
+     */
+    public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity);
+
+    /**
+     * Same as {@link #readAclsById(ObjectIdentity[])} except it returns only a single Acl.<p>This method
+     * should not be called as it does not leverage the underlaying implementation's potential ability to filter
+     * <code>Acl</code> entries based on a {@link Sid} parameter.</p>
+     *
+     * @param object DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     */
+    public Acl readAclById(ObjectIdentity object) throws NotFoundException;
+
+    /**
+     * Same as {@link #readAclsById(ObjectIdentity[], Sid[])} except it returns only a single Acl.
+     *
+     * @param object DOCUMENT ME!
+     * @param sids DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     */
+    public Acl readAclById(ObjectIdentity object, Sid[] sids)
+        throws NotFoundException;
+
+    /**
+     * Obtains all the <code>Acl</code>s that apply for the passed <code>Object</code>s.<p>The returned map is
+     * keyed on the passed objects, with the values being the <code>Acl</code> instances. Any unknown objects will not
+     * have a map key.</p>
+     *
+     * @param objects the objects to find ACL information for
+     *
+     * @return a map with zero or more elements (never <code>null</code>)
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     */
+    public Map readAclsById(ObjectIdentity[] objects) throws NotFoundException;
+
+    /**
+     * Obtains all the <code>Acl</code>s that apply for the passed <code>Object</code>s, but only for the
+     * security identifies passed.<p>Implementations <em>MAY</em> provide a subset of the ACLs via this method
+     * although this is NOT a requirement. This is intended to allow performance optimisations within implementations.
+     * Callers should therefore use this method in preference to the alternative overloaded version which does not
+     * have performance optimisation opportunities.</p>
+     *  <p>The returned map is keyed on the passed objects, with the values being the <code>Acl</code>
+     * instances. Any unknown objects (or objects for which the interested <code>Sid</code>s do not have entries) will
+     * not have a map key.</p>
+     *
+     * @param objects the objects to find ACL information for
+     * @param sids the security identities for which ACL information is required (may be <code>null</code> to denote
+     *        all entries)
+     *
+     * @return a map with zero or more elements (never <code>null</code>)
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     */
+    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
+        throws NotFoundException;
+}

+ 38 - 39
sandbox/other/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java → core/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java

@@ -1,49 +1,48 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.AcegiSecurityException;
-
-
-/**
- * Thrown if an <code>Acl</code> entry already exists for the object.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class AlreadyExistsException extends AcegiSecurityException {
-    //~ Constructors ===================================================================================================
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an <code>Acl</code> entry already exists for the object.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AlreadyExistsException extends AcegiSecurityException {
+    //~ Constructors ===================================================================================================
+
 /**
 /**
      * Constructs an <code>AlreadyExistsException</code> with the specified message.
      * Constructs an <code>AlreadyExistsException</code> with the specified message.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
-     */
-    public AlreadyExistsException(String msg) {
-        super(msg);
-    }
-
+     */
+    public AlreadyExistsException(String msg) {
+        super(msg);
+    }
+
 /**
 /**
      * Constructs an <code>AlreadyExistsException</code> with the specified message
      * Constructs an <code>AlreadyExistsException</code> with the specified message
      * and root cause.
      * and root cause.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
      * @param t root cause
      * @param t root cause
-     */
-    public AlreadyExistsException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}
+     */
+    public AlreadyExistsException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 24 - 25
sandbox/other/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java → core/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java

@@ -1,31 +1,30 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
 /**
 /**
  * Represents an ACE that provides auditing information.
  * Represents an ACE that provides auditing information.
  * 
  * 
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
  *
  *
- */
-public interface AuditableAccessControlEntry extends AccessControlEntry {
-    //~ Methods ========================================================================================================
-
-    public boolean isAuditFailure();
-
-    public boolean isAuditSuccess();
-}
+ */
+public interface AuditableAccessControlEntry extends AccessControlEntry {
+    //~ Methods ========================================================================================================
+
+    public boolean isAuditFailure();
+
+    public boolean isAuditSuccess();
+}

+ 25 - 23
sandbox/other/src/main/java/org/acegisecurity/acls/AuditableAcl.java → core/src/main/java/org/acegisecurity/acls/AuditableAcl.java

@@ -1,29 +1,31 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import java.io.Serializable;
+
+
 /**
 /**
  * A mutable ACL that provides audit capabilities.
  * A mutable ACL that provides audit capabilities.
  * 
  * 
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
  *
  *
- */
-public interface AuditableAcl extends MutableAcl {
-    //~ Methods ========================================================================================================
-
-    public void updateAuditing(Long aceId, boolean auditSuccess, boolean auditFailure);
-}
+ */
+public interface AuditableAcl extends MutableAcl {
+    //~ Methods ========================================================================================================
+
+    public void updateAuditing(Serializable aceId, boolean auditSuccess, boolean auditFailure);
+}

+ 38 - 39
sandbox/other/src/main/java/org/acegisecurity/acls/ChildrenExistException.java → core/src/main/java/org/acegisecurity/acls/ChildrenExistException.java

@@ -1,50 +1,49 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.AcegiSecurityException;
-
-
-/**
- * Thrown if an {@link Acl} cannot be deleted because children <code>Acl</code>s exist.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class ChildrenExistException extends AcegiSecurityException {
-    //~ Constructors ===================================================================================================
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an {@link Acl} cannot be deleted because children <code>Acl</code>s exist.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ChildrenExistException extends AcegiSecurityException {
+    //~ Constructors ===================================================================================================
+
 /**
 /**
      * Constructs an <code>ChildrenExistException</code> with the specified
      * Constructs an <code>ChildrenExistException</code> with the specified
      * message.
      * message.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
-     */
-    public ChildrenExistException(String msg) {
-        super(msg);
-    }
-
+     */
+    public ChildrenExistException(String msg) {
+        super(msg);
+    }
+
 /**
 /**
      * Constructs an <code>ChildrenExistException</code> with the specified
      * Constructs an <code>ChildrenExistException</code> with the specified
      * message and root cause.
      * message and root cause.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
      * @param t root cause
      * @param t root cause
-     */
-    public ChildrenExistException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}
+     */
+    public ChildrenExistException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 38 - 39
sandbox/other/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java → core/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java

@@ -1,49 +1,48 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.AcegiSecurityException;
-
-
-/**
- * Thrown if an ACL identity could not be extracted from an object.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class IdentityUnavailableException extends AcegiSecurityException {
-    //~ Constructors ===================================================================================================
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an ACL identity could not be extracted from an object.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class IdentityUnavailableException extends AcegiSecurityException {
+    //~ Constructors ===================================================================================================
+
 /**
 /**
      * Constructs an <code>IdentityUnavailableException</code> with the specified message.
      * Constructs an <code>IdentityUnavailableException</code> with the specified message.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
-     */
-    public IdentityUnavailableException(String msg) {
-        super(msg);
-    }
-
+     */
+    public IdentityUnavailableException(String msg) {
+        super(msg);
+    }
+
 /**
 /**
      * Constructs an <code>IdentityUnavailableException</code> with the specified message
      * Constructs an <code>IdentityUnavailableException</code> with the specified message
      * and root cause.
      * and root cause.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
      * @param t root cause
      * @param t root cause
-     */
-    public IdentityUnavailableException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}
+     */
+    public IdentityUnavailableException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 65 - 50
sandbox/other/src/main/java/org/acegisecurity/acls/MutableAcl.java → core/src/main/java/org/acegisecurity/acls/MutableAcl.java

@@ -1,25 +1,24 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.sid.Sid;
-
-import java.io.Serializable;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.sid.Sid;
+
+import java.io.Serializable;
+
+
 /**
 /**
  * A mutable <code>Acl</code>.
  * A mutable <code>Acl</code>.
  * 
  * 
@@ -30,31 +29,47 @@ import java.io.Serializable;
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface MutableAcl extends Acl {
-    //~ Methods ========================================================================================================
-
-    public void deleteAce(Long aceId) throws NotFoundException;
-
-    /**
-     * Obtains an identifier that represents this <code>MutableAcl</code>.
-     *
-     * @return the identifier, or <code>null</code> if unsaved
-     */
-    public Serializable getId();
-
-    public void insertAce(Long afterAceId, Permission permission, Sid sid, boolean granting)
-        throws NotFoundException;
-
-    public void setEntriesInheriting(boolean entriesInheriting);
-
-    /**
-     * Changes the parent of this ACL.
-     *
-     * @param newParent the new parent
-     */
-    public void setParent(MutableAcl newParent);
-
-    public void updateAce(Long aceId, Permission permission)
-        throws NotFoundException;
-}
+ */
+public interface MutableAcl extends Acl {
+    //~ Methods ========================================================================================================
+
+    public void deleteAce(Serializable aceId) throws NotFoundException;
+
+    /**
+     * Retrieves all of the non-deleted {@link AccessControlEntry} instances currently stored by the
+     * <code>MutableAcl</code>. The returned objects should be immutable outside the package, and therefore it is safe
+     * to return them to the caller for informational purposes. The <code>AccessControlEntry</code> information is
+     * needed so that invocations of update and delete methods on the <code>MutableAcl</code> can refer to a valid
+     * {@link AccessControlEntry#getId()}.
+     *
+     * @return DOCUMENT ME!
+     */
+    public AccessControlEntry[] getEntries();
+
+    /**
+     * Obtains an identifier that represents this <code>MutableAcl</code>.
+     *
+     * @return the identifier, or <code>null</code> if unsaved
+     */
+    public Serializable getId();
+
+    public void insertAce(Serializable afterAceId, Permission permission, Sid sid, boolean granting)
+        throws NotFoundException;
+
+    /**
+     * Change the value returned by {@link Acl#isEntriesInheriting()}.
+     *
+     * @param entriesInheriting the new value
+     */
+    public void setEntriesInheriting(boolean entriesInheriting);
+
+    /**
+     * Changes the parent of this ACL.
+     *
+     * @param newParent the new parent
+     */
+    public void setParent(MutableAcl newParent);
+
+    public void updateAce(Serializable aceId, Permission permission)
+        throws NotFoundException;
+}

+ 60 - 69
sandbox/other/src/main/java/org/acegisecurity/acls/MutableAclService.java → core/src/main/java/org/acegisecurity/acls/MutableAclService.java

@@ -1,74 +1,65 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+
+
 /**
 /**
  * Provides support for creating and storing <code>Acl</code> instances.
  * Provides support for creating and storing <code>Acl</code> instances.
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface MutableAclService extends AclService {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Creates an empty <code>Acl</code> object in the database. It will have no entries. The returned object
-     * will then be used to add entries.
-     *
-     * @param object the object identity to create
-     *
-     * @return an ACL object with its ID set
-     *
-     * @throws AlreadyExistsException if the passed object identity already has a record
-     */
-    public MutableAcl createAcl(ObjectIdentity object)
-        throws AlreadyExistsException;
-
-    /**
-     * Removes the specified entry from the database.
-     *
-     * @param object the object identity to remove
-     * @param deleteChildren whether to cascade the delete to children
-     *
-     * @throws ChildrenExistException if the deleteChildren argument was <code>false</code> but children exist
-     */
-    public void deleteAcl(ObjectIdentity object, boolean deleteChildren)
-        throws ChildrenExistException;
-
-    /**
-     * Locates all object identities that use the specified parent.  This is useful for administration tools,
-     * and before issuing a {@link #deleteAcl(ObjectIdentity, boolean)}.
-     *
-     * @param parentIdentity to locate children of
-     *
-     * @return the children (or <code>null</code> if none were found)
-     */
-    public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity);
-
-    /**
-     * Changes an existing <code>Acl</code> in the database.
-     *
-     * @param acl to modify
-     *
-     * @throws NotFoundException if the relevant record could not be found (did you remember to use {@link
-     *         #createAcl(ObjectIdentity)} to create the object, rather than creating it with the <code>new</code>
-     *         keyword?)
-     */
-    public void updateAcl(MutableAcl acl) throws NotFoundException;
-}
+ */
+public interface MutableAclService extends AclService {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Creates an empty <code>Acl</code> object in the database. It will have no entries. The returned object
+     * will then be used to add entries.
+     *
+     * @param objectIdentity the object identity to create
+     *
+     * @return an ACL object with its ID set
+     *
+     * @throws AlreadyExistsException if the passed object identity already has a record
+     */
+    public MutableAcl createAcl(ObjectIdentity objectIdentity)
+        throws AlreadyExistsException;
+
+    /**
+     * Removes the specified entry from the database.
+     *
+     * @param objectIdentity the object identity to remove
+     * @param deleteChildren whether to cascade the delete to children
+     *
+     * @throws ChildrenExistException if the deleteChildren argument was <code>false</code> but children exist
+     */
+    public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
+        throws ChildrenExistException;
+
+    /**
+     * Changes an existing <code>Acl</code> in the database.
+     *
+     * @param acl to modify
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws NotFoundException if the relevant record could not be found (did you remember to use {@link
+     *         #createAcl(ObjectIdentity)} to create the object, rather than creating it with the <code>new</code>
+     *         keyword?)
+     */
+    public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException;
+}

+ 38 - 39
sandbox/other/src/main/java/org/acegisecurity/acls/NotFoundException.java → core/src/main/java/org/acegisecurity/acls/NotFoundException.java

@@ -1,49 +1,48 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.AcegiSecurityException;
-
-
-/**
- * Thrown if an ACL-related object cannot be found.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class NotFoundException extends AcegiSecurityException {
-    //~ Constructors ===================================================================================================
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an ACL-related object cannot be found.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class NotFoundException extends AcegiSecurityException {
+    //~ Constructors ===================================================================================================
+
 /**
 /**
      * Constructs an <code>NotFoundException</code> with the specified message.
      * Constructs an <code>NotFoundException</code> with the specified message.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
-     */
-    public NotFoundException(String msg) {
-        super(msg);
-    }
-
+     */
+    public NotFoundException(String msg) {
+        super(msg);
+    }
+
 /**
 /**
      * Constructs an <code>NotFoundException</code> with the specified message
      * Constructs an <code>NotFoundException</code> with the specified message
      * and root cause.
      * and root cause.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
      * @param t root cause
      * @param t root cause
-     */
-    public NotFoundException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}
+     */
+    public NotFoundException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 25 - 28
sandbox/other/src/main/java/org/acegisecurity/acls/OwnershipAcl.java → core/src/main/java/org/acegisecurity/acls/OwnershipAcl.java

@@ -1,23 +1,22 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.sid.Sid;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.sid.Sid;
+
+
 /**
 /**
  * A mutable ACL that provides ownership capabilities.
  * A mutable ACL that provides ownership capabilities.
  * 
  * 
@@ -28,11 +27,9 @@ import org.acegisecurity.acls.sid.Sid;
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface OwnershipAcl extends MutableAcl {
-    //~ Methods ========================================================================================================
-
-    public Sid getOwner();
-
-    public void setOwner(Sid newOwner);
-}
+ */
+public interface OwnershipAcl extends MutableAcl {
+    //~ Methods ========================================================================================================
+
+    public void setOwner(Sid newOwner);
+}

+ 51 - 52
sandbox/other/src/main/java/org/acegisecurity/acls/Permission.java → core/src/main/java/org/acegisecurity/acls/Permission.java

@@ -1,57 +1,56 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.acls.sid.Sid;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.acls.sid.Sid;
+
+
 /**
 /**
  * Represents a permission granted to a {@link Sid} for a given domain object.
  * Represents a permission granted to a {@link Sid} for a given domain object.
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface Permission {
-    //~ Static fields/initializers =====================================================================================
-
-    public static final char RESERVED_ON = '~';
-    public static final char RESERVED_OFF = '.';
-    public static final String THIRTY_TWO_RESERVED_OFF = "................................";
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Returns the bits that represents the permission.
-     *
-     * @return the bits that represent the permission
-     */
-    public int getMask();
-
-    /**
-     * Returns a 32-character long bit pattern <code>String</code> representing this permission.<p>Implementations
-     * are free to format the pattern as they see fit, although under no circumstances may {@link #RESERVED_OFF} or
-     * {@link #RESERVED_ON} be used within the pattern. An exemption is in the case of {@link #RESERVED_OFF} which is
-     * used to denote a bit that is off (clear). Implementations may also elect to use {@link #RESERVED_ON} internally
-     * for computation purposes, although this method may not return any <code>String</code> containing {@link
-     * #RESERVED_ON}.</p>
-     *  <p>The returned String must be 32 characters in length.</p>
-     *  <p>This method is only used for user interface and logging purposes. It is not used in any permission
-     * calculations. Therefore, duplication of characters within the output is permitted.</p>
-     *
-     * @return a 32-character bit pattern
-     */
-    public String getPattern();
-}
+ */
+public interface Permission {
+    //~ Static fields/initializers =====================================================================================
+
+    public static final char RESERVED_ON = '~';
+    public static final char RESERVED_OFF = '.';
+    public static final String THIRTY_TWO_RESERVED_OFF = "................................";
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Returns the bits that represents the permission.
+     *
+     * @return the bits that represent the permission
+     */
+    public int getMask();
+
+    /**
+     * Returns a 32-character long bit pattern <code>String</code> representing this permission.<p>Implementations
+     * are free to format the pattern as they see fit, although under no circumstances may {@link #RESERVED_OFF} or
+     * {@link #RESERVED_ON} be used within the pattern. An exemption is in the case of {@link #RESERVED_OFF} which is
+     * used to denote a bit that is off (clear). Implementations may also elect to use {@link #RESERVED_ON} internally
+     * for computation purposes, although this method may not return any <code>String</code> containing {@link
+     * #RESERVED_ON}.</p>
+     *  <p>The returned String must be 32 characters in length.</p>
+     *  <p>This method is only used for user interface and logging purposes. It is not used in any permission
+     * calculations. Therefore, duplication of characters within the output is permitted.</p>
+     *
+     * @return a 32-character bit pattern
+     */
+    public String getPattern();
+}

+ 39 - 40
sandbox/other/src/main/java/org/acegisecurity/acls/UnloadedSidException.java → core/src/main/java/org/acegisecurity/acls/UnloadedSidException.java

@@ -1,50 +1,49 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls;
-
-import org.acegisecurity.AcegiSecurityException;
-
-
-/**
- * Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of <code>Sid</code>s and
- * the caller has requested details for an unloaded <code>Sid</code>.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class UnloadedSidException extends AcegiSecurityException {
-    //~ Constructors ===================================================================================================
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls;
+
+import org.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of <code>Sid</code>s and
+ * the caller has requested details for an unloaded <code>Sid</code>.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class UnloadedSidException extends AcegiSecurityException {
+    //~ Constructors ===================================================================================================
+
 /**
 /**
      * Constructs an <code>NotFoundException</code> with the specified message.
      * Constructs an <code>NotFoundException</code> with the specified message.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
-     */
-    public UnloadedSidException(String msg) {
-        super(msg);
-    }
-
+     */
+    public UnloadedSidException(String msg) {
+        super(msg);
+    }
+
 /**
 /**
      * Constructs an <code>NotFoundException</code> with the specified message
      * Constructs an <code>NotFoundException</code> with the specified message
      * and root cause.
      * and root cause.
      *
      *
      * @param msg the detail message
      * @param msg the detail message
      * @param t root cause
      * @param t root cause
-     */
-    public UnloadedSidException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}
+     */
+    public UnloadedSidException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 133 - 149
sandbox/other/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java → core/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java

@@ -1,149 +1,133 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.domain;
-
-import org.acegisecurity.acls.AccessControlEntry;
-import org.acegisecurity.acls.Acl;
-import org.acegisecurity.acls.AuditableAccessControlEntry;
-import org.acegisecurity.acls.Permission;
-import org.acegisecurity.acls.sid.Sid;
-
-import org.springframework.util.Assert;
-
-import java.io.Serializable;
-
-
-/**
- * An immutable default implementation of <code>AccessControlEntry</code>.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry {
-    //~ Instance fields ================================================================================================
-
-    private Acl acl;
-    private Permission permission;
-    private Serializable id;
-    private Sid sid;
-    private boolean aceDirty = false;
-    private boolean auditFailure = false;
-    private boolean auditSuccess = false;
-    private boolean granting;
-
-    //~ Constructors ===================================================================================================
-
-    public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting,
-        boolean auditSuccess, boolean auditFailure) {
-        Assert.notNull(acl, "Acl required");
-        Assert.notNull(sid, "Sid required");
-        Assert.notNull(permission, "Permission required");
-        this.id = id;
-        this.acl = acl; // can be null
-        this.sid = sid;
-        this.permission = permission;
-        this.granting = granting;
-        this.auditSuccess = auditSuccess;
-        this.auditFailure = auditFailure;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public void clearDirtyFlags() {
-        this.aceDirty = false;
-    }
-
-    public boolean equals(Object arg0) {
-        if (!(arg0 instanceof AccessControlEntryImpl)) {
-            return false;
-        }
-
-        AccessControlEntryImpl rhs = (AccessControlEntryImpl) arg0;
-
-        if ((this.aceDirty != rhs.isAceDirty()) || (this.auditFailure != rhs.isAuditFailure())
-            || (this.auditSuccess != rhs.isAuditSuccess()) || (this.granting != rhs.isGranting())
-            || !this.acl.equals(rhs.getAcl()) || !this.id.equals(rhs.getId())
-            || !this.permission.equals(rhs.getPermission()) || !this.sid.equals(rhs.getSid())) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public Acl getAcl() {
-        return acl;
-    }
-
-    public Serializable getId() {
-        return id;
-    }
-
-    public Permission getPermission() {
-        return permission;
-    }
-
-    public Sid getSid() {
-        return sid;
-    }
-
-    public boolean isAceDirty() {
-        return aceDirty;
-    }
-
-    public boolean isAuditFailure() {
-        return auditFailure;
-    }
-
-    public boolean isAuditSuccess() {
-        return auditSuccess;
-    }
-
-    public boolean isGranting() {
-        return granting;
-    }
-
-    void setAuditFailure(boolean auditFailure) {
-        this.auditFailure = auditFailure;
-        this.aceDirty = true;
-    }
-
-    void setAuditSuccess(boolean auditSuccess) {
-        this.auditSuccess = auditSuccess;
-        this.aceDirty = true;
-    }
-
-    void setId(Serializable id) {
-        this.id = id;
-    }
-
-    void setPermission(Permission permission) {
-        Assert.notNull(permission, "Permission required");
-        this.permission = permission;
-        this.aceDirty = true;
-    }
-
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("AccessControlEntryImpl[");
-        sb.append("id: ").append(this.id).append("; ");
-        sb.append("granting: ").append(this.granting).append("; ");
-        sb.append("sid: ").append(this.sid).append("; ");
-        sb.append("permission: ").append(this.permission);
-        sb.append("]");
-
-        return sb.toString();
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AuditableAccessControlEntry;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.sid.Sid;
+
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+
+
+/**
+ * An immutable default implementation of <code>AccessControlEntry</code>.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry {
+    //~ Instance fields ================================================================================================
+
+    private Acl acl;
+    private Permission permission;
+    private Serializable id;
+    private Sid sid;
+    private boolean auditFailure = false;
+    private boolean auditSuccess = false;
+    private boolean granting;
+
+    //~ Constructors ===================================================================================================
+
+    public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting,
+        boolean auditSuccess, boolean auditFailure) {
+        Assert.notNull(acl, "Acl required");
+        Assert.notNull(sid, "Sid required");
+        Assert.notNull(permission, "Permission required");
+        this.id = id;
+        this.acl = acl; // can be null
+        this.sid = sid;
+        this.permission = permission;
+        this.granting = granting;
+        this.auditSuccess = auditSuccess;
+        this.auditFailure = auditFailure;
+    }
+
+    //~ Methods ========================================================================================================
+
+    public boolean equals(Object arg0) {
+        if (!(arg0 instanceof AccessControlEntryImpl)) {
+            return false;
+        }
+
+        AccessControlEntryImpl rhs = (AccessControlEntryImpl) arg0;
+
+        if ((this.auditFailure != rhs.isAuditFailure()) || (this.auditSuccess != rhs.isAuditSuccess())
+            || (this.granting != rhs.isGranting()) || !this.acl.equals(rhs.getAcl()) || !this.id.equals(rhs.getId())
+            || !this.permission.equals(rhs.getPermission()) || !this.sid.equals(rhs.getSid())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public Acl getAcl() {
+        return acl;
+    }
+
+    public Serializable getId() {
+        return id;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public Sid getSid() {
+        return sid;
+    }
+
+    public boolean isAuditFailure() {
+        return auditFailure;
+    }
+
+    public boolean isAuditSuccess() {
+        return auditSuccess;
+    }
+
+    public boolean isGranting() {
+        return granting;
+    }
+
+    void setAuditFailure(boolean auditFailure) {
+        this.auditFailure = auditFailure;
+    }
+
+    void setAuditSuccess(boolean auditSuccess) {
+        this.auditSuccess = auditSuccess;
+    }
+
+    void setPermission(Permission permission) {
+        Assert.notNull(permission, "Permission required");
+        this.permission = permission;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("AccessControlEntryImpl[");
+        sb.append("id: ").append(this.id).append("; ");
+        sb.append("granting: ").append(this.granting).append("; ");
+        sb.append("sid: ").append(this.sid).append("; ");
+        sb.append("permission: ").append(this.permission).append("; ");
+        sb.append("auditSuccess: ").append(this.auditSuccess).append("; ");
+        sb.append("auditFailure: ").append(this.auditFailure);
+        sb.append("]");
+
+        return sb.toString();
+    }
+}

+ 38 - 0
core/src/main/java/org/acegisecurity/acls/domain/AclAuthorizationStrategy.java

@@ -0,0 +1,38 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.Acl;
+
+
+/**
+ * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call
+ * adminstrative methods on the <code>AclImpl</code>.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AclAuthorizationStrategy {
+    //~ Static fields/initializers =====================================================================================
+
+    public static final int CHANGE_OWNERSHIP = 0;
+    public static final int CHANGE_AUDITING = 1;
+    public static final int CHANGE_GENERAL = 2;
+
+    //~ Methods ========================================================================================================
+
+    public void securityCheck(Acl acl, int changeType);
+}

+ 125 - 0
core/src/main/java/org/acegisecurity/acls/domain/AclAuthorizationStrategyImpl.java

@@ -0,0 +1,125 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.AccessDeniedException;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
+import org.acegisecurity.acls.sid.SidRetrievalStrategy;
+import org.acegisecurity.acls.sid.SidRetrievalStrategyImpl;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Default implementation of {@link AclAuthorizationStrategy}.<p>Permission will be granted provided the current
+ * principal is either the owner (as defined by the ACL), has {@link BasePermission#ADMINISTRATION} (as defined by the
+ * ACL and via a {@link Sid} retrieved for the current principal via {@link #sidRetrievalStrategy}), or if the current
+ * principal holds the relevant system-wide {@link GrantedAuthority} and injected into the constructor.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
+    //~ Instance fields ================================================================================================
+
+    private GrantedAuthority gaGeneralChanges;
+    private GrantedAuthority gaModifyAuditing;
+    private GrantedAuthority gaTakeOwnership;
+    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+
+    //~ Constructors ===================================================================================================
+
+/**
+     * Constructor. The only mandatory parameter relates to the system-wide {@link GrantedAuthority} instances that
+     * can be held to always permit ACL changes.
+     * 
+     * @param auths an array of <code>GrantedAuthority</code>s that have
+     * special permissions (index 0 is the authority needed to change
+     * ownership, index 1 is the authority needed to modify auditing details,
+     * index 2 is the authority needed to change other ACL and ACE details) (required)
+     */
+    public AclAuthorizationStrategyImpl(GrantedAuthority[] auths) {
+        Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
+        Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required");
+        this.gaTakeOwnership = auths[0];
+        this.gaModifyAuditing = auths[1];
+        this.gaGeneralChanges = auths[2];
+    }
+
+    //~ Methods ========================================================================================================
+
+    public void securityCheck(Acl acl, int changeType) {
+        if ((SecurityContextHolder.getContext() == null)
+            || (SecurityContextHolder.getContext().getAuthentication() == null)
+            || !SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
+            throw new AccessDeniedException("Authenticated principal required to operate with ACLs");
+        }
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        // Check if authorized by virtue of ACL ownership
+        Sid currentUser = new PrincipalSid(authentication);
+
+        if (currentUser.equals(acl.getOwner()) && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
+            return;
+        }
+
+        // Not authorized by ACL ownership; try via adminstrative permissions
+        GrantedAuthority requiredAuthority = null;
+
+        if (changeType == CHANGE_AUDITING) {
+            requiredAuthority = this.gaModifyAuditing;
+        } else if (changeType == CHANGE_GENERAL) {
+            requiredAuthority = this.gaGeneralChanges;
+        } else if (changeType == CHANGE_OWNERSHIP) {
+            requiredAuthority = this.gaTakeOwnership;
+        } else {
+            throw new IllegalArgumentException("Unknown change type");
+        }
+
+        // Iterate this principal's authorities to determine right
+        GrantedAuthority[] auths = authentication.getAuthorities();
+
+        for (int i = 0; i < auths.length; i++) {
+            if (requiredAuthority.equals(auths[i])) {
+                return;
+            }
+        }
+
+        // Try to get permission via ACEs within the ACL
+        Sid[] sids = sidRetrievalStrategy.getSids(authentication);
+
+        if (acl.isGranted(new Permission[] {BasePermission.ADMINISTRATION}, sids, false)) {
+            return;
+        }
+
+        throw new AccessDeniedException(
+            "Principal does not have required ACL permissions to perform requested operation");
+    }
+
+    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+        Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
+        this.sidRetrievalStrategy = sidRetrievalStrategy;
+    }
+}

+ 55 - 148
sandbox/other/src/main/java/org/acegisecurity/acls/domain/AclImpl.java → core/src/main/java/org/acegisecurity/acls/domain/AclImpl.java

@@ -12,13 +12,8 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package org.acegisecurity.acls.domain;
 package org.acegisecurity.acls.domain;
 
 
-import org.acegisecurity.AccessDeniedException;
-import org.acegisecurity.Authentication;
-import org.acegisecurity.GrantedAuthority;
-
 import org.acegisecurity.acls.AccessControlEntry;
 import org.acegisecurity.acls.AccessControlEntry;
 import org.acegisecurity.acls.Acl;
 import org.acegisecurity.acls.Acl;
 import org.acegisecurity.acls.AuditableAcl;
 import org.acegisecurity.acls.AuditableAcl;
@@ -28,11 +23,8 @@ import org.acegisecurity.acls.OwnershipAcl;
 import org.acegisecurity.acls.Permission;
 import org.acegisecurity.acls.Permission;
 import org.acegisecurity.acls.UnloadedSidException;
 import org.acegisecurity.acls.UnloadedSidException;
 import org.acegisecurity.acls.objectidentity.ObjectIdentity;
 import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.sid.PrincipalSid;
 import org.acegisecurity.acls.sid.Sid;
 import org.acegisecurity.acls.sid.Sid;
 
 
-import org.acegisecurity.context.SecurityContextHolder;
-
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -49,29 +41,17 @@ import java.util.Vector;
  * @version $Id
  * @version $Id
  */
  */
 public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
 public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
-    //~ Static fields/initializers =====================================================================================
-
-    private static final int CHANGE_OWNERSHIP = 0;
-    private static final int CHANGE_AUDITING = 1;
-    private static final int CHANGE_GENERAL = 2;
-
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
     private Acl parentAcl;
     private Acl parentAcl;
-    private AuditLogger auditLogger = new ConsoleAuditLogger(); // AuditableAcl
-    private GrantedAuthority gaGeneralChanges;
-    private GrantedAuthority gaModifyAuditing;
-    private GrantedAuthority gaTakeOwnership;
+    private AclAuthorizationStrategy aclAuthorizationStrategy;
+    private AuditLogger auditLogger;
     private List aces = new Vector();
     private List aces = new Vector();
-    private List deletedAces = new Vector();
-    private Long id;
     private ObjectIdentity objectIdentity;
     private ObjectIdentity objectIdentity;
+    private Serializable id;
     private Sid owner; // OwnershipAcl
     private Sid owner; // OwnershipAcl
     private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
     private Sid[] loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
-    private boolean aclDirty = false; // for snapshot detection
-    private boolean addedAces = false; // for snapshot detection
-    private boolean entriesInheriting = false;
-    private boolean updatedAces = false; // for snapshot detection
+    private boolean entriesInheriting = true;
 
 
     //~ Constructors ===================================================================================================
     //~ Constructors ===================================================================================================
 
 
@@ -81,17 +61,19 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
      *
      *
      * @param objectIdentity the object identity this ACL relates to (required)
      * @param objectIdentity the object identity this ACL relates to (required)
      * @param id the primary key assigned to this ACL (required)
      * @param id the primary key assigned to this ACL (required)
-     * @param auths an array of <code>GrantedAuthority</code>s that have
-     * special permissions (index 0 is the authority needed to change
-     * ownership, index 1 is the authority needed to modify auditing details,
-     * index 2 is the authority needed to change other ACL and ACE details) (required)
+     * @param aclAuthorizationStrategy authorization strategy (required)
+     * @param auditLogger audit logger (required)
      */
      */
-    public AclImpl(ObjectIdentity objectIdentity, Long id, GrantedAuthority[] auths) {
+    public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
+        AuditLogger auditLogger) {
         Assert.notNull(objectIdentity, "Object Identity required");
         Assert.notNull(objectIdentity, "Object Identity required");
         Assert.notNull(id, "Id required");
         Assert.notNull(id, "Id required");
+        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+        Assert.notNull(auditLogger, "AuditLogger required");
         this.objectIdentity = objectIdentity;
         this.objectIdentity = objectIdentity;
         this.id = id;
         this.id = id;
-        this.setAuthorities(auths);
+        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+        this.auditLogger = auditLogger;
     }
     }
 
 
 /**
 /**
@@ -100,10 +82,8 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
      *
      *
      * @param objectIdentity the object identity this ACL relates to (required)
      * @param objectIdentity the object identity this ACL relates to (required)
      * @param id the primary key assigned to this ACL (required)
      * @param id the primary key assigned to this ACL (required)
-     * @param auths an array of <code>GrantedAuthority</code>s that have
-     * special permissions (index 0 is the authority needed to change
-     * ownership, index 1 is the authority needed to modify auditing details,
-     * index 2 is the authority needed to change other ACL and ACE details) (required)
+     * @param aclAuthorizationStrategy authorization strategy (required)
+     * @param auditLogger audit logger (required)
      * @param parentAcl the parent (may be <code>null</code>)
      * @param parentAcl the parent (may be <code>null</code>)
      * @param loadedSids the loaded SIDs if only a subset were loaded (may be
      * @param loadedSids the loaded SIDs if only a subset were loaded (may be
      *        <code>null</code>)
      *        <code>null</code>)
@@ -111,14 +91,17 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
      *        this ACL
      *        this ACL
      * @param owner the owner (required)
      * @param owner the owner (required)
      */
      */
-    public AclImpl(ObjectIdentity objectIdentity, Long id, Acl parentAcl, GrantedAuthority[] auths, Sid[] loadedSids,
-        boolean entriesInheriting, Sid owner) {
+    public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
+        AuditLogger auditLogger, Acl parentAcl, Sid[] loadedSids, boolean entriesInheriting, Sid owner) {
         Assert.notNull(objectIdentity, "Object Identity required");
         Assert.notNull(objectIdentity, "Object Identity required");
         Assert.notNull(id, "Id required");
         Assert.notNull(id, "Id required");
+        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
         Assert.notNull(owner, "Owner required");
         Assert.notNull(owner, "Owner required");
+        Assert.notNull(auditLogger, "AuditLogger required");
         this.objectIdentity = objectIdentity;
         this.objectIdentity = objectIdentity;
         this.id = id;
         this.id = id;
-        setAuthorities(auths);
+        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+        this.auditLogger = auditLogger;
         this.parentAcl = parentAcl; // may be null
         this.parentAcl = parentAcl; // may be null
         this.loadedSids = loadedSids; // may be null
         this.loadedSids = loadedSids; // may be null
         this.entriesInheriting = entriesInheriting;
         this.entriesInheriting = entriesInheriting;
@@ -133,17 +116,8 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
-    /**
-     * Clears the dirty flags on the <code>Acl</code>, but not any associated ACEs.
-     */
-    public void clearDirtyFlags() {
-        this.aclDirty = false;
-        this.addedAces = false;
-        this.updatedAces = false;
-    }
-
-    public void deleteAce(Long aceId) throws NotFoundException {
-        securityCheck(CHANGE_GENERAL);
+    public void deleteAce(Serializable aceId) throws NotFoundException {
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 
 
         synchronized (aces) {
         synchronized (aces) {
             int offset = findAceOffset(aceId);
             int offset = findAceOffset(aceId);
@@ -152,12 +126,11 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
                 throw new NotFoundException("Requested ACE ID not found");
                 throw new NotFoundException("Requested ACE ID not found");
             }
             }
 
 
-            aces.remove(offset);
-            deletedAces.add(aceId);
+            this.aces.remove(offset);
         }
         }
     }
     }
 
 
-    private int findAceOffset(Long aceId) {
+    private int findAceOffset(Serializable aceId) {
         Assert.notNull(aceId, "ACE ID is required");
         Assert.notNull(aceId, "ACE ID is required");
 
 
         synchronized (aces) {
         synchronized (aces) {
@@ -174,7 +147,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
     }
     }
 
 
     public AccessControlEntry[] getEntries() {
     public AccessControlEntry[] getEntries() {
-        // Can safely return AccessControlEntry directly, as they're immutable
+        // Can safely return AccessControlEntry directly, as they're immutable outside the ACL package
         return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
         return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
     }
     }
 
 
@@ -194,9 +167,9 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
         return parentAcl;
         return parentAcl;
     }
     }
 
 
-    public void insertAce(Long afterAceId, Permission permission, Sid sid, boolean granting)
+    public void insertAce(Serializable afterAceId, Permission permission, Sid sid, boolean granting)
         throws NotFoundException {
         throws NotFoundException {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
         Assert.notNull(permission, "Permission required");
         Assert.notNull(permission, "Permission required");
         Assert.notNull(sid, "Sid required");
         Assert.notNull(sid, "Sid required");
 
 
@@ -210,17 +183,11 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
                     throw new NotFoundException("Requested ACE ID not found");
                     throw new NotFoundException("Requested ACE ID not found");
                 }
                 }
 
 
-                aces.add(offset + 1, ace);
+                this.aces.add(offset + 1, ace);
             } else {
             } else {
-                aces.add(ace);
+                this.aces.add(ace);
             }
             }
         }
         }
-
-        this.addedAces = true;
-    }
-
-    public boolean isAclDirty() {
-        return aclDirty;
     }
     }
 
 
     public boolean isEntriesInheriting() {
     public boolean isEntriesInheriting() {
@@ -231,18 +198,19 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
      * Determines authorization.  The order of the <code>permission</code> and <code>sid</code> arguments is
      * Determines authorization.  The order of the <code>permission</code> and <code>sid</code> arguments is
      * <em>extremely important</em>! The method will iterate through each of the <code>permission</code>s in the order
      * <em>extremely important</em>! The method will iterate through each of the <code>permission</code>s in the order
      * specified. For each iteration, all of the <code>sid</code>s will be considered, again in the order they are
      * specified. For each iteration, all of the <code>sid</code>s will be considered, again in the order they are
-     * presented. The iteration of each <code>permission:sid</code> combination will then inspect the ACEs in the
-     * order they appear in the ACL. When the <em>first full match</em> is found (ie an ACE that has the SID currently
-     * being searched for and the exact permission bit mask being search for), the grant or deny flag for that ACE
-     * will prevail. If the ACE specifies to grant access, the method will return <code>true</code>. If the ACE
-     * specifies to deny access, the loop will stop and the next <code>permission</code> iteration will be performed.
-     * If each permission indicates to deny access, the first deny ACE found will be considered the reason for the
-     * failure (as it was the first match found, and is therefore the one most logically requiring changes - although
-     * not always). If absolutely no matching ACE was found at all for any permission, the parent ACL will be tried
-     * (provided that there is a parent and {@link #isEntriesInheriting()} is <code>true</code>. The parent ACL will
-     * also scan its parent and so on. If ultimately no matching ACE is found, a <code>NotFoundException</code> will
-     * be thrown and the caller will need to decide how to handle the permission check. Similarly, if any of the
-     * passed SIDs were not loaded by the ACL, the <code>UnloadedSidException</code> will be thrown.
+     * presented. A search will then be performed for the first {@link AccessControlEntry} object that directly
+     * matches that <code>permission:sid</code> combination. When the <em>first full match</em> is found (ie an ACE
+     * that has the SID currently being searched for and the exact permission bit mask being search for), the grant or
+     * deny flag for that ACE will prevail. If the ACE specifies to grant access, the method will return
+     * <code>true</code>. If the ACE specifies to deny access, the loop will stop and the next <code>permission</code>
+     * iteration will be performed. If each permission indicates to deny access, the first deny ACE found will be
+     * considered the reason for the failure (as it was the first match found, and is therefore the one most logically
+     * requiring changes - although not always). If absolutely no matching ACE was found at all for any permission,
+     * the parent ACL will be tried (provided that there is a parent and {@link #isEntriesInheriting()} is
+     * <code>true</code>. The parent ACL will also scan its parent and so on. If ultimately no matching ACE is found,
+     * a <code>NotFoundException</code> will be thrown and the caller will need to decide how to handle the permission
+     * check. Similarly, if any of the SID arguments presented to the method were not loaded by the ACL,
+     * <code>UnloadedSidException</code> will be thrown.
      *
      *
      * @param permission the exact permissions to scan for (order is important)
      * @param permission the exact permissions to scan for (order is important)
      * @param sids the exact SIDs to scan for (order is important)
      * @param sids the exact SIDs to scan for (order is important)
@@ -335,7 +303,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             return true;
             return true;
         }
         }
 
 
-        // This ACL applies to a SID subset. Iterate to check it applies
+        // This ACL applies to a SID subset only. Iterate to check it applies.
         for (int i = 0; i < sids.length; i++) {
         for (int i = 0; i < sids.length; i++) {
             boolean found = false;
             boolean found = false;
 
 
@@ -356,83 +324,22 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
         return true;
         return true;
     }
     }
 
 
-    protected void securityCheck(int changeType) {
-        if ((SecurityContextHolder.getContext() == null)
-            || (SecurityContextHolder.getContext().getAuthentication() == null)
-            || !SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
-            throw new AccessDeniedException("Authenticated principal required to operate with ACLs");
-        }
-
-        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-
-        // Check if authorized by virtue of ACL ownership
-        Sid currentUser = new PrincipalSid(authentication);
-
-        if (currentUser.equals(this.owner) && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
-            return;
-        }
-
-        // Not authorized by ACL ownership; try via adminstrative permissions
-        GrantedAuthority requiredAuthority = null;
-
-        if (changeType == CHANGE_AUDITING) {
-            requiredAuthority = this.gaModifyAuditing;
-        } else if (changeType == CHANGE_GENERAL) {
-            requiredAuthority = this.gaGeneralChanges;
-        } else if (changeType == CHANGE_OWNERSHIP) {
-            requiredAuthority = this.gaTakeOwnership;
-        } else {
-            throw new IllegalArgumentException("Unknown change type");
-        }
-
-        // Iterate this principal's authorities to determine right
-        GrantedAuthority[] auths = authentication.getAuthorities();
-
-        for (int i = 0; i < auths.length; i++) {
-            if (requiredAuthority.equals(auths[i])) {
-                return;
-            }
-        }
-
-        throw new AccessDeniedException(
-            "Principal does not have required ACL permissions to perform requested operation");
-    }
-
-    /**
-     * Change the special adminstrative permissions honoured by this object.<p>Normally a principal must be the
-     * owner of the ACL in order to make most changes. The authorities passed to this method provide a way for
-     * non-owners to modify the ACL (and indeed modify audit parameters, which are not available to ACL owners).</p>
-     *
-     * @param auths an array of <code>GrantedAuthority</code>s that have administrative permissions (index 0 is the
-     *        authority needed to change ownership, index 1 is the authority needed to modify auditing details, index
-     *        2 is the authority needed to change other ACL and ACE details)
-     */
-    private void setAuthorities(GrantedAuthority[] auths) {
-        Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
-        Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required");
-        this.gaTakeOwnership = auths[0];
-        this.gaModifyAuditing = auths[1];
-        this.gaGeneralChanges = auths[2];
-    }
-
     public void setEntriesInheriting(boolean entriesInheriting) {
     public void setEntriesInheriting(boolean entriesInheriting) {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
         this.entriesInheriting = entriesInheriting;
         this.entriesInheriting = entriesInheriting;
-        this.aclDirty = true;
     }
     }
 
 
     public void setOwner(Sid newOwner) {
     public void setOwner(Sid newOwner) {
-        securityCheck(CHANGE_OWNERSHIP);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
         Assert.notNull(newOwner, "Owner required");
         Assert.notNull(newOwner, "Owner required");
         this.owner = newOwner;
         this.owner = newOwner;
-        this.aclDirty = true;
     }
     }
 
 
     public void setParent(MutableAcl newParent) {
     public void setParent(MutableAcl newParent) {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
         Assert.notNull(newParent, "New Parent required");
         Assert.notNull(newParent, "New Parent required");
+        Assert.isTrue(!newParent.equals(this), "Cannot be the parent of yourself");
         this.parentAcl = newParent;
         this.parentAcl = newParent;
-        this.aclDirty = true;
     }
     }
 
 
     public String toString() {
     public String toString() {
@@ -455,6 +362,10 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             sb.append(iterator.next().toString()).append("\r\n");
             sb.append(iterator.next().toString()).append("\r\n");
         }
         }
 
 
+        if (count == 0) {
+            sb.append("no ACEs; ");
+        }
+
         sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
         sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
         sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
         sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
         sb.append("]");
         sb.append("]");
@@ -462,9 +373,9 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
         return sb.toString();
         return sb.toString();
     }
     }
 
 
-    public void updateAce(Long aceId, Permission permission)
+    public void updateAce(Serializable aceId, Permission permission)
         throws NotFoundException {
         throws NotFoundException {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 
 
         synchronized (aces) {
         synchronized (aces) {
             int offset = findAceOffset(aceId);
             int offset = findAceOffset(aceId);
@@ -476,12 +387,10 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
             ace.setPermission(permission);
             ace.setPermission(permission);
         }
         }
-
-        this.updatedAces = true;
     }
     }
 
 
-    public void updateAuditing(Long aceId, boolean auditSuccess, boolean auditFailure) {
-        securityCheck(CHANGE_AUDITING);
+    public void updateAuditing(Serializable aceId, boolean auditSuccess, boolean auditFailure) {
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING);
 
 
         synchronized (aces) {
         synchronized (aces) {
             int offset = findAceOffset(aceId);
             int offset = findAceOffset(aceId);
@@ -494,7 +403,5 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             ace.setAuditSuccess(auditSuccess);
             ace.setAuditSuccess(auditSuccess);
             ace.setAuditFailure(auditFailure);
             ace.setAuditFailure(auditFailure);
         }
         }
-
-        this.updatedAces = true;
     }
     }
 }
 }

+ 25 - 26
sandbox/other/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java → core/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java

@@ -1,32 +1,31 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.domain;
-
-import org.acegisecurity.acls.AccessControlEntry;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AccessControlEntry;
+
+
 /**
 /**
  * Used by <code>AclImpl</code> to log audit events.
  * Used by <code>AclImpl</code> to log audit events.
  * 
  * 
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
  *
  *
- */
-public interface AuditLogger {
-    //~ Methods ========================================================================================================
-
-    public void logIfNeeded(boolean granted, AccessControlEntry ace);
-}
+ */
+public interface AuditLogger {
+    //~ Methods ========================================================================================================
+
+    public void logIfNeeded(boolean granted, AccessControlEntry ace);
+}

+ 164 - 0
core/src/main/java/org/acegisecurity/acls/domain/BasePermission.java

@@ -0,0 +1,164 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AclFormattingUtils;
+import org.acegisecurity.acls.Permission;
+
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Field;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ * A set of standard permissions.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasePermission implements Permission {
+    //~ Static fields/initializers =====================================================================================
+
+    public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
+    public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
+    public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
+    public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
+    public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16
+    private static Map locallyDeclaredPermissionsByInteger = new HashMap();
+    private static Map locallyDeclaredPermissionsByName = new HashMap();
+
+    static {
+        Field[] fields = BasePermission.class.getDeclaredFields();
+
+        for (int i = 0; i < fields.length; i++) {
+            try {
+                Object fieldValue = fields[i].get(null);
+
+                if (BasePermission.class.isAssignableFrom(fieldValue.getClass())) {
+                    // Found a BasePermission static field
+                    BasePermission perm = (BasePermission) fieldValue;
+                    locallyDeclaredPermissionsByInteger.put(new Integer(perm.getMask()), perm);
+                    locallyDeclaredPermissionsByName.put(fields[i].getName(), perm);
+                }
+            } catch (Exception ignore) {}
+        }
+    }
+
+    //~ Instance fields ================================================================================================
+
+    private char code;
+    private int mask;
+
+    //~ Constructors ===================================================================================================
+
+    private BasePermission(int mask, char code) {
+        this.mask = mask;
+        this.code = code;
+    }
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Dynamically creates a <code>CumulativePermission</code> or <code>BasePermission</code> representing the
+     * active bits in the passed mask.
+     *
+     * @param mask to build
+     *
+     * @return a Permission representing the requested object
+     */
+    public static Permission buildFromMask(int mask) {
+        if (locallyDeclaredPermissionsByInteger.containsKey(new Integer(mask))) {
+            // The requested mask has an exactly match against a statically-defined BasePermission, so return it
+            return (Permission) locallyDeclaredPermissionsByInteger.get(new Integer(mask));
+        }
+
+        // To get this far, we have to use a CumulativePermission
+        CumulativePermission permission = new CumulativePermission();
+
+        for (int i = 0; i < 32; i++) {
+            int permissionToCheck = 1 << i;
+
+            if ((mask & permissionToCheck) == permissionToCheck) {
+                Permission p = (Permission) locallyDeclaredPermissionsByInteger.get(new Integer(permissionToCheck));
+                Assert.state(p != null,
+                    "Mask " + permissionToCheck + " does not have a corresponding static BasePermission");
+                permission.set(p);
+            }
+        }
+
+        return permission;
+    }
+
+    public static Permission[] buildFromMask(int[] masks) {
+        if ((masks == null) || (masks.length == 0)) {
+            return new Permission[] {};
+        }
+
+        List list = new Vector();
+
+        for (int i = 0; i < masks.length; i++) {
+            list.add(BasePermission.buildFromMask(masks[i]));
+        }
+
+        return (Permission[]) list.toArray(new Permission[] {});
+    }
+
+    public static Permission buildFromName(String name) {
+        Assert.isTrue(locallyDeclaredPermissionsByName.containsKey(name), "Unknown permission '" + name + "'");
+
+        return (Permission) locallyDeclaredPermissionsByName.get(name);
+    }
+
+    public static Permission[] buildFromName(String[] names) {
+        if ((names == null) || (names.length == 0)) {
+            return new Permission[] {};
+        }
+
+        List list = new Vector();
+
+        for (int i = 0; i < names.length; i++) {
+            list.add(BasePermission.buildFromName(names[i]));
+        }
+
+        return (Permission[]) list.toArray(new Permission[] {});
+    }
+
+    public boolean equals(Object arg0) {
+        if (!(arg0 instanceof BasePermission)) {
+            return false;
+        }
+
+        BasePermission rhs = (BasePermission) arg0;
+
+        return (this.mask == rhs.getMask());
+    }
+
+    public int getMask() {
+        return mask;
+    }
+
+    public String getPattern() {
+        return AclFormattingUtils.printBinary(mask, code);
+    }
+
+    public String toString() {
+        return "BasePermission[" + getPattern() + "=" + mask + "]";
+    }
+}

+ 45 - 46
sandbox/other/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java → core/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java

@@ -1,46 +1,45 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.domain;
-
-import org.acegisecurity.acls.AccessControlEntry;
-import org.acegisecurity.acls.AuditableAccessControlEntry;
-
-import org.springframework.util.Assert;
-
-
-/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
-  */
-public class ConsoleAuditLogger implements AuditLogger {
-    //~ Methods ========================================================================================================
-
-    public void logIfNeeded(boolean granted, AccessControlEntry ace) {
-        Assert.notNull(ace, "AccessControlEntry required");
-
-        if (ace instanceof AuditableAccessControlEntry) {
-            AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
-
-            if (granted && auditableAce.isAuditSuccess()) {
-                System.out.println("GRANTED due to ACE: " + ace);
-            } else if (!granted && auditableAce.isAuditFailure()) {
-                System.out.println("DENIED due to ACE: " + ace);
-            }
-        }
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.AuditableAccessControlEntry;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * A bsaic implementation of {@link AuditLogger}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ConsoleAuditLogger implements AuditLogger {
+    //~ Methods ========================================================================================================
+
+    public void logIfNeeded(boolean granted, AccessControlEntry ace) {
+        Assert.notNull(ace, "AccessControlEntry required");
+        
+        if (ace instanceof AuditableAccessControlEntry) {
+        	AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
+
+            if (granted && auditableAce.isAuditSuccess()) {
+                System.out.println("GRANTED due to ACE: " + ace);
+            } else if (!granted && auditableAce.isAuditFailure()) {
+                System.out.println("DENIED due to ACE: " + ace);
+            }
+        }
+    }
+}

+ 78 - 79
sandbox/other/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java → core/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java

@@ -1,79 +1,78 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.domain;
-
-import org.acegisecurity.acls.AclFormattingUtils;
-import org.acegisecurity.acls.Permission;
-
-
-/**
- * Represents a <code>Permission</code> that is constructed at runtime from other permissions.<p>Methods return
- * <code>this</code>, in order to facilitate method chaining.</p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class CumulativePermission implements Permission {
-    //~ Instance fields ================================================================================================
-
-    private String pattern = THIRTY_TWO_RESERVED_OFF;
-    private int mask = 0;
-
-    //~ Methods ========================================================================================================
-
-    public CumulativePermission clear(Permission permission) {
-        this.mask &= ~permission.getMask();
-        this.pattern = AclFormattingUtils.demergePatterns(this.pattern, permission.getPattern());
-
-        return this;
-    }
-
-    public CumulativePermission clear() {
-        this.mask = 0;
-        this.pattern = THIRTY_TWO_RESERVED_OFF;
-
-        return this;
-    }
-
-    public boolean equals(Object arg0) {
-        if (!(arg0 instanceof CumulativePermission)) {
-            return false;
-        }
-
-        CumulativePermission rhs = (CumulativePermission) arg0;
-
-        return (this.mask == rhs.getMask());
-    }
-
-    public int getMask() {
-        return this.mask;
-    }
-
-    public String getPattern() {
-        return this.pattern;
-    }
-
-    public CumulativePermission set(Permission permission) {
-        this.mask |= permission.getMask();
-        this.pattern = AclFormattingUtils.mergePatterns(this.pattern, permission.getPattern());
-
-        return this;
-    }
-
-    public String toString() {
-        return "CumulativePermission[" + pattern + "=" + this.mask + "]";
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AclFormattingUtils;
+import org.acegisecurity.acls.Permission;
+
+
+/**
+ * Represents a <code>Permission</code> that is constructed at runtime from other permissions.<p>Methods return
+ * <code>this</code>, in order to facilitate method chaining.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class CumulativePermission implements Permission {
+    //~ Instance fields ================================================================================================
+
+    private String pattern = THIRTY_TWO_RESERVED_OFF;
+    private int mask = 0;
+
+    //~ Methods ========================================================================================================
+
+    public CumulativePermission clear(Permission permission) {
+        this.mask &= ~permission.getMask();
+        this.pattern = AclFormattingUtils.demergePatterns(this.pattern, permission.getPattern());
+
+        return this;
+    }
+
+    public CumulativePermission clear() {
+        this.mask = 0;
+        this.pattern = THIRTY_TWO_RESERVED_OFF;
+
+        return this;
+    }
+
+    public boolean equals(Object arg0) {
+        if (!(arg0 instanceof CumulativePermission)) {
+            return false;
+        }
+
+        CumulativePermission rhs = (CumulativePermission) arg0;
+
+        return (this.mask == rhs.getMask());
+    }
+
+    public int getMask() {
+        return this.mask;
+    }
+
+    public String getPattern() {
+        return this.pattern;
+    }
+
+    public CumulativePermission set(Permission permission) {
+        this.mask |= permission.getMask();
+        this.pattern = AclFormattingUtils.mergePatterns(this.pattern, permission.getPattern());
+
+        return this;
+    }
+
+    public String toString() {
+        return "CumulativePermission[" + pattern + "=" + this.mask + "]";
+    }
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/acls/domain/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Basic implementation of access control lists (ACLs) interfaces.
+</body>
+</html>

+ 36 - 33
sandbox/other/src/main/java/org/acegisecurity/acls/jdbc/AclCache.java → core/src/main/java/org/acegisecurity/acls/jdbc/AclCache.java

@@ -1,39 +1,42 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import org.acegisecurity.acls.domain.AclImpl;
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+
+import java.io.Serializable;
+
+
 /**
 /**
  * A caching layer for {@link JdbcAclService}.
  * A caching layer for {@link JdbcAclService}.
  * 
  * 
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
  *
  *
- */
-public interface AclCache {
-    //~ Methods ========================================================================================================
-
-    public void evictFromCache(Long pk);
-
-    public AclImpl getFromCache(ObjectIdentity objectIdentity);
-
-    public AclImpl getFromCache(Long pk);
-
-    public void putInCache(AclImpl acl); // should walk tree as well!
-}
+ */
+public interface AclCache {
+    //~ Methods ========================================================================================================
+
+    public void evictFromCache(Serializable pk);
+
+    public void evictFromCache(ObjectIdentity objectIdentity);
+
+    public MutableAcl getFromCache(ObjectIdentity objectIdentity);
+
+    public MutableAcl getFromCache(Serializable pk);
+
+    public void putInCache(MutableAcl acl);
+}

+ 518 - 516
sandbox/other/src/main/java/org/acegisecurity/acls/jdbc/BasicLookupStrategy.java → core/src/main/java/org/acegisecurity/acls/jdbc/BasicLookupStrategy.java

@@ -1,521 +1,523 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import org.acegisecurity.GrantedAuthority;
-
-import org.acegisecurity.acls.AccessControlEntry;
-import org.acegisecurity.acls.Acl;
-import org.acegisecurity.acls.NotFoundException;
-import org.acegisecurity.acls.Permission;
-import org.acegisecurity.acls.UnloadedSidException;
-import org.acegisecurity.acls.domain.AccessControlEntryImpl;
-import org.acegisecurity.acls.domain.AclImpl;
-import org.acegisecurity.acls.domain.BasePermission;
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
-import org.acegisecurity.acls.sid.GrantedAuthoritySid;
-import org.acegisecurity.acls.sid.PrincipalSid;
-import org.acegisecurity.acls.sid.Sid;
-
-import org.springframework.dao.DataAccessException;
-
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.PreparedStatementSetter;
-import org.springframework.jdbc.core.ResultSetExtractor;
-
-import org.springframework.util.Assert;
-
-import java.lang.reflect.Field;
-
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.sql.DataSource;
-
-
-/**
- * Performs lookups in a manner that is compatible with ANSI SQL.<p>NB: This implementation does attempt to provide
- * reasonably optimised lookups - within the constraints of a normalised database and standard ANSI SQL features. If
- * you are willing to sacrifice either of these constraints (eg use a particular database feature such as hierarchical
- * queries or materalized views, or reduce normalisation) you are likely to achieve better performance. In such
- * situations you will need to provide your own custom <code>LookupStrategy</code>. This class does not support
- * subclassing, as it is likely to change in future releases and therefore subclassing is unsupported.</p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public final class BasicLookupStrategy implements LookupStrategy {
-    //~ Instance fields ================================================================================================
-
-    private AclCache aclCache;
-    private JdbcTemplate jdbcTemplate;
-    private GrantedAuthority[] auths;
-    private int batchSize = 50;
-
-    //~ Constructors ===================================================================================================
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.UnloadedSidException;
+import org.acegisecurity.acls.domain.AccessControlEntryImpl;
+import org.acegisecurity.acls.domain.AclAuthorizationStrategy;
+import org.acegisecurity.acls.domain.AclImpl;
+import org.acegisecurity.acls.domain.AuditLogger;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.GrantedAuthoritySid;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
+
+import org.acegisecurity.util.FieldUtils;
+
+import org.springframework.dao.DataAccessException;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.PreparedStatementSetter;
+import org.springframework.jdbc.core.ResultSetExtractor;
+
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Field;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+
+/**
+ * Performs lookups in a manner that is compatible with ANSI SQL.<p>NB: This implementation does attempt to provide
+ * reasonably optimised lookups - within the constraints of a normalised database and standard ANSI SQL features. If
+ * you are willing to sacrifice either of these constraints (eg use a particular database feature such as hierarchical
+ * queries or materalized views, or reduce normalisation) you are likely to achieve better performance. In such
+ * situations you will need to provide your own custom <code>LookupStrategy</code>. This class does not support
+ * subclassing, as it is likely to change in future releases and therefore subclassing is unsupported.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public final class BasicLookupStrategy implements LookupStrategy {
+    //~ Instance fields ================================================================================================
+
+    private AclAuthorizationStrategy aclAuthorizationStrategy;
+    private AclCache aclCache;
+    private AuditLogger auditLogger;
+    private JdbcTemplate jdbcTemplate;
+    private int batchSize = 50;
+
+    //~ Constructors ===================================================================================================
+
 /**
 /**
      * Constructor accepting mandatory arguments
      * Constructor accepting mandatory arguments
      *
      *
      * @param dataSource to access the database
      * @param dataSource to access the database
      * @param aclCache the cache where fully-loaded elements can be stored
      * @param aclCache the cache where fully-loaded elements can be stored
-     * @param auths as per the format defined by {@link
-     *        AclImpl#setAuthorities(GrantedAuthority[])} for instances
-     *        created by this implementation
-     */
-    public BasicLookupStrategy(DataSource dataSource, AclCache aclCache, GrantedAuthority[] auths) {
-        Assert.notNull(dataSource, "DataSource required");
-        Assert.notNull(aclCache, "AclCache required");
-        Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
-        Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required");
-        this.jdbcTemplate = new JdbcTemplate(dataSource);
-        this.aclCache = aclCache;
-        this.auths = auths;
-    }
-
-    //~ Methods ========================================================================================================
-
-    private static String computeRepeatingSql(String repeatingSql, int requiredRepetitions) {
-        Assert.isTrue(requiredRepetitions >= 1, "Must be => 1");
-
-        String startSql = "select ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY, ACL_ENTRY.ACE_ORDER, "
-            + "ACL_OBJECT_IDENTITY.ID as ACL_ID, " + "ACL_OBJECT_IDENTITY.PARENT_OBJECT, "
-            + "ACL_OBJECT_IDENTITY,ENTRIES_INHERITING, "
-            + "ACL_ENTRY.ID as ACE_ID, ACL_ENTRY.MASK, ACL_ENTRY.GRANTING, ACL_ENTRY.AUDIT_SUCCESS, ACL_ENTRY.AUDIT_FAILURE, "
-            + "ACE_SID.PRINCIPAL as ACE_PRINCIPAL, ACE_SID.SID as ACE_SID, "
-            + "ACL_SID.PRINCIPAL as ACL_PRINCIPAL, ACL_SID.SID as ACL_SID, " + "ACL_CLASS.CLASS "
-            + "from ACL_OBJECT_IDENTITY, ACL_ENTRY, ACL_SID ACE_SID, ACL_SID ACL_SID, ACL_CLASS "
-            + "where ACL_ENTRY.ACL_OBJECT_IDENTITY = ACL_OBJECT_IDENTITY.ID " + "and ACE_SID.ID = ACL_ENTRY.SID "
-            + "and ACL_SID.ID = ACL_OBJECT_IDENTITY.OWNER_SID "
-            + "and ACL_CLASS.ID = ACL_OBJECT_IDENTITY.OBJECT_ID_CLASS " + "and ( ";
-
-        String endSql = ") order by ACL_ENTRY.ACL_OBJECT_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc";
-
-        StringBuffer sqlStringBuffer = new StringBuffer();
-        sqlStringBuffer.append(startSql);
-
-        for (int i = 1; i <= requiredRepetitions; i++) {
-            sqlStringBuffer.append(repeatingSql);
-
-            if (i != requiredRepetitions) {
-                sqlStringBuffer.append(" or ");
-            }
-        }
-
-        sqlStringBuffer.append(endSql);
-
-        return sqlStringBuffer.toString();
-    }
-
-    /**
-     * The final phase of converting the <code>Map</code> of <code>AclImpl</code> instances which contain
-     * <code>StubAclParent</code>s into proper, valid <code>AclImpl</code>s with correct ACL parents.
-     *
-     * @param inputMap the unconverted <code>AclImpl</code>s
-     * @param currentIdentity the current<code>Acl</code> that we wish to convert (this may be
-     *
-     * @return
-     *
-     * @throws IllegalStateException DOCUMENT ME!
-     */
-    private AclImpl convert(Map inputMap, Long currentIdentity) {
-        Assert.notEmpty(inputMap, "InputMap required");
-        Assert.notNull(currentIdentity, "CurrentIdentity required");
-
-        // Retrieve this Acl from the InputMap
-        Acl uncastAcl = (Acl) inputMap.get(currentIdentity);
-        Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl");
-
-        AclImpl inputAcl = (AclImpl) uncastAcl;
-
-        Acl parent = inputAcl.getParentAcl();
-
-        if ((parent != null) && parent instanceof StubAclParent) {
-            // Lookup the parent
-            StubAclParent stubAclParent = (StubAclParent) parent;
-            parent = convert(inputMap, stubAclParent.getId());
-        }
-
-        // Now we have the parent (if there is one), create the true AclImpl
-        AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), (Long) inputAcl.getId(), parent, auths, null,
-                inputAcl.isEntriesInheriting(), inputAcl.getOwner());
-
-        // Copy the "aces" from the input to the destination
-        Field field = getAccessibleField(AclImpl.class, "aces");
-
-        try {
-            field.set(result, field.get(inputAcl));
-        } catch (IllegalAccessException ex) {
-            throw new IllegalStateException("Could not obtain or set AclImpl.ace field");
-        }
-
-        return result;
-    }
-
-    /**
-     * Accepts the current <code>ResultSet</code> row, and converts it into an <code>AclImpl</code> that
-     * contains a <code>StubAclParent</code>
-     *
-     * @param acls the Map we should add the converted Acl to
-     * @param rs the ResultSet focused on a current row
-     *
-     * @throws SQLException if something goes wrong converting values
-     * @throws IllegalStateException DOCUMENT ME!
-     */
-    private void convertCurrentResultIntoObject(Map acls, ResultSet rs)
-        throws SQLException {
-        Long id = new Long(rs.getLong("ACL_ID"));
-
-        // If we already have an ACL for this ID, just create the ACE
-        AclImpl acl = (AclImpl) acls.get(id);
-
-        if (acl == null) {
-            // Make an AclImpl and pop it into the Map
-            ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("CLASS"),
-                    new Long(rs.getLong("OBJECT_ID_IDENTITY")));
-
-            Acl parentAcl = null;
-            long parentAclId = rs.getLong("PARENT_OBJECT");
-
-            if (parentAclId != 0) {
-                parentAcl = new StubAclParent(new Long(parentAclId));
-            }
-
-            boolean entriesInheriting = rs.getBoolean("ENTRIES_INHERITING");
-            Sid owner;
-
-            if (rs.getBoolean("ACL_PRINCIPAL")) {
-                owner = new PrincipalSid(rs.getString("ACL_SID"));
-            } else {
-                owner = new GrantedAuthoritySid(rs.getString("ACL_SID"));
-            }
-
-            acl = new AclImpl(objectIdentity, id, parentAcl, auths, null, entriesInheriting, owner);
-            acls.put(id, acl);
-        }
-
-        // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order)
-        Long aceId = new Long(rs.getLong("ACE_ID"));
-        Sid recipient;
-
-        if (rs.getBoolean("ACE_PRINCIPAL")) {
-            recipient = new PrincipalSid(rs.getString("ACE_SID"));
-        } else {
-            recipient = new GrantedAuthoritySid(rs.getString("ACE_SID"));
-        }
-
-        Permission permission = BasePermission.buildFromMask(rs.getInt("MASK"));
-        boolean granting = rs.getBoolean("GRANTING");
-        boolean auditSuccess = rs.getBoolean("AUDIT_SUCCESS");
-        boolean auditFailure = rs.getBoolean("AUDIT_FAILURE");
-
-        AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting,
-                auditSuccess, auditFailure);
-
-        Field acesField = getAccessibleField(AclImpl.class, "aces");
-        List aces;
-
-        try {
-            aces = (List) acesField.get(acl);
-        } catch (IllegalAccessException ex) {
-            throw new IllegalStateException("Could not obtain AclImpl.ace field: cause[" + ex.getMessage() + "]");
-
-        }
-
-        // Add the ACE if it doesn't already exist in the ACL.aces field
-        if (!aces.contains(ace)) {
-            aces.add(ace);
-        }
-    }
-
-    private static Field getAccessibleField(Class clazz, String protectedField) {
-        Field field = null;
-
-        try {
-            field = clazz.getDeclaredField(protectedField);
-        } catch (NoSuchFieldException nsf) {}
-
-        if (field == null) {
-            // Unable to locate, so try the superclass (if there is one)
-            if (clazz.getSuperclass() != null) {
-                field = getAccessibleField(clazz.getSuperclass(), protectedField);
-            } else {
-                throw new IllegalArgumentException("Couldn't find '" + protectedField + "' field");
-            }
-        }
-
-        // We have a field, so process
-        field.setAccessible(true);
-
-        return field;
-    }
-
-    /**
-     * Looks up a batch of <code>ObjectIdentity</code>s directly from the database.<p>The caller is responsible
-     * for optimization issues, such as selecting the identities to lookup, ensuring the cache doesn't contain them
-     * already, and adding the returned elements to the cache etc.</p>
-     *  <p>This subclass is required to return fully valid <code>Acl</code>s, including properly-configured
-     * parent ACLs.</p>
-     *
-     * @param objectIdentities DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     */
-    private Map lookupObjectIdentities(final ObjectIdentity[] objectIdentities) {
-        Assert.notEmpty(objectIdentities, "Must provide identities to lookup");
-
-        final Map acls = new HashMap(); // contains Acls with StubAclParents
-
-        // Make the "acls" map contain all requested objectIdentities
-        // (including markers to each parent in the hierarchy)
-        String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = ? and ACL_CLASS.CLASS = ?)",
-                objectIdentities.length);
-        System.out.println("Executing lookupObjectIdentities; length: " + objectIdentities.length);
-        jdbcTemplate.query(sql,
-            new PreparedStatementSetter() {
-                public void setValues(PreparedStatement ps)
-                    throws SQLException {
-                    for (int i = 0; i < objectIdentities.length; i++) {
-                        // Determine prepared statement values for this iteration
-                        String javaType = objectIdentities[i].getJavaType().getName();
-                        Assert.isInstanceOf(Long.class, objectIdentities[i].getIdentifier(),
-                            "This class requires ObjectIdentity.getIdentifier() to be a Long");
-
-                        long id = ((Long) objectIdentities[i].getIdentifier()).longValue();
-
-                        // Inject values
-                        ps.setLong((2 * i) + 1, id);
-                        ps.setString((2 * i) + 2, javaType);
-                    }
-                }
-            }, new ProcessResultSet(acls));
-
-        // Finally, convert our "acls" containing StubAclParents into true Acls
-        Map resultMap = new HashMap();
-        Iterator iter = acls.values().iterator();
-
-        while (iter.hasNext()) {
-            Acl inputAcl = (Acl) iter.next();
-            Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl");
-            Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(), "Acl.getId() must be Long");
-
-            Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId());
-            resultMap.put(result.getObjectIdentity(), result);
-        }
-
-        return resultMap;
-    }
-
-    /**
-     * Locates the primary key IDs specified in "findNow", adding AclImpl instances with StubAclParents to the
-     * "acls" Map.
-     *
-     * @param acls the AclImpls (with StubAclParents)
-     * @param findNow Long-based primary keys to retrieve
-     */
-    private void lookupPrimaryKeys(final Map acls, final Set findNow) {
-        Assert.notNull(acls, "ACLs are required");
-        Assert.notEmpty(findNow, "Items to find now required");
-
-        String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.ID = ?)", findNow.size());
-        System.out.println("Executing lookupPrimaryKeys; length: " + findNow.size());
-
-        jdbcTemplate.query(sql,
-            new PreparedStatementSetter() {
-                public void setValues(PreparedStatement ps)
-                    throws SQLException {
-                    Iterator iter = findNow.iterator();
-                    int i = 0;
-
-                    while (iter.hasNext()) {
-                        i++;
-                        ps.setLong(i, ((Long) iter.next()).longValue());
-                    }
-                }
-            }, new ProcessResultSet(acls));
-    }
-
-    /**
-     * The main method.<p>WARNING: This implementation completely disregards the "sids" argument! Every item in
-     * the cache is expected to contain all SIDs.</p>
-     *  <p>The implementation works in batch sizes specfied by {@link #batchSize}.</p>
-     *
-     * @param objects DOCUMENT ME!
-     * @param sids DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     *
-     * @throws NotFoundException DOCUMENT ME!
-     * @throws IllegalStateException DOCUMENT ME!
-     */
-    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
-        throws NotFoundException {
-        Assert.isTrue(batchSize >= 1, "BatchSize must be >= 1");
-        Assert.notEmpty(objects, "Objects to lookup required");
-
-        Map result = new HashMap(); // contains FULLY loaded Acl objects
-
-        Set currentBatchToLoad = new HashSet(); // contains ObjectIdentitys
-
-        for (int i = 0; i < objects.length; i++) {
-            // Check we don't already have this ACL in the results
-            if (result.containsKey(objects[i])) {
-                continue; // already in results, so move to next element
-            }
-
-            // Check cache for the present ACL entry
-            Acl acl = aclCache.getFromCache(objects[i]);
-
-            // Ensure any cached element supports all the requested SIDs
-            // (they should always, as our base impl doesn't filter on SID)
-            if (acl != null) {
-                if (acl.isSidLoaded(sids)) {
-                    result.put(acl.getObjectIdentity(), acl);
-
-                    continue; // now in results, so move to next element
-                } else {
-                    throw new IllegalStateException(
-                        "Error: SID-filtered element detected when implementation does not perform SID filtering - have you added something to the cache manually?");
-                }
-            }
-
-            // To get this far, we have no choice but to retrieve it via JDBC
-            // (although we don't do it until we get a batch of them to load)
-            currentBatchToLoad.add(objects[i]);
-
-            // Is it time to load from JDBC the currentBatchToLoad?
-            if ((currentBatchToLoad.size() == this.batchSize) || ((i + 1) == objects.length)) {
-                Map loadedBatch = lookupObjectIdentities((ObjectIdentity[]) currentBatchToLoad.toArray(
-                            new ObjectIdentity[] {}));
-
-                // Add loaded batch (all elements 100% initialized) to results
-                result.putAll(loadedBatch);
-
-                // Add the loaded batch to the cache
-                Iterator loadedAclIterator = loadedBatch.values().iterator();
-
-                while (loadedAclIterator.hasNext()) {
-                    aclCache.putInCache((AclImpl) loadedAclIterator.next());
-                }
-
-                currentBatchToLoad.clear();
-            }
-        }
-
-        // TODO: Now we're done, check every requested object identity was found (throw NotFoundException if needed)
-        return result;
-    }
-
-    public void setBatchSize(int batchSize) {
-        this.batchSize = batchSize;
-    }
-
-    //~ Inner Classes ==================================================================================================
-
-    private class ProcessResultSet implements ResultSetExtractor {
-        private Map acls;
-
-        public ProcessResultSet(Map acls) {
-            Assert.notNull(acls, "ACLs cannot be null");
-            this.acls = acls;
-        }
-
-        public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
-            Set parentIdsToLookup = new HashSet(); // Set of parent_id Longs
-
-            while (rs.next()) {
-                // Convert current row into an Acl (albeit with a StubAclParent)
-                convertCurrentResultIntoObject(acls, rs);
-
-                // Figure out if this row means we need to lookup another parent
-                long parentId = rs.getLong("PARENT_OBJECT");
-
-                if (parentId != 0) {
-                    // See if its already in the "acls"
-                    if (acls.containsKey(new Long(parentId))) {
-                        continue; // skip this while element
-                    }
-
-                    // Now try to find it in the cache
-                    Acl cached = aclCache.getFromCache(new Long(parentId));
-
-                    if (cached == null) {
-                        parentIdsToLookup.add(new Long(parentId));
-                    } else {
-                        // Pop into the acls map, so our convert method doesn't
-                        // need to deal with an unsynchronized AclCache
-                        Assert.isInstanceOf(AclImpl.class, cached, "Cached ACL must be an AclImpl");
-                        acls.put(((AclImpl) cached).getId(), cached);
-                    }
-                }
-            }
-
-            // Lookup parents, adding Acls (with StubAclParents) to "acl" map
-            if (parentIdsToLookup.size() > 0) {
-                lookupPrimaryKeys(acls, parentIdsToLookup);
-            }
-
-            // Return null to meet ResultSetExtractor method contract
-            return null;
-        }
-    }
-
-    private class StubAclParent implements Acl {
-        private Long id;
-
-        public StubAclParent(Long id) {
-            this.id = id;
-        }
-
-        public AccessControlEntry[] getEntries() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public Long getId() {
-            return id;
-        }
-
-        public ObjectIdentity getObjectIdentity() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public Acl getParentAcl() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public boolean isEntriesInheriting() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
-            throws NotFoundException, UnloadedSidException {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public boolean isSidLoaded(Sid[] sids) {
-            throw new UnsupportedOperationException("Stub only");
-        }
-    }
-}
+     * @param aclAuthorizationStrategy authorization strategy (required)
+     */
+    public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
+        AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) {
+        Assert.notNull(dataSource, "DataSource required");
+        Assert.notNull(aclCache, "AclCache required");
+        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+        Assert.notNull(auditLogger, "AuditLogger required");
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.aclCache = aclCache;
+        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+        this.auditLogger = auditLogger;
+    }
+
+    //~ Methods ========================================================================================================
+
+    private static String computeRepeatingSql(String repeatingSql, int requiredRepetitions) {
+        Assert.isTrue(requiredRepetitions >= 1, "Must be => 1");
+
+        String startSql = "select ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY, ACL_ENTRY.ACE_ORDER, "
+            + "ACL_OBJECT_IDENTITY.ID as ACL_ID, " + "ACL_OBJECT_IDENTITY.PARENT_OBJECT, "
+            + "ACL_OBJECT_IDENTITY,ENTRIES_INHERITING, "
+            + "ACL_ENTRY.ID as ACE_ID, ACL_ENTRY.MASK, ACL_ENTRY.GRANTING, ACL_ENTRY.AUDIT_SUCCESS, ACL_ENTRY.AUDIT_FAILURE, "
+            + "ACL_SID.PRINCIPAL as ACE_PRINCIPAL, ACL_SID.SID as ACE_SID, "
+            + "ACLI_SID.PRINCIPAL as ACL_PRINCIPAL, ACLI_SID.SID as ACL_SID, " + "ACL_CLASS.CLASS "
+            + "from ACL_OBJECT_IDENTITY, ACL_SID ACLI_SID, ACL_CLASS "
+            + "LEFT JOIN ACL_ENTRY ON ACL_OBJECT_IDENTITY.ID = ACL_ENTRY.ACL_OBJECT_IDENTITY "
+            + "LEFT JOIN ACL_SID ON ACL_ENTRY.SID = ACL_SID.ID where ACLI_SID.ID = ACL_OBJECT_IDENTITY.OWNER_SID "
+            + "and ACL_CLASS.ID = ACL_OBJECT_IDENTITY.OBJECT_ID_CLASS " + "and ( ";
+
+        String endSql = ") order by ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc";
+
+        StringBuffer sqlStringBuffer = new StringBuffer();
+        sqlStringBuffer.append(startSql);
+
+        for (int i = 1; i <= requiredRepetitions; i++) {
+            sqlStringBuffer.append(repeatingSql);
+
+            if (i != requiredRepetitions) {
+                sqlStringBuffer.append(" or ");
+            }
+        }
+
+        sqlStringBuffer.append(endSql);
+
+        return sqlStringBuffer.toString();
+    }
+
+    /**
+     * The final phase of converting the <code>Map</code> of <code>AclImpl</code> instances which contain
+     * <code>StubAclParent</code>s into proper, valid <code>AclImpl</code>s with correct ACL parents.
+     *
+     * @param inputMap the unconverted <code>AclImpl</code>s
+     * @param currentIdentity the current<code>Acl</code> that we wish to convert (this may be
+     *
+     * @return
+     *
+     * @throws IllegalStateException DOCUMENT ME!
+     */
+    private AclImpl convert(Map inputMap, Long currentIdentity) {
+        Assert.notEmpty(inputMap, "InputMap required");
+        Assert.notNull(currentIdentity, "CurrentIdentity required");
+
+        // Retrieve this Acl from the InputMap
+        Acl uncastAcl = (Acl) inputMap.get(currentIdentity);
+        Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl");
+
+        AclImpl inputAcl = (AclImpl) uncastAcl;
+
+        Acl parent = inputAcl.getParentAcl();
+
+        if ((parent != null) && parent instanceof StubAclParent) {
+            // Lookup the parent
+            StubAclParent stubAclParent = (StubAclParent) parent;
+            parent = convert(inputMap, stubAclParent.getId());
+        }
+
+        // Now we have the parent (if there is one), create the true AclImpl
+        AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), (Long) inputAcl.getId(), aclAuthorizationStrategy,
+                auditLogger, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner());
+
+        // Copy the "aces" from the input to the destination
+        Field field = FieldUtils.getField(AclImpl.class, "aces");
+
+        try {
+            field.setAccessible(true);
+            field.set(result, field.get(inputAcl));
+        } catch (IllegalAccessException ex) {
+            throw new IllegalStateException("Could not obtain or set AclImpl.ace field");
+        }
+
+        return result;
+    }
+
+    /**
+     * Accepts the current <code>ResultSet</code> row, and converts it into an <code>AclImpl</code> that
+     * contains a <code>StubAclParent</code>
+     *
+     * @param acls the Map we should add the converted Acl to
+     * @param rs the ResultSet focused on a current row
+     *
+     * @throws SQLException if something goes wrong converting values
+     * @throws IllegalStateException DOCUMENT ME!
+     */
+    private void convertCurrentResultIntoObject(Map acls, ResultSet rs)
+        throws SQLException {
+        Long id = new Long(rs.getLong("ACL_ID"));
+
+        // If we already have an ACL for this ID, just create the ACE
+        AclImpl acl = (AclImpl) acls.get(id);
+
+        if (acl == null) {
+            // Make an AclImpl and pop it into the Map
+            ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("CLASS"),
+                    new Long(rs.getLong("OBJECT_ID_IDENTITY")));
+
+            Acl parentAcl = null;
+            long parentAclId = rs.getLong("PARENT_OBJECT");
+
+            if (parentAclId != 0) {
+                parentAcl = new StubAclParent(new Long(parentAclId));
+            }
+
+            boolean entriesInheriting = rs.getBoolean("ENTRIES_INHERITING");
+            Sid owner;
+
+            if (rs.getBoolean("ACL_PRINCIPAL")) {
+                owner = new PrincipalSid(rs.getString("ACL_SID"));
+            } else {
+                owner = new GrantedAuthoritySid(rs.getString("ACL_SID"));
+            }
+
+            acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy, auditLogger, parentAcl, null,
+                    entriesInheriting, owner);
+            acls.put(id, acl);
+        }
+
+        // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order)
+        // It is permissable to have no ACEs in an ACL (which is detected by a null ACE_SID)
+        if (rs.getString("ACE_SID") != null) {
+            Long aceId = new Long(rs.getLong("ACE_ID"));
+            Sid recipient;
+
+            if (rs.getBoolean("ACE_PRINCIPAL")) {
+                recipient = new PrincipalSid(rs.getString("ACE_SID"));
+            } else {
+                recipient = new GrantedAuthoritySid(rs.getString("ACE_SID"));
+            }
+
+            Permission permission = BasePermission.buildFromMask(rs.getInt("MASK"));
+            boolean granting = rs.getBoolean("GRANTING");
+            boolean auditSuccess = rs.getBoolean("AUDIT_SUCCESS");
+            boolean auditFailure = rs.getBoolean("AUDIT_FAILURE");
+
+            AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting,
+                    auditSuccess, auditFailure);
+
+            Field acesField = FieldUtils.getField(AclImpl.class, "aces");
+            List aces;
+
+            try {
+                acesField.setAccessible(true);
+                aces = (List) acesField.get(acl);
+            } catch (IllegalAccessException ex) {
+                throw new IllegalStateException("Could not obtain AclImpl.ace field: cause[" + ex.getMessage() + "]");
+            }
+
+            // Add the ACE if it doesn't already exist in the ACL.aces field
+            if (!aces.contains(ace)) {
+                aces.add(ace);
+            }
+        }
+    }
+
+    /**
+     * Looks up a batch of <code>ObjectIdentity</code>s directly from the database.<p>The caller is responsible
+     * for optimization issues, such as selecting the identities to lookup, ensuring the cache doesn't contain them
+     * already, and adding the returned elements to the cache etc.</p>
+     *  <p>This subclass is required to return fully valid <code>Acl</code>s, including properly-configured
+     * parent ACLs.</p>
+     *
+     * @param objectIdentities DOCUMENT ME!
+     * @param sids DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     */
+    private Map lookupObjectIdentities(final ObjectIdentity[] objectIdentities, Sid[] sids) {
+        Assert.notEmpty(objectIdentities, "Must provide identities to lookup");
+
+        final Map acls = new HashMap(); // contains Acls with StubAclParents
+
+        // Make the "acls" map contain all requested objectIdentities
+        // (including markers to each parent in the hierarchy)
+        String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = ? and ACL_CLASS.CLASS = ?)",
+                objectIdentities.length);
+
+        jdbcTemplate.query(sql,
+            new PreparedStatementSetter() {
+                public void setValues(PreparedStatement ps)
+                    throws SQLException {
+                    for (int i = 0; i < objectIdentities.length; i++) {
+                        // Determine prepared statement values for this iteration
+                        String javaType = objectIdentities[i].getJavaType().getName();
+                        Assert.isInstanceOf(Long.class, objectIdentities[i].getIdentifier(),
+                            "This class requires ObjectIdentity.getIdentifier() to be a Long");
+
+                        long id = ((Long) objectIdentities[i].getIdentifier()).longValue();
+
+                        // Inject values
+                        ps.setLong((2 * i) + 1, id);
+                        ps.setString((2 * i) + 2, javaType);
+                    }
+                }
+            }, new ProcessResultSet(acls, sids));
+
+        // Finally, convert our "acls" containing StubAclParents into true Acls
+        Map resultMap = new HashMap();
+        Iterator iter = acls.values().iterator();
+
+        while (iter.hasNext()) {
+            Acl inputAcl = (Acl) iter.next();
+            Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl");
+            Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(), "Acl.getId() must be Long");
+
+            Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId());
+            resultMap.put(result.getObjectIdentity(), result);
+        }
+
+        return resultMap;
+    }
+
+    /**
+     * Locates the primary key IDs specified in "findNow", adding AclImpl instances with StubAclParents to the
+     * "acls" Map.
+     *
+     * @param acls the AclImpls (with StubAclParents)
+     * @param findNow Long-based primary keys to retrieve
+     * @param sids DOCUMENT ME!
+     */
+    private void lookupPrimaryKeys(final Map acls, final Set findNow, final Sid[] sids) {
+        Assert.notNull(acls, "ACLs are required");
+        Assert.notEmpty(findNow, "Items to find now required");
+
+        String sql = computeRepeatingSql("(ACL_OBJECT_IDENTITY.ID = ?)", findNow.size());
+
+        jdbcTemplate.query(sql,
+            new PreparedStatementSetter() {
+                public void setValues(PreparedStatement ps)
+                    throws SQLException {
+                    Iterator iter = findNow.iterator();
+                    int i = 0;
+
+                    while (iter.hasNext()) {
+                        i++;
+                        ps.setLong(i, ((Long) iter.next()).longValue());
+                    }
+                }
+            }, new ProcessResultSet(acls, sids));
+    }
+
+    /**
+     * The main method.<p>WARNING: This implementation completely disregards the "sids" parameter! Every item
+     * in the cache is expected to contain all SIDs. If you have serious performance needs (eg a very large number of
+     * SIDs per object identity), you'll probably want to develop a custom {@link LookupStrategy} implementation
+     * instead.</p>
+     *  <p>The implementation works in batch sizes specfied by {@link #batchSize}.</p>
+     *
+     * @param objects DOCUMENT ME!
+     * @param sids DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     * @throws IllegalStateException DOCUMENT ME!
+     */
+    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
+        throws NotFoundException {
+        Assert.isTrue(batchSize >= 1, "BatchSize must be >= 1");
+        Assert.notEmpty(objects, "Objects to lookup required");
+
+        // Map<ObjectIdentity,Acl>
+        Map result = new HashMap(); // contains FULLY loaded Acl objects
+
+        Set currentBatchToLoad = new HashSet(); // contains ObjectIdentitys
+
+        for (int i = 0; i < objects.length; i++) {
+            // Check we don't already have this ACL in the results
+            if (result.containsKey(objects[i])) {
+                continue; // already in results, so move to next element
+            }
+
+            // Check cache for the present ACL entry
+            Acl acl = aclCache.getFromCache(objects[i]);
+
+            // Ensure any cached element supports all the requested SIDs
+            // (they should always, as our base impl doesn't filter on SID)
+            if (acl != null) {
+                if (acl.isSidLoaded(sids)) {
+                    result.put(acl.getObjectIdentity(), acl);
+
+                    continue; // now in results, so move to next element
+                } else {
+                    throw new IllegalStateException(
+                        "Error: SID-filtered element detected when implementation does not perform SID filtering - have you added something to the cache manually?");
+                }
+            }
+
+            // To get this far, we have no choice but to retrieve it via JDBC
+            // (although we don't do it until we get a batch of them to load)
+            currentBatchToLoad.add(objects[i]);
+
+            // Is it time to load from JDBC the currentBatchToLoad?
+            if ((currentBatchToLoad.size() == this.batchSize) || ((i + 1) == objects.length)) {
+                Map loadedBatch = lookupObjectIdentities((ObjectIdentity[]) currentBatchToLoad.toArray(
+                            new ObjectIdentity[] {}), sids);
+
+                // Add loaded batch (all elements 100% initialized) to results
+                result.putAll(loadedBatch);
+
+                // Add the loaded batch to the cache
+                Iterator loadedAclIterator = loadedBatch.values().iterator();
+
+                while (loadedAclIterator.hasNext()) {
+                    aclCache.putInCache((AclImpl) loadedAclIterator.next());
+                }
+
+                currentBatchToLoad.clear();
+            }
+        }
+
+        // Now we're done, check every requested object identity was found (throw NotFoundException if needed)
+        for (int i = 0; i < objects.length; i++) {
+            if (!result.containsKey(objects[i])) {
+                throw new NotFoundException("Unable to find ACL information for object identity '"
+                    + objects[i].toString() + "'");
+            }
+        }
+
+        return result;
+    }
+
+    public void setBatchSize(int batchSize) {
+        this.batchSize = batchSize;
+    }
+
+    //~ Inner Classes ==================================================================================================
+
+    private class ProcessResultSet implements ResultSetExtractor {
+        private Map acls;
+        private Sid[] sids;
+
+        public ProcessResultSet(Map acls, Sid[] sids) {
+            Assert.notNull(acls, "ACLs cannot be null");
+            this.acls = acls;
+            this.sids = sids; // can be null
+        }
+
+        public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
+            Set parentIdsToLookup = new HashSet(); // Set of parent_id Longs
+
+            while (rs.next()) {
+                // Convert current row into an Acl (albeit with a StubAclParent)
+                convertCurrentResultIntoObject(acls, rs);
+
+                // Figure out if this row means we need to lookup another parent
+                long parentId = rs.getLong("PARENT_OBJECT");
+
+                if (parentId != 0) {
+                    // See if it's already in the "acls"
+                    if (acls.containsKey(new Long(parentId))) {
+                        continue; // skip this while iteration
+                    }
+
+                    // Now try to find it in the cache
+                    MutableAcl cached = aclCache.getFromCache(new Long(parentId));
+
+                    if ((cached == null) || !cached.isSidLoaded(sids)) {
+                        parentIdsToLookup.add(new Long(parentId));
+                    } else {
+                        // Pop into the acls map, so our convert method doesn't
+                        // need to deal with an unsynchronized AclCache
+                        acls.put(cached.getId(), cached);
+                    }
+                }
+            }
+
+            // Lookup parents, adding Acls (with StubAclParents) to "acl" map
+            if (parentIdsToLookup.size() > 0) {
+                lookupPrimaryKeys(acls, parentIdsToLookup, sids);
+            }
+
+            // Return null to meet ResultSetExtractor method contract
+            return null;
+        }
+    }
+
+    private class StubAclParent implements Acl {
+        private Long id;
+
+        public StubAclParent(Long id) {
+            this.id = id;
+        }
+
+        public AccessControlEntry[] getEntries() {
+            throw new UnsupportedOperationException("Stub only");
+        }
+
+        public Long getId() {
+            return id;
+        }
+
+        public ObjectIdentity getObjectIdentity() {
+            throw new UnsupportedOperationException("Stub only");
+        }
+
+        public Sid getOwner() {
+            throw new UnsupportedOperationException("Stub only");
+        }
+
+        public Acl getParentAcl() {
+            throw new UnsupportedOperationException("Stub only");
+        }
+
+        public boolean isEntriesInheriting() {
+            throw new UnsupportedOperationException("Stub only");
+        }
+
+        public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
+            throws NotFoundException, UnloadedSidException {
+            throw new UnsupportedOperationException("Stub only");
+        }
+
+        public boolean isSidLoaded(Sid[] sids) {
+            throw new UnsupportedOperationException("Stub only");
+        }
+    }
+}

+ 115 - 93
sandbox/other/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java → core/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java

@@ -1,93 +1,115 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import net.sf.ehcache.Cache;
-import net.sf.ehcache.CacheException;
-import net.sf.ehcache.Element;
-
-import org.acegisecurity.acls.domain.AclImpl;
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-
-import org.springframework.util.Assert;
-
-
-/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
-  */
-public class EhCacheBasedAclCache implements AclCache {
-    //~ Instance fields ================================================================================================
-
-    private Cache cache;
-
-    //~ Constructors ===================================================================================================
-
-    public EhCacheBasedAclCache(Cache cache) {
-        Assert.notNull(cache, "Cache required");
-        this.cache = cache;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public void evictFromCache(Long pk) {
-        AclImpl acl = getFromCache(pk);
-
-        if (acl != null) {
-            cache.remove(pk);
-            cache.remove(acl.getObjectIdentity());
-        }
-    }
-
-    public AclImpl getFromCache(ObjectIdentity objectIdentity) {
-        Element element = null;
-
-        try {
-            element = cache.get(objectIdentity);
-        } catch (CacheException ignored) {}
-
-        if (element == null) {
-            return null;
-        }
-
-        return (AclImpl) element.getValue();
-    }
-
-    public AclImpl getFromCache(Long pk) {
-        Element element = null;
-
-        try {
-            element = cache.get(pk);
-        } catch (CacheException ignored) {}
-
-        if (element == null) {
-            return null;
-        }
-
-        return (AclImpl) element.getValue();
-    }
-
-    public void putInCache(AclImpl acl) {
-        if ((acl.getParentAcl() != null) && acl.getParentAcl() instanceof AclImpl) {
-            putInCache((AclImpl) acl.getParentAcl());
-        }
-
-        cache.put(new Element(acl.getObjectIdentity(), acl));
-        cache.put(new Element(acl.getId(), acl));
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.Element;
+
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+
+
+/**
+ * Simple implementation of {@link AclCache} that delegates to EH-CACHE.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EhCacheBasedAclCache implements AclCache {
+    //~ Instance fields ================================================================================================
+
+    private Cache cache;
+
+    //~ Constructors ===================================================================================================
+
+    public EhCacheBasedAclCache(Cache cache) {
+        Assert.notNull(cache, "Cache required");
+        this.cache = cache;
+    }
+
+    //~ Methods ========================================================================================================
+
+    public void evictFromCache(Serializable pk) {
+        Assert.notNull(pk, "Primary key (identifier) required");
+
+        MutableAcl acl = getFromCache(pk);
+
+        if (acl != null) {
+            cache.remove(acl.getId());
+            cache.remove(acl.getObjectIdentity());
+        }
+    }
+
+    public void evictFromCache(ObjectIdentity objectIdentity) {
+        Assert.notNull(objectIdentity, "ObjectIdentity required");
+
+        MutableAcl acl = getFromCache(objectIdentity);
+
+        if (acl != null) {
+            cache.remove(acl.getId());
+            cache.remove(acl.getObjectIdentity());
+        }
+    }
+
+    public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
+        Assert.notNull(objectIdentity, "ObjectIdentity required");
+
+        Element element = null;
+
+        try {
+            element = cache.get(objectIdentity);
+        } catch (CacheException ignored) {}
+
+        if (element == null) {
+            return null;
+        }
+
+        return (MutableAcl) element.getValue();
+    }
+
+    public MutableAcl getFromCache(Serializable pk) {
+        Assert.notNull(pk, "Primary key (identifier) required");
+
+        Element element = null;
+
+        try {
+            element = cache.get(pk);
+        } catch (CacheException ignored) {}
+
+        if (element == null) {
+            return null;
+        }
+
+        return (MutableAcl) element.getValue();
+    }
+
+    public void putInCache(MutableAcl acl) {
+        Assert.notNull(acl, "Acl required");
+        Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
+        Assert.notNull(acl.getId(), "ID required");
+
+        if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
+            putInCache((MutableAcl) acl.getParentAcl());
+        }
+
+        cache.put(new Element(acl.getObjectIdentity(), acl));
+        cache.put(new Element(acl.getId(), acl));
+    }
+}

+ 114 - 0
core/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java

@@ -0,0 +1,114 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.Sid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+import org.springframework.util.Assert;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+
+/**
+ * Simple JDBC-based implementation of <code>AclService</code>.<p>Requires the "dirty" flags in {@link
+ * org.acegisecurity.acls.domain.AclImpl} and {@link org.acegisecurity.acls.domain.AccessControlEntryImpl} to be set,
+ * so that the implementation can detect changed parameters easily.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class JdbcAclService implements AclService {
+    //~ Static fields/initializers =====================================================================================
+
+    protected static final Log log = LogFactory.getLog(JdbcAclService.class);
+    private static final String selectAclObjectWithParent = "SELECT obj.object_id_identity obj_id, class.class class "
+        + "FROM acl_object_identity obj, acl_object_identity parent, acl_class class "
+        + "WHERE obj.parent_object = parent.id AND obj.object_id_class = class.id "
+        + "AND parent.object_id_identity = ? AND parent.object_id_class = ("
+        + "SELECT id FROM acl_class WHERE acl_class.class = ?)";
+
+    //~ Instance fields ================================================================================================
+
+    protected JdbcTemplate jdbcTemplate;
+    private LookupStrategy lookupStrategy;
+
+    //~ Constructors ===================================================================================================
+
+    public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
+        Assert.notNull(dataSource, "DataSource required");
+        Assert.notNull(lookupStrategy, "LookupStrategy required");
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.lookupStrategy = lookupStrategy;
+    }
+
+    //~ Methods ========================================================================================================
+
+    public ObjectIdentity[] findChildren(ObjectIdentity parentIdentity) {
+        Object[] args = {parentIdentity.getIdentifier(), parentIdentity.getJavaType().getName()};
+        List objects = jdbcTemplate.query(selectAclObjectWithParent, args,
+                new RowMapper() {
+                    public Object mapRow(ResultSet rs, int rowNum)
+                        throws SQLException {
+                        String javaType = rs.getString("class");
+                        String identifier = rs.getString("obj_id");
+
+                        return new ObjectIdentityImpl(javaType, identifier);
+                    }
+                });
+
+        return (ObjectIdentityImpl[]) objects.toArray(new ObjectIdentityImpl[] {});
+    }
+
+    public Acl readAclById(ObjectIdentity object, Sid[] sids)
+        throws NotFoundException {
+        Map map = readAclsById(new ObjectIdentity[] {object}, sids);
+
+        if (map.size() == 0) {
+            throw new NotFoundException("Could not find ACL");
+        } else {
+            return (Acl) map.get(object);
+        }
+    }
+
+    public Acl readAclById(ObjectIdentity object) throws NotFoundException {
+        return readAclById(object, null);
+    }
+
+    public Map readAclsById(ObjectIdentity[] objects) {
+        return readAclsById(objects, null);
+    }
+
+    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
+        throws NotFoundException {
+        return lookupStrategy.readAclsById(objects, sids);
+    }
+}

+ 368 - 0
core/src/main/java/org/acegisecurity/acls/jdbc/JdbcMutableAclService.java

@@ -0,0 +1,368 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.Authentication;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AlreadyExistsException;
+import org.acegisecurity.acls.ChildrenExistException;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.MutableAclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.domain.AccessControlEntryImpl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.GrantedAuthoritySid;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.springframework.dao.DataAccessException;
+
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
+
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Array;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+
+/**
+ * Provides a base implementation of {@link MutableAclService}.
+ *
+ * @author Ben Alex
+ * @author Johannes Zlattinger
+ * @version $Id$
+ */
+public class JdbcMutableAclService extends JdbcAclService implements MutableAclService {
+    //~ Instance fields ================================================================================================
+
+    private AclCache aclCache;
+    private String deleteClassByClassNameString = "DELETE FROM acl_class WHERE class=?";
+    private String deleteEntryByObjectIdentityForeignKey = "DELETE FROM acl_entry WHERE acl_object_identity=?";
+    private String deleteObjectIdentityByPrimaryKey = "DELETE FROM acl_object_identity WHERE id=?";
+    private String identityQuery = "call identity()";
+    private String insertClass = "INSERT INTO acl_class (id, class) VALUES (null, ?)";
+    private String insertEntry = "INSERT INTO acl_entry "
+        + "(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)"
+        + "VALUES (null, ?, ?, ?, ?, ?, ?, ?)";
+    private String insertObjectIdentity = "INSERT INTO acl_object_identity "
+        + "(id, object_id_class, object_id_identity, owner_sid, entries_inheriting) " + "VALUES (null, ?, ?, ?, ?)";
+    private String insertSid = "INSERT INTO acl_sid (id, principal, sid) VALUES (null, ?, ?)";
+    private String selectClassPrimaryKey = "SELECT id FROM acl_class WHERE class=?";
+    private String selectCountObjectIdentityRowsForParticularClassNameString = "SELECT COUNT(acl_object_identity.id) "
+        + "FROM acl_object_identity, acl_class WHERE acl_class.id = acl_object_identity.object_id_class and class=?";
+    private String selectObjectIdentityPrimaryKey = "SELECT acl_object_identity.id FROM acl_object_identity, acl_class "
+        + "WHERE acl_object_identity.object_id_class = acl_class.id and acl_class.class=? "
+        + "and acl_object_identity.object_id_identity = ?";
+    private String selectSidPrimaryKey = "SELECT id FROM acl_sid WHERE principal=? AND sid=?";
+    private String updateObjectIdentity = "UPDATE acl_object_identity SET "
+        + "parent_object = ?, owner_sid = ?, entries_inheriting = ?" + "where id = ?";
+
+    //~ Constructors ===================================================================================================
+
+    public JdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
+        super(dataSource, lookupStrategy);
+        Assert.notNull(aclCache, "AclCache required");
+        this.aclCache = aclCache;
+    }
+
+    //~ Methods ========================================================================================================
+
+    public MutableAcl createAcl(ObjectIdentity objectIdentity)
+        throws AlreadyExistsException {
+        Assert.notNull(objectIdentity, "Object Identity required");
+
+        // Check this object identity hasn't already been persisted
+        if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) {
+            throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists");
+        }
+
+        // Need to retrieve the current principal, in order to know who "owns" this ACL (can be changed later on)
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        PrincipalSid sid = new PrincipalSid(auth);
+
+        // Create the acl_object_identity row
+        createObjectIdentity(objectIdentity, sid);
+
+        // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
+        Acl acl = readAclById(objectIdentity);
+        Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned");
+
+        return (MutableAcl) acl;
+    }
+
+    /**
+     * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl object.
+     *
+     * @param acl containing the ACEs to insert
+     */
+    protected void createEntries(final MutableAcl acl) {
+        jdbcTemplate.batchUpdate(insertEntry,
+            new BatchPreparedStatementSetter() {
+                public int getBatchSize() {
+                    return acl.getEntries().length;
+                }
+
+                public void setValues(PreparedStatement stmt, int i)
+                    throws SQLException {
+                    AccessControlEntry entry_ = (AccessControlEntry) Array.get(acl.getEntries(), i);
+                    Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class");
+
+                    AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_;
+
+                    stmt.setLong(1, ((Long) acl.getId()).longValue());
+                    stmt.setInt(2, i);
+                    stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true).longValue());
+                    stmt.setInt(4, entry.getPermission().getMask());
+                    stmt.setBoolean(5, entry.isGranting());
+                    stmt.setBoolean(6, entry.isAuditSuccess());
+                    stmt.setBoolean(7, entry.isAuditFailure());
+                }
+            });
+    }
+
+    /**
+     * Creates an entry in the acl_object_identity table for the passed ObjectIdentity. The Sid is also
+     * necessary, as acl_object_identity has defined the sid column as non-null.
+     *
+     * @param object to represent an acl_object_identity for
+     * @param owner for the SID column (will be created if there is no acl_sid entry for this particular Sid already)
+     */
+    protected void createObjectIdentity(ObjectIdentity object, Sid owner) {
+        Long sidId = createOrRetrieveSidPrimaryKey(owner, true);
+        Long classId = createOrRetrieveClassPrimaryKey(object.getJavaType(), true);
+        jdbcTemplate.update(insertObjectIdentity,
+            new Object[] {classId, object.getIdentifier().toString(), sidId, new Boolean(true)});
+    }
+
+    /**
+     * Retrieves the primary key from acl_class, creating a new row if needed and the allowCreate property is
+     * true.
+     *
+     * @param clazz to find or create an entry for (this implementation uses the fully-qualified class name String)
+     * @param allowCreate true if creation is permitted if not found
+     *
+     * @return the primary key or null if not found
+     */
+    protected Long createOrRetrieveClassPrimaryKey(Class clazz, boolean allowCreate) {
+        List classIds = jdbcTemplate.queryForList(selectClassPrimaryKey, new Object[] {clazz.getName()}, Long.class);
+        Long classId = null;
+
+        if (classIds.isEmpty()) {
+            if (allowCreate) {
+                classId = null;
+                jdbcTemplate.update(insertClass, new Object[] {clazz.getName()});
+                Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
+                classId = new Long(jdbcTemplate.queryForLong(identityQuery));
+            }
+        } else {
+            classId = (Long) classIds.iterator().next();
+        }
+
+        return classId;
+    }
+
+    /**
+     * Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate property is
+     * true.
+     *
+     * @param sid to find or create
+     * @param allowCreate true if creation is permitted if not found
+     *
+     * @return the primary key or null if not found
+     *
+     * @throws IllegalArgumentException DOCUMENT ME!
+     */
+    protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
+        Assert.notNull(sid, "Sid required");
+
+        String sidName = null;
+        boolean principal = true;
+
+        if (sid instanceof PrincipalSid) {
+            sidName = ((PrincipalSid) sid).getPrincipal();
+        } else if (sid instanceof GrantedAuthoritySid) {
+            sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
+            principal = false;
+        } else {
+            throw new IllegalArgumentException("Unsupported implementation of Sid");
+        }
+
+        List sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey, new Object[] {new Boolean(principal), sidName},
+                Long.class);
+        Long sidId = null;
+
+        if (sidIds.isEmpty()) {
+            if (allowCreate) {
+                sidId = null;
+                jdbcTemplate.update(insertSid, new Object[] {new Boolean(principal), sidName});
+                Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
+                sidId = new Long(jdbcTemplate.queryForLong(identityQuery));
+            }
+        } else {
+            sidId = (Long) sidIds.iterator().next();
+        }
+
+        return sidId;
+    }
+
+    public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
+        throws ChildrenExistException {
+        Assert.notNull(objectIdentity, "Object Identity required");
+        Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier");
+
+        // Recursively call this method for children, or handle children if they don't want automatic recursion
+        ObjectIdentity[] children = findChildren(objectIdentity);
+
+        if (deleteChildren) {
+            for (int i = 0; i < children.length; i++) {
+                deleteAcl(children[i], true);
+            }
+        } else if (children.length > 0) {
+            throw new ChildrenExistException("Cannot delete '" + objectIdentity + "' (has " + children.length
+                + " children)");
+        }
+
+        // Delete this ACL's ACEs in the acl_entry table
+        deleteEntries(objectIdentity);
+
+        // Delete this ACL's acl_object_identity row
+        deleteObjectIdentityAndOptionallyClass(objectIdentity);
+
+        // Clear the cache
+        aclCache.evictFromCache(objectIdentity);
+    }
+
+    /**
+     * Deletes all ACEs defined in the acl_entry table belonging to the presented ObjectIdentity
+     *
+     * @param oid the rows in acl_entry to delete
+     */
+    protected void deleteEntries(ObjectIdentity oid) {
+        jdbcTemplate.update(deleteEntryByObjectIdentityForeignKey, new Object[] {retrieveObjectIdentityPrimaryKey(oid)});
+    }
+
+    /**
+     * Deletes a single row from acl_object_identity that is associated with the presented ObjectIdentity. In
+     * addition, deletes the corresponding row from acl_class if there are no more entries in acl_object_identity that
+     * use that particular acl_class. This keeps the acl_class table reasonably small.
+     *
+     * @param oid to delete the acl_object_identity (and clean up acl_class for that class name if appropriate)
+     */
+    protected void deleteObjectIdentityAndOptionallyClass(ObjectIdentity oid) {
+        // Delete the acl_object_identity row
+        jdbcTemplate.update(deleteObjectIdentityByPrimaryKey, new Object[] {retrieveObjectIdentityPrimaryKey(oid)});
+
+        // Delete the acl_class row, assuming there are no other references to it in acl_object_identity
+        Object[] className = {oid.getJavaType().getName()};
+        long numObjectIdentities = jdbcTemplate.queryForLong(selectCountObjectIdentityRowsForParticularClassNameString,
+                className);
+
+        if (numObjectIdentities == 0) {
+            // No more rows
+            jdbcTemplate.update(deleteClassByClassNameString, className);
+        }
+    }
+
+    /**
+     * Retrieves the primary key from the acl_object_identity table for the passed ObjectIdentity. Unlike some
+     * other methods in this implementation, this method will NOT create a row (use {@link
+     * #createObjectIdentity(ObjectIdentity, Sid)} instead).
+     *
+     * @param oid to find
+     *
+     * @return the object identity or null if not found
+     */
+    protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) {
+        try {
+            return new Long(jdbcTemplate.queryForLong(selectObjectIdentityPrimaryKey,
+                    new Object[] {oid.getJavaType().getName(), oid.getIdentifier()}));
+        } catch (DataAccessException notFound) {
+            return null;
+        }
+    }
+
+    /**
+     * This implementation will simply delete all ACEs in the database and recreate them on each invocation of
+     * this method. A more comprehensive implementation might use dirty state checking, or more likely use ORM
+     * capabilities for create, update and delete operations of {@link MutableAcl}.
+     *
+     * @param acl DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     */
+    public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
+        Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier");
+
+        // Delete this ACL's ACEs in the acl_entry table
+        deleteEntries(acl.getObjectIdentity());
+
+        // Create this ACL's ACEs in the acl_entry table
+        createEntries(acl);
+
+        // Change the mutable columns in acl_object_identity
+        updateObjectIdentity(acl);
+
+        // Clear the cache
+        aclCache.evictFromCache(acl.getObjectIdentity());
+
+        // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
+        return (MutableAcl) super.readAclById(acl.getObjectIdentity());
+    }
+
+    /**
+     * Updates an existing acl_object_identity row, with new information presented in the passed MutableAcl
+     * object. Also will create an acl_sid entry if needed for the Sid that owns the MutableAcl.
+     *
+     * @param acl to modify (a row must already exist in acl_object_identity)
+     *
+     * @throws NotFoundException DOCUMENT ME!
+     */
+    protected void updateObjectIdentity(MutableAcl acl) {
+        Long parentId = null;
+
+        if (acl.getParentAcl() != null) {
+            Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl().getObjectIdentity(),
+                "Implementation only supports ObjectIdentityImpl");
+
+            ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl().getObjectIdentity();
+            parentId = retrieveObjectIdentityPrimaryKey(oii);
+        }
+
+        Assert.notNull(acl.getOwner(), "Owner is required in this implementation");
+
+        Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true);
+        int count = jdbcTemplate.update(updateObjectIdentity,
+                new Object[] {parentId, ownerSid, new Boolean(acl.isEntriesInheriting()), acl.getId()});
+
+        if (count != 1) {
+            throw new NotFoundException("Unable to locate ACL to update");
+        }
+    }
+}

+ 38 - 39
sandbox/other/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java → core/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java

@@ -1,44 +1,43 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.sid.Sid;
-
-import java.util.Map;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+
+import java.util.Map;
+
+
 /**
 /**
  * Performs optimised lookups for {@link JdbcAclService}.
  * Performs optimised lookups for {@link JdbcAclService}.
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface LookupStrategy {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Perform database-specific optimized lookup.
-     *
-     * @param objects the identities to lookup (required)
-     * @param sids the SIDs for which identities are required (may be <code>null</code> - implementations may elect not
-     *        to provide SID optimisations)
-     *
-     * @return the <code>Map</code> pursuant to the interface contract for {@link
-     *         org.acegisecurity.acls.AclService#readAclsById(ObjectIdentity[], Sid[])}
-     */
-    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids);
-}
+ */
+public interface LookupStrategy {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Perform database-specific optimized lookup.
+     *
+     * @param objects the identities to lookup (required)
+     * @param sids the SIDs for which identities are required (may be <code>null</code> - implementations may elect not
+     *        to provide SID optimisations)
+     *
+     * @return the <code>Map</code> pursuant to the interface contract for {@link
+     *         org.acegisecurity.acls.AclService#readAclsById(ObjectIdentity[], Sid[])}
+     */
+    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids);
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/acls/jdbc/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+JDBC-based persistence of ACL information.
+</body>
+</html>

+ 56 - 57
sandbox/other/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java → core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java

@@ -1,23 +1,22 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.objectidentity;
-
-import java.io.Serializable;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.objectidentity;
+
+import java.io.Serializable;
+
+
 /**
 /**
  * Interface representing the identity of an individual domain object instance.
  * Interface representing the identity of an individual domain object instance.
  * 
  * 
@@ -32,40 +31,40 @@ import java.io.Serializable;
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface ObjectIdentity extends Serializable {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
-     *
-     * @param obj to be compared
-     *
-     * @return <code>true</code> if the objects are equal, <code>false</code> otherwise
-     */
-    public boolean equals(Object obj);
-
-    /**
-     * Obtains the actual identifier. This identifier must not be reused to represent other domain objects with
-     * the same <code>javaType</code>.<p>Because ACLs are largely immutable, it is strongly recommended to use
-     * a synthetic identifier (such as a database sequence number for the primary key). Do not use an identifier with
-     * business meaning, as that business meaning may change.</p>
-     *
-     * @return the identifier (unique within this <code>javaType</code>
-     */
-    public Serializable getIdentifier();
-
-    /**
-     * Obtains the Java type represented by the domain object.
-     *
-     * @return the Java type of the domain object
-     */
-    public Class getJavaType();
-
-    /**
-     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
-     *
-     * @return a hash code representation of this object
-     */
-    public int hashCode();
-}
+ */
+public interface ObjectIdentity extends Serializable {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
+     *
+     * @param obj to be compared
+     *
+     * @return <code>true</code> if the objects are equal, <code>false</code> otherwise
+     */
+    public boolean equals(Object obj);
+
+    /**
+     * Obtains the actual identifier. This identifier must not be reused to represent other domain objects with
+     * the same <code>javaType</code>.<p>Because ACLs are largely immutable, it is strongly recommended to use
+     * a synthetic identifier (such as a database sequence number for the primary key). Do not use an identifier with
+     * business meaning, as that business meaning may change.</p>
+     *
+     * @return the identifier (unique within this <code>javaType</code>
+     */
+    public Serializable getIdentifier();
+
+    /**
+     * Obtains the Java type represented by the domain object.
+     *
+     * @return the Java type of the domain object
+     */
+    public Class getJavaType();
+
+    /**
+     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
+     *
+     * @return a hash code representation of this object
+     */
+    public int hashCode();
+}

+ 141 - 140
sandbox/other/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java → core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java

@@ -1,146 +1,147 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.objectidentity;
-
-import org.acegisecurity.acl.basic.AclObjectIdentity;
-
-import org.acegisecurity.acls.IdentityUnavailableException;
-
-import org.springframework.util.Assert;
-import org.springframework.util.ReflectionUtils;
-
-import java.io.Serializable;
-
-import java.lang.reflect.Method;
-
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.objectidentity;
+
+import org.acegisecurity.acl.basic.AclObjectIdentity;
+
+import org.acegisecurity.acls.IdentityUnavailableException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+import java.io.Serializable;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Simple implementation of {@link AclObjectIdentity}.<P>Uses <code>String</code>s to store the identity of the
+ * domain object instance. Also offers a constructor that uses reflection to build the identity information.</p>
+ */
+public class ObjectIdentityImpl implements ObjectIdentity {
+    //~ Instance fields ================================================================================================
+
+    private Class javaType;
+    private Serializable identifier;
+
+    //~ Constructors ===================================================================================================
+
+    public ObjectIdentityImpl(String javaType, Serializable identifier) {
+        Assert.hasText(javaType, "Java Type required");
+        Assert.notNull(identifier, "identifier required");
+
+        try {
+            this.javaType = Class.forName(javaType);
+        } catch (Exception ex) {
+            ReflectionUtils.handleReflectionException(ex);
+        }
+
+        this.identifier = identifier;
+    }
+
+    public ObjectIdentityImpl(Class javaType, Serializable identifier) {
+        Assert.notNull(javaType, "Java Type required");
+        Assert.notNull(identifier, "identifier required");
+        this.javaType = javaType;
+        this.identifier = identifier;
+    }
+
 /**
 /**
- * Simple implementation of {@link AclObjectIdentity}.<P>Uses <code>String</code>s to store the identity of the
- * domain object instance. Also offers a constructor that uses reflection to build the identity information.</p>
- */
-public class ObjectIdentityImpl implements ObjectIdentity {
-    //~ Instance fields ================================================================================================
-
-    private Class javaType;
-    private Serializable identifier;
-
-    //~ Constructors ===================================================================================================
-
-    public ObjectIdentityImpl(String javaType, Serializable identifier) {
-        Assert.hasText(javaType, "Java Type required");
-        Assert.notNull(identifier, "identifier required");
-
-        try {
-            this.javaType = Class.forName(javaType);
-        } catch (Exception ex) {
-            ReflectionUtils.handleReflectionException(ex);
-        }
-
-        this.identifier = identifier;
-    }
-
-    public ObjectIdentityImpl(Class javaType, Serializable identifier) {
-        Assert.notNull(javaType, "Java Type required");
-        Assert.notNull(identifier, "identifier required");
-        this.javaType = javaType;
-        this.identifier = identifier;
-    }
-
-/**
-     * Creates the <code>NamedEntityObjectIdentity</code> based on the passed
+     * Creates the <code>ObjectIdentityImpl</code> based on the passed
      * object instance. The passed object must provide a <code>getId()</code>
      * object instance. The passed object must provide a <code>getId()</code>
-     * method, otherwise an exception will be thrown.
+     * method, otherwise an exception will be thrown. The object passed will
+     * be considered the {@link #javaType}, so if more control is required,
+     * an alternate constructor should be used instead.
      *
      *
      * @param object the domain object instance to create an identity for
      * @param object the domain object instance to create an identity for
      *
      *
      * @throws IdentityUnavailableException if identity could not be extracted
      * @throws IdentityUnavailableException if identity could not be extracted
-     */
-    public ObjectIdentityImpl(Object object) throws IdentityUnavailableException {
-        Assert.notNull(object, "object cannot be null");
-
-        this.javaType = object.getClass();
-
-        Object result;
-
-        try {
-            Method method = this.javaType.getMethod("getId", new Class[] {});
-            result = method.invoke(object, new Object[] {});
-        } catch (Exception e) {
-            throw new IdentityUnavailableException("Could not extract identity from object " + object, e);
-        }
-
-        Assert.isInstanceOf(Serializable.class, result, "Getter must provide a return value of type Serializable");
-        this.identifier = (Serializable) result;
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Important so caching operates properly.<P>Considers an object of the same class equal if it has the same
-     * <code>classname</code> and <code>id</code> properties.</p>
-     *
-     * @param arg0 object to compare
-     *
-     * @return <code>true</code> if the presented object matches this object
-     */
-    public boolean equals(Object arg0) {
-        if (arg0 == null) {
-            return false;
-        }
-
-        if (!(arg0 instanceof ObjectIdentityImpl)) {
-            return false;
-        }
-
-        ObjectIdentityImpl other = (ObjectIdentityImpl) arg0;
-
-        if (this.getIdentifier().equals(other.getIdentifier()) && this.getJavaType().equals(other.getJavaType())) {
-            return true;
-        }
-
-        return false;
-    }
-
-    public Serializable getIdentifier() {
-        return identifier;
-    }
-
-    public Class getJavaType() {
-        return javaType;
-    }
-
-    /**
-     * Important so caching operates properly.
-     *
-     * @return the hash
-     */
-    public int hashCode() {
-        int code = 31;
-        code ^= this.javaType.hashCode();
-        code ^= this.identifier.hashCode();
-
-        return code;
-    }
-
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-        sb.append(this.getClass().getName()).append("[");
-        sb.append("Java Type: ").append(this.javaType);
-        sb.append("; Identifier: ").append(this.identifier).append("]");
-
-        return sb.toString();
-    }
-}
+     */
+    public ObjectIdentityImpl(Object object) throws IdentityUnavailableException {
+        Assert.notNull(object, "object cannot be null");
+
+        this.javaType = object.getClass();
+
+        Object result;
+
+        try {
+            Method method = this.javaType.getMethod("getId", new Class[] {});
+            result = method.invoke(object, new Object[] {});
+        } catch (Exception e) {
+            throw new IdentityUnavailableException("Could not extract identity from object " + object, e);
+        }
+
+        Assert.isInstanceOf(Serializable.class, result, "Getter must provide a return value of type Serializable");
+        this.identifier = (Serializable) result;
+    }
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Important so caching operates properly.<P>Considers an object of the same class equal if it has the same
+     * <code>classname</code> and <code>id</code> properties.</p>
+     *
+     * @param arg0 object to compare
+     *
+     * @return <code>true</code> if the presented object matches this object
+     */
+    public boolean equals(Object arg0) {
+        if (arg0 == null) {
+            return false;
+        }
+
+        if (!(arg0 instanceof ObjectIdentityImpl)) {
+            return false;
+        }
+
+        ObjectIdentityImpl other = (ObjectIdentityImpl) arg0;
+
+        if (this.getIdentifier().equals(other.getIdentifier()) && this.getJavaType().equals(other.getJavaType())) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public Serializable getIdentifier() {
+        return identifier;
+    }
+
+    public Class getJavaType() {
+        return javaType;
+    }
+
+    /**
+     * Important so caching operates properly.
+     *
+     * @return the hash
+     */
+    public int hashCode() {
+        int code = 31;
+        code ^= this.javaType.hashCode();
+        code ^= this.identifier.hashCode();
+
+        return code;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(this.getClass().getName()).append("[");
+        sb.append("Java Type: ").append(this.javaType);
+        sb.append("; Identifier: ").append(this.identifier).append("]");
+
+        return sb.toString();
+    }
+}

+ 30 - 0
core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityRetrievalStrategy.java

@@ -0,0 +1,30 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.acls.objectidentity;
+
+/**
+ * Strategy interface that provides the ability to determine which {@link ObjectIdentity}
+ * will be returned for a particular domain object
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface ObjectIdentityRetrievalStrategy {
+    //~ Methods ========================================================================================================
+
+    public ObjectIdentity getObjectIdentity(Object domainObject);
+}

+ 31 - 0
core/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityRetrievalStrategyImpl.java

@@ -0,0 +1,31 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.acls.objectidentity;
+
+/**
+ * Basic implementation of {@link ObjectIdentityRetrievalStrategy} that uses the constructor of {@link
+ * ObjectIdentityImpl} to create the {@link ObjectIdentity}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ObjectIdentityRetrievalStrategyImpl implements ObjectIdentityRetrievalStrategy {
+    //~ Methods ========================================================================================================
+
+    public ObjectIdentity getObjectIdentity(Object domainObject) {
+        return new ObjectIdentityImpl(domainObject);
+    }
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/acls/objectidentity/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Provides indirection between ACL packages and domain objects.
+</body>
+</html>

+ 5 - 0
core/src/main/java/org/acegisecurity/acls/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Interfaces and shared classes to manage access control lists (ACLs) for domain object instances.
+</body>
+</html>

+ 67 - 68
sandbox/other/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java → core/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java

@@ -1,68 +1,67 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.sid;
-
-import org.acegisecurity.GrantedAuthority;
-
-import org.springframework.util.Assert;
-
-
-/**
- * Represents a <code>GrantedAuthority</code> as a <code>Sid</code>.<p>This is a basic implementation that simply
- * uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal objects may
- * wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class GrantedAuthoritySid implements Sid {
-    //~ Instance fields ================================================================================================
-
-    private String grantedAuthority;
-
-    //~ Constructors ===================================================================================================
-
-    public GrantedAuthoritySid(String grantedAuthority) {
-        Assert.hasText(grantedAuthority, "GrantedAuthority required");
-        this.grantedAuthority = grantedAuthority;
-    }
-
-    public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
-        Assert.notNull(grantedAuthority, "GrantedAuthority required");
-        Assert.notNull(grantedAuthority.getAuthority(),
-            "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
-        this.grantedAuthority = grantedAuthority.getAuthority();
-    }
-
-    //~ Methods ========================================================================================================
-
-    public boolean equals(Object object) {
-        if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
-            return false;
-        }
-
-        // Delegate to getGrantedAuthority() to perform actual comparison (both should be identical) 
-        return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority());
-    }
-
-    public String getGrantedAuthority() {
-        return grantedAuthority;
-    }
-
-    public String toString() {
-        return "GrantedAuthoritySid[" + this.grantedAuthority + "]";
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.sid;
+
+import org.acegisecurity.GrantedAuthority;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Represents a <code>GrantedAuthority</code> as a <code>Sid</code>.<p>This is a basic implementation that simply
+ * uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal objects may
+ * wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class GrantedAuthoritySid implements Sid {
+    //~ Instance fields ================================================================================================
+
+    private String grantedAuthority;
+
+    //~ Constructors ===================================================================================================
+
+    public GrantedAuthoritySid(String grantedAuthority) {
+        Assert.hasText(grantedAuthority, "GrantedAuthority required");
+        this.grantedAuthority = grantedAuthority;
+    }
+
+    public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
+        Assert.notNull(grantedAuthority, "GrantedAuthority required");
+        Assert.notNull(grantedAuthority.getAuthority(),
+            "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
+        this.grantedAuthority = grantedAuthority.getAuthority();
+    }
+
+    //~ Methods ========================================================================================================
+
+    public boolean equals(Object object) {
+        if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
+            return false;
+        }
+
+        // Delegate to getGrantedAuthority() to perform actual comparison (both should be identical) 
+        return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority());
+    }
+
+    public String getGrantedAuthority() {
+        return grantedAuthority;
+    }
+
+    public String toString() {
+        return "GrantedAuthoritySid[" + this.grantedAuthority + "]";
+    }
+}

+ 72 - 73
sandbox/other/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java → core/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java

@@ -1,73 +1,72 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.sid;
-
-import org.acegisecurity.Authentication;
-
-import org.acegisecurity.userdetails.UserDetails;
-
-import org.springframework.util.Assert;
-
-
-/**
- * Represents an <code>Authentication.getPrincipal()</code> as a <code>Sid</code>.<p>This is a basic implementation
- * that simply uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal
- * objects may wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class PrincipalSid implements Sid {
-    //~ Instance fields ================================================================================================
-
-    private String principal;
-
-    //~ Constructors ===================================================================================================
-
-    public PrincipalSid(String principal) {
-        Assert.hasText(principal, "Principal required");
-        this.principal = principal;
-    }
-
-    public PrincipalSid(Authentication authentication) {
-        Assert.notNull(authentication, "Authentication required");
-        Assert.notNull(authentication.getPrincipal(), "Principal required");
-        this.principal = authentication.getPrincipal().toString();
-
-        if (authentication.getPrincipal() instanceof UserDetails) {
-            this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
-        }
-    }
-
-    //~ Methods ========================================================================================================
-
-    public boolean equals(Object object) {
-        if ((object == null) || !(object instanceof PrincipalSid)) {
-            return false;
-        }
-
-        // Delegate to getPrincipal() to perform actual comparison (both should be identical) 
-        return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal());
-    }
-
-    public String getPrincipal() {
-        return principal;
-    }
-
-    public String toString() {
-        return "PrincipalSid[" + this.principal + "]";
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.sid;
+
+import org.acegisecurity.Authentication;
+
+import org.acegisecurity.userdetails.UserDetails;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Represents an <code>Authentication.getPrincipal()</code> as a <code>Sid</code>.<p>This is a basic implementation
+ * that simply uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal
+ * objects may wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class PrincipalSid implements Sid {
+    //~ Instance fields ================================================================================================
+
+    private String principal;
+
+    //~ Constructors ===================================================================================================
+
+    public PrincipalSid(String principal) {
+        Assert.hasText(principal, "Principal required");
+        this.principal = principal;
+    }
+
+    public PrincipalSid(Authentication authentication) {
+        Assert.notNull(authentication, "Authentication required");
+        Assert.notNull(authentication.getPrincipal(), "Principal required");
+        this.principal = authentication.getPrincipal().toString();
+
+        if (authentication.getPrincipal() instanceof UserDetails) {
+            this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
+        }
+    }
+
+    //~ Methods ========================================================================================================
+
+    public boolean equals(Object object) {
+        if ((object == null) || !(object instanceof PrincipalSid)) {
+            return false;
+        }
+
+        // Delegate to getPrincipal() to perform actual comparison (both should be identical) 
+        return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal());
+    }
+
+    public String getPrincipal() {
+        return principal;
+    }
+
+    public String toString() {
+        return "PrincipalSid[" + this.principal + "]";
+    }
+}

+ 36 - 37
sandbox/other/src/main/java/org/acegisecurity/acls/sid/Sid.java → core/src/main/java/org/acegisecurity/acls/sid/Sid.java

@@ -1,20 +1,19 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.sid;
-
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.sid;
+
 /**
 /**
  * A security identity recognised by the ACL system.
  * A security identity recognised by the ACL system.
  * 
  * 
@@ -29,23 +28,23 @@ package org.acegisecurity.acls.sid;
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$
- */
-public interface Sid {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
-     *
-     * @param obj to be compared
-     *
-     * @return <code>true</code> if the objects are equal, <code>false</code> otherwise
-     */
-    public boolean equals(Object obj);
-
-    /**
-     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
-     *
-     * @return a hash code representation of this object
-     */
-    public int hashCode();
-}
+ */
+public interface Sid {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
+     *
+     * @param obj to be compared
+     *
+     * @return <code>true</code> if the objects are equal, <code>false</code> otherwise
+     */
+    public boolean equals(Object obj);
+
+    /**
+     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
+     *
+     * @return a hash code representation of this object
+     */
+    public int hashCode();
+}

+ 32 - 0
core/src/main/java/org/acegisecurity/acls/sid/SidRetrievalStrategy.java

@@ -0,0 +1,32 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.acls.sid;
+
+import org.acegisecurity.Authentication;
+
+
+/**
+ * Strategy interface that provides an ability to determine the {@link Sid} instances applicable
+ * for an {@link Authentication}.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface SidRetrievalStrategy {
+    //~ Methods ========================================================================================================
+
+    public Sid[] getSids(Authentication authentication);
+}

+ 48 - 0
core/src/main/java/org/acegisecurity/acls/sid/SidRetrievalStrategyImpl.java

@@ -0,0 +1,48 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.acls.sid;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * Basic implementation of {@link SidRetrievalStrategy} that creates a {@link Sid} for the principal, as well as
+ * every granted authority the principal holds.<p>The returned array will always contain the {@link PrincipalSid}
+ * before any {@link GrantedAuthoritySid} elements.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SidRetrievalStrategyImpl implements SidRetrievalStrategy {
+    //~ Methods ========================================================================================================
+
+    public Sid[] getSids(Authentication authentication) {
+        List list = new Vector();
+        list.add(new PrincipalSid(authentication));
+
+        GrantedAuthority[] authorities = authentication.getAuthorities();
+
+        for (int i = 0; i < authorities.length; i++) {
+            list.add(new GrantedAuthoritySid(authorities[i]));
+        }
+
+        return (Sid[]) list.toArray(new Sid[] {});
+    }
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/acls/sid/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Provides indirection between ACL packages and security identities, such as principals and GrantedAuthority[]s.
+</body>
+</html>

+ 130 - 0
core/src/main/java/org/acegisecurity/afterinvocation/AbstractAclProvider.java

@@ -0,0 +1,130 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.afterinvocation;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.ConfigAttribute;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategy;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
+import org.acegisecurity.acls.sid.Sid;
+import org.acegisecurity.acls.sid.SidRetrievalStrategy;
+import org.acegisecurity.acls.sid.SidRetrievalStrategyImpl;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision$
+  */
+public abstract class AbstractAclProvider implements AfterInvocationProvider {
+    //~ Instance fields ================================================================================================
+
+    private AclService aclService;
+    private Class processDomainObjectClass = Object.class;
+    private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+    private String processConfigAttribute;
+    private Permission[] requirePermission = {BasePermission.READ};
+
+    //~ Constructors ===================================================================================================
+
+    public AbstractAclProvider(AclService aclService, String processConfigAttribute, Permission[] requirePermission) {
+        Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
+        Assert.notNull(aclService, "An AclService is mandatory");
+
+        if ((requirePermission == null) || (requirePermission.length == 0)) {
+            throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
+        }
+
+        this.aclService = aclService;
+        this.processConfigAttribute = processConfigAttribute;
+        this.requirePermission = requirePermission;
+    }
+
+    //~ Methods ========================================================================================================
+
+    protected Class getProcessDomainObjectClass() {
+        return processDomainObjectClass;
+    }
+
+    protected boolean hasPermission(Authentication authentication, Object domainObject) {
+        // Obtain the OID applicable to the domain object
+        ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
+
+        // Obtain the SIDs applicable to the principal
+        Sid[] sids = sidRetrievalStrategy.getSids(authentication);
+
+        Acl acl = null;
+
+        try {
+            // Lookup only ACLs for SIDs we're interested in
+            acl = aclService.readAclById(objectIdentity, sids);
+
+            return acl.isGranted(requirePermission, sids, false);
+        } catch (NotFoundException ignore) {
+            return false;
+        }
+    }
+
+    public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
+        Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required");
+        this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
+    }
+
+    protected void setProcessConfigAttribute(String processConfigAttribute) {
+        Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
+        this.processConfigAttribute = processConfigAttribute;
+    }
+
+    public void setProcessDomainObjectClass(Class processDomainObjectClass) {
+        Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null");
+        this.processDomainObjectClass = processDomainObjectClass;
+    }
+
+    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+        Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
+        this.sidRetrievalStrategy = sidRetrievalStrategy;
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute.getAttribute() != null) && attribute.getAttribute().equals(this.processConfigAttribute)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * This implementation supports any type of class, because it does not query the presented secure object.
+     *
+     * @param clazz the secure object
+     *
+     * @return always <code>true</code>
+     */
+    public boolean supports(Class clazz) {
+        return true;
+    }
+}

+ 133 - 0
core/src/main/java/org/acegisecurity/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java

@@ -0,0 +1,133 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.afterinvocation;
+
+import org.acegisecurity.AccessDeniedException;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthorizationServiceException;
+import org.acegisecurity.ConfigAttribute;
+import org.acegisecurity.ConfigAttributeDefinition;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.Permission;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+
+/**
+ * <p>Given a <code>Collection</code> of domain object instances returned from a secure object invocation, remove
+ * any <code>Collection</code> elements the principal does not have appropriate permission to access as defined by the
+ * {@link AclService}.</p>
+ *  <p>The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with
+ * each <code>Collection</code> domain object instance element for the current <code>Authentication</code> object.</p>
+ *  <p>This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then lookup the ACLs from the <code>AclService</code> and ensure the
+ * principal is {@link Acl#isGranted(org.acegisecurity.acls.Permission[], org.acegisecurity.acls.sid.Sid[], boolean)}
+ * when presenting the {@link #requirePermission} array to that method.</p>
+ *  <p>If the principal does not have permission, that element will not be included in the returned
+ * <code>Collection</code>.</p>
+ *  <p>Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code> with a {@link
+ * #processConfigAttribute} of <code>AFTER_ACL_COLLECTION_READ</code> and a {@link #requirePermission} of
+ * <code>BasePermission.READ</code>. These are also the defaults.</p>
+ *  <p>If the provided <code>returnObject</code> is <code>null</code>, a <code>null</code><code>Collection</code>
+ * will be returned. If the provided <code>returnObject</code> is not a <code>Collection</code>, an {@link
+ * AuthorizationServiceException} will be thrown.</p>
+ *  <p>All comparisons and prefixes are case sensitive.</p>
+ *
+ * @author Ben Alex
+ * @author Paulo Neves
+ * @version $Id$
+ */
+public class AclEntryAfterInvocationCollectionFilteringProvider extends AbstractAclProvider {
+    //~ Static fields/initializers =====================================================================================
+
+    protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class);
+
+    //~ Constructors ===================================================================================================
+
+    public AclEntryAfterInvocationCollectionFilteringProvider(AclService aclService, Permission[] requirePermission) {
+        super(aclService, "AFTER_ACL_COLLECTION_READ", requirePermission);
+    }
+
+    //~ Methods ========================================================================================================
+
+    public Object decide(Authentication authentication, Object object, ConfigAttributeDefinition config,
+        Object returnedObject) throws AccessDeniedException {
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attr = (ConfigAttribute) iter.next();
+
+            if (this.supports(attr)) {
+                // Need to process the Collection for this invocation
+                if (returnedObject == null) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Return object is null, skipping");
+                    }
+
+                    return null;
+                }
+
+                Filterer filterer = null;
+
+                if (returnedObject instanceof Collection) {
+                    Collection collection = (Collection) returnedObject;
+                    filterer = new CollectionFilterer(collection);
+                } else if (returnedObject.getClass().isArray()) {
+                    Object[] array = (Object[]) returnedObject;
+                    filterer = new ArrayFilterer(array);
+                } else {
+                    throw new AuthorizationServiceException(
+                        "A Collection or an array (or null) was required as the returnedObject, but the returnedObject was: "
+                        + returnedObject);
+                }
+
+                // Locate unauthorised Collection elements
+                Iterator collectionIter = filterer.iterator();
+
+                while (collectionIter.hasNext()) {
+                    Object domainObject = collectionIter.next();
+
+                    boolean hasPermission = false;
+
+                    if (domainObject == null) {
+                        hasPermission = true;
+                    } else if (!getProcessDomainObjectClass().isAssignableFrom(domainObject.getClass())) {
+                        hasPermission = true;
+                    } else {
+                        hasPermission = hasPermission(authentication, domainObject);
+
+                        if (!hasPermission) {
+                            filterer.remove(domainObject);
+
+                            if (logger.isDebugEnabled()) {
+                                logger.debug("Principal is NOT authorised for element: " + domainObject);
+                            }
+                        }
+                    }
+                }
+
+                return filterer.getFilteredObject();
+            }
+        }
+
+        return returnedObject;
+    }
+}

+ 119 - 0
core/src/main/java/org/acegisecurity/afterinvocation/AclEntryAfterInvocationProvider.java

@@ -0,0 +1,119 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.afterinvocation;
+
+import org.acegisecurity.AccessDeniedException;
+import org.acegisecurity.AcegiMessageSource;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.ConfigAttribute;
+import org.acegisecurity.ConfigAttributeDefinition;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.Permission;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
+import java.util.Iterator;
+
+
+/**
+ * <p>Given a domain object instance returned from a secure object invocation, ensures the principal has
+ * appropriate permission as defined by the {@link AclService}.</p>
+ *  <p>The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with a
+ * domain object instance for the current <code>Authentication</code> object.</p>
+ *  <p>This after invocation provider will fire if any  {@link ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then lookup the ACLs from the <code>AclService</code> and ensure the
+ * principal is {@link Acl#isGranted(org.acegisecurity.acls.Permission[], org.acegisecurity.acls.sid.Sid[], boolean)}
+ * when presenting the {@link #requirePermission} array to that method.</p>
+ *  <p>Often users will setup an <code>AclEntryAfterInvocationProvider</code> with a {@link
+ * #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a {@link #requirePermission} of
+ * <code>BasePermission.READ</code>. These are also the defaults.</p>
+ *  <p>If the principal does not have sufficient permissions, an <code>AccessDeniedException</code> will be thrown.</p>
+ *  <p>If the provided <code>returnObject</code> is <code>null</code>, permission will always be granted and
+ * <code>null</code> will be returned.</p>
+ *  <p>All comparisons and prefixes are case sensitive.</p>
+ */
+public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements MessageSourceAware {
+    //~ Static fields/initializers =====================================================================================
+
+    protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationProvider.class);
+
+    //~ Instance fields ================================================================================================
+
+    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
+
+    //~ Constructors ===================================================================================================
+
+    public AclEntryAfterInvocationProvider(AclService aclService, Permission[] requirePermission) {
+        super(aclService, "AFTER_ACL_READ", requirePermission);
+    }
+
+    //~ Methods ========================================================================================================
+
+    public Object decide(Authentication authentication, Object object, ConfigAttributeDefinition config,
+        Object returnedObject) throws AccessDeniedException {
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attr = (ConfigAttribute) iter.next();
+
+            if (this.supports(attr)) {
+                // Need to make an access decision on this invocation
+                if (returnedObject == null) {
+                    // AclManager interface contract prohibits nulls
+                    // As they have permission to null/nothing, grant access
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Return object is null, skipping");
+                    }
+
+                    return null;
+                }
+
+                if (!getProcessDomainObjectClass().isAssignableFrom(returnedObject.getClass())) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Return object is not applicable for this provider, skipping");
+                    }
+
+                    return null;
+                }
+
+                if (hasPermission(authentication, returnedObject)) {
+                    return returnedObject;
+                } else {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Denying access");
+                    }
+
+                    throw new AccessDeniedException(messages.getMessage(
+                            "BasicAclEntryAfterInvocationProvider.noPermission",
+                            new Object[] {authentication.getName(), returnedObject},
+                            "Authentication {0} has NO permissions to the domain object {1}"));
+                }
+            }
+        }
+
+        return returnedObject;
+    }
+
+    public void setMessageSource(MessageSource messages) {
+        this.messages = new MessageSourceAccessor(messages);
+    }
+}

+ 101 - 0
core/src/main/java/org/acegisecurity/afterinvocation/ArrayFilterer.java

@@ -0,0 +1,101 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.afterinvocation;
+
+import org.apache.commons.collections.iterators.ArrayIterator;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.lang.reflect.Array;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+
+/**
+ * A filter used to filter arrays.
+ *
+ * @author Ben Alex
+ * @author Paulo Neves
+ * @version $Id$
+ */
+class ArrayFilterer implements Filterer {
+    //~ Static fields/initializers =====================================================================================
+
+    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
+
+    //~ Instance fields ================================================================================================
+
+    private Set removeList;
+    private Object[] list;
+
+    //~ Constructors ===================================================================================================
+
+    ArrayFilterer(Object[] list) {
+        this.list = list;
+
+        // Collect the removed objects to a HashSet so that
+        // it is fast to lookup them when a filtered array
+        // is constructed.
+        removeList = new HashSet();
+    }
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * 
+     * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
+     */
+    public Object getFilteredObject() {
+        // Recreate an array of same type and filter the removed objects.
+        int originalSize = list.length;
+        int sizeOfResultingList = originalSize - removeList.size();
+        Object[] filtered = (Object[]) Array.newInstance(list.getClass().getComponentType(), sizeOfResultingList);
+
+        for (int i = 0, j = 0; i < list.length; i++) {
+            Object object = list[i];
+
+            if (!removeList.contains(object)) {
+                filtered[j] = object;
+                j++;
+            }
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Original array contained " + originalSize + " elements; now contains " + sizeOfResultingList
+                + " elements");
+        }
+
+        return filtered;
+    }
+
+    /**
+     * 
+     * @see org.acegisecurity.afterinvocation.Filterer#iterator()
+     */
+    public Iterator iterator() {
+        return new ArrayIterator(list);
+    }
+
+    /**
+     * 
+     * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
+     */
+    public void remove(Object object) {
+        removeList.add(object);
+    }
+}

+ 26 - 208
core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java

@@ -12,7 +12,6 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package org.acegisecurity.afterinvocation;
 package org.acegisecurity.afterinvocation;
 
 
 import org.acegisecurity.AccessDeniedException;
 import org.acegisecurity.AccessDeniedException;
@@ -26,7 +25,6 @@ import org.acegisecurity.acl.AclManager;
 import org.acegisecurity.acl.basic.BasicAclEntry;
 import org.acegisecurity.acl.basic.BasicAclEntry;
 import org.acegisecurity.acl.basic.SimpleAclEntry;
 import org.acegisecurity.acl.basic.SimpleAclEntry;
 
 
-import org.apache.commons.collections.iterators.ArrayIterator;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.logging.LogFactory;
 
 
@@ -34,12 +32,8 @@ import org.springframework.beans.factory.InitializingBean;
 
 
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
-import java.lang.reflect.Array;
-
 import java.util.Collection;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Iterator;
-import java.util.Set;
 
 
 
 
 /**
 /**
@@ -135,42 +129,40 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider implements
 
 
                     boolean hasPermission = false;
                     boolean hasPermission = false;
 
 
-                    AclEntry[] acls = null;
-
                     if (domainObject == null) {
                     if (domainObject == null) {
                         hasPermission = true;
                         hasPermission = true;
                     } else if (!processDomainObjectClass.isAssignableFrom(domainObject.getClass())) {
                     } else if (!processDomainObjectClass.isAssignableFrom(domainObject.getClass())) {
                         hasPermission = true;
                         hasPermission = true;
                     } else {
                     } else {
-                        acls = aclManager.getAcls(domainObject, authentication);
-                    }
-
-                    if ((acls != null) && (acls.length != 0)) {
-                        for (int i = 0; i < acls.length; i++) {
-                            // Locate processable AclEntrys
-                            if (acls[i] instanceof BasicAclEntry) {
-                                BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
-
-                                // See if principal has any of the required permissions
-                                for (int y = 0; y < requirePermission.length; y++) {
-                                    if (processableAcl.isPermitted(requirePermission[y])) {
-                                        hasPermission = true;
-
-                                        if (logger.isDebugEnabled()) {
-                                            logger.debug("Principal is authorised for element: " + domainObject
-                                                + " due to ACL: " + processableAcl.toString());
+                        AclEntry[] acls = aclManager.getAcls(domainObject, authentication);
+
+                        if ((acls != null) && (acls.length != 0)) {
+                            for (int i = 0; i < acls.length; i++) {
+                                // Locate processable AclEntrys
+                                if (acls[i] instanceof BasicAclEntry) {
+                                    BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
+
+                                    // See if principal has any of the required permissions
+                                    for (int y = 0; y < requirePermission.length; y++) {
+                                        if (processableAcl.isPermitted(requirePermission[y])) {
+                                            hasPermission = true;
+
+                                            if (logger.isDebugEnabled()) {
+                                                logger.debug("Principal is authorised for element: " + domainObject
+                                                    + " due to ACL: " + processableAcl.toString());
+                                            }
                                         }
                                         }
                                     }
                                     }
                                 }
                                 }
                             }
                             }
                         }
                         }
-                    }
 
 
-                    if (!hasPermission) {
-                        filterer.remove(domainObject);
+                        if (!hasPermission) {
+                            filterer.remove(domainObject);
 
 
-                        if (logger.isDebugEnabled()) {
-                            logger.debug("Principal is NOT authorised for element: " + domainObject);
+                            if (logger.isDebugEnabled()) {
+                                logger.debug("Principal is NOT authorised for element: " + domainObject);
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
@@ -212,9 +204,11 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider implements
     }
     }
 
 
     /**
     /**
-     * Allow setting permissions with String literals instead of integers as {@link #setRequirePermission(int[])}
-     * 
+     * Allow setting permissions with String literals instead of integers as {@link
+     * #setRequirePermission(int[])}
+     *
      * @param requirePermission permission literals
      * @param requirePermission permission literals
+     *
      * @see SimpleAclEntry#parsePermissions(String[]) for valid values
      * @see SimpleAclEntry#parsePermissions(String[]) for valid values
      */
      */
     public void setRequirePermissionFromString(String[] requirePermission) {
     public void setRequirePermissionFromString(String[] requirePermission) {
@@ -240,179 +234,3 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider implements
         return true;
         return true;
     }
     }
 }
 }
-
-
-/**
- * Filter strategy interface.
- */
-interface Filterer {
-    //~ Methods ========================================================================================================
-
-    /**
-     * Gets the filtered collection or array.
-     *
-     * @return the filtered collection or array
-     */
-    public Object getFilteredObject();
-
-    /**
-     * Returns an iterator over the filtered collection or array.
-     *
-     * @return an Iterator
-     */
-    public Iterator iterator();
-
-    /**
-     * Removes the the given object from the resulting list.
-     *
-     * @param object the object to be removed
-     */
-    public void remove(Object object);
-}
-
-
-/**
- * A filter used to filter Collections.
- */
-class CollectionFilterer implements Filterer {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
-
-    //~ Instance fields ================================================================================================
-
-    private Collection collection;
-
-    // collectionIter offers significant performance optimisations (as
-    // per acegisecurity-developer mailing list conversation 19/5/05)
-    private Iterator collectionIter;
-    private Set removeList;
-
-    //~ Constructors ===================================================================================================
-
-    CollectionFilterer(Collection collection) {
-        this.collection = collection;
-
-        // We create a Set of objects to be removed from the Collection,
-        // as ConcurrentModificationException prevents removal during
-        // iteration, and making a new Collection to be returned is
-        // problematic as the original Collection implementation passed
-        // to the method may not necessarily be re-constructable (as
-        // the Collection(collection) constructor is not guaranteed and
-        // manually adding may lose sort order or other capabilities)
-        removeList = new HashSet();
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * 
-     * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
-     */
-    public Object getFilteredObject() {
-        // Now the Iterator has ended, remove Objects from Collection
-        Iterator removeIter = removeList.iterator();
-
-        int originalSize = collection.size();
-
-        while (removeIter.hasNext()) {
-            collection.remove(removeIter.next());
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Original collection contained " + originalSize + " elements; now contains "
-                + collection.size() + " elements");
-        }
-
-        return collection;
-    }
-
-    /**
-     * 
-     * @see org.acegisecurity.afterinvocation.Filterer#iterator()
-     */
-    public Iterator iterator() {
-        collectionIter = collection.iterator();
-
-        return collectionIter;
-    }
-
-    /**
-     * 
-     * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
-     */
-    public void remove(Object object) {
-        removeList.add(object);
-    }
-}
-
-
-/**
- * A filter used to filter arrays.
- */
-class ArrayFilterer implements Filterer {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
-
-    //~ Instance fields ================================================================================================
-
-    private Set removeList;
-    private Object[] list;
-
-    //~ Constructors ===================================================================================================
-
-    ArrayFilterer(Object[] list) {
-        this.list = list;
-
-        // Collect the removed objects to a HashSet so that
-        // it is fast to lookup them when a filtered array
-        // is constructed.
-        removeList = new HashSet();
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * 
-     * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
-     */
-    public Object getFilteredObject() {
-        // Recreate an array of same type and filter the removed objects.
-        int originalSize = list.length;
-        int sizeOfResultingList = originalSize - removeList.size();
-        Object[] filtered = (Object[]) Array.newInstance(list.getClass().getComponentType(), sizeOfResultingList);
-
-        for (int i = 0, j = 0; i < list.length; i++) {
-            Object object = list[i];
-
-            if (!removeList.contains(object)) {
-                filtered[j] = object;
-                j++;
-            }
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Original array contained " + originalSize + " elements; now contains " + sizeOfResultingList
-                + " elements");
-        }
-
-        return filtered;
-    }
-
-    /**
-     * 
-     * @see org.acegisecurity.afterinvocation.Filterer#iterator()
-     */
-    public Iterator iterator() {
-        return new ArrayIterator(list);
-    }
-
-    /**
-     * 
-     * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
-     */
-    public void remove(Object object) {
-        removeList.add(object);
-    }
-}

+ 104 - 0
core/src/main/java/org/acegisecurity/afterinvocation/CollectionFilterer.java

@@ -0,0 +1,104 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.afterinvocation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+
+/**
+ * A filter used to filter Collections.
+ *
+ * @author Ben Alex
+ * @author Paulo Neves
+ * @version $Id$
+ */
+class CollectionFilterer implements Filterer {
+    //~ Static fields/initializers =====================================================================================
+
+    protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class);
+
+    //~ Instance fields ================================================================================================
+
+    private Collection collection;
+
+    // collectionIter offers significant performance optimisations (as
+    // per acegisecurity-developer mailing list conversation 19/5/05)
+    private Iterator collectionIter;
+    private Set removeList;
+
+    //~ Constructors ===================================================================================================
+
+    CollectionFilterer(Collection collection) {
+        this.collection = collection;
+
+        // We create a Set of objects to be removed from the Collection,
+        // as ConcurrentModificationException prevents removal during
+        // iteration, and making a new Collection to be returned is
+        // problematic as the original Collection implementation passed
+        // to the method may not necessarily be re-constructable (as
+        // the Collection(collection) constructor is not guaranteed and
+        // manually adding may lose sort order or other capabilities)
+        removeList = new HashSet();
+    }
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * 
+     * @see org.acegisecurity.afterinvocation.Filterer#getFilteredObject()
+     */
+    public Object getFilteredObject() {
+        // Now the Iterator has ended, remove Objects from Collection
+        Iterator removeIter = removeList.iterator();
+
+        int originalSize = collection.size();
+
+        while (removeIter.hasNext()) {
+            collection.remove(removeIter.next());
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Original collection contained " + originalSize + " elements; now contains "
+                + collection.size() + " elements");
+        }
+
+        return collection;
+    }
+
+    /**
+     * 
+     * @see org.acegisecurity.afterinvocation.Filterer#iterator()
+     */
+    public Iterator iterator() {
+        collectionIter = collection.iterator();
+
+        return collectionIter;
+    }
+
+    /**
+     * 
+     * @see org.acegisecurity.afterinvocation.Filterer#remove(java.lang.Object)
+     */
+    public void remove(Object object) {
+        removeList.add(object);
+    }
+}

+ 51 - 0
core/src/main/java/org/acegisecurity/afterinvocation/Filterer.java

@@ -0,0 +1,51 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.afterinvocation;
+
+import java.util.Iterator;
+
+
+/**
+ * Filter strategy interface.
+ * 
+ * @author Ben Alex
+ * @author Paulo Neves
+ * @version $Id$
+ */
+interface Filterer {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Gets the filtered collection or array.
+     *
+     * @return the filtered collection or array
+     */
+    public Object getFilteredObject();
+
+    /**
+     * Returns an iterator over the filtered collection or array.
+     *
+     * @return an Iterator
+     */
+    public Iterator iterator();
+
+    /**
+     * Removes the the given object from the resulting list.
+     *
+     * @param object the object to be removed
+     */
+    public void remove(Object object);
+}

+ 223 - 0
core/src/main/java/org/acegisecurity/taglibs/authz/AccessControlListTag.java

@@ -0,0 +1,223 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.taglibs.authz;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategy;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
+import org.acegisecurity.acls.sid.Sid;
+import org.acegisecurity.acls.sid.SidRetrievalStrategy;
+import org.acegisecurity.acls.sid.SidRetrievalStrategyImpl;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.ApplicationContext;
+
+import org.springframework.web.context.support.WebApplicationContextUtils;
+import org.springframework.web.util.ExpressionEvaluationUtils;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.Tag;
+import javax.servlet.jsp.tagext.TagSupport;
+
+
+/**
+ * An implementation of {@link javax.servlet.jsp.tagext.Tag} that allows its body through if some authorizations
+ * are granted to the request's principal.<p>One or more comma separate numeric are specified via the
+ * <code>hasPermission</code> attribute. Those permissions are then converted into {@link Permission} instances. These
+ * instances are then presented as an array to the {@link Acl#isGranted(Permission[],
+ * org.acegisecurity.acls.sid.Sid[], boolean)} method. The {@link Sid} presented is determined by the {@link
+ * SidRetrievalStrategy}.</p>
+ *  <p>For this class to operate it must be able to access the application context via the
+ * <code>WebApplicationContextUtils</code> and locate an {@link AclService} and {@link SidRetrievalStrategy}.
+ * Application contexts must provide one and only one of these Java types.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AccessControlListTag extends TagSupport {
+    //~ Static fields/initializers =====================================================================================
+
+    protected static final Log logger = LogFactory.getLog(AccessControlListTag.class);
+
+    //~ Instance fields ================================================================================================
+
+    private AclService aclService;
+    private ApplicationContext applicationContext;
+    private Object domainObject;
+    private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy;
+    private SidRetrievalStrategy sidRetrievalStrategy;
+    private String hasPermission = "";
+
+    //~ Methods ========================================================================================================
+
+    public int doStartTag() throws JspException {
+        initializeIfRequired();
+
+        if ((null == hasPermission) || "".equals(hasPermission)) {
+            return Tag.SKIP_BODY;
+        }
+
+        final String evaledPermissionsString = ExpressionEvaluationUtils.evaluateString("hasPermission", hasPermission,
+                pageContext);
+
+        Permission[] requiredPermissions = null;
+
+        try {
+            requiredPermissions = parsePermissionsString(evaledPermissionsString);
+        } catch (NumberFormatException nfe) {
+            throw new JspException(nfe);
+        }
+
+        Object resolvedDomainObject = null;
+
+        if (domainObject instanceof String) {
+            resolvedDomainObject = ExpressionEvaluationUtils.evaluate("domainObject", (String) domainObject,
+                    Object.class, pageContext);
+        } else {
+            resolvedDomainObject = domainObject;
+        }
+
+        if (resolvedDomainObject == null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("domainObject resolved to null, so including tag body");
+            }
+
+            // Of course they have access to a null object!
+            return Tag.EVAL_BODY_INCLUDE;
+        }
+
+        if (SecurityContextHolder.getContext().getAuthentication() == null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug(
+                    "SecurityContextHolder did not return a non-null Authentication object, so skipping tag body");
+            }
+
+            return Tag.SKIP_BODY;
+        }
+
+        Sid[] sids = sidRetrievalStrategy.getSids(SecurityContextHolder.getContext().getAuthentication());
+        ObjectIdentity oid = objectIdentityRetrievalStrategy.getObjectIdentity(resolvedDomainObject);
+
+        // Obtain aclEntrys applying to the current Authentication object
+        try {
+            Acl acl = aclService.readAclById(oid, sids);
+
+            if (acl.isGranted(requiredPermissions, sids, false)) {
+                return Tag.EVAL_BODY_INCLUDE;
+            } else {
+                return Tag.SKIP_BODY;
+            }
+        } catch (NotFoundException nfe) {
+            return Tag.SKIP_BODY;
+        }
+    }
+
+    /**
+     * Allows test cases to override where application context obtained from.
+     *
+     * @param pageContext so the <code>ServletContext</code> can be accessed as required by Spring's
+     *        <code>WebApplicationContextUtils</code>
+     *
+     * @return the Spring application context (never <code>null</code>)
+     */
+    protected ApplicationContext getContext(PageContext pageContext) {
+        ServletContext servletContext = pageContext.getServletContext();
+
+        return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
+    }
+
+    public Object getDomainObject() {
+        return domainObject;
+    }
+
+    public String getHasPermission() {
+        return hasPermission;
+    }
+
+    private void initializeIfRequired() throws JspException {
+        if (applicationContext == null) {
+            this.applicationContext = getContext(pageContext);
+
+            Map map = applicationContext.getBeansOfType(AclService.class);
+
+            if (map.size() != 1) {
+                throw new JspException(
+                    "Found incorrect number of AclService instances in application context - you must have only have one!");
+            }
+
+            aclService = (AclService) map.values().iterator().next();
+
+            map = applicationContext.getBeansOfType(SidRetrievalStrategy.class);
+
+            if (map.size() == 0) {
+                sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+            } else if (map.size() == 1) {
+                sidRetrievalStrategy = (SidRetrievalStrategy) map.values().iterator().next();
+            } else {
+                throw new JspException(
+                    "Found incorrect number of SidRetrievalStrategy instances in application context - you must have only have one!");
+            }
+
+            map = applicationContext.getBeansOfType(ObjectIdentityRetrievalStrategy.class);
+
+            if (map.size() == 0) {
+                objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+            } else if (map.size() == 1) {
+                objectIdentityRetrievalStrategy = (ObjectIdentityRetrievalStrategy) map.values().iterator().next();
+            } else {
+                throw new JspException(
+                    "Found incorrect number of ObjectIdentityRetrievalStrategy instances in application context - you must have only have one!");
+            }
+        }
+    }
+
+    private Permission[] parsePermissionsString(String integersString)
+        throws NumberFormatException {
+        final Set permissions = new HashSet();
+        final StringTokenizer tokenizer;
+        tokenizer = new StringTokenizer(integersString, ",", false);
+
+        while (tokenizer.hasMoreTokens()) {
+            String integer = tokenizer.nextToken();
+            permissions.add(BasePermission.buildFromMask(new Integer(integer).intValue()));
+        }
+
+        return (Permission[]) permissions.toArray(new Permission[] {});
+    }
+
+    public void setDomainObject(Object domainObject) {
+        this.domainObject = domainObject;
+    }
+
+    public void setHasPermission(String hasPermission) {
+        this.hasPermission = hasPermission;
+    }
+}

+ 1 - 5
core/src/main/java/org/acegisecurity/vote/AbstractAclVoter.java

@@ -12,13 +12,10 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package org.acegisecurity.vote;
 package org.acegisecurity.vote;
 
 
 import org.acegisecurity.AuthorizationServiceException;
 import org.acegisecurity.AuthorizationServiceException;
 
 
-import org.acegisecurity.acl.AclManager;
-
 import org.aopalliance.intercept.MethodInvocation;
 import org.aopalliance.intercept.MethodInvocation;
 
 
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.JoinPoint;
@@ -28,8 +25,7 @@ import org.springframework.util.Assert;
 
 
 
 
 /**
 /**
- * <p>Given a domain object instance passed as a method argument, ensures the principal has appropriate permission
- * as defined by the {@link AclManager}.</p>
+ * <p>Provides helper methods for writing domain object ACL voters. Is not bound to any particular ACL system.</p>
  *
  *
  * @author Ben Alex
  * @author Ben Alex
  * @version $Id$
  * @version $Id$

+ 251 - 0
core/src/main/java/org/acegisecurity/vote/AclEntryVoter.java

@@ -0,0 +1,251 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.vote;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthorizationServiceException;
+import org.acegisecurity.ConfigAttribute;
+import org.acegisecurity.ConfigAttributeDefinition;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategy;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategyImpl;
+import org.acegisecurity.acls.sid.Sid;
+import org.acegisecurity.acls.sid.SidRetrievalStrategy;
+import org.acegisecurity.acls.sid.SidRetrievalStrategyImpl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import java.util.Iterator;
+
+
+/**
+ * <p>Given a domain object instance passed as a method argument, ensures the principal has appropriate permission
+ * as indicated by the {@link AclService}.</p>
+ *  <p>The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with a
+ * domain object instance for the current <code>Authentication</code> object.</p>
+ *  <p>The voter will vote if any  {@link ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then locate the first method argument of type {@link
+ * #processDomainObjectClass}. Assuming that method argument is non-null, the provider will then lookup the ACLs from
+ * the <code>AclManager</code> and ensure the principal is  {@link Acl#isGranted(org.acegisecurity.acls.Permission[],
+ * org.acegisecurity.acls.sid.Sid[], boolean)}  when presenting the {@link #requirePermission} array to that method.</p>
+ *  <p>If the method argument is <code>null</code>, the voter will abstain from voting. If the method argument
+ * could not be found, an {@link org.acegisecurity.AuthorizationServiceException} will be thrown.</p>
+ *  <p>In practical terms users will typically setup a number of <code>AclEntryVoter</code>s. Each will have a
+ * different {@link #processDomainObjectClass}, {@link #processConfigAttribute} and {@link #requirePermission}
+ * combination. For example, a small application might employ the following instances of <code>AclEntryVoter</code>:
+ *  <ul>
+ *      <li>Process domain object class <code>BankAccount</code>, configuration attribute
+ *      <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission <code>BasePermission.READ</code></li>
+ *      <li>Process domain object class <code>BankAccount</code>, configuration attribute
+ *      <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
+ *      <code>BasePermission.CREATE</code> (allowing the principal to have <b>either</b> of these two permissions</li>
+ *      <li>Process domain object class <code>Customer</code>, configuration attribute
+ *      <code>VOTE_ACL_CUSTOMER_READ</code>, require permission <code>BasePermission.READ</code></li>
+ *      <li>Process domain object class <code>Customer</code>, configuration attribute
+ *      <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
+ *      <code>BasePermission.CREATE</code></li>
+ *  </ul>
+ *  Alternatively, you could have used a common superclass or interface for the {@link #processDomainObjectClass}
+ * if both <code>BankAccount</code> and <code>Customer</code> had common parents.</p>
+ *  <p>If the principal does not have sufficient permissions, the voter will vote to deny access.</p>
+ *  <p>All comparisons and prefixes are case sensitive.</p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AclEntryVoter extends AbstractAclVoter {
+    //~ Static fields/initializers =====================================================================================
+
+    private static final Log logger = LogFactory.getLog(AclEntryVoter.class);
+
+    //~ Instance fields ================================================================================================
+
+    private AclService aclService;
+    private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+    private String internalMethod;
+    private String processConfigAttribute;
+    private Permission[] requirePermission;
+
+    //~ Constructors ===================================================================================================
+
+    public AclEntryVoter(AclService aclService, String processConfigAttribute, Permission[] requirePermission) {
+        Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory");
+        Assert.notNull(aclService, "An AclService is mandatory");
+
+        if ((requirePermission == null) || (requirePermission.length == 0)) {
+            throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
+        }
+
+        this.aclService = aclService;
+        this.processConfigAttribute = processConfigAttribute;
+        this.requirePermission = requirePermission;
+    }
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * Optionally specifies a method of the domain object that will be used to obtain a contained domain
+     * object. That contained domain object will be used for the ACL evaluation. This is useful if a domain object
+     * contains a parent that an ACL evaluation should be targeted for, instead of the child domain object (which
+     * perhaps is being created and as such does not yet have any ACL permissions)
+     *
+     * @return <code>null</code> to use the domain object, or the name of a method (that requires no arguments) that
+     *         should be invoked to obtain an <code>Object</code> which will be the domain object used for ACL
+     *         evaluation
+     */
+    public String getInternalMethod() {
+        return internalMethod;
+    }
+
+    public String getProcessConfigAttribute() {
+        return processConfigAttribute;
+    }
+
+    public void setInternalMethod(String internalMethod) {
+        this.internalMethod = internalMethod;
+    }
+
+    public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
+        Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required");
+        this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
+    }
+
+    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+        Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
+        this.sidRetrievalStrategy = sidRetrievalStrategy;
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getProcessConfigAttribute())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attr = (ConfigAttribute) iter.next();
+
+            if (this.supports(attr)) {
+                // Need to make an access decision on this invocation
+                // Attempt to locate the domain object instance to process
+                Object domainObject = getDomainObjectInstance(object);
+
+                // If domain object is null, vote to abstain
+                if (domainObject == null) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Voting to abstain - domainObject is null");
+                    }
+
+                    return AccessDecisionVoter.ACCESS_ABSTAIN;
+                }
+
+                // Evaluate if we are required to use an inner domain object
+                if ((internalMethod != null) && !"".equals(internalMethod)) {
+                    try {
+                        Class clazz = domainObject.getClass();
+                        Method method = clazz.getMethod(internalMethod, new Class[] {});
+                        domainObject = method.invoke(domainObject, new Object[] {});
+                    } catch (NoSuchMethodException nsme) {
+                        throw new AuthorizationServiceException("Object of class '" + domainObject.getClass()
+                            + "' does not provide the requested internalMethod: " + internalMethod);
+                    } catch (IllegalAccessException iae) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("IllegalAccessException", iae);
+
+                            if (iae.getCause() != null) {
+                                logger.debug("Cause: " + iae.getCause().getMessage(), iae.getCause());
+                            }
+                        }
+
+                        throw new AuthorizationServiceException("Problem invoking internalMethod: " + internalMethod
+                            + " for object: " + domainObject);
+                    } catch (InvocationTargetException ite) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("InvocationTargetException", ite);
+
+                            if (ite.getCause() != null) {
+                                logger.debug("Cause: " + ite.getCause().getMessage(), ite.getCause());
+                            }
+                        }
+
+                        throw new AuthorizationServiceException("Problem invoking internalMethod: " + internalMethod
+                            + " for object: " + domainObject);
+                    }
+                }
+
+                // Obtain the OID applicable to the domain object
+                ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
+
+                // Obtain the SIDs applicable to the principal
+                Sid[] sids = sidRetrievalStrategy.getSids(authentication);
+
+                Acl acl;
+
+                try {
+                    // Lookup only ACLs for SIDs we're interested in
+                    acl = aclService.readAclById(objectIdentity, sids);
+                } catch (NotFoundException nfe) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Voting to deny access - no ACLs apply for this principal");
+                    }
+
+                    return AccessDecisionVoter.ACCESS_DENIED;
+                }
+
+                try {
+                    if (acl.isGranted(requirePermission, sids, false)) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Voting to grant access");
+                        }
+
+                        return AccessDecisionVoter.ACCESS_GRANTED;
+                    } else {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug(
+                                "Voting to deny access - ACLs returned, but insufficient permissions for this principal");
+                        }
+
+                        return AccessDecisionVoter.ACCESS_DENIED;
+                    }
+                } catch (NotFoundException nfe) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Voting to deny access - no ACLs apply for this principal");
+                    }
+
+                    return AccessDecisionVoter.ACCESS_DENIED;
+                }
+            }
+        }
+
+        // No configuration attribute matched, so abstain
+        return AccessDecisionVoter.ACCESS_ABSTAIN;
+    }
+}

+ 30 - 0
core/src/main/resources/org/acegisecurity/taglibs/authz.tld

@@ -113,4 +113,34 @@
 		</attribute>
 		</attribute>
 	</tag>
 	</tag>
 
 
+	<tag>
+		<name>accesscontrollist</name>
+		<tag-class>org.acegisecurity.taglibs.authz.AccessControlListTag</tag-class>
+		<description>
+            Allows inclusion of a tag body if the current Authentication
+			has one of the specified permissions to the presented
+			domain object instance.
+		</description>
+
+		<attribute>
+			<name>hasPermission</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+			<description>
+                A comma separated list of integers, each representing a
+				required bit mask permission from a subclass of
+				org.acegisecurity.acl.basic.AbstractBasicAclEntry.
+			</description>
+		</attribute>
+		<attribute>
+			<name>domainObject</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+			<description>
+                The actual domain object instance for which permissions
+				are being evaluated.
+			</description>
+		</attribute>
+	</tag>
+	
 </taglib>
 </taglib>

+ 14 - 2
sandbox/other/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java → core/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java

@@ -12,11 +12,12 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package org.acegisecurity.acls.domain;
 package org.acegisecurity.acls.domain;
 
 
 import junit.framework.TestCase;
 import junit.framework.TestCase;
 
 
+import org.acegisecurity.acls.Permission;
+
 
 
 /**
 /**
  * Tests BasePermission and CumulativePermission.
  * Tests BasePermission and CumulativePermission.
@@ -30,7 +31,18 @@ public class PermissionTests extends TestCase {
     public void testExpectedIntegerValues() {
     public void testExpectedIntegerValues() {
         assertEquals(1, BasePermission.READ.getMask());
         assertEquals(1, BasePermission.READ.getMask());
         assertEquals(16, BasePermission.ADMINISTRATION.getMask());
         assertEquals(16, BasePermission.ADMINISTRATION.getMask());
-        assertEquals(17, new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask());
+        assertEquals(7,
+            new CumulativePermission().set(BasePermission.READ).set(BasePermission.WRITE).set(BasePermission.CREATE)
+                                      .getMask());
+        assertEquals(17,
+            new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask());
+    }
+
+    public void testFromInteger() {
+        Permission permission = BasePermission.buildFromMask(7);
+        System.out.println("7 =  " + permission.toString());
+        permission = BasePermission.buildFromMask(4);
+        System.out.println("4 =  " + permission.toString());
     }
     }
 
 
     public void testStringConversion() {
     public void testStringConversion() {

+ 47 - 48
sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java → core/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java

@@ -1,48 +1,47 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import org.springframework.core.io.Resource;
-
-import org.springframework.jdbc.core.JdbcTemplate;
-
-import org.springframework.util.Assert;
-import org.springframework.util.FileCopyUtils;
-
-import java.io.IOException;
-
-import javax.sql.DataSource;
-
-
-/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
-  */
-public class DatabaseSeeder {
-    //~ Constructors ===================================================================================================
-
-    public DatabaseSeeder(DataSource dataSource, Resource resource)
-        throws IOException {
-        Assert.notNull(dataSource, "dataSource required");
-        Assert.notNull(resource, "resource required");
-
-        JdbcTemplate template = new JdbcTemplate(dataSource);
-        String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
-        template.execute(sql);
-    }
-}
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.springframework.core.io.Resource;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+
+import java.io.IOException;
+
+import javax.sql.DataSource;
+
+
+/**
+ * Seeds the database for {@link JdbcAclServiceTests}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class DatabaseSeeder {
+    //~ Constructors ===================================================================================================
+
+    public DatabaseSeeder(DataSource dataSource, Resource resource)
+        throws IOException {
+        Assert.notNull(dataSource, "dataSource required");
+        Assert.notNull(resource, "resource required");
+
+        JdbcTemplate template = new JdbcTemplate(dataSource);
+        String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
+        template.execute(sql);
+    }
+}

+ 222 - 0
core/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java

@@ -0,0 +1,222 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.acegisecurity.providers.TestingAuthenticationToken;
+
+import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
+
+import java.util.Map;
+
+
+/**
+ * Integration tests the ACL system using an in-memory database.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class JdbcAclServiceTests extends AbstractTransactionalDataSourceSpringContextTests {
+    //~ Instance fields ================================================================================================
+
+    private JdbcMutableAclService jdbcMutableAclService;
+
+    //~ Methods ========================================================================================================
+
+    protected String[] getConfigLocations() {
+        return new String[] {"classpath:org/acegisecurity/acls/jdbc/applicationContext-test.xml"};
+    }
+
+    public void setJdbcMutableAclService(JdbcMutableAclService jdbcAclService) {
+        this.jdbcMutableAclService = jdbcAclService;
+    }
+
+    public void testLifecycle() {
+        setComplete();
+
+        Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
+        auth.setAuthenticated(true);
+        SecurityContextHolder.getContext().setAuthentication(auth);
+
+        ObjectIdentity topParentOid = new ObjectIdentityImpl("sample.contact.Contact", new Long(100));
+        ObjectIdentity middleParentOid = new ObjectIdentityImpl("sample.contact.Contact", new Long(101));
+        ObjectIdentity childOid = new ObjectIdentityImpl("sample.contact.Contact", new Long(102));
+
+        MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
+        MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
+        MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+
+        // Specify the inheritence hierarchy
+        middleParent.setParent(topParent);
+        child.setParent(middleParent);
+
+        // Now let's add a couple of permissions
+        topParent.insertAce(null, BasePermission.READ, new PrincipalSid(auth), true);
+        topParent.insertAce(null, BasePermission.WRITE, new PrincipalSid(auth), false);
+        middleParent.insertAce(null, BasePermission.DELETE, new PrincipalSid(auth), true);
+        child.insertAce(null, BasePermission.DELETE, new PrincipalSid(auth), false);
+
+        // Explictly save the changed ACL
+        jdbcMutableAclService.updateAcl(topParent);
+        jdbcMutableAclService.updateAcl(middleParent);
+        jdbcMutableAclService.updateAcl(child);
+
+        // Let's check if we can read them back correctly
+        Map map = jdbcMutableAclService.readAclsById(new ObjectIdentity[] {topParentOid, middleParentOid, childOid});
+        assertEquals(3, map.size());
+
+        // Replace our current objects with their retrieved versions
+        topParent = (MutableAcl) map.get(topParentOid);
+        middleParent = (MutableAcl) map.get(middleParentOid);
+        child = (MutableAcl) map.get(childOid);
+
+        // Check the retrieved versions has IDs
+        assertNotNull(topParent.getId());
+        assertNotNull(middleParent.getId());
+        assertNotNull(child.getId());
+
+        // Check their parents were correctly persisted
+        assertNull(topParent.getParentAcl());
+        assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
+        assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
+
+        // Check their ACEs were correctly persisted
+        assertEquals(2, topParent.getEntries().length);
+        assertEquals(1, middleParent.getEntries().length);
+        assertEquals(1, child.getEntries().length);
+
+        // Check the retrieved rights are correct
+        assertTrue(topParent.isGranted(new Permission[] {BasePermission.READ}, new Sid[] {new PrincipalSid(auth)}, false));
+        assertFalse(topParent.isGranted(new Permission[] {BasePermission.WRITE}, new Sid[] {new PrincipalSid(auth)},
+                false));
+        assertTrue(middleParent.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)},
+                false));
+        assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, false));
+
+        try {
+            child.isGranted(new Permission[] {BasePermission.ADMINISTRATION}, new Sid[] {new PrincipalSid(auth)}, false);
+            fail("Should have thrown NotFoundException");
+        } catch (NotFoundException expected) {
+            assertTrue(true);
+        }
+
+        // Now check the inherited rights (when not explicitly overridden) also look OK
+        assertTrue(child.isGranted(new Permission[] {BasePermission.READ}, new Sid[] {new PrincipalSid(auth)}, false));
+        assertFalse(child.isGranted(new Permission[] {BasePermission.WRITE}, new Sid[] {new PrincipalSid(auth)}, false));
+        assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, false));
+
+        // Next change the child so it doesn't inherit permissions from above
+        child.setEntriesInheriting(false);
+        jdbcMutableAclService.updateAcl(child);
+        child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+        assertFalse(child.isEntriesInheriting());
+
+        // Check the child permissions no longer inherit
+        assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, true));
+
+        try {
+            child.isGranted(new Permission[] {BasePermission.READ}, new Sid[] {new PrincipalSid(auth)}, true);
+            fail("Should have thrown NotFoundException");
+        } catch (NotFoundException expected) {
+            assertTrue(true);
+        }
+
+        try {
+            child.isGranted(new Permission[] {BasePermission.WRITE}, new Sid[] {new PrincipalSid(auth)}, true);
+            fail("Should have thrown NotFoundException");
+        } catch (NotFoundException expected) {
+            assertTrue(true);
+        }
+
+        // Let's add an identical permission to the child, but it'll appear AFTER the current permission, so has no impact
+        child.insertAce(null, BasePermission.DELETE, new PrincipalSid(auth), true);
+
+        // Let's also add another permission to the child 
+        child.insertAce(null, BasePermission.CREATE, new PrincipalSid(auth), true);
+
+        // Save the changed child
+        jdbcMutableAclService.updateAcl(child);
+        child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+        assertEquals(3, child.getEntries().length);
+
+        // Output permissions
+        for (int i = 0; i < child.getEntries().length; i++) {
+            System.out.println(child.getEntries()[i]);
+        }
+
+        // Check the permissions are as they should be
+        assertFalse(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, true)); // as earlier permission overrode
+        assertTrue(child.isGranted(new Permission[] {BasePermission.CREATE}, new Sid[] {new PrincipalSid(auth)}, true));
+
+        // Now check the first ACE (index 0) really is DELETE for our Sid and is non-granting
+        AccessControlEntry entry = child.getEntries()[0];
+        assertEquals(BasePermission.DELETE.getMask(), entry.getPermission().getMask());
+        assertEquals(new PrincipalSid(auth), entry.getSid());
+        assertFalse(entry.isGranting());
+        assertNotNull(entry.getId());
+
+        // Now delete that first ACE
+        child.deleteAce(entry.getId());
+
+        // Save and check it worked
+        child = jdbcMutableAclService.updateAcl(child);
+        assertEquals(2, child.getEntries().length);
+        assertTrue(child.isGranted(new Permission[] {BasePermission.DELETE}, new Sid[] {new PrincipalSid(auth)}, false));
+
+        SecurityContextHolder.clearContext();
+    }
+
+/*    public void testCumulativePermissions() {
+   setComplete();
+   Authentication auth = new TestingAuthenticationToken("ben", "ignored", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ADMINISTRATOR")});
+   auth.setAuthenticated(true);
+   SecurityContextHolder.getContext().setAuthentication(auth);
+
+   ObjectIdentity topParentOid = new ObjectIdentityImpl("sample.contact.Contact", new Long(110));
+   MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
+
+   // Add an ACE permission entry
+   CumulativePermission cm = new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION);
+   assertEquals(17, cm.getMask());
+       topParent.insertAce(null, cm, new PrincipalSid(auth), true);
+       assertEquals(1, topParent.getEntries().length);
+
+       // Explictly save the changed ACL
+       topParent = jdbcMutableAclService.updateAcl(topParent);
+
+       // Check the mask was retrieved correctly
+       assertEquals(17, topParent.getEntries()[0].getPermission().getMask());
+       assertTrue(topParent.isGranted(new Permission[] {cm}, new Sid[] {new PrincipalSid(auth)}, true));
+
+       SecurityContextHolder.clearContext();
+   }
+ */
+}

+ 29 - 17
sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml → core/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml

@@ -15,6 +15,10 @@
 		<constructor-arg ref="dataSource"/>
 		<constructor-arg ref="dataSource"/>
 		<constructor-arg value="classpath:org/acegisecurity/acls/jdbc/testData.sql"/>
 		<constructor-arg value="classpath:org/acegisecurity/acls/jdbc/testData.sql"/>
 	</bean>
 	</bean>
+	
+	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+		<property name="dataSource" ref="dataSource"/>
+	</bean>
 	
 	
 	<bean id="aclCache" class="org.acegisecurity.acls.jdbc.EhCacheBasedAclCache">
 	<bean id="aclCache" class="org.acegisecurity.acls.jdbc.EhCacheBasedAclCache">
 		<constructor-arg>
 		<constructor-arg>
@@ -31,26 +35,33 @@
     
     
 	<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
 	<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
 		<constructor-arg ref="dataSource"/>
 		<constructor-arg ref="dataSource"/>
-		<constructor-arg ref="aclCache"/>
-		<constructor-arg>
-			<list>
-				<bean class="org.acegisecurity.GrantedAuthorityImpl">
-					<constructor-arg value="ROLE_ADMINISTRATOR"/>
-				</bean>
-				<bean class="org.acegisecurity.GrantedAuthorityImpl">
-					<constructor-arg value="ROLE_ADMINISTRATOR"/>
-				</bean>
-				<bean class="org.acegisecurity.GrantedAuthorityImpl">
-					<constructor-arg value="ROLE_ADMINISTRATOR"/>
-				</bean>
-			</list>
+		<constructor-arg ref="aclCache"/>
+		<constructor-arg ref="aclAuthorizationStrategy"/>
+		<constructor-arg>
+			<bean class="org.acegisecurity.acls.domain.ConsoleAuditLogger"/>
 		</constructor-arg>
 		</constructor-arg>
+	</bean>
+	
+	<bean id="aclAuthorizationStrategy" class="org.acegisecurity.acls.domain.AclAuthorizationStrategyImpl">
+		<constructor-arg>
+			<list>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+			</list>
+		</constructor-arg>
 	</bean>
 	</bean>
 	
 	
-	<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcAclService">
+	<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcMutableAclService">
 		<constructor-arg ref="dataSource"/>
 		<constructor-arg ref="dataSource"/>
-		<constructor-arg ref="aclCache"/>
-		<constructor-arg ref="lookupStrategy"/>
+		<constructor-arg ref="lookupStrategy"/>
+		<constructor-arg ref="aclCache"/>
 	</bean>
 	</bean>
 
 
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
@@ -58,7 +69,8 @@
             <value>org.hsqldb.jdbcDriver</value>
             <value>org.hsqldb.jdbcDriver</value>
         </property>
         </property>
         <property name="url">
         <property name="url">
-            <value>jdbc:hsqldb:mem:test</value>
+            <value>jdbc:hsqldb:mem:test</value>
+            <!--  <value>jdbc:hsqldb:hsql://localhost/acl</value> -->
         </property>
         </property>
         <property name="username">
         <property name="username">
             <value>sa</value>
             <value>sa</value>

+ 12 - 8
sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/select.sql → core/src/test/java/org/acegisecurity/acls/jdbc/select.sql

@@ -1,23 +1,27 @@
 -- Not required. Just shows the sort of queries being sent to DB.
 -- Not required. Just shows the sort of queries being sent to DB.
 
 
+
 select ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY, ACL_ENTRY.ACE_ORDER, 
 select ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY, ACL_ENTRY.ACE_ORDER, 
 ACL_OBJECT_IDENTITY.ID as ACL_ID,
 ACL_OBJECT_IDENTITY.ID as ACL_ID,
 ACL_OBJECT_IDENTITY.PARENT_OBJECT,
 ACL_OBJECT_IDENTITY.PARENT_OBJECT,
 ACL_OBJECT_IDENTITY,ENTRIES_INHERITING,
 ACL_OBJECT_IDENTITY,ENTRIES_INHERITING,
 ACL_ENTRY.ID as ACE_ID, ACL_ENTRY.MASK, ACL_ENTRY.GRANTING, ACL_ENTRY.AUDIT_SUCCESS, ACL_ENTRY.AUDIT_FAILURE,
 ACL_ENTRY.ID as ACE_ID, ACL_ENTRY.MASK, ACL_ENTRY.GRANTING, ACL_ENTRY.AUDIT_SUCCESS, ACL_ENTRY.AUDIT_FAILURE,
-ACE_SID.PRINCIPAL as ACE_PRINCIPAL, ACE_SID.SID as ACE_SID,
-ACL_SID.PRINCIPAL as ACL_PRINCIPAL, ACL_SID.SID as ACL_SID,
+ACL_SID.PRINCIPAL as ACE_PRINCIPAL, ACL_SID.SID as ACE_SID,
+ACLI_SID.PRINCIPAL as ACL_PRINCIPAL, ACLI_SID.SID as ACL_SID,
 ACL_CLASS.CLASS
 ACL_CLASS.CLASS
-from ACL_OBJECT_IDENTITY, ACL_ENTRY, ACL_SID ACE_SID, ACL_SID ACL_SID, ACL_CLASS
-where ACL_ENTRY.ACL_OBJECT_IDENTITY = ACL_OBJECT_IDENTITY.ID
 
 
-and ACE_SID.ID = ACL_ENTRY.SID
-and ACL_SID.ID = ACL_OBJECT_IDENTITY.OWNER_SID
+from ACL_OBJECT_IDENTITY, ACL_SID ACLI_SID, ACL_CLASS
+LEFT JOIN ACL_ENTRY ON ACL_OBJECT_IDENTITY.ID = ACL_ENTRY.ACL_OBJECT_IDENTITY
+LEFT JOIN ACL_SID ON ACL_ENTRY.SID = ACL_SID.ID
+
+where 
+
+ACLI_SID.ID = ACL_OBJECT_IDENTITY.OWNER_SID
 and ACL_CLASS.ID = ACL_OBJECT_IDENTITY.OBJECT_ID_CLASS
 and ACL_CLASS.ID = ACL_OBJECT_IDENTITY.OBJECT_ID_CLASS
 and (
 and (
 (ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 1 
 (ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 1 
 and ACL_CLASS.CLASS = 'sample.contact.Contact')
 and ACL_CLASS.CLASS = 'sample.contact.Contact')
 or 
 or 
-(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 2
+(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 2000
 and ACL_CLASS.CLASS = 'sample.contact.Contact')
 and ACL_CLASS.CLASS = 'sample.contact.Contact')
-) order by ACL_ENTRY.ACL_OBJECT_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc
+) order by ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc

+ 45 - 0
core/src/test/java/org/acegisecurity/acls/jdbc/testData.sql

@@ -0,0 +1,45 @@
+-- Injected into DatabaseSeeder via applicationContext-test.xml (see test case JdbcAclServiceTests)
+
+-- DROP TABLE ACL_ENTRY;
+-- DROP TABLE ACL_OBJECT_IDENTITY;
+-- DROP TABLE ACL_CLASS;
+-- DROP TABLE ACL_SID;
+
+
+CREATE TABLE ACL_SID(
+ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
+PRINCIPAL BOOLEAN NOT NULL,
+SID VARCHAR_IGNORECASE(100) NOT NULL,
+CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));
+
+CREATE TABLE ACL_CLASS(
+ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
+CLASS VARCHAR_IGNORECASE(100) NOT NULL,
+CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));
+
+INSERT INTO ACL_CLASS VALUES (1, 'sample.contact.Contact');
+
+CREATE TABLE ACL_OBJECT_IDENTITY(
+ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
+OBJECT_ID_CLASS BIGINT NOT NULL,
+OBJECT_ID_IDENTITY BIGINT NOT NULL,
+PARENT_OBJECT BIGINT,
+OWNER_SID BIGINT,
+ENTRIES_INHERITING BOOLEAN NOT NULL,
+CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),
+CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),
+CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),
+CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));
+
+CREATE TABLE ACL_ENTRY(
+ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
+ACL_OBJECT_IDENTITY BIGINT NOT NULL,
+ACE_ORDER INT NOT NULL,
+SID BIGINT NOT NULL,
+MASK INTEGER NOT NULL,
+GRANTING BOOLEAN NOT NULL,
+AUDIT_SUCCESS BOOLEAN NOT NULL,
+AUDIT_FAILURE BOOLEAN NOT NULL,
+CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),
+CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),
+CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));

+ 291 - 15
doc/docbook/acegi.xml

@@ -4702,9 +4702,97 @@ public boolean supports(Class clazz);</programlisting></para>
         <sect2 id="after-invocation-acl-aware">
         <sect2 id="after-invocation-acl-aware">
           <title>ACL-Aware AfterInvocationProviders</title>
           <title>ACL-Aware AfterInvocationProviders</title>
 
 
-          <para>TODO: This section will be removed when we deprecate the
-          existing ACL package. It should be discussed with the context of the
-          ACL implementation chapter instead.</para>
+          <para>PLEASE NOTE: Acegi Security 1.0.3 contains a preview of a new
+          ACL module. The new ACL module is a significant rewrite of the
+          existing ACL module. The new module can be found under the
+          <literal>org.acegisecurity.acls</literal> package, with the old ACL
+          module under <literal>org.acegisecurity.acl</literal>. We encourage
+          users to consider testing with the new ACL module and build
+          applications with it. The old ACL module should be considered
+          deprecated and may be removed from a future release. The following
+          information relates to the new ACL package, and is thus
+          recommended.</para>
+
+          <para>A common services layer method we've all written at one stage
+          or another looks like this:</para>
+
+          <para><programlisting>public Contact getById(Integer id);</programlisting></para>
+
+          <para>Quite often, only principals with permission to read the
+          <literal>Contact</literal> should be allowed to obtain it. In this
+          situation the <literal>AccessDecisionManager</literal> approach
+          provided by the <literal>AbstractSecurityInterceptor</literal> will
+          not suffice. This is because the identity of the
+          <literal>Contact</literal> is all that is available before the
+          secure object is invoked. The
+          <literal>AclAfterInvocationProvider</literal> delivers a solution,
+          and is configured as follows:</para>
+
+          <para><programlisting>&lt;bean id="afterAclRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationProvider"&gt;
+  &lt;constructor-arg&gt;
+    &lt;ref bean="aclService"/&gt;
+  &lt;/constructor-arg&gt;
+  &lt;constructor-arg&gt;
+    &lt;list&gt;
+      &lt;ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/&gt;
+      &lt;ref local="org.acegisecurity.acls.domain.BasePermission.READ"/&gt;
+    &lt;/list&gt;
+  &lt;/constructor-arg&gt;
+&lt;/bean&gt;      </programlisting></para>
+
+          <para>In the above example, the <literal>Contact</literal> will be
+          retrieved and passed to the
+          <literal>AclEntryAfterInvocationProvider</literal>. The provider
+          will thrown an <literal>AccessDeniedException</literal> if one of
+          the listed <literal>requirePermission</literal>s is not held by the
+          <literal>Authentication</literal>. The
+          <literal>AclEntryAfterInvocationProvider</literal> queries the
+          <literal>Acl</literal>Service to determine the ACL that applies for
+          this domain object to this <literal>Authentication</literal>.</para>
+
+          <para>Similar to the
+          <literal>AclEntryAfterInvocationProvider</literal> is
+          <literal>AclEntryAfterInvocationCollectionFilteringProvider</literal>.
+          It is designed to remove <literal>Collection</literal> or array
+          elements for which a principal does not have access. It never thrown
+          an <literal>AccessDeniedException</literal> - simply silently
+          removes the offending elements. The provider is configured as
+          follows:</para>
+
+          <para><programlisting>&lt;bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider"&gt;
+  &lt;constructor-arg&gt;
+    &lt;ref bean="aclService"/&gt;
+  &lt;/constructor-arg&gt;
+  &lt;constructor-arg&gt;
+    &lt;list&gt;
+      &lt;ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/&gt;
+      &lt;ref local="org.acegisecurity.acls.domain.BasePermission.READ"/&gt;
+    &lt;/list&gt;
+  &lt;/constructor-arg&gt;
+&lt;/bean&gt;    </programlisting></para>
+
+          <para>As you can imagine, the returned <literal>Object</literal>
+          must be a <literal>Collection</literal> or array for this provider
+          to operate. It will remove any element if the
+          <literal>AclManager</literal> indicates the
+          <literal>Authentication</literal> does not hold one of the listed
+          <literal>requirePermission</literal>s.</para>
+
+          <para>The Contacts sample application demonstrates these two
+          <literal>AfterInvocationProvider</literal>s.</para>
+        </sect2>
+
+        <sect2 id="after-invocation-acl-aware-old">
+          <title>ACL-Aware AfterInvocationProviders (old ACL module)</title>
+
+          <para>PLEASE NOTE: Acegi Security 1.0.3 contains a preview of a new
+          ACL module. The new ACL module is a significant rewrite of the
+          existing ACL module. The new module can be found under the
+          <literal>org.acegisecurity.acls</literal> package, with the old ACL
+          module under <literal>org.acegisecurity.acl</literal>. We encourage
+          users to consider testing with the new ACL module and build
+          applications with it. The old ACL module should be considered
+          deprecated and may be removed from a future release.</para>
 
 
           <para>A common services layer method we've all written at one stage
           <para>A common services layer method we've all written at one stage
           or another looks like this:</para>
           or another looks like this:</para>
@@ -4838,25 +4926,28 @@ public boolean supports(Class clazz);</programlisting></para>
         <literal>ifAllGranted</literal>, and finally, <literal>if
         <literal>ifAllGranted</literal>, and finally, <literal>if
         AnyGranted</literal>.</para>
         AnyGranted</literal>.</para>
 
 
-        <para><literal>AclTag</literal> is used to include content if the
-        current principal has a ACL to the indicated domain object.</para>
+        <para><literal>AccessControlListTag</literal> is used to include
+        content if the current principal has an ACL to the indicated domain
+        object.</para>
 
 
         <para>The following JSP fragment illustrates how to use the
         <para>The following JSP fragment illustrates how to use the
-        <literal>AclTag</literal>:</para>
+        <literal>AccessControlListTag</literal>:</para>
 
 
-        <para><programlisting>
-&lt;authz:acl domainObject="${contact}" hasPermission="16,1"&gt;
+        <para><programlisting>&lt;authz:accesscontrollist domainObject="${contact}" hasPermission="8,16"&gt;
   &lt;td&gt;&lt;A HREF="&lt;c:url value="del.htm"&gt;&lt;c:param name="contactId" value="${contact.id}"/&gt;&lt;/c:url&gt;"&gt;Del&lt;/A&gt;&lt;/td&gt;
   &lt;td&gt;&lt;A HREF="&lt;c:url value="del.htm"&gt;&lt;c:param name="contactId" value="${contact.id}"/&gt;&lt;/c:url&gt;"&gt;Del&lt;/A&gt;&lt;/td&gt;
-&lt;/authz:acl&gt;
-
-          </programlisting></para>
+&lt;/authz:accesscontrollist&gt;</programlisting></para>
 
 
         <para>This tag would cause the tag's body to be output if the
         <para>This tag would cause the tag's body to be output if the
         principal holds either permission 16 or permission 1 for the "contact"
         principal holds either permission 16 or permission 1 for the "contact"
         domain object. The numbers are actually integers that are used with
         domain object. The numbers are actually integers that are used with
-        <literal>AbstractBasicAclEntry</literal> bit masking. Please refer to
-        the ACL section of this reference guide to understand more about the
-        ACL capabilities of Acegi Security</para>
+        <literal>BasePermission</literal> bit masking. Please refer to the ACL
+        section of this reference guide to understand more about the ACL
+        capabilities of Acegi Security.</para>
+
+        <para><literal>AclTag</literal> is part of the old ACL module and
+        should be considered deprecated. For the sake of historical reference,
+        works exactly the samae as
+        <literal>AccessControlListTag</literal>.</para>
       </sect1>
       </sect1>
     </chapter>
     </chapter>
 
 
@@ -5371,6 +5462,191 @@ public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
       <section id="domain-acls-overview">
       <section id="domain-acls-overview">
         <title>Overview</title>
         <title>Overview</title>
 
 
+        <para>PLEASE NOTE: Acegi Security 1.0.3 contains a preview of a new
+        ACL module. The new ACL module is a significant rewrite of the
+        existing ACL module. The new module can be found under the
+        <literal>org.acegisecurity.acls</literal> package, with the old ACL
+        module under <literal>org.acegisecurity.acl</literal>. We encourage
+        users to consider testing with the new ACL module and build
+        applications with it. The old ACL module should be considered
+        deprecated and may be removed from a future release.</para>
+
+        <para>Complex applications often will find the need to define access
+        permissions not simply at a web request or method invocation level.
+        Instead, security decisions need to comprise both who
+        (<literal>Authentication</literal>), where
+        (<literal>MethodInvocation</literal>) and what
+        (<literal>SomeDomainObject</literal>). In other words, authorization
+        decisions also need to consider the actual domain object instance
+        subject of a method invocation.</para>
+
+        <para>Imagine you're designing an application for a pet clinic. There
+        will be two main groups of users of your Spring-based application:
+        staff of the pet clinic, as well as the pet clinic's customers. The
+        staff will have access to all of the data, whilst your customers will
+        only be able to see their own customer records. To make it a little
+        more interesting, your customers can allow other users to see their
+        customer records, such as their "puppy preschool "mentor or president
+        of their local "Pony Club". Using Acegi Security as the foundation,
+        you have several approaches that can be used:<orderedlist>
+            <listitem>
+              <para>Write your business methods to enforce the security. You
+              could consult a collection within the
+              <literal>Customer</literal> domain object instance to determine
+              which users have access. By using the
+              <literal>SecurityContextHolder.getContext().getAuthentication()</literal>,
+              you'll be able to access the <literal>Authentication</literal>
+              object.</para>
+            </listitem>
+
+            <listitem>
+              <para>Write an <literal>AccessDecisionVoter</literal> to enforce
+              the security from the <literal>GrantedAuthority[]</literal>s
+              stored in the <literal>Authentication</literal> object. This
+              would mean your <literal>AuthenticationManager</literal> would
+              need to populate the <literal>Authentication</literal> with
+              custom <literal>GrantedAuthority</literal>[]s representing each
+              of the <literal>Customer</literal> domain object instances the
+              principal has access to.</para>
+            </listitem>
+
+            <listitem>
+              <para>Write an <literal>AccessDecisionVoter</literal> to enforce
+              the security and open the target <literal>Customer</literal>
+              domain object directly. This would mean your voter needs access
+              to a DAO that allows it to retrieve the
+              <literal>Customer</literal> object. It would then access the
+              <literal>Customer</literal> object's collection of approved
+              users and make the appropriate decision.</para>
+            </listitem>
+          </orderedlist></para>
+
+        <para>Each one of these approaches is perfectly legitimate. However,
+        the first couples your authorization checking to your business code.
+        The main problems with this include the enhanced difficulty of unit
+        testing and the fact it would be more difficult to reuse the
+        <literal>Customer</literal> authorization logic elsewhere. Obtaining
+        the <literal>GrantedAuthority[]</literal>s from the
+        <literal>Authentication</literal> object is also fine, but will not
+        scale to large numbers of <literal>Customer</literal>s. If a user
+        might be able to access 5,000 <literal>Customer</literal>s (unlikely
+        in this case, but imagine if it were a popular vet for a large Pony
+        Club!) the amount of memory consumed and time required to construct
+        the <literal>Authentication</literal> object would be undesirable. The
+        final method, opening the <literal>Customer</literal> directly from
+        external code, is probably the best of the three. It achieves
+        separation of concerns, and doesn't misuse memory or CPU cycles, but
+        it is still inefficient in that both the
+        <literal>AccessDecisionVoter</literal> and the eventual business
+        method itself will perform a call to the DAO responsible for
+        retrieving the <literal>Customer</literal> object. Two accesses per
+        method invocation is clearly undesirable. In addition, with every
+        approach listed you'll need to write your own access control list
+        (ACL) persistence and business logic from scratch.</para>
+
+        <para>Fortunately, there is another alternative, which we'll talk
+        about below.</para>
+      </section>
+
+      <section id="domain-acls-key-concepts">
+        <title>Key Concepts</title>
+
+        <para>The org.acegisecurity.acls package should be consulted for its
+        major interfaces. The key interfaces are:</para>
+
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para><literal>Acl</literal>: Every domain object has one and only
+            one <literal>Acl</literal> object, which internally holds the
+            <literal>AccessControlEntry</literal>s as well as knows the owner
+            of the <literal>Acl</literal>. An Acl does not refer directly to
+            the domain object, but instead to an
+            <literal>ObjectIdentity</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal><literal>AccessControlEntry</literal></literal>: An
+            Acl holds multiple <literal>AccessControlEntry</literal>s, which
+            are often abbreviated as ACEs in the framework. Each ACE refers to
+            a specific tuple of <literal>Permission</literal>,
+            <literal>Sid</literal> and <literal>Acl</literal>. An ACE can also
+            be granting or non-granting and contain audit settings.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>Permission</literal>: A permission represents an
+            immutable particular bit mask, and offers convenience functions
+            for bit masking and outputting information.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>Sid</literal>: The ACL module needs to refer to
+            principals and <literal>GrantedAuthority[]</literal>s. A level of
+            indirection is provided by the <literal>Sid</literal> interface.
+            Common classes include <literal>PrincipalSid</literal> (to
+            represent the principal inside an
+            <literal>Authentication</literal> object) and
+            <literal>GrantedAuthoritySid</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>ObjectIdentity</literal>: Each domain object is
+            represented internally within the ACL module by an
+            <literal>ObjectIdentity</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>AclService</literal>: Retrieves the
+            <literal>Acl</literal> applicable for a given
+            <literal>ObjectIdentity</literal>.</para>
+          </listitem>
+
+          <listitem>
+            <para><literal>MutableAclService</literal>: Allows a modified
+            <literal>Acl</literal> to be presented for persistence. It is not
+            essential to use this interface if you do not wish.</para>
+          </listitem>
+        </itemizedlist>
+
+        <para>The ACL module was based on extensive feedback from the user
+        community following real-world use of the original ACL module. This
+        feedback resulted in a rearchitecture of the ACL module to offer
+        significantly enhanced performance (particularly in the area of
+        database retrieval), significantly better encapsulation, higher
+        cohesion, and enhanced customisation points.</para>
+
+        <para>The Contacts Sample that ships with Acegi Security 1.0.3 offers
+        a demonstration of the new ACL module. Converting Contacts from using
+        the old module to the new module was relatively simple, and users of
+        the old ACL module will likely find their applications can be modified
+        with relatively little work.</para>
+
+        <para>We will document the new ACL module more fully with a subsequent
+        release. Please note that the new ACL module should be considered a
+        preview only (ie do not use in production without proper prior
+        testing), and there is a small chance there may be changes between
+        1.0.3 and 1.1.0 when it will become final. Nevertheless,
+        compatibility-affecting changes are considered quite unlikely,
+        especially given the module is already based on several years of
+        feedback from users of the original ACL module.</para>
+      </section>
+    </chapter>
+
+    <chapter id="domain-acls-old">
+      <title>Domain Object Security (old ACL module)</title>
+
+      <section id="domain-acls-overview-old">
+        <title>Overview</title>
+
+        <para>PLEASE NOTE: Acegi Security 1.0.3 contains a preview of a new
+        ACL module. The new ACL module is a significant rewrite of the
+        existing ACL module. The new module can be found under the
+        <literal>org.acegisecurity.acls</literal> package, with the old ACL
+        module under <literal>org.acegisecurity.acl</literal>. We encourage
+        users to consider testing with the new ACL module and build
+        applications with it. The old ACL module should be considered
+        deprecated and may be removed from a future release.</para>
+
         <para>Complex applications often will find the need to define access
         <para>Complex applications often will find the need to define access
         permissions not simply at a web request or method invocation level.
         permissions not simply at a web request or method invocation level.
         Instead, security decisions need to comprise both who
         Instead, security decisions need to comprise both who
@@ -5448,7 +5724,7 @@ public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
         about below.</para>
         about below.</para>
       </section>
       </section>
 
 
-      <section id="domain-acls-basic">
+      <section id="domain-acls-basic-old">
         <title>Basic ACL Package</title>
         <title>Basic ACL Package</title>
 
 
         <para>Please note that our Basic ACL services are currently being
         <para>Please note that our Basic ACL services are currently being

+ 1 - 1
project.xml

@@ -265,7 +265,7 @@
     <dependency>
     <dependency>
       <groupId>hsqldb</groupId>
       <groupId>hsqldb</groupId>
       <artifactId>hsqldb</artifactId>
       <artifactId>hsqldb</artifactId>
-      <version>1.7.3.0</version>
+      <version>1.8.0.4</version>
       <type>jar</type>
       <type>jar</type>
       <url>http://hsqldb.sourceforge.net/</url>
       <url>http://hsqldb.sourceforge.net/</url>
       <properties>
       <properties>

+ 50 - 37
samples/contacts-tiger/src/main/java/sample/contact/annotation/ContactManagerBackend.java

@@ -12,15 +12,20 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact.annotation;
 package sample.contact.annotation;
 
 
 import org.acegisecurity.Authentication;
 import org.acegisecurity.Authentication;
 
 
-import org.acegisecurity.acl.basic.AclObjectIdentity;
-import org.acegisecurity.acl.basic.BasicAclExtendedDao;
-import org.acegisecurity.acl.basic.NamedEntityObjectIdentity;
-import org.acegisecurity.acl.basic.SimpleAclEntry;
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.MutableAclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
 
 
 import org.acegisecurity.annotation.Secured;
 import org.acegisecurity.annotation.Secured;
 
 
@@ -54,28 +59,36 @@ import java.util.Random;
 public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
 public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
-    private BasicAclExtendedDao basicAclExtendedDao;
     private ContactDao contactDao;
     private ContactDao contactDao;
-    private int counter = 100;
+
+    // TODO: Assignment of annotations against class does not result in match in sample application
+    private MutableAclService mutableAclService;
+    private int counter = 1000;
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
     @Secured({"ACL_CONTACT_ADMIN"})
     @Secured({"ACL_CONTACT_ADMIN"})
-    public void addPermission(Contact contact, String recipient, Integer permission) {
-        SimpleAclEntry simpleAclEntry = new SimpleAclEntry();
-        simpleAclEntry.setAclObjectIdentity(makeObjectIdentity(contact));
-        simpleAclEntry.setMask(permission.intValue());
-        simpleAclEntry.setRecipient(recipient);
-        basicAclExtendedDao.create(simpleAclEntry);
+    public void addPermission(Contact contact, Sid recipient, Permission permission) {
+        MutableAcl acl;
+        ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
+
+        try {
+            acl = (MutableAcl) mutableAclService.readAclById(oid);
+        } catch (NotFoundException nfe) {
+            acl = mutableAclService.createAcl(oid);
+        }
+
+        acl.insertAce(null, permission, recipient, true);
+        mutableAclService.updateAcl(acl);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
-            logger.debug("Added permission " + permission + " for recipient " + recipient + " contact " + contact);
+            logger.debug("Added permission " + permission + " for Sid " + recipient + " contact " + contact);
         }
         }
     }
     }
 
 
     public void afterPropertiesSet() throws Exception {
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(contactDao, "contactDao required");
         Assert.notNull(contactDao, "contactDao required");
-        Assert.notNull(basicAclExtendedDao, "basicAclExtendedDao required");
+        Assert.notNull(mutableAclService, "mutableAclService required");
     }
     }
 
 
     @Secured({"ROLE_USER"})
     @Secured({"ROLE_USER"})
@@ -84,8 +97,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         contact.setId(new Long(counter++));
         contact.setId(new Long(counter++));
         contactDao.create(contact);
         contactDao.create(contact);
 
 
-        // Grant the current principal access to the contact 
-        addPermission(contact, getUsername(), new Integer(SimpleAclEntry.ADMINISTRATION));
+        // Grant the current principal administrative permission to the contact 
+        addPermission(contact, new PrincipalSid(getUsername()), BasePermission.ADMINISTRATION);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Created contact " + contact + " and granted admin permission to recipient " + getUsername());
             logger.debug("Created contact " + contact + " and granted admin permission to recipient " + getUsername());
@@ -97,7 +110,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         contactDao.delete(contact.getId());
         contactDao.delete(contact.getId());
 
 
         // Delete the ACL information as well
         // Delete the ACL information as well
-        basicAclExtendedDao.delete(makeObjectIdentity(contact));
+        ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
+        mutableAclService.deleteAcl(oid, false);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Deleted contact " + contact + " including ACL permissions");
             logger.debug("Deleted contact " + contact + " including ACL permissions");
@@ -105,8 +119,20 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
     }
     }
 
 
     @Secured({"ACL_CONTACT_ADMIN"})
     @Secured({"ACL_CONTACT_ADMIN"})
-    public void deletePermission(Contact contact, String recipient) {
-        basicAclExtendedDao.delete(makeObjectIdentity(contact), recipient);
+    public void deletePermission(Contact contact, Sid recipient, Permission permission) {
+        ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
+        MutableAcl acl = (MutableAcl) mutableAclService.readAclById(oid);
+
+        // Remove all permissions associated with this particular recipient (string equality to KISS)
+        AccessControlEntry[] entries = acl.getEntries();
+
+        for (int i = 0; i < entries.length; i++) {
+            if (entries[i].getSid().equals(recipient) && entries[i].getPermission().equals(permission)) {
+                acl.deleteAce(entries[i].getId());
+            }
+        }
+
+        mutableAclService.updateAcl(acl);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Deleted contact " + contact + " ACL permissions for recipient " + recipient);
             logger.debug("Deleted contact " + contact + " ACL permissions for recipient " + recipient);
@@ -131,15 +157,10 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         }
         }
 
 
         List list = contactDao.findAllPrincipals();
         List list = contactDao.findAllPrincipals();
-        list.addAll(contactDao.findAllRoles());
 
 
         return list;
         return list;
     }
     }
 
 
-    public BasicAclExtendedDao getBasicAclExtendedDao() {
-        return basicAclExtendedDao;
-    }
-
     @Secured({"ROLE_USER", "AFTER_ACL_READ"})
     @Secured({"ROLE_USER", "AFTER_ACL_READ"})
     @Transactional(readOnly = true)
     @Transactional(readOnly = true)
     public Contact getById(Long id) {
     public Contact getById(Long id) {
@@ -150,10 +171,6 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         return contactDao.getById(id);
         return contactDao.getById(id);
     }
     }
 
 
-    public ContactDao getContactDao() {
-        return contactDao;
-    }
-
     /**
     /**
      * This is a public method.
      * This is a public method.
      *
      *
@@ -181,18 +198,14 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         }
         }
     }
     }
 
 
-    private AclObjectIdentity makeObjectIdentity(Contact contact) {
-        return new NamedEntityObjectIdentity(contact.getClass().getName(), contact.getId().toString());
-    }
-
-    public void setBasicAclExtendedDao(BasicAclExtendedDao basicAclExtendedDao) {
-        this.basicAclExtendedDao = basicAclExtendedDao;
-    }
-
     public void setContactDao(ContactDao contactDao) {
     public void setContactDao(ContactDao contactDao) {
         this.contactDao = contactDao;
         this.contactDao = contactDao;
     }
     }
 
 
+    public void setMutableAclService(MutableAclService mutableAclService) {
+        this.mutableAclService = mutableAclService;
+    }
+
     public void update(Contact contact) {
     public void update(Contact contact) {
         contactDao.update(contact);
         contactDao.update(contact);
 
 

+ 0 - 163
samples/contacts-tiger/src/main/webapp/common/WEB-INF/applicationContext-common-authorization.xml

@@ -1,163 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
-
-<!--
-  - Application context containing authentication beans.
-  -
-  - Used by all artifacts.
-  -
-  - $Id$
-  -->
-
-<beans>
-
-   <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
-
-   <!-- ACL permission masks used by this application -->
-   <bean id="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION</value></property>
-   </bean>
-   <bean id="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ</value></property>
-   </bean>
-   <bean id="net.sf.acegisecurity.acl.basic.SimpleAclEntry.DELETE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>net.sf.acegisecurity.acl.basic.SimpleAclEntry.DELETE</value></property>
-   </bean>
-
-
-   <!-- An access decision voter that reads ROLE_* configuration settings -->
-   <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
-
-   <!-- An access decision voter that reads ACL_CONTACT_READ configuration settings -->
-   <bean id="aclContactReadVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_READ</value></property>
-      <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
-   </bean>
-
-   <!-- An access decision voter that reads ACL_CONTACT_DELETE configuration settings -->
-   <bean id="aclContactDeleteVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_DELETE</value></property>
-      <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.DELETE"/>
-        </list>
-      </property>
-   </bean>
-
-   <!-- An access decision voter that reads ACL_CONTACT_ADMIN configuration settings -->
-   <bean id="aclContactAdminVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_ADMIN</value></property>
-      <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-        </list>
-      </property>
-   </bean>
-
-   <!-- An access decision manager used by the business objects -->
-   <bean id="businessAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
-      <property name="allowIfAllAbstainDecisions"><value>false</value></property>
-      <property name="decisionVoters">
-         <list>
-            <ref local="roleVoter"/>
-            <ref local="aclContactReadVoter"/>
-            <ref local="aclContactDeleteVoter"/>
-            <ref local="aclContactAdminVoter"/>
-         </list>
-      </property>
-   </bean>
-
-   <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
-
-   <bean id="aclManager" class="net.sf.acegisecurity.acl.AclProviderManager">
-      <property name="providers">
-         <list>
-            <ref local="basicAclProvider"/>
-         </list>
-      </property>
-   </bean>
-
-   <bean id="basicAclProvider" class="net.sf.acegisecurity.acl.basic.BasicAclProvider">
-      <property name="basicAclDao"><ref local="basicAclExtendedDao"/></property>
-   </bean>
-
-   <bean id="basicAclExtendedDao" class="net.sf.acegisecurity.acl.basic.jdbc.JdbcExtendedDaoImpl">
-      <property name="dataSource"><ref bean="dataSource"/></property>
-   </bean>
-
-   <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
-
-   <bean id="afterInvocationManager" class="net.sf.acegisecurity.afterinvocation.AfterInvocationProviderManager">
-      <property name="providers">
-         <list>
-            <ref local="afterAclRead"/>
-            <ref local="afterAclCollectionRead"/>
-         </list>
-      </property>
-   </bean>
-   
-   <!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
-   <bean id="afterAclCollectionRead" class="net.sf.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
-   </bean>
-   
-   <!-- Processes AFTER_ACL_READ configuration settings -->
-   <bean id="afterAclRead" class="net.sf.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationProvider">
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
-   </bean>
-
-
-   <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
-
-   <!-- getRandomContact() is public.
-
-    The create, getAll, getById etc have ROLE_USER to ensure user is
-    authenticated (all users hold ROLE_USER in this application).
-
-    The delete and update methods don't need a ROLE_USER as they will
-    ensure the user is authenticated via their ACL_CONTACT_DELETE or
-    ACL_CONTACT_READ attribute, which also ensures the user has permission
-    to the Contact presented as a method argument.
-    -->
-   <bean id="contactManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
-      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
-      <property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
-      <property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
-      <property name="objectDefinitionSource">
-         <value>
-            sample.contact.ContactManager.create=ROLE_USER
-            sample.contact.ContactManager.getAllRecipients=ROLE_USER
-            sample.contact.ContactManager.getAll=ROLE_USER,AFTER_ACL_COLLECTION_READ
-            sample.contact.ContactManager.getById=ROLE_USER,AFTER_ACL_READ
-            sample.contact.ContactManager.delete=ACL_CONTACT_DELETE
-            sample.contact.ContactManager.deletePermission=ACL_CONTACT_ADMIN
-            sample.contact.ContactManager.addPermission=ACL_CONTACT_ADMIN
-         </value>
-      </property>
-   </bean>
-
-</beans>

+ 0 - 72
samples/contacts-tiger/src/main/webapp/common/WEB-INF/applicationContext-common-business.xml

@@ -1,72 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
-
-<!--
-  - Application context containing business beans.
-  -
-  - Used by all artifacts.
-  -
-  - $Id$
-  -->
-
-<beans>
-
-    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
-        <property name="driverClassName">
-            <value>org.hsqldb.jdbcDriver</value>
-        </property>
-        <property name="url">
-            <value>jdbc:hsqldb:mem:contacts</value>
-        </property>
-        <property name="username">
-            <value>sa</value>
-        </property>
-        <property name="password">
-            <value></value>
-        </property>
-    </bean>
-	
-	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
-		<property name="dataSource"><ref local="dataSource"/></property>
-	</bean>
-	
-	<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
-    	<property name="transactionManager"><ref bean="transactionManager"/></property>
-		<property name="transactionAttributeSource">
-			<value>
-				sample.contact.ContactManager.create=PROPAGATION_REQUIRED
-				sample.contact.ContactManager.getAllRecipients=PROPAGATION_REQUIRED,readOnly
-				sample.contact.ContactManager.getAll=PROPAGATION_REQUIRED,readOnly
-				sample.contact.ContactManager.getById=PROPAGATION_REQUIRED,readOnly
-				sample.contact.ContactManager.delete=PROPAGATION_REQUIRED
-				sample.contact.ContactManager.deletePermission=PROPAGATION_REQUIRED
-				sample.contact.ContactManager.addPermission=PROPAGATION_REQUIRED
-			</value>
-		</property>
-	</bean>
-
-   <bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
-	   <property name="dataSource"><ref local="dataSource"/></property>
-   </bean>
-   
-   <bean id="contactDao" class="sample.contact.ContactDaoSpring">
-	   <property name="dataSource"><ref local="dataSource"/></property>
-   </bean>
-
-   <bean id="contactManager" class="org.springframework.aop.framework.ProxyFactoryBean">
-      <property name="proxyInterfaces"><value>sample.contact.ContactManager</value></property>
-      <property name="interceptorNames">
-         <list>
-            <idref local="transactionInterceptor"/>
-            <idref bean="contactManagerSecurity"/>
-            <idref local="contactManagerTarget"/>
-         </list>
-      </property>
-   </bean>
-
-   <bean id="contactManagerTarget" class="sample.contact.ContactManagerBackend">
-	   <property name="contactDao"><ref local="contactDao"/></property>
-	   <property name="basicAclExtendedDao"><ref bean="basicAclExtendedDao"/></property>
-   </bean>
-
-</beans>

+ 32 - 2
samples/contacts-tiger/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml

@@ -21,7 +21,7 @@
          <value>
          <value>
 		    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 		    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 		    PATTERN_TYPE_APACHE_ANT
 		    PATTERN_TYPE_APACHE_ANT
-            /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+            /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,switchUserProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
          </value>
          </value>
       </property>
       </property>
     </bean>
     </bean>
@@ -38,7 +38,7 @@
       </property>
       </property>
    </bean>
    </bean>
 
 
-   <bean id="jdbcDaoImpl" class="org.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
+   <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
       <property name="dataSource"><ref bean="dataSource"/></property>
       <property name="dataSource"><ref bean="dataSource"/></property>
    </bean>
    </bean>
 
 
@@ -90,6 +90,7 @@
    </bean>
    </bean>
 
 
    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
+      <property name="authenticationManager"><ref local="authenticationManager"/></property>
       <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
       <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
    </bean>
    </bean>
 
 
@@ -101,6 +102,18 @@
    <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
    <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
       <property name="key"><value>springRocks</value></property>
       <property name="key"><value>springRocks</value></property>
    </bean>
    </bean>
+   
+   <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
+      <constructor-arg>
+         <list>
+              <ref bean="rememberMeServices"/>
+              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+         </list>
+      </constructor-arg>
+   </bean>
+   
+   <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>
 
 
    <!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
    <!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 
 
@@ -136,6 +149,11 @@
 
 
    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
       <property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
       <property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
+      <property name="accessDeniedHandler">
+      	<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
+      		<property name="errorPage" value="/accessDenied.jsp"/>
+      	</bean>
+      </property>
    </bean>
    </bean>
 
 
    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
@@ -173,10 +191,22 @@
 			    /index.jsp=ROLE_ANONYMOUS,ROLE_USER
 			    /index.jsp=ROLE_ANONYMOUS,ROLE_USER
 			    /hello.htm=ROLE_ANONYMOUS,ROLE_USER
 			    /hello.htm=ROLE_ANONYMOUS,ROLE_USER
 			    /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
 			    /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
+			    /switchuser.jsp=ROLE_SUPERVISOR
+			    /j_acegi_switch_user=ROLE_SUPERVISOR
 			    /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
 			    /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
 				/**=ROLE_USER
 				/**=ROLE_USER
          </value>
          </value>
       </property>
       </property>
    </bean>
    </bean>
 
 
+   <!-- Filter used to switch the user context. Note: the switch and exit url must be secured 
+        based on the role granted the ability to 'switch' to another user -->
+   <!-- In this example 'marissa' has ROLE_SUPERVISOR that can switch to regular ROLE_USER(s) -->
+   <bean id="switchUserProcessingFilter" class="org.acegisecurity.ui.switchuser.SwitchUserProcessingFilter">
+      <property name="userDetailsService" ref="jdbcDaoImpl" />
+	  <property name="switchUserUrl"><value>/j_acegi_switch_user</value></property>
+	  <property name="exitUserUrl"><value>/j_acegi_exit_user</value></property>
+	  <property name="targetUrl"><value>/acegi-security-sample-contacts-filter/secure/index.htm</value></property>
+   </bean>    
+
 </beans>
 </beans>

+ 114 - 71
samples/contacts-tiger/src/main/webapp/filter/WEB-INF/applicationContext-annotations.xml

@@ -12,60 +12,72 @@
    <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
    <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
 
 
    <!-- ACL permission masks used by this application -->
    <!-- ACL permission masks used by this application -->
-   <bean id="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION</value></property>
+   <bean id="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+      <property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION</value></property>
    </bean>
    </bean>
-   <bean id="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ</value></property>
+   <bean id="org.acegisecurity.acls.domain.BasePermission.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+      <property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.READ</value></property>
    </bean>
    </bean>
-   <bean id="net.sf.acegisecurity.acl.basic.SimpleAclEntry.DELETE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>net.sf.acegisecurity.acl.basic.SimpleAclEntry.DELETE</value></property>
+   <bean id="org.acegisecurity.acls.domain.BasePermission.DELETE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+      <property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.DELETE</value></property>
    </bean>
    </bean>
 
 
 
 
    <!-- An access decision voter that reads ROLE_* configuration settings -->
    <!-- An access decision voter that reads ROLE_* configuration settings -->
-   <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
+   <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
 
 
    <!-- An access decision voter that reads ACL_CONTACT_READ configuration settings -->
    <!-- An access decision voter that reads ACL_CONTACT_READ configuration settings -->
-   <bean id="aclContactReadVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_READ</value></property>
+   <bean id="aclContactReadVoter" class="org.acegisecurity.vote.AclEntryVoter">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<value>ACL_CONTACT_READ</value>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
+      	</list>
+      </constructor-arg>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
    </bean>
    </bean>
 
 
    <!-- An access decision voter that reads ACL_CONTACT_DELETE configuration settings -->
    <!-- An access decision voter that reads ACL_CONTACT_DELETE configuration settings -->
-   <bean id="aclContactDeleteVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_DELETE</value></property>
+   <bean id="aclContactDeleteVoter" class="org.acegisecurity.vote.AclEntryVoter">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<value>ACL_CONTACT_DELETE</value>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.DELETE"/>
+      	</list>
+      </constructor-arg>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.DELETE"/>
-        </list>
-      </property>
    </bean>
    </bean>
 
 
    <!-- An access decision voter that reads ACL_CONTACT_ADMIN configuration settings -->
    <!-- An access decision voter that reads ACL_CONTACT_ADMIN configuration settings -->
-   <bean id="aclContactAdminVoter" class="net.sf.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_ADMIN</value></property>
+   <bean id="aclContactAdminVoter" class="org.acegisecurity.vote.AclEntryVoter">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<value>ACL_CONTACT_ADMIN</value>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+      	</list>
+      </constructor-arg>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-        </list>
-      </property>
    </bean>
    </bean>
 
 
    <!-- An access decision manager used by the business objects -->
    <!-- An access decision manager used by the business objects -->
-   <bean id="businessAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
+   <bean id="businessAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
       <property name="allowIfAllAbstainDecisions"><value>false</value></property>
       <property name="allowIfAllAbstainDecisions"><value>false</value></property>
       <property name="decisionVoters">
       <property name="decisionVoters">
          <list>
          <list>
@@ -79,25 +91,53 @@
 
 
    <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
    <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
 
 
-   <bean id="aclManager" class="net.sf.acegisecurity.acl.AclProviderManager">
-      <property name="providers">
-         <list>
-            <ref local="basicAclProvider"/>
-         </list>
-      </property>
-   </bean>
-
-   <bean id="basicAclProvider" class="net.sf.acegisecurity.acl.basic.BasicAclProvider">
-      <property name="basicAclDao"><ref local="basicAclExtendedDao"/></property>
-   </bean>
-
-   <bean id="basicAclExtendedDao" class="net.sf.acegisecurity.acl.basic.jdbc.JdbcExtendedDaoImpl">
-      <property name="dataSource"><ref bean="dataSource"/></property>
-   </bean>
+	<bean id="aclCache" class="org.acegisecurity.acls.jdbc.EhCacheBasedAclCache">
+		<constructor-arg>
+		   <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
+		      <property name="cacheManager">
+				<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
+		      </property>
+		      <property name="cacheName">
+		         <value>aclCache</value>
+		      </property>
+		   </bean>
+		</constructor-arg>
+	</bean>
+    
+	<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
+		<constructor-arg ref="dataSource"/>
+		<constructor-arg ref="aclCache"/>
+		<constructor-arg ref="aclAuthorizationStrategy"/>
+		<constructor-arg>
+			<bean class="org.acegisecurity.acls.domain.ConsoleAuditLogger"/>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="aclAuthorizationStrategy" class="org.acegisecurity.acls.domain.AclAuthorizationStrategyImpl">
+		<constructor-arg>
+			<list>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+			</list>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcMutableAclService">
+		<constructor-arg ref="dataSource"/>
+		<constructor-arg ref="lookupStrategy"/>
+		<constructor-arg ref="aclCache"/>
+	</bean>
 
 
    <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
    <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
 
 
-   <bean id="afterInvocationManager" class="net.sf.acegisecurity.afterinvocation.AfterInvocationProviderManager">
+   <bean id="afterInvocationManager" class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">
       <property name="providers">
       <property name="providers">
          <list>
          <list>
             <ref local="afterAclRead"/>
             <ref local="afterAclRead"/>
@@ -107,38 +147,41 @@
    </bean>
    </bean>
    
    
    <!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
    <!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
-   <bean id="afterAclCollectionRead" class="net.sf.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
+   <bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
+      	</list>
+      </constructor-arg>
    </bean>
    </bean>
    
    
    <!-- Processes AFTER_ACL_READ configuration settings -->
    <!-- Processes AFTER_ACL_READ configuration settings -->
-   <bean id="afterAclRead" class="net.sf.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationProvider">
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="net.sf.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
+   <bean id="afterAclRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationProvider">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
+      	</list>
+      </constructor-arg>
    </bean>
    </bean>
 
 
-
     <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
     <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
 
 
-	<bean id="attributes" class="net.sf.acegisecurity.annotation.SecurityAnnotationAttributes"/>
+	<bean id="attributes" class="org.acegisecurity.annotation.SecurityAnnotationAttributes"/>
 	
 	
-	<bean id="objectDefinitionSource" class="net.sf.acegisecurity.intercept.method.MethodDefinitionAttributes">
+	<bean id="objectDefinitionSource" class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
 		<property name="attributes"><ref local="attributes"/></property>
 		<property name="attributes"><ref local="attributes"/></property>
 	</bean>
 	</bean>
 
 
 	<!-- We don't validate config attributes, as it's unsupported by MethodDefinitionAttributes -->
 	<!-- We don't validate config attributes, as it's unsupported by MethodDefinitionAttributes -->
-	<bean id="securityInterceptor" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
+	<bean id="securityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
     	<property name="validateConfigAttributes"><value>false</value></property>
     	<property name="validateConfigAttributes"><value>false</value></property>
     	<property name="authenticationManager"><ref bean="authenticationManager"/></property>
     	<property name="authenticationManager"><ref bean="authenticationManager"/></property>
     	<property name="accessDecisionManager"><ref bean="businessAccessDecisionManager"/></property>
     	<property name="accessDecisionManager"><ref bean="businessAccessDecisionManager"/></property>
@@ -160,7 +203,7 @@
 		which in the above configuration is a JDK 5 Annotations Attributes-based source.
 		which in the above configuration is a JDK 5 Annotations Attributes-based source.
 	-->
 	-->
 	<bean id="methodSecurityAdvisor"
 	<bean id="methodSecurityAdvisor"
-		class="net.sf.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"
+		class="org.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"
 		autowire="constructor" >
 		autowire="constructor" >
 	</bean>
 	</bean>
 		   
 		   

+ 4 - 2
samples/contacts-tiger/src/main/webapp/filter/WEB-INF/applicationContext-business.xml

@@ -31,7 +31,9 @@
 	</bean>
 	</bean>
 	
 	
    <bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
    <bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
-	   <property name="dataSource"><ref local="dataSource"/></property>
+	   <property name="dataSource" ref="dataSource"/>
+	   <property name="mutableAclService" ref="aclService"/>
+	   <property name="platformTransactionManager" ref="transactionManager"/>
    </bean>
    </bean>
    
    
    <bean id="contactDao" class="sample.contact.ContactDaoSpring">
    <bean id="contactDao" class="sample.contact.ContactDaoSpring">
@@ -42,7 +44,7 @@
    <!-- Advised Contact Manager using Java 5 Annotations --> 
    <!-- Advised Contact Manager using Java 5 Annotations --> 
    <bean id="contactManager" class="sample.contact.annotation.ContactManagerBackend">
    <bean id="contactManager" class="sample.contact.annotation.ContactManagerBackend">
 	   <property name="contactDao"><ref local="contactDao"/></property>
 	   <property name="contactDao"><ref local="contactDao"/></property>
-	   <property name="basicAclExtendedDao"><ref bean="basicAclExtendedDao"/></property>
+	   <property name="mutableAclService"><ref bean="aclService"/></property>
    </bean>
    </bean>
 
 
 </beans>
 </beans>

+ 3 - 3
samples/contacts-tiger/src/main/webapp/filter/WEB-INF/web.xml

@@ -33,10 +33,10 @@
 
 
    <filter>
    <filter>
         <filter-name>Acegi Filter Chain Proxy</filter-name>
         <filter-name>Acegi Filter Chain Proxy</filter-name>
-        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
+        <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
         <init-param>
         <init-param>
             <param-name>targetClass</param-name>
             <param-name>targetClass</param-name>
-            <param-value>net.sf.acegisecurity.util.FilterChainProxy</param-value>
+            <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
         </init-param>
         </init-param>
    </filter>
    </filter>
 
 
@@ -64,7 +64,7 @@
         to the WebApplicationContext
         to the WebApplicationContext
  -->  
  -->  
     <listener>
     <listener>
-        <listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
+        <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
     </listener>
     </listener>
  
  
   <!--
   <!--

+ 16 - 0
samples/contacts-tiger/src/main/webapp/filter/accessDenied.jsp

@@ -0,0 +1,16 @@
+<%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
+<%@ page import="org.acegisecurity.Authentication" %>
+<%@ page import="org.acegisecurity.ui.AccessDeniedHandlerImpl" %>
+
+<h1>Sorry, access is denied</h1>
+
+
+<p>
+<%= request.getAttribute(AccessDeniedHandlerImpl.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)%>
+
+<p>
+
+<%		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+		if (auth != null) { %>
+			Authentication object as a String: <%= auth.toString() %><BR><BR>
+<%      } %>

+ 0 - 5
samples/contacts-tiger/src/main/webapp/filter/error.html

@@ -1,5 +0,0 @@
-<html>
-	<title>Access denied!</title>
-	<h1>Access Denied</h1>
-	We're sorry, but you are not authorized to perform the requested operation.
-</html>

+ 1 - 2
samples/contacts/src/main/java/sample/contact/AddPermission.java

@@ -12,7 +12,6 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
 import org.acegisecurity.acl.basic.SimpleAclEntry;
 import org.acegisecurity.acl.basic.SimpleAclEntry;
@@ -28,7 +27,7 @@ public class AddPermission {
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
     public Contact contact;
     public Contact contact;
-    public Integer permission = new Integer(SimpleAclEntry.NOTHING);
+    public Integer permission = new Integer(SimpleAclEntry.READ);
     public String recipient;
     public String recipient;
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================

+ 12 - 18
samples/contacts/src/main/java/sample/contact/AddPermissionController.java

@@ -12,10 +12,11 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
-import org.acegisecurity.acl.basic.SimpleAclEntry;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.sid.PrincipalSid;
 
 
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.InitializingBean;
 
 
@@ -59,7 +60,7 @@ public class AddPermissionController extends SimpleFormController implements Ini
     protected ModelAndView disallowDuplicateFormSubmission(HttpServletRequest request, HttpServletResponse response)
     protected ModelAndView disallowDuplicateFormSubmission(HttpServletRequest request, HttpServletResponse response)
         throws Exception {
         throws Exception {
         BindException errors = new BindException(formBackingObject(request), getCommandName());
         BindException errors = new BindException(formBackingObject(request), getCommandName());
-        errors.reject("err.duplicateFormSubmission", "Duplicate form submission.");
+        errors.reject("err.duplicateFormSubmission", "Duplicate form submission. *");
 
 
         return showForm(request, response, errors);
         return showForm(request, response, errors);
     }
     }
@@ -76,10 +77,6 @@ public class AddPermissionController extends SimpleFormController implements Ini
         return addPermission;
         return addPermission;
     }
     }
 
 
-    public ContactManager getContactManager() {
-        return contactManager;
-    }
-
     protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
     protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
         throws Exception {
         throws Exception {
         return disallowDuplicateFormSubmission(request, response);
         return disallowDuplicateFormSubmission(request, response);
@@ -87,16 +84,12 @@ public class AddPermissionController extends SimpleFormController implements Ini
 
 
     private Map listPermissions(HttpServletRequest request) {
     private Map listPermissions(HttpServletRequest request) {
         Map map = new LinkedHashMap();
         Map map = new LinkedHashMap();
-        map.put(new Integer(SimpleAclEntry.NOTHING),
-            getApplicationContext().getMessage("select.none", null, "None", request.getLocale()));
-        map.put(new Integer(SimpleAclEntry.ADMINISTRATION),
+        map.put(new Integer(BasePermission.ADMINISTRATION.getMask()),
             getApplicationContext().getMessage("select.administer", null, "Administer", request.getLocale()));
             getApplicationContext().getMessage("select.administer", null, "Administer", request.getLocale()));
-        map.put(new Integer(SimpleAclEntry.READ),
+        map.put(new Integer(BasePermission.READ.getMask()),
             getApplicationContext().getMessage("select.read", null, "Read", request.getLocale()));
             getApplicationContext().getMessage("select.read", null, "Read", request.getLocale()));
-        map.put(new Integer(SimpleAclEntry.DELETE),
+        map.put(new Integer(BasePermission.DELETE.getMask()),
             getApplicationContext().getMessage("select.delete", null, "Delete", request.getLocale()));
             getApplicationContext().getMessage("select.delete", null, "Delete", request.getLocale()));
-        map.put(new Integer(SimpleAclEntry.READ_WRITE_DELETE),
-            getApplicationContext().getMessage("select.readWriteDelete", null, "Read+Write+Delete", request.getLocale()));
 
 
         return map;
         return map;
     }
     }
@@ -120,13 +113,14 @@ public class AddPermissionController extends SimpleFormController implements Ini
         BindException errors) throws Exception {
         BindException errors) throws Exception {
         AddPermission addPermission = (AddPermission) command;
         AddPermission addPermission = (AddPermission) command;
 
 
+        PrincipalSid sid = new PrincipalSid(addPermission.getRecipient());
+        Permission permission = BasePermission.buildFromMask(addPermission.getPermission().intValue());
+
         try {
         try {
-            contactManager.addPermission(addPermission.getContact(), addPermission.getRecipient(),
-                addPermission.getPermission());
+            contactManager.addPermission(addPermission.getContact(), sid, permission);
         } catch (DataAccessException existingPermission) {
         } catch (DataAccessException existingPermission) {
             existingPermission.printStackTrace();
             existingPermission.printStackTrace();
-            errors.rejectValue("recipient", "err.recipientExistsForContact",
-                "This recipient already has permissions to this contact.");
+            errors.rejectValue("recipient", "err.recipientExistsForContact", "Addition failure.");
 
 
             return showForm(request, response, errors);
             return showForm(request, response, errors);
         }
         }

+ 3 - 5
samples/contacts/src/main/java/sample/contact/AddPermissionValidator.java

@@ -12,10 +12,9 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
-import org.acegisecurity.acl.basic.SimpleAclEntry;
+import org.acegisecurity.acls.domain.BasePermission;
 
 
 import org.springframework.validation.Errors;
 import org.springframework.validation.Errors;
 import org.springframework.validation.ValidationUtils;
 import org.springframework.validation.ValidationUtils;
@@ -44,9 +43,8 @@ public class AddPermissionValidator implements Validator {
         if (addPermission.getPermission() != null) {
         if (addPermission.getPermission() != null) {
             int permission = addPermission.getPermission().intValue();
             int permission = addPermission.getPermission().intValue();
 
 
-            if ((permission != SimpleAclEntry.NOTHING) && (permission != SimpleAclEntry.ADMINISTRATION)
-                && (permission != SimpleAclEntry.READ) && (permission != SimpleAclEntry.DELETE)
-                && (permission != SimpleAclEntry.READ_WRITE_DELETE)) {
+            if ((permission != BasePermission.ADMINISTRATION.getMask())
+                && (permission != BasePermission.READ.getMask()) && (permission != BasePermission.DELETE.getMask())) {
                 errors.rejectValue("permission", "err.permission.invalid", "The indicated permission is invalid. *");
                 errors.rejectValue("permission", "err.permission.invalid", "The indicated permission is invalid. *");
             }
             }
         }
         }

+ 9 - 17
samples/contacts/src/main/java/sample/contact/AdminPermissionController.java

@@ -12,11 +12,11 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
-import org.acegisecurity.acl.AclEntry;
-import org.acegisecurity.acl.AclManager;
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
 
 
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.InitializingBean;
 
 
@@ -45,22 +45,14 @@ import javax.servlet.http.HttpServletResponse;
 public class AdminPermissionController implements Controller, InitializingBean {
 public class AdminPermissionController implements Controller, InitializingBean {
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
-    private AclManager aclManager;
+    private AclService aclService;
     private ContactManager contactManager;
     private ContactManager contactManager;
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
     public void afterPropertiesSet() throws Exception {
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(contactManager, "A ContactManager implementation is required");
         Assert.notNull(contactManager, "A ContactManager implementation is required");
-        Assert.notNull(aclManager, "An aclManager implementation is required");
-    }
-
-    public AclManager getAclManager() {
-        return aclManager;
-    }
-
-    public ContactManager getContactManager() {
-        return contactManager;
+        Assert.notNull(aclService, "An aclService implementation is required");
     }
     }
 
 
     public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
     public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
@@ -68,17 +60,17 @@ public class AdminPermissionController implements Controller, InitializingBean {
         int id = RequestUtils.getRequiredIntParameter(request, "contactId");
         int id = RequestUtils.getRequiredIntParameter(request, "contactId");
 
 
         Contact contact = contactManager.getById(new Long(id));
         Contact contact = contactManager.getById(new Long(id));
-        AclEntry[] acls = aclManager.getAcls(contact);
+        Acl acl = aclService.readAclById(new ObjectIdentityImpl(contact));
 
 
         Map model = new HashMap();
         Map model = new HashMap();
         model.put("contact", contact);
         model.put("contact", contact);
-        model.put("acls", acls);
+        model.put("acl", acl);
 
 
         return new ModelAndView("adminPermission", "model", model);
         return new ModelAndView("adminPermission", "model", model);
     }
     }
 
 
-    public void setAclManager(AclManager aclManager) {
-        this.aclManager = aclManager;
+    public void setAclService(AclService aclService) {
+        this.aclService = aclService;
     }
     }
 
 
     public void setContactManager(ContactManager contact) {
     public void setContactManager(ContactManager contact) {

+ 5 - 3
samples/contacts/src/main/java/sample/contact/ContactManager.java

@@ -12,9 +12,11 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.sid.Sid;
+
 import java.util.List;
 import java.util.List;
 
 
 
 
@@ -27,13 +29,13 @@ import java.util.List;
 public interface ContactManager {
 public interface ContactManager {
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
-    public void addPermission(Contact contact, String recipient, Integer permission);
+    public void addPermission(Contact contact, Sid recipient, Permission permission);
 
 
     public void create(Contact contact);
     public void create(Contact contact);
 
 
     public void delete(Contact contact);
     public void delete(Contact contact);
 
 
-    public void deletePermission(Contact contact, String recipient);
+    public void deletePermission(Contact contact, Sid recipient, Permission permission);
 
 
     public List getAll();
     public List getAll();
 
 

+ 47 - 36
samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java

@@ -12,15 +12,20 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
 import org.acegisecurity.Authentication;
 import org.acegisecurity.Authentication;
 
 
-import org.acegisecurity.acl.basic.AclObjectIdentity;
-import org.acegisecurity.acl.basic.BasicAclExtendedDao;
-import org.acegisecurity.acl.basic.NamedEntityObjectIdentity;
-import org.acegisecurity.acl.basic.SimpleAclEntry;
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.MutableAclService;
+import org.acegisecurity.acls.NotFoundException;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
 
 
 import org.acegisecurity.context.SecurityContextHolder;
 import org.acegisecurity.context.SecurityContextHolder;
 
 
@@ -45,27 +50,33 @@ import java.util.Random;
 public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
 public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
-    private BasicAclExtendedDao basicAclExtendedDao;
     private ContactDao contactDao;
     private ContactDao contactDao;
+    private MutableAclService mutableAclService;
     private int counter = 1000;
     private int counter = 1000;
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
-    public void addPermission(Contact contact, String recipient, Integer permission) {
-        SimpleAclEntry simpleAclEntry = new SimpleAclEntry();
-        simpleAclEntry.setAclObjectIdentity(makeObjectIdentity(contact));
-        simpleAclEntry.setMask(permission.intValue());
-        simpleAclEntry.setRecipient(recipient);
-        basicAclExtendedDao.create(simpleAclEntry);
+    public void addPermission(Contact contact, Sid recipient, Permission permission) {
+        MutableAcl acl;
+        ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
+
+        try {
+            acl = (MutableAcl) mutableAclService.readAclById(oid);
+        } catch (NotFoundException nfe) {
+            acl = mutableAclService.createAcl(oid);
+        }
+
+        acl.insertAce(null, permission, recipient, true);
+        mutableAclService.updateAcl(acl);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
-            logger.debug("Added permission " + permission + " for recipient " + recipient + " contact " + contact);
+            logger.debug("Added permission " + permission + " for Sid " + recipient + " contact " + contact);
         }
         }
     }
     }
 
 
     public void afterPropertiesSet() throws Exception {
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(contactDao, "contactDao required");
         Assert.notNull(contactDao, "contactDao required");
-        Assert.notNull(basicAclExtendedDao, "basicAclExtendedDao required");
+        Assert.notNull(mutableAclService, "mutableAclService required");
     }
     }
 
 
     public void create(Contact contact) {
     public void create(Contact contact) {
@@ -73,8 +84,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         contact.setId(new Long(counter++));
         contact.setId(new Long(counter++));
         contactDao.create(contact);
         contactDao.create(contact);
 
 
-        // Grant the current principal access to the contact 
-        addPermission(contact, getUsername(), new Integer(SimpleAclEntry.ADMINISTRATION));
+        // Grant the current principal administrative permission to the contact 
+        addPermission(contact, new PrincipalSid(getUsername()), BasePermission.ADMINISTRATION);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Created contact " + contact + " and granted admin permission to recipient " + getUsername());
             logger.debug("Created contact " + contact + " and granted admin permission to recipient " + getUsername());
@@ -85,15 +96,28 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         contactDao.delete(contact.getId());
         contactDao.delete(contact.getId());
 
 
         // Delete the ACL information as well
         // Delete the ACL information as well
-        basicAclExtendedDao.delete(makeObjectIdentity(contact));
+        ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
+        mutableAclService.deleteAcl(oid, false);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Deleted contact " + contact + " including ACL permissions");
             logger.debug("Deleted contact " + contact + " including ACL permissions");
         }
         }
     }
     }
 
 
-    public void deletePermission(Contact contact, String recipient) {
-        basicAclExtendedDao.delete(makeObjectIdentity(contact), recipient);
+    public void deletePermission(Contact contact, Sid recipient, Permission permission) {
+        ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
+        MutableAcl acl = (MutableAcl) mutableAclService.readAclById(oid);
+
+        // Remove all permissions associated with this particular recipient (string equality to KISS)
+        AccessControlEntry[] entries = acl.getEntries();
+
+        for (int i = 0; i < entries.length; i++) {
+            if (entries[i].getSid().equals(recipient) && entries[i].getPermission().equals(permission)) {
+                acl.deleteAce(entries[i].getId());
+            }
+        }
+
+        mutableAclService.updateAcl(acl);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Deleted contact " + contact + " ACL permissions for recipient " + recipient);
             logger.debug("Deleted contact " + contact + " ACL permissions for recipient " + recipient);
@@ -114,15 +138,10 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         }
         }
 
 
         List list = contactDao.findAllPrincipals();
         List list = contactDao.findAllPrincipals();
-        list.addAll(contactDao.findAllRoles());
 
 
         return list;
         return list;
     }
     }
 
 
-    public BasicAclExtendedDao getBasicAclExtendedDao() {
-        return basicAclExtendedDao;
-    }
-
     public Contact getById(Long id) {
     public Contact getById(Long id) {
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Returning contact with id: " + id);
             logger.debug("Returning contact with id: " + id);
@@ -131,10 +150,6 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         return contactDao.getById(id);
         return contactDao.getById(id);
     }
     }
 
 
-    public ContactDao getContactDao() {
-        return contactDao;
-    }
-
     /**
     /**
      * This is a public method.
      * This is a public method.
      *
      *
@@ -162,18 +177,14 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         }
         }
     }
     }
 
 
-    private AclObjectIdentity makeObjectIdentity(Contact contact) {
-        return new NamedEntityObjectIdentity(contact.getClass().getName(), contact.getId().toString());
-    }
-
-    public void setBasicAclExtendedDao(BasicAclExtendedDao basicAclExtendedDao) {
-        this.basicAclExtendedDao = basicAclExtendedDao;
-    }
-
     public void setContactDao(ContactDao contactDao) {
     public void setContactDao(ContactDao contactDao) {
         this.contactDao = contactDao;
         this.contactDao = contactDao;
     }
     }
 
 
+    public void setMutableAclService(MutableAclService mutableAclService) {
+        this.mutableAclService = mutableAclService;
+    }
+
     public void update(Contact contact) {
     public void update(Contact contact) {
         contactDao.update(contact);
         contactDao.update(contact);
 
 

+ 147 - 80
samples/contacts/src/main/java/sample/contact/DataSourcePopulator.java

@@ -12,13 +12,34 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.MutableAclService;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.AclImpl;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.acegisecurity.acls.sid.PrincipalSid;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.InitializingBean;
 
 
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.JdbcTemplate;
 
 
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
 import java.util.Random;
 import java.util.Random;
@@ -35,8 +56,10 @@ import javax.sql.DataSource;
 public class DataSourcePopulator implements InitializingBean {
 public class DataSourcePopulator implements InitializingBean {
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
-    private DataSource dataSource;
+    JdbcTemplate template;
+    private MutableAclService mutableAclService;
     Random rnd = new Random();
     Random rnd = new Random();
+    TransactionTemplate tt;
     String[] firstNames = {
     String[] firstNames = {
             "Bob", "Mary", "James", "Jane", "Kristy", "Kirsty", "Kate", "Jeni", "Angela", "Melanie", "Kent", "William",
             "Bob", "Mary", "James", "Jane", "Kristy", "Kirsty", "Kate", "Jeni", "Angela", "Melanie", "Kent", "William",
             "Geoff", "Jeff", "Adrian", "Amanda", "Lisa", "Elizabeth", "Prue", "Richard", "Darin", "Phillip", "Michael",
             "Geoff", "Jeff", "Adrian", "Amanda", "Lisa", "Elizabeth", "Prue", "Richard", "Darin", "Phillip", "Michael",
@@ -47,91 +70,28 @@ public class DataSourcePopulator implements InitializingBean {
             "Edwards", "Gates", "Black", "Brown", "Gray", "Marwell", "Booch", "Johnson", "McTaggart", "Parklin",
             "Edwards", "Gates", "Black", "Brown", "Gray", "Marwell", "Booch", "Johnson", "McTaggart", "Parklin",
             "Findlay", "Robinson", "Giugni", "Lang", "Chi", "Carmichael"
             "Findlay", "Robinson", "Giugni", "Lang", "Chi", "Carmichael"
         };
         };
-    private int createEntities = 1000;
+    private int createEntities = 50;
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
     public void afterPropertiesSet() throws Exception {
     public void afterPropertiesSet() throws Exception {
-        Assert.notNull(dataSource, "dataSource required");
-
-        JdbcTemplate template = new JdbcTemplate(dataSource);
+        Assert.notNull(mutableAclService, "mutableAclService required");
+        Assert.notNull(template, "dataSource required");
+        Assert.notNull(tt, "platformTransactionManager required");
 
 
-        template.execute(
-            "CREATE TABLE CONTACTS(ID BIGINT NOT NULL PRIMARY KEY, CONTACT_NAME VARCHAR_IGNORECASE(50) NOT NULL, EMAIL VARCHAR_IGNORECASE(50) NOT NULL)");
-        template.execute("INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');"); // marissa
-        template.execute("INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');"); // marissa
-        template.execute("INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');"); // marissa
-        template.execute("INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');"); // marissa + dianne + scott
-        template.execute("INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');"); // dianne
-        template.execute("INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');"); // dianne + scott
-        template.execute("INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');"); // scott
-        template.execute("INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');"); // dianne + scott
-        template.execute("INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');"); // scott
+        // Set a user account that will initially own all the created data
+        Authentication authRequest = new UsernamePasswordAuthenticationToken("marissa", "koala",
+                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_IGNORED")});
+        SecurityContextHolder.getContext().setAuthentication(authRequest);
 
 
-        for (int i = 10; i < createEntities; i++) {
-            String[] person = selectPerson();
-            template.execute("INSERT INTO contacts VALUES (" + i + ", '" + person[2] + "', '" + person[0].toLowerCase()
-                + "@" + person[1].toLowerCase() + ".com');");
-        }
-
-        template.execute(
-            "CREATE TABLE ACL_OBJECT_IDENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100)  NOT NULL PRIMARY KEY,OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,PARENT_OBJECT BIGINT,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT UNIQUE_OBJECT_IDENTITY UNIQUE(OBJECT_IDENTITY),CONSTRAINT SYS_FK_3 FOREIGN KEY(PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID))");
-        template.execute(
-            "INSERT INTO acl_object_identity VALUES (1, 'sample.contact.Contact:1', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
         template.execute(
         template.execute(
-            "INSERT INTO acl_object_identity VALUES (2, 'sample.contact.Contact:2', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
+            "CREATE TABLE ACL_SID(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,PRINCIPAL BOOLEAN NOT NULL,SID VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));");
         template.execute(
         template.execute(
-            "INSERT INTO acl_object_identity VALUES (3, 'sample.contact.Contact:3', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
+            "CREATE TABLE ACL_CLASS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,CLASS VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
         template.execute(
         template.execute(
-            "INSERT INTO acl_object_identity VALUES (4, 'sample.contact.Contact:4', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
+            "CREATE TABLE ACL_OBJECT_IDENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,OBJECT_ID_CLASS BIGINT NOT NULL,OBJECT_ID_IDENTITY BIGINT NOT NULL,PARENT_OBJECT BIGINT,OWNER_SID BIGINT,ENTRIES_INHERITING BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
         template.execute(
         template.execute(
-            "INSERT INTO acl_object_identity VALUES (5, 'sample.contact.Contact:5', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
-        template.execute(
-            "INSERT INTO acl_object_identity VALUES (6, 'sample.contact.Contact:6', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
-        template.execute(
-            "INSERT INTO acl_object_identity VALUES (7, 'sample.contact.Contact:7', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
-        template.execute(
-            "INSERT INTO acl_object_identity VALUES (8, 'sample.contact.Contact:8', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
-        template.execute(
-            "INSERT INTO acl_object_identity VALUES (9, 'sample.contact.Contact:9', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
-
-        for (int i = 10; i < createEntities; i++) {
-            template.execute("INSERT INTO acl_object_identity VALUES (" + i + ", 'sample.contact.Contact:" + i
-                + "', null, 'org.acegisecurity.acl.basic.SimpleAclEntry');");
-        }
-
-        template.execute(
-            "CREATE TABLE ACL_PERMISSION(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100)  NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY BIGINT NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,MASK INTEGER NOT NULL,CONSTRAINT UNIQUE_RECIPIENT UNIQUE(ACL_OBJECT_IDENTITY,RECIPIENT),CONSTRAINT SYS_FK_7 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID))");
-        template.execute("INSERT INTO acl_permission VALUES (null, 1, 'marissa', 1);"); // administer
-        template.execute("INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);"); // read
-        template.execute("INSERT INTO acl_permission VALUES (null, 3, 'marissa', 22);"); // read+write+delete
-        template.execute("INSERT INTO acl_permission VALUES (null, 4, 'marissa', 1);"); // administer
-        template.execute("INSERT INTO acl_permission VALUES (null, 4, 'dianne', 1);"); // administer
-        template.execute("INSERT INTO acl_permission VALUES (null, 4, 'scott', 2);"); // read
-        template.execute("INSERT INTO acl_permission VALUES (null, 5, 'dianne', 2);"); // read
-        template.execute("INSERT INTO acl_permission VALUES (null, 6, 'dianne', 22);"); // read+write+delete
-        template.execute("INSERT INTO acl_permission VALUES (null, 6, 'scott', 2);"); // read
-        template.execute("INSERT INTO acl_permission VALUES (null, 7, 'scott', 1);"); // administer
-        template.execute("INSERT INTO acl_permission VALUES (null, 8, 'dianne', 2);"); // read
-        template.execute("INSERT INTO acl_permission VALUES (null, 8, 'scott', 2);"); // read
-        template.execute("INSERT INTO acl_permission VALUES (null, 9, 'scott', 22);"); // read+write+delete
-
-        String[] users = {"bill", "bob", "jane"}; // don't want to mess around with consistent sample data
-        int[] permissions = {1, 2, 22};
-
-        for (int i = 10; i < createEntities; i++) {
-            String user = users[rnd.nextInt(users.length)];
-            int permission = permissions[rnd.nextInt(permissions.length)];
-            template.execute("INSERT INTO acl_permission VALUES (null, " + i + ", '" + user + "', " + permission + ");");
-
-            String user2 = users[rnd.nextInt(users.length)];
-            int permission2 = permissions[rnd.nextInt(permissions.length)];
-
-            if (!user2.equals(user)) {
-                template.execute("INSERT INTO acl_permission VALUES (null, " + i + ", '" + user2 + "', " + permission2
-                    + ");");
-            }
-        }
+            "CREATE TABLE ACL_ENTRY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL,MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL,AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));");
 
 
         template.execute(
         template.execute(
             "CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,ENABLED BOOLEAN NOT NULL);");
             "CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,ENABLED BOOLEAN NOT NULL);");
@@ -139,6 +99,9 @@ public class DataSourcePopulator implements InitializingBean {
             "CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
             "CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
         template.execute("CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");
         template.execute("CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");
 
 
+        template.execute(
+            "CREATE TABLE CONTACTS(ID BIGINT NOT NULL PRIMARY KEY, CONTACT_NAME VARCHAR_IGNORECASE(50) NOT NULL, EMAIL VARCHAR_IGNORECASE(50) NOT NULL)");
+
         /*
         /*
            Passwords encoded using MD5, NOT in Base64 format, with null as salt
            Passwords encoded using MD5, NOT in Base64 format, with null as salt
            Encoded password for marissa is "koala"
            Encoded password for marissa is "koala"
@@ -165,14 +128,100 @@ public class DataSourcePopulator implements InitializingBean {
         template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');");
         template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');");
         template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');");
         template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');");
         template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');");
         template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');");
+
+        template.execute("INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');");
+        template.execute("INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');");
+        template.execute("INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');");
+        template.execute("INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');");
+        template.execute("INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');");
+        template.execute("INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');");
+        template.execute("INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');");
+        template.execute("INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');");
+        template.execute("INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');");
+
+        for (int i = 10; i < createEntities; i++) {
+            String[] person = selectPerson();
+            template.execute("INSERT INTO contacts VALUES (" + i + ", '" + person[2] + "', '" + person[0].toLowerCase()
+                + "@" + person[1].toLowerCase() + ".com');");
+        }
+
+        // Create acl_object_identity rows (and also acl_class rows as needed
+        for (int i = 1; i < createEntities; i++) {
+            final ObjectIdentity objectIdentity = new ObjectIdentityImpl(Contact.class, new Long(i));
+            tt.execute(new TransactionCallback() {
+                    public Object doInTransaction(TransactionStatus arg0) {
+                        MutableAcl acl = mutableAclService.createAcl(objectIdentity);
+
+                        return null;
+                    }
+                });
+        }
+
+        // Now grant some permissions
+        grantPermissions(1, "marissa", BasePermission.ADMINISTRATION);
+        grantPermissions(2, "marissa", BasePermission.READ);
+        grantPermissions(3, "marissa", BasePermission.READ);
+        grantPermissions(3, "marissa", BasePermission.WRITE);
+        grantPermissions(3, "marissa", BasePermission.DELETE);
+        grantPermissions(4, "marissa", BasePermission.ADMINISTRATION);
+        grantPermissions(4, "dianne", BasePermission.ADMINISTRATION);
+        grantPermissions(4, "scott", BasePermission.READ);
+        grantPermissions(5, "dianne", BasePermission.ADMINISTRATION);
+        grantPermissions(5, "dianne", BasePermission.READ);
+        grantPermissions(6, "dianne", BasePermission.READ);
+        grantPermissions(6, "dianne", BasePermission.WRITE);
+        grantPermissions(6, "dianne", BasePermission.DELETE);
+        grantPermissions(6, "scott", BasePermission.READ);
+        grantPermissions(7, "scott", BasePermission.ADMINISTRATION);
+        grantPermissions(8, "dianne", BasePermission.ADMINISTRATION);
+        grantPermissions(8, "dianne", BasePermission.READ);
+        grantPermissions(8, "scott", BasePermission.READ);
+        grantPermissions(9, "scott", BasePermission.ADMINISTRATION);
+        grantPermissions(9, "scott", BasePermission.READ);
+        grantPermissions(9, "scott", BasePermission.WRITE);
+        grantPermissions(9, "scott", BasePermission.DELETE);
+
+        // Now expressly change the owner of the first ten contacts
+        // We have to do this last, because "marissa" owns all of them (doing it sooner would prevent ACL updates)
+        // Note that ownership has no impact on permissions - they're separate (ownership only allows ACl editing)
+        changeOwner(5, "dianne");
+        changeOwner(6, "dianne");
+        changeOwner(7, "scott");
+        changeOwner(8, "dianne");
+        changeOwner(9, "scott");
+
+        String[] users = {"bill", "bob", "jane"}; // don't want to mess around with consistent sample data
+        Permission[] permissions = {BasePermission.ADMINISTRATION, BasePermission.READ, BasePermission.DELETE};
+
+        for (int i = 10; i < createEntities; i++) {
+            String user = users[rnd.nextInt(users.length)];
+            Permission permission = permissions[rnd.nextInt(permissions.length)];
+            grantPermissions(i, user, permission);
+
+            String user2 = users[rnd.nextInt(users.length)];
+            Permission permission2 = permissions[rnd.nextInt(permissions.length)];
+            grantPermissions(i, user2, permission2);
+        }
+
+        SecurityContextHolder.clearContext();
+    }
+
+    private void changeOwner(int contactNumber, String newOwnerUsername) {
+        AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(Contact.class,
+                    new Long(contactNumber)));
+        acl.setOwner(new PrincipalSid(newOwnerUsername));
+        updateAclInTransaction(acl);
     }
     }
 
 
     public int getCreateEntities() {
     public int getCreateEntities() {
         return createEntities;
         return createEntities;
     }
     }
 
 
-    public DataSource getDataSource() {
-        return dataSource;
+    private void grantPermissions(int contactNumber, String recipientUsername, Permission permission) {
+        AclImpl acl = (AclImpl) mutableAclService.readAclById(new ObjectIdentityImpl(Contact.class,
+                    new Long(contactNumber)));
+        acl.insertAce(null, permission, new PrincipalSid(recipientUsername), true);
+        updateAclInTransaction(acl);
     }
     }
 
 
     private String[] selectPerson() {
     private String[] selectPerson() {
@@ -187,6 +236,24 @@ public class DataSourcePopulator implements InitializingBean {
     }
     }
 
 
     public void setDataSource(DataSource dataSource) {
     public void setDataSource(DataSource dataSource) {
-        this.dataSource = dataSource;
+        this.template = new JdbcTemplate(dataSource);
+    }
+
+    public void setMutableAclService(MutableAclService mutableAclService) {
+        this.mutableAclService = mutableAclService;
+    }
+
+    public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
+        this.tt = new TransactionTemplate(platformTransactionManager);
+    }
+
+    private void updateAclInTransaction(final MutableAcl acl) {
+        tt.execute(new TransactionCallback() {
+                public Object doInTransaction(TransactionStatus arg0) {
+                    mutableAclService.updateAcl(acl);
+
+                    return null;
+                }
+            });
     }
     }
 }
 }

+ 18 - 17
samples/contacts/src/main/java/sample/contact/DeletePermissionController.java

@@ -12,10 +12,13 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
-import org.acegisecurity.acl.AclManager;
+import org.acegisecurity.acls.AclService;
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.sid.PrincipalSid;
+import org.acegisecurity.acls.sid.Sid;
 
 
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.InitializingBean;
 
 
@@ -44,42 +47,40 @@ import javax.servlet.http.HttpServletResponse;
 public class DeletePermissionController implements Controller, InitializingBean {
 public class DeletePermissionController implements Controller, InitializingBean {
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
-    private AclManager aclManager;
+    private AclService aclService;
     private ContactManager contactManager;
     private ContactManager contactManager;
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
     public void afterPropertiesSet() throws Exception {
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(contactManager, "A ContactManager implementation is required");
         Assert.notNull(contactManager, "A ContactManager implementation is required");
-        Assert.notNull(aclManager, "An aclManager implementation is required");
-    }
-
-    public AclManager getAclManager() {
-        return aclManager;
-    }
-
-    public ContactManager getContactManager() {
-        return contactManager;
+        Assert.notNull(aclService, "An aclService implementation is required");
     }
     }
 
 
     public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
     public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
         throws ServletException, IOException {
+        // <c:param name="sid" value="${acl.sid.principal}"/><c:param name="permission" value="${acl.permission.mask}"/></c:url>">Del</A>
         int contactId = RequestUtils.getRequiredIntParameter(request, "contactId");
         int contactId = RequestUtils.getRequiredIntParameter(request, "contactId");
-        String recipient = RequestUtils.getRequiredStringParameter(request, "recipient");
+        String sid = RequestUtils.getRequiredStringParameter(request, "sid");
+        int mask = RequestUtils.getRequiredIntParameter(request, "permission");
 
 
         Contact contact = contactManager.getById(new Long(contactId));
         Contact contact = contactManager.getById(new Long(contactId));
 
 
-        contactManager.deletePermission(contact, recipient);
+        Sid sidObject = new PrincipalSid(sid);
+        Permission permission = BasePermission.buildFromMask(mask);
+
+        contactManager.deletePermission(contact, sidObject, permission);
 
 
         Map model = new HashMap();
         Map model = new HashMap();
         model.put("contact", contact);
         model.put("contact", contact);
-        model.put("recipient", recipient);
+        model.put("sid", sidObject);
+        model.put("permission", permission);
 
 
         return new ModelAndView("deletePermission", "model", model);
         return new ModelAndView("deletePermission", "model", model);
     }
     }
 
 
-    public void setAclManager(AclManager aclManager) {
-        this.aclManager = aclManager;
+    public void setAclService(AclService aclService) {
+        this.aclService = aclService;
     }
     }
 
 
     public void setContactManager(ContactManager contact) {
     public void setContactManager(ContactManager contact) {

+ 107 - 64
samples/contacts/src/main/resources/applicationContext-common-authorization.xml

@@ -22,14 +22,14 @@
    <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
    <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
 
 
    <!-- ACL permission masks used by this application -->
    <!-- ACL permission masks used by this application -->
-   <bean id="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION</value></property>
+   <bean id="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+      <property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION</value></property>
    </bean>
    </bean>
-   <bean id="org.acegisecurity.acl.basic.SimpleAclEntry.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.READ</value></property>
+   <bean id="org.acegisecurity.acls.domain.BasePermission.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+      <property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.READ</value></property>
    </bean>
    </bean>
-   <bean id="org.acegisecurity.acl.basic.SimpleAclEntry.DELETE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
-      <property name="staticField"><value>org.acegisecurity.acl.basic.SimpleAclEntry.DELETE</value></property>
+   <bean id="org.acegisecurity.acls.domain.BasePermission.DELETE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
+      <property name="staticField"><value>org.acegisecurity.acls.domain.BasePermission.DELETE</value></property>
    </bean>
    </bean>
 
 
 
 
@@ -37,41 +37,53 @@
    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
 
 
    <!-- An access decision voter that reads ACL_CONTACT_READ configuration settings -->
    <!-- An access decision voter that reads ACL_CONTACT_READ configuration settings -->
-   <bean id="aclContactReadVoter" class="org.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_READ</value></property>
+   <bean id="aclContactReadVoter" class="org.acegisecurity.vote.AclEntryVoter">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<value>ACL_CONTACT_READ</value>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
+      	</list>
+      </constructor-arg>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
    </bean>
    </bean>
 
 
    <!-- An access decision voter that reads ACL_CONTACT_DELETE configuration settings -->
    <!-- An access decision voter that reads ACL_CONTACT_DELETE configuration settings -->
-   <bean id="aclContactDeleteVoter" class="org.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_DELETE</value></property>
+   <bean id="aclContactDeleteVoter" class="org.acegisecurity.vote.AclEntryVoter">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<value>ACL_CONTACT_DELETE</value>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.DELETE"/>
+      	</list>
+      </constructor-arg>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.DELETE"/>
-        </list>
-      </property>
    </bean>
    </bean>
 
 
    <!-- An access decision voter that reads ACL_CONTACT_ADMIN configuration settings -->
    <!-- An access decision voter that reads ACL_CONTACT_ADMIN configuration settings -->
-   <bean id="aclContactAdminVoter" class="org.acegisecurity.vote.BasicAclEntryVoter">
-      <property name="processConfigAttribute"><value>ACL_CONTACT_ADMIN</value></property>
+   <bean id="aclContactAdminVoter" class="org.acegisecurity.vote.AclEntryVoter">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<value>ACL_CONTACT_ADMIN</value>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+      	</list>
+      </constructor-arg>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
       <property name="processDomainObjectClass"><value>sample.contact.Contact</value></property>
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-        </list>
-      </property>
    </bean>
    </bean>
 
 
    <!-- An access decision manager used by the business objects -->
    <!-- An access decision manager used by the business objects -->
@@ -89,21 +101,49 @@
 
 
    <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
    <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
 
 
-   <bean id="aclManager" class="org.acegisecurity.acl.AclProviderManager">
-      <property name="providers">
-         <list>
-            <ref local="basicAclProvider"/>
-         </list>
-      </property>
-   </bean>
-
-   <bean id="basicAclProvider" class="org.acegisecurity.acl.basic.BasicAclProvider">
-      <property name="basicAclDao"><ref local="basicAclExtendedDao"/></property>
-   </bean>
-
-   <bean id="basicAclExtendedDao" class="org.acegisecurity.acl.basic.jdbc.JdbcExtendedDaoImpl">
-      <property name="dataSource"><ref bean="dataSource"/></property>
-   </bean>
+	<bean id="aclCache" class="org.acegisecurity.acls.jdbc.EhCacheBasedAclCache">
+		<constructor-arg>
+		   <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
+		      <property name="cacheManager">
+				<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
+		      </property>
+		      <property name="cacheName">
+		         <value>aclCache</value>
+		      </property>
+		   </bean>
+		</constructor-arg>
+	</bean>
+    
+	<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
+		<constructor-arg ref="dataSource"/>
+		<constructor-arg ref="aclCache"/>
+		<constructor-arg ref="aclAuthorizationStrategy"/>
+		<constructor-arg>
+			<bean class="org.acegisecurity.acls.domain.ConsoleAuditLogger"/>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="aclAuthorizationStrategy" class="org.acegisecurity.acls.domain.AclAuthorizationStrategyImpl">
+		<constructor-arg>
+			<list>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+				<bean class="org.acegisecurity.GrantedAuthorityImpl">
+					<constructor-arg value="ROLE_ADMINISTRATOR"/>
+				</bean>
+			</list>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcMutableAclService">
+		<constructor-arg ref="dataSource"/>
+		<constructor-arg ref="lookupStrategy"/>
+		<constructor-arg ref="aclCache"/>
+	</bean>
 
 
    <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
    <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
 
 
@@ -117,28 +157,31 @@
    </bean>
    </bean>
    
    
    <!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
    <!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
-   <bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
+   <bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
+      	</list>
+      </constructor-arg>
    </bean>
    </bean>
    
    
    <!-- Processes AFTER_ACL_READ configuration settings -->
    <!-- Processes AFTER_ACL_READ configuration settings -->
-   <bean id="afterAclRead" class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationProvider">
-      <property name="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
-        </list>
-      </property>
+   <bean id="afterAclRead" class="org.acegisecurity.afterinvocation.AclEntryAfterInvocationProvider">
+      <constructor-arg>
+      	<ref bean="aclService"/>
+      </constructor-arg>
+      <constructor-arg>
+      	<list>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.ADMINISTRATION"/>
+          <ref local="org.acegisecurity.acls.domain.BasePermission.READ"/>
+      	</list>
+      </constructor-arg>
    </bean>
    </bean>
 
 
-
    <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
    <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
 
 
    <!-- getRandomContact() is public.
    <!-- getRandomContact() is public.

+ 6 - 3
samples/contacts/src/main/resources/applicationContext-common-business.xml

@@ -16,7 +16,8 @@
             <value>org.hsqldb.jdbcDriver</value>
             <value>org.hsqldb.jdbcDriver</value>
         </property>
         </property>
         <property name="url">
         <property name="url">
-            <value>jdbc:hsqldb:mem:contacts</value>
+            <value>jdbc:hsqldb:mem:test</value>
+            <!-- <value>jdbc:hsqldb:hsql://localhost/acl</value> -->
         </property>
         </property>
         <property name="username">
         <property name="username">
             <value>sa</value>
             <value>sa</value>
@@ -46,7 +47,9 @@
 	</bean>
 	</bean>
 
 
    <bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
    <bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
-	   <property name="dataSource"><ref local="dataSource"/></property>
+	   <property name="dataSource" ref="dataSource"/>
+	   <property name="mutableAclService" ref="aclService"/>
+	   <property name="platformTransactionManager" ref="transactionManager"/>
    </bean>
    </bean>
    
    
    <bean id="contactDao" class="sample.contact.ContactDaoSpring">
    <bean id="contactDao" class="sample.contact.ContactDaoSpring">
@@ -66,7 +69,7 @@
 
 
    <bean id="contactManagerTarget" class="sample.contact.ContactManagerBackend">
    <bean id="contactManagerTarget" class="sample.contact.ContactManagerBackend">
 	   <property name="contactDao"><ref local="contactDao"/></property>
 	   <property name="contactDao"><ref local="contactDao"/></property>
-	   <property name="basicAclExtendedDao"><ref bean="basicAclExtendedDao"/></property>
+	   <property name="mutableAclService"><ref bean="aclService"/></property>
    </bean>
    </bean>
 
 
 </beans>
 </beans>

+ 2 - 2
samples/contacts/src/main/webapp/common/WEB-INF/contacts-servlet.xml

@@ -29,12 +29,12 @@
 
 
     <bean id="adminPermissionController" class="sample.contact.AdminPermissionController">
     <bean id="adminPermissionController" class="sample.contact.AdminPermissionController">
     	<property name="contactManager"><ref bean="contactManager"/></property>
     	<property name="contactManager"><ref bean="contactManager"/></property>
-    	<property name="aclManager"><ref bean="aclManager"/></property>
+    	<property name="aclService"><ref bean="aclService"/></property>
  	</bean>
  	</bean>
 
 
     <bean id="deletePermissionController" class="sample.contact.DeletePermissionController">
     <bean id="deletePermissionController" class="sample.contact.DeletePermissionController">
     	<property name="contactManager"><ref bean="contactManager"/></property>
     	<property name="contactManager"><ref bean="contactManager"/></property>
-    	<property name="aclManager"><ref bean="aclManager"/></property>
+    	<property name="aclService"><ref bean="aclService"/></property>
  	</bean>
  	</bean>
 
 
     <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
     <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

+ 3 - 14
samples/contacts/src/main/webapp/common/WEB-INF/jsp/adminPermission.jsp

@@ -1,4 +1,3 @@
-<%@ page import="org.acegisecurity.acl.basic.SimpleAclEntry" %>
 <%@ include file="/WEB-INF/jsp/include.jsp" %>
 <%@ include file="/WEB-INF/jsp/include.jsp" %>
 
 
 <html>
 <html>
@@ -11,27 +10,17 @@
 </code>
 </code>
 <P>
 <P>
 <table cellpadding=3 border=0>
 <table cellpadding=3 border=0>
-<c:forEach var="acl" items="${model.acls}">
-  <c:if test="${acl.class.name eq 'org.acegisecurity.acl.basic.SimpleAclEntry'}">
+<c:forEach var="acl" items="${model.acl.entries}">
     <tr>
     <tr>
       <td>
       <td>
         <code>
         <code>
-          <%
-            SimpleAclEntry simpleAcl = ((SimpleAclEntry) pageContext.getAttribute("acl"));
-            String permissionBlock = simpleAcl.printPermissionsBlock(); 
-          %>
-          <%= permissionBlock %>
-          [<c:out value="${acl.mask}"/>]
-          <c:out value="${acl.recipient}"/>
+          <c:out value="${acl}"/>
         </code>
         </code>
       </td>
       </td>
       <td>
       <td>
-      <!-- This application doesn't use ACL inheritance, so we can safely use
-           the model's contact and know it was directly assigned the ACL -->
-        <A HREF="<c:url value="deletePermission.htm"><c:param name="contactId" value="${model.contact.id}"/><c:param name="recipient" value="${acl.recipient}"/></c:url>">Del</A>
+      <A HREF="<c:url value="deletePermission.htm"><c:param name="contactId" value="${model.contact.id}"/><c:param name="sid" value="${acl.sid.principal}"/><c:param name="permission" value="${acl.permission.mask}"/></c:url>">Del</A>
       </td>
       </td>
     </tr>
     </tr>
-  </c:if>
 </c:forEach>
 </c:forEach>
 </table>
 </table>
 <p><a href="<c:url value="addPermission.htm"><c:param name="contactId" value="${model.contact.id}"/></c:url>">Add Permission</a>   <a href="<c:url value="index.htm"/>">Manage</a>
 <p><a href="<c:url value="addPermission.htm"><c:param name="contactId" value="${model.contact.id}"/></c:url>">Add Permission</a>   <a href="<c:url value="index.htm"/>">Manage</a>

+ 4 - 2
samples/contacts/src/main/webapp/common/WEB-INF/jsp/deletePermission.jsp

@@ -1,4 +1,3 @@
-<%@ page import="org.acegisecurity.acl.basic.SimpleAclEntry" %>
 <%@ include file="/WEB-INF/jsp/include.jsp" %>
 <%@ include file="/WEB-INF/jsp/include.jsp" %>
 
 
 <html>
 <html>
@@ -11,8 +10,11 @@
 </code>
 </code>
 <P>
 <P>
 <code>
 <code>
-<c:out value="${model.recipient}"/>
+<c:out value="${model.sid}"/>
 </code>
 </code>
+<code>
+<c:out value="${model.permission}"/>
+</code>
 <p><a href="<c:url value="index.htm"/>">Manage</a>
 <p><a href="<c:url value="index.htm"/>">Manage</a>
 </body>
 </body>
 </html>
 </html>

+ 4 - 4
samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp

@@ -18,12 +18,12 @@
   <td>
   <td>
       <c:out value="${contact.email}"/>
       <c:out value="${contact.email}"/>
   </td>
   </td>
-  <authz:acl domainObject="${contact}" hasPermission="16,1">
+  <authz:accesscontrollist domainObject="${contact}" hasPermission="8,16">
     <td><A HREF="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</A></td>
     <td><A HREF="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</A></td>
-  </authz:acl>
-  <authz:acl domainObject="${contact}" hasPermission="1">
+  </authz:accesscontrollist>
+  <authz:accesscontrollist domainObject="${contact}" hasPermission="16">
     <td><A HREF="<c:url value="adminPermission.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Admin Permission</A></td>
     <td><A HREF="<c:url value="adminPermission.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Admin Permission</A></td>
-  </authz:acl>
+  </authz:accesscontrollist>
   </tr>
   </tr>
 </c:forEach>
 </c:forEach>
 </table>
 </table>

+ 0 - 115
samples/contacts/src/test/java/sample/contact/AbstractContactsSampleTest.java

@@ -1,115 +0,0 @@
-package sample.contact;
-
-import java.util.Iterator;
-import java.util.List;
-
-import org.acegisecurity.Authentication;
-import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
-import org.acegisecurity.context.SecurityContextImpl;
-import org.acegisecurity.context.SecurityContextHolder;
-import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
-import org.springframework.test.AbstractTransactionalSpringContextTests;
-
-/**
- * Provides simplified access to the <code>ContactManager</code> bean and
- * convenience test support methods.
- * 
- * @author David Leal
- * @author Ben Alex
- */
-public abstract class AbstractContactsSampleTest extends AbstractTransactionalSpringContextTests {
-
-    protected ContactManager contactManager;
-
-    protected String[] getConfigLocations() {
-    	setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
-        return new String[] { "applicationContext-common-authorization.xml",
-                "applicationContext-common-business.xml",
-                "applicationContext-contacts-test.xml" };
-    }
-
-    /**
-     * Locates the first <code>Contact</code> of the exact name specified.
-     * 
-     * <p>
-     * Uses the {@link ContactManager#getAll()} method.
-     * </p>
-     * 
-     * @param id
-     *            Identify of the contact to locate (must be an exact match)
-     * 
-     * @return the domain or <code>null</code> if not found
-     */
-    protected Contact getContact(String id) {
-        List contacts = contactManager.getAll();
-        Iterator iter = contacts.iterator();
-
-        while (iter.hasNext()) {
-            Contact contact = (Contact) iter.next();
-
-            if (contact.getId().equals(id)) {
-                return contact;
-            }
-        }
-
-        return null;
-    }
-
-    protected void assertContainsContact(String id, List contacts) {
-        Iterator iter = contacts.iterator();
-        System.out.println(contacts);
-        while (iter.hasNext()) {
-            Contact contact = (Contact) iter.next();
-
-            if (contact.getId().toString().equals(id)) {
-                return;
-            }
-        }
-
-        fail("List of contacts should have contained: " + id);
-    }
-
-    protected void assertNotContainsContact(String id, List contacts) {
-        Iterator iter = contacts.iterator();
-
-        while (iter.hasNext()) {
-            Contact domain = (Contact) iter.next();
-
-            if (domain.getId().toString().equals(id)) {
-                fail("List of contact should NOT (but did) contain: " + id);
-            }
-        }
-    }
-
-    protected void makeActiveUser(String username) {
-        String password = "";
-
-        if ("marissa".equals(username)) {
-            password = "koala";
-        } else if ("dianne".equals(username)) {
-            password = "emu";
-        } else if ("scott".equals(username)) {
-            password = "wombat";
-        } else if ("peter".equals(username)) {
-            password = "opal";
-        }
-
-        Authentication authRequest = new UsernamePasswordAuthenticationToken(
-                username, password);
-        SecurityContextImpl secureContext = new SecurityContextImpl();
-        secureContext.setAuthentication(authRequest);
-        SecurityContextHolder.setContext(secureContext);
-    }
-
-    protected void onTearDownInTransaction() {
-        destroySecureContext();
-    }
-
-    private static void destroySecureContext() {
-        SecurityContextHolder.setContext(new SecurityContextImpl());
-    }
-
-	public void setContactManager(ContactManager contactManager) {
-		this.contactManager = contactManager;
-	}
-}

+ 132 - 26
samples/contacts/src/test/java/sample/contact/GetAllContactsTests.java

@@ -12,68 +12,174 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-
 package sample.contact;
 package sample.contact;
 
 
+import org.acegisecurity.Authentication;
+
+import org.acegisecurity.acls.domain.BasePermission;
+import org.acegisecurity.acls.sid.PrincipalSid;
+
+import org.acegisecurity.context.SecurityContextHolder;
+
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+
+import org.springframework.test.AbstractTransactionalSpringContextTests;
+
+import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 
 
 
 
 /**
 /**
- * Tests {@link
- * com.acegitech.dns.domain.DomainManager#findAllDomainsLike(String)}.
+ * Tests {@link ContactManager}.
  *
  *
  * @author David Leal
  * @author David Leal
+ * @author Ben Alex
  */
  */
-public class GetAllContactsTests extends AbstractContactsSampleTest {
-    //~ Methods ================================================================
+public class GetAllContactsTests extends AbstractTransactionalSpringContextTests {
+    //~ Instance fields ================================================================================================
+
+    protected ContactManager contactManager;
+
+    //~ Methods ========================================================================================================
+
+    protected void assertContainsContact(String id, List contacts) {
+        Iterator iter = contacts.iterator();
+        System.out.println(contacts);
+
+        while (iter.hasNext()) {
+            Contact contact = (Contact) iter.next();
+
+            if (contact.getId().toString().equals(id)) {
+                return;
+            }
+        }
+
+        fail("List of contacts should have contained: " + id);
+    }
+
+    protected void assertNotContainsContact(String id, List contacts) {
+        Iterator iter = contacts.iterator();
+
+        while (iter.hasNext()) {
+            Contact domain = (Contact) iter.next();
+
+            if (domain.getId().toString().equals(id)) {
+                fail("List of contact should NOT (but did) contain: " + id);
+            }
+        }
+    }
+
+    private static void destroySecureContext() {
+        SecurityContextHolder.clearContext();
+    }
+
+    protected String[] getConfigLocations() {
+        setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
+
+        return new String[] {
+            "applicationContext-common-authorization.xml", "applicationContext-common-business.xml",
+            "applicationContext-contacts-test.xml"
+        };
+    }
+
+    /**
+     * Locates the first <code>Contact</code> of the exact name specified.<p>Uses the {@link
+     * ContactManager#getAll()} method.</p>
+     *
+     * @param id Identify of the contact to locate (must be an exact match)
+     *
+     * @return the domain or <code>null</code> if not found
+     */
+    protected Contact getContact(String id) {
+        List contacts = contactManager.getAll();
+        Iterator iter = contacts.iterator();
+
+        while (iter.hasNext()) {
+            Contact contact = (Contact) iter.next();
 
 
-    public void testFindAllDomainsLikeAsDianne() {
+            if (contact.getId().equals(id)) {
+                return contact;
+            }
+        }
+
+        return null;
+    }
+
+    protected void makeActiveUser(String username) {
+        String password = "";
+
+        if ("marissa".equals(username)) {
+            password = "koala";
+        } else if ("dianne".equals(username)) {
+            password = "emu";
+        } else if ("scott".equals(username)) {
+            password = "wombat";
+        } else if ("peter".equals(username)) {
+            password = "opal";
+        }
+
+        Authentication authRequest = new UsernamePasswordAuthenticationToken(username, password);
+        SecurityContextHolder.getContext().setAuthentication(authRequest);
+    }
+
+    protected void onTearDownInTransaction() {
+        destroySecureContext();
+    }
+
+    public void setContactManager(ContactManager contactManager) {
+        this.contactManager = contactManager;
+    }
+
+    public void testDianne() {
         makeActiveUser("dianne"); // has ROLE_USER
         makeActiveUser("dianne"); // has ROLE_USER
-        
+
         List contacts = contactManager.getAll();
         List contacts = contactManager.getAll();
         assertEquals(4, contacts.size());
         assertEquals(4, contacts.size());
-        
+
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(5), contacts);
         assertContainsContact(Long.toString(5), contacts);
         assertContainsContact(Long.toString(6), contacts);
         assertContainsContact(Long.toString(6), contacts);
         assertContainsContact(Long.toString(8), contacts);
         assertContainsContact(Long.toString(8), contacts);
-        
+
         assertNotContainsContact(Long.toString(1), contacts);
         assertNotContainsContact(Long.toString(1), contacts);
         assertNotContainsContact(Long.toString(2), contacts);
         assertNotContainsContact(Long.toString(2), contacts);
         assertNotContainsContact(Long.toString(3), contacts);
         assertNotContainsContact(Long.toString(3), contacts);
-        
     }
     }
 
 
-    public void testFindAllDomainsLikeAsMarissa() {
+    public void testMarissa() {
         makeActiveUser("marissa"); // has ROLE_SUPERVISOR
         makeActiveUser("marissa"); // has ROLE_SUPERVISOR
-        
-        List contacts = contactManager.getAll();        
-        
+
+        List contacts = contactManager.getAll();
+
         assertEquals(4, contacts.size());
         assertEquals(4, contacts.size());
-        
+
         assertContainsContact(Long.toString(1), contacts);
         assertContainsContact(Long.toString(1), contacts);
         assertContainsContact(Long.toString(2), contacts);
         assertContainsContact(Long.toString(2), contacts);
         assertContainsContact(Long.toString(3), contacts);
         assertContainsContact(Long.toString(3), contacts);
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(4), contacts);
-        
-        assertNotContainsContact(Long.toString(5), contacts);       
-              
+
+        assertNotContainsContact(Long.toString(5), contacts);
+
+        Contact c1 = contactManager.getById(new Long(4));
+
+        contactManager.deletePermission(c1, new PrincipalSid("bob"), BasePermission.ADMINISTRATION);
     }
     }
 
 
-    public void testFindAllDomainsLikeAsScott() {
+    public void testScott() {
         makeActiveUser("scott"); // has ROLE_USER
         makeActiveUser("scott"); // has ROLE_USER
-        
+
         List contacts = contactManager.getAll();
         List contacts = contactManager.getAll();
-        
-        assertEquals(5, contacts.size());        
-        
+
+        assertEquals(5, contacts.size());
+
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(6), contacts);
         assertContainsContact(Long.toString(6), contacts);
         assertContainsContact(Long.toString(7), contacts);
         assertContainsContact(Long.toString(7), contacts);
         assertContainsContact(Long.toString(8), contacts);
         assertContainsContact(Long.toString(8), contacts);
         assertContainsContact(Long.toString(9), contacts);
         assertContainsContact(Long.toString(9), contacts);
-        
-        assertNotContainsContact(Long.toString(1), contacts);        
-                       
+
+        assertNotContainsContact(Long.toString(1), contacts);
     }
     }
 }
 }

+ 0 - 106
sandbox/other/src/main/java/org/acegisecurity/acls/domain/BasePermission.java

@@ -1,106 +0,0 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.domain;
-
-import org.acegisecurity.acls.AclFormattingUtils;
-import org.acegisecurity.acls.Permission;
-
-/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
-  */
-public class BasePermission implements Permission {
-    //~ Static fields/initializers =====================================================================================
-
-    public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
-    public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
-    public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
-    public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
-    public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16
-
-    //~ Instance fields ================================================================================================
-
-    private char code;
-    private int mask;
-
-    //~ Constructors ===================================================================================================
-
-    private BasePermission(int mask, char code) {
-        this.mask = mask;
-        this.code = code;
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Dynamically creates a <code>CumulativePermission</code> representing the active bits in the passed mask.
-     * NB: Only uses <code>BasePermission</code>!
-     *
-     * @param mask to review
-     *
-     * @return DOCUMENT ME!
-     */
-    public static Permission buildFromMask(int mask) {
-        CumulativePermission permission = new CumulativePermission();
-
-        // TODO: Write the rest of it to iterate through the 32 bits and instantiate BasePermissions
-        if (mask == 1) {
-            permission.set(READ);
-        }
-
-        if (mask == 2) {
-            permission.set(WRITE);
-        }
-
-        if (mask == 4) {
-            permission.set(CREATE);
-        }
-
-        if (mask == 8) {
-            permission.set(DELETE);
-        }
-        
-        if (mask == 16) {
-            permission.set(ADMINISTRATION);
-        }
-
-        return permission;
-    }
-
-    public boolean equals(Object arg0) {
-        if (!(arg0 instanceof BasePermission)) {
-            return false;
-        }
-
-        BasePermission rhs = (BasePermission) arg0;
-
-        return (this.mask == rhs.getMask());
-    }
-
-    public int getMask() {
-        return mask;
-    }
-
-    public String getPattern() {
-        return AclFormattingUtils.printBinary(mask, code);
-    }
-
-    public String toString() {
-        return "BasePermission[" + getPattern() + "=" + mask + "]";
-    }
-}

+ 0 - 78
sandbox/other/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java

@@ -1,78 +0,0 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import org.acegisecurity.acls.AclService;
-import org.acegisecurity.acls.NotFoundException;
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.sid.Sid;
-
-import org.springframework.jdbc.core.JdbcTemplate;
-
-import org.springframework.util.Assert;
-
-import java.util.Map;
-
-import javax.sql.DataSource;
-
-
-/**
- * Simple JDBC-based implementation of <code>AclService</code>.<p>Requires the "dirty" flags in {@link
- * org.acegisecurity.acls.domain.AclImpl} and {@link org.acegisecurity.acls.domain.AccessControlEntryImpl} to be set,
- * so that the implementation can detect changed parameters easily.</p>
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class JdbcAclService implements AclService /*, MutableAclService */ {
-    //~ Instance fields ================================================================================================
-
-    private AclCache aclCache;
-    private JdbcTemplate template;
-    private LookupStrategy lookupStrategy;
-
-    //~ Constructors ===================================================================================================
-
-    public JdbcAclService(DataSource dataSource, AclCache aclCache, LookupStrategy lookupStrategy) {
-        Assert.notNull(dataSource, "DataSource required");
-        Assert.notNull(aclCache, "AclCache required");
-        Assert.notNull(lookupStrategy, "LookupStrategy required");
-        this.template = new JdbcTemplate(dataSource);
-        this.aclCache = aclCache;
-        this.lookupStrategy = lookupStrategy;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public Map readAclsById(ObjectIdentity[] objects) {
-        return readAclsById(objects, null);
-    }
-
-    /**
-     * Method required by interface.
-     *
-     * @param objects DOCUMENT ME!
-     * @param sids DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     *
-     * @throws NotFoundException DOCUMENT ME!
-     */
-    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids)
-        throws NotFoundException {
-        return lookupStrategy.readAclsById(objects, sids);
-    }
-}

+ 0 - 5
sandbox/other/src/main/java/org/acegisecurity/acls/package.html

@@ -1,5 +0,0 @@
-<html>
-<body>
-Enables retrieval of access control lists (ACLs) for domain object instances.
-</body>
-</html>

+ 0 - 65
sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java

@@ -1,65 +0,0 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.acegisecurity.acls.jdbc;
-
-import org.acegisecurity.acls.Acl;
-import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
-
-import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
-
-import java.util.Iterator;
-import java.util.Map;
-
-
-/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
-  */
-public class JdbcAclServiceTests extends AbstractDependencyInjectionSpringContextTests {
-    //~ Instance fields ================================================================================================
-
-    private JdbcAclService jdbcAclService;
-
-    //~ Methods ========================================================================================================
-
-    protected String[] getConfigLocations() {
-        return new String[] {"classpath:org/acegisecurity/acls/jdbc/applicationContext-test.xml"};
-    }
-
-    public void setJdbcAclService(JdbcAclService jdbcAclService) {
-        this.jdbcAclService = jdbcAclService;
-    }
-
-    public void testStub() {
-        ObjectIdentity id1 = new ObjectIdentityImpl("java.lang.Object", new Long(1));
-        ObjectIdentity id2 = new ObjectIdentityImpl("java.lang.Object", new Long(2));
-        ObjectIdentity id3 = new ObjectIdentityImpl("java.lang.Object", new Long(3));
-        ObjectIdentity id4 = new ObjectIdentityImpl("java.lang.Object", new Long(4));
-        ObjectIdentity id5 = new ObjectIdentityImpl("java.lang.Object", new Long(5));
-        ObjectIdentity id6 = new ObjectIdentityImpl("java.lang.Object", new Long(6));
-        Map map = jdbcAclService.readAclsById(new ObjectIdentity[] {id1, id2, id3, id4, id5, id6});
-        Iterator iterator = map.keySet().iterator();
-
-        while (iterator.hasNext()) {
-            ObjectIdentity identity = (ObjectIdentity) iterator.next();
-            assertEquals(identity, ((Acl) map.get(identity)).getObjectIdentity());
-            System.out.println(map.get(identity));
-        }
-    }
-}

+ 0 - 70
sandbox/other/src/test/java/org/acegisecurity/acls/jdbc/testData.sql

@@ -1,70 +0,0 @@
-CREATE TABLE ACL_SID(
-ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
-PRINCIPAL BOOLEAN NOT NULL,
-SID VARCHAR_IGNORECASE(100) NOT NULL,
-CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));
-
-INSERT INTO ACL_SID VALUES (1, TRUE, 'MARISSA');
-INSERT INTO ACL_SID VALUES (2, TRUE, 'DIANNE');
-INSERT INTO ACL_SID VALUES (3, TRUE, 'SCOTT');
-INSERT INTO ACL_SID VALUES (4, TRUE, 'PETER');
-
-CREATE TABLE ACL_CLASS(
-ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
-CLASS VARCHAR_IGNORECASE(100) NOT NULL,
-CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));
-
-INSERT INTO ACL_CLASS VALUES (1, 'sample.contact.Contact');
-
-CREATE TABLE ACL_OBJECT_IDENTITY(
-ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
-OBJECT_ID_CLASS BIGINT NOT NULL,
-OBJECT_ID_IDENTITY BIGINT NOT NULL,
-PARENT_OBJECT BIGINT,
-OWNER_SID BIGINT,
-ENTRIES_INHERITING BOOLEAN NOT NULL,
-CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),
-CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),
-CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),
-CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));
-
-INSERT INTO ACL_OBJECT_IDENTITY VALUES (1, 1, 1, NULL, 1, TRUE);
-INSERT INTO ACL_OBJECT_IDENTITY VALUES (2, 1, 2, 1, 2, TRUE);
-INSERT INTO ACL_OBJECT_IDENTITY VALUES (3, 1, 3, 1, 1, FALSE);
-INSERT INTO ACL_OBJECT_IDENTITY VALUES (4, 1, 4, 1, 2, TRUE);
-INSERT INTO ACL_OBJECT_IDENTITY VALUES (5, 1, 5, 1, 2, FALSE);
-INSERT INTO ACL_OBJECT_IDENTITY VALUES (6, 1, 6, NULL, 1, TRUE);
-
-CREATE TABLE ACL_ENTRY(
-ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,
-ACL_OBJECT_IDENTITY BIGINT NOT NULL,
-ACE_ORDER INT NOT NULL,
-SID BIGINT NOT NULL,
-MASK INTEGER NOT NULL,
-GRANTING BOOLEAN NOT NULL,
-AUDIT_SUCCESS BOOLEAN NOT NULL,
-AUDIT_FAILURE BOOLEAN NOT NULL,
-CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),
-CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),
-CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));
-
-INSERT INTO ACL_ENTRY VALUES (1, 1, 1, 2, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (2, 1, 2, 1, 2, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (3, 1, 3, 3, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (4, 2, 1, 3, 4, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (5, 2, 2, 4, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (6, 3, 1, 3, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (7, 3, 2, 4, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (8, 3, 3, 1, 8, FALSE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (9, 4, 1, 4, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (10, 5, 1, 2, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (11, 5, 2, 3, 8, FALSE, TRUE, TRUE);
-INSERT INTO ACL_ENTRY VALUES (12, 5, 3, 1, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (13, 5, 4, 4, 8, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (14, 6, 1, 2, 1, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (15, 6, 2, 1, 2, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (16, 6, 3, 2, 4, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (17, 6, 4, 3, 2, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (18, 6, 5, 3, 1, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (19, 6, 6, 4, 4, TRUE, FALSE, FALSE);
-INSERT INTO ACL_ENTRY VALUES (20, 6, 7, 4, 2, TRUE, FALSE, FALSE);