Browse Source

SEC-239: New ACL module.

Ben Alex 19 năm trước cách đây
mục cha
commit
9f512c384e
94 tập tin đã thay đổi với 5762 bổ sung3679 xóa
  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/resources"/>
 	<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/resources"/>
 	<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-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/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/javax.servlet/jars/servlet-api-2.4.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/tomcat/jars/catalina-4.1.9.jar"/>

+ 1 - 1
core/pom.xml

@@ -93,7 +93,7 @@
     <dependency>
       <groupId>hsqldb</groupId>
       <artifactId>hsqldb</artifactId>
-      <version>1.7.3.0</version>
+      <version>1.8.0.4</version>
       <scope>test</scope>
     </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}. 
  * 
@@ -30,28 +29,28 @@ import java.io.Serializable;
  * @author Ben Alex
  * @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.
  * 
@@ -40,105 +39,114 @@ import java.io.Serializable;
  *
  * @author Ben Alex
  * @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.
  *
  * @author Ben Alex
  * @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.
      *
      * @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
      * and root cause.
      *
      * @param msg the detail message
      * @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.
  * 
  * @author Ben Alex
  * @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.
  * 
  * @author Ben Alex
  * @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
      * 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
      * message and root cause.
      *
      * @param msg the detail message
      * @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.
      *
      * @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
      * and root cause.
      *
      * @param msg the detail message
      * @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>.
  * 
@@ -30,31 +29,47 @@ import java.io.Serializable;
  *
  * @author Ben Alex
  * @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.
  *
  * @author Ben Alex
  * @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.
      *
      * @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
      * and root cause.
      *
      * @param msg the detail message
      * @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.
  * 
@@ -28,11 +27,9 @@ import org.acegisecurity.acls.sid.Sid;
  *
  * @author Ben Alex
  * @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.
  *
  * @author Ben Alex
  * @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.
      *
      * @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
      * and root cause.
      *
      * @param msg the detail message
      * @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
  * 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.AccessControlEntry;
 import org.acegisecurity.acls.Acl;
 import org.acegisecurity.acls.AuditableAcl;
@@ -28,11 +23,8 @@ import org.acegisecurity.acls.OwnershipAcl;
 import org.acegisecurity.acls.Permission;
 import org.acegisecurity.acls.UnloadedSidException;
 import org.acegisecurity.acls.objectidentity.ObjectIdentity;
-import org.acegisecurity.acls.sid.PrincipalSid;
 import org.acegisecurity.acls.sid.Sid;
 
-import org.acegisecurity.context.SecurityContextHolder;
-
 import org.springframework.util.Assert;
 
 import java.io.Serializable;
@@ -49,29 +41,17 @@ import java.util.Vector;
  * @version $Id
  */
 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 ================================================================================================
 
     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 deletedAces = new Vector();
-    private Long id;
     private ObjectIdentity objectIdentity;
+    private Serializable id;
     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 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 ===================================================================================================
 
@@ -81,17 +61,19 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
      *
      * @param objectIdentity the object identity this ACL relates to (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(id, "Id required");
+        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+        Assert.notNull(auditLogger, "AuditLogger required");
         this.objectIdentity = objectIdentity;
         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 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 loadedSids the loaded SIDs if only a subset were loaded (may be
      *        <code>null</code>)
@@ -111,14 +91,17 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
      *        this ACL
      * @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(id, "Id required");
+        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
         Assert.notNull(owner, "Owner required");
+        Assert.notNull(auditLogger, "AuditLogger required");
         this.objectIdentity = objectIdentity;
         this.id = id;
-        setAuthorities(auths);
+        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+        this.auditLogger = auditLogger;
         this.parentAcl = parentAcl; // may be null
         this.loadedSids = loadedSids; // may be null
         this.entriesInheriting = entriesInheriting;
@@ -133,17 +116,8 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
 
     //~ 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) {
             int offset = findAceOffset(aceId);
@@ -152,12 +126,11 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
                 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");
 
         synchronized (aces) {
@@ -174,7 +147,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
     }
 
     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[] {});
     }
 
@@ -194,9 +167,9 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
         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 {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
         Assert.notNull(permission, "Permission 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");
                 }
 
-                aces.add(offset + 1, ace);
+                this.aces.add(offset + 1, ace);
             } else {
-                aces.add(ace);
+                this.aces.add(ace);
             }
         }
-
-        this.addedAces = true;
-    }
-
-    public boolean isAclDirty() {
-        return aclDirty;
     }
 
     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
      * <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
-     * 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 sids the exact SIDs to scan for (order is important)
@@ -335,7 +303,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             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++) {
             boolean found = false;
 
@@ -356,83 +324,22 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
         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) {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
         this.entriesInheriting = entriesInheriting;
-        this.aclDirty = true;
     }
 
     public void setOwner(Sid newOwner) {
-        securityCheck(CHANGE_OWNERSHIP);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
         Assert.notNull(newOwner, "Owner required");
         this.owner = newOwner;
-        this.aclDirty = true;
     }
 
     public void setParent(MutableAcl newParent) {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
         Assert.notNull(newParent, "New Parent required");
+        Assert.isTrue(!newParent.equals(this), "Cannot be the parent of yourself");
         this.parentAcl = newParent;
-        this.aclDirty = true;
     }
 
     public String toString() {
@@ -455,6 +362,10 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             sb.append(iterator.next().toString()).append("\r\n");
         }
 
+        if (count == 0) {
+            sb.append("no ACEs; ");
+        }
+
         sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
         sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
         sb.append("]");
@@ -462,9 +373,9 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
         return sb.toString();
     }
 
-    public void updateAce(Long aceId, Permission permission)
+    public void updateAce(Serializable aceId, Permission permission)
         throws NotFoundException {
-        securityCheck(CHANGE_GENERAL);
+        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
 
         synchronized (aces) {
             int offset = findAceOffset(aceId);
@@ -476,12 +387,10 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
             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) {
             int offset = findAceOffset(aceId);
@@ -494,7 +403,5 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
             ace.setAuditSuccess(auditSuccess);
             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.
  * 
  * @author Ben Alex
  * @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}.
  * 
  * @author Ben Alex
  * @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
      *
      * @param dataSource to access the database
      * @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}.
  *
  * @author Ben Alex
  * @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.
  * 
@@ -32,40 +31,40 @@ import java.io.Serializable;
  *
  * @author Ben Alex
  * @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>
-     * 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
      *
      * @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.
  * 
@@ -29,23 +28,23 @@ package org.acegisecurity.acls.sid;
  *
  * @author Ben Alex
  * @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
  * limitations under the License.
  */
-
 package org.acegisecurity.afterinvocation;
 
 import org.acegisecurity.AccessDeniedException;
@@ -26,7 +25,6 @@ import org.acegisecurity.acl.AclManager;
 import org.acegisecurity.acl.basic.BasicAclEntry;
 import org.acegisecurity.acl.basic.SimpleAclEntry;
 
-import org.apache.commons.collections.iterators.ArrayIterator;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -34,12 +32,8 @@ import org.springframework.beans.factory.InitializingBean;
 
 import org.springframework.util.Assert;
 
-import java.lang.reflect.Array;
-
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Set;
 
 
 /**
@@ -135,42 +129,40 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider implements
 
                     boolean hasPermission = false;
 
-                    AclEntry[] acls = null;
-
                     if (domainObject == null) {
                         hasPermission = true;
                     } else if (!processDomainObjectClass.isAssignableFrom(domainObject.getClass())) {
                         hasPermission = true;
                     } 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
+     *
      * @see SimpleAclEntry#parsePermissions(String[]) for valid values
      */
     public void setRequirePermissionFromString(String[] requirePermission) {
@@ -240,179 +234,3 @@ public class BasicAclEntryAfterInvocationCollectionFilteringProvider implements
         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
  * limitations under the License.
  */
-
 package org.acegisecurity.vote;
 
 import org.acegisecurity.AuthorizationServiceException;
 
-import org.acegisecurity.acl.AclManager;
-
 import org.aopalliance.intercept.MethodInvocation;
 
 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
  * @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>
 	</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>

+ 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
  * limitations under the License.
  */
-
 package org.acegisecurity.acls.domain;
 
 import junit.framework.TestCase;
 
+import org.acegisecurity.acls.Permission;
+
 
 /**
  * Tests BasePermission and CumulativePermission.
@@ -30,7 +31,18 @@ public class PermissionTests extends TestCase {
     public void testExpectedIntegerValues() {
         assertEquals(1, BasePermission.READ.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() {

+ 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 value="classpath:org/acegisecurity/acls/jdbc/testData.sql"/>
 	</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">
 		<constructor-arg>
@@ -31,26 +35,33 @@
     
 	<bean id="lookupStrategy" class="org.acegisecurity.acls.jdbc.BasicLookupStrategy">
 		<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>
+	</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.JdbcAclService">
+	<bean id="aclService" class="org.acegisecurity.acls.jdbc.JdbcMutableAclService">
 		<constructor-arg ref="dataSource"/>
-		<constructor-arg ref="aclCache"/>
-		<constructor-arg ref="lookupStrategy"/>
+		<constructor-arg ref="lookupStrategy"/>
+		<constructor-arg ref="aclCache"/>
 	</bean>
 
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
@@ -58,7 +69,8 @@
             <value>org.hsqldb.jdbcDriver</value>
         </property>
         <property name="url">
-            <value>jdbc:hsqldb:mem:test</value>
+            <value>jdbc:hsqldb:mem:test</value>
+            <!--  <value>jdbc:hsqldb:hsql://localhost/acl</value> -->
         </property>
         <property name="username">
             <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.
 
+
 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_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_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_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 1 
 and ACL_CLASS.CLASS = 'sample.contact.Contact')
 or 
-(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 2
+(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 2000
 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">
           <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
           or another looks like this:</para>
@@ -4838,25 +4926,28 @@ public boolean supports(Class clazz);</programlisting></para>
         <literal>ifAllGranted</literal>, and finally, <literal>if
         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
-        <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;/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
         principal holds either permission 16 or permission 1 for the "contact"
         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>
     </chapter>
 
@@ -5371,6 +5462,191 @@ public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
       <section id="domain-acls-overview">
         <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
         permissions not simply at a web request or method invocation level.
         Instead, security decisions need to comprise both who
@@ -5448,7 +5724,7 @@ public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
         about below.</para>
       </section>
 
-      <section id="domain-acls-basic">
+      <section id="domain-acls-basic-old">
         <title>Basic ACL Package</title>
 
         <para>Please note that our Basic ACL services are currently being

+ 1 - 1
project.xml

@@ -265,7 +265,7 @@
     <dependency>
       <groupId>hsqldb</groupId>
       <artifactId>hsqldb</artifactId>
-      <version>1.7.3.0</version>
+      <version>1.8.0.4</version>
       <type>jar</type>
       <url>http://hsqldb.sourceforge.net/</url>
       <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
  * limitations under the License.
  */
-
 package sample.contact.annotation;
 
 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;
 
@@ -54,28 +59,36 @@ import java.util.Random;
 public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
     //~ Instance fields ================================================================================================
 
-    private BasicAclExtendedDao basicAclExtendedDao;
     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 ========================================================================================================
 
     @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()) {
-            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 {
         Assert.notNull(contactDao, "contactDao required");
-        Assert.notNull(basicAclExtendedDao, "basicAclExtendedDao required");
+        Assert.notNull(mutableAclService, "mutableAclService required");
     }
 
     @Secured({"ROLE_USER"})
@@ -84,8 +97,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         contact.setId(new Long(counter++));
         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()) {
             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());
 
         // 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()) {
             logger.debug("Deleted contact " + contact + " including ACL permissions");
@@ -105,8 +119,20 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
     }
 
     @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()) {
             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.addAll(contactDao.findAllRoles());
 
         return list;
     }
 
-    public BasicAclExtendedDao getBasicAclExtendedDao() {
-        return basicAclExtendedDao;
-    }
-
     @Secured({"ROLE_USER", "AFTER_ACL_READ"})
     @Transactional(readOnly = true)
     public Contact getById(Long id) {
@@ -150,10 +171,6 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         return contactDao.getById(id);
     }
 
-    public ContactDao getContactDao() {
-        return contactDao;
-    }
-
     /**
      * 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) {
         this.contactDao = contactDao;
     }
 
+    public void setMutableAclService(MutableAclService mutableAclService) {
+        this.mutableAclService = mutableAclService;
+    }
+
     public void update(Contact 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>
 		    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 		    PATTERN_TYPE_APACHE_ANT
-            /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+            /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,switchUserProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
          </value>
       </property>
     </bean>
@@ -38,7 +38,7 @@
       </property>
    </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>
    </bean>
 
@@ -90,6 +90,7 @@
    </bean>
 
    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
+      <property name="authenticationManager"><ref local="authenticationManager"/></property>
       <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
    </bean>
 
@@ -101,6 +102,18 @@
    <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
       <property name="key"><value>springRocks</value></property>
    </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 ==================== -->
 
@@ -136,6 +149,11 @@
 
    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
       <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 id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
@@ -173,10 +191,22 @@
 			    /index.jsp=ROLE_ANONYMOUS,ROLE_USER
 			    /hello.htm=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
 				/**=ROLE_USER
          </value>
       </property>
    </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>

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

@@ -12,60 +12,72 @@
    <!-- ~~~~~~~~~~~~~~~~~~ "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 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 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 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>
 
 
    <!-- 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 -->
-   <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="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>
+   <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="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>
+   <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="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">
+   <bean id="businessAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
       <property name="allowIfAllAbstainDecisions"><value>false</value></property>
       <property name="decisionVoters">
          <list>
@@ -79,25 +91,53 @@
 
    <!-- ========= 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 =========== -->
 
-   <bean id="afterInvocationManager" class="net.sf.acegisecurity.afterinvocation.AfterInvocationProviderManager">
+   <bean id="afterInvocationManager" class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">
       <property name="providers">
          <list>
             <ref local="afterAclRead"/>
@@ -107,38 +147,41 @@
    </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 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>
    
    <!-- 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>
 
-
     <!-- ================= 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>
 	</bean>
 
 	<!-- 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="authenticationManager"><ref bean="authenticationManager"/></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.
 	-->
 	<bean id="methodSecurityAdvisor"
-		class="net.sf.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"
+		class="org.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"
 		autowire="constructor" >
 	</bean>
 		   

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

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

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

@@ -33,10 +33,10 @@
 
    <filter>
         <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>
             <param-name>targetClass</param-name>
-            <param-value>net.sf.acegisecurity.util.FilterChainProxy</param-value>
+            <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
         </init-param>
    </filter>
 
@@ -64,7 +64,7 @@
         to the WebApplicationContext
  -->  
     <listener>
-        <listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
+        <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
     </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
  * limitations under the License.
  */
-
 package sample.contact;
 
 import org.acegisecurity.acl.basic.SimpleAclEntry;
@@ -28,7 +27,7 @@ public class AddPermission {
     //~ Instance fields ================================================================================================
 
     public Contact contact;
-    public Integer permission = new Integer(SimpleAclEntry.NOTHING);
+    public Integer permission = new Integer(SimpleAclEntry.READ);
     public String recipient;
 
     //~ 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
  * limitations under the License.
  */
-
 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;
 
@@ -59,7 +60,7 @@ public class AddPermissionController extends SimpleFormController implements Ini
     protected ModelAndView disallowDuplicateFormSubmission(HttpServletRequest request, HttpServletResponse response)
         throws Exception {
         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);
     }
@@ -76,10 +77,6 @@ public class AddPermissionController extends SimpleFormController implements Ini
         return addPermission;
     }
 
-    public ContactManager getContactManager() {
-        return contactManager;
-    }
-
     protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
         throws Exception {
         return disallowDuplicateFormSubmission(request, response);
@@ -87,16 +84,12 @@ public class AddPermissionController extends SimpleFormController implements Ini
 
     private Map listPermissions(HttpServletRequest request) {
         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()));
-        map.put(new Integer(SimpleAclEntry.READ),
+        map.put(new Integer(BasePermission.READ.getMask()),
             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()));
-        map.put(new Integer(SimpleAclEntry.READ_WRITE_DELETE),
-            getApplicationContext().getMessage("select.readWriteDelete", null, "Read+Write+Delete", request.getLocale()));
 
         return map;
     }
@@ -120,13 +113,14 @@ public class AddPermissionController extends SimpleFormController implements Ini
         BindException errors) throws Exception {
         AddPermission addPermission = (AddPermission) command;
 
+        PrincipalSid sid = new PrincipalSid(addPermission.getRecipient());
+        Permission permission = BasePermission.buildFromMask(addPermission.getPermission().intValue());
+
         try {
-            contactManager.addPermission(addPermission.getContact(), addPermission.getRecipient(),
-                addPermission.getPermission());
+            contactManager.addPermission(addPermission.getContact(), sid, permission);
         } catch (DataAccessException existingPermission) {
             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);
         }

+ 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
  * limitations under the License.
  */
-
 package sample.contact;
 
-import org.acegisecurity.acl.basic.SimpleAclEntry;
+import org.acegisecurity.acls.domain.BasePermission;
 
 import org.springframework.validation.Errors;
 import org.springframework.validation.ValidationUtils;
@@ -44,9 +43,8 @@ public class AddPermissionValidator implements Validator {
         if (addPermission.getPermission() != null) {
             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. *");
             }
         }

+ 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
  * limitations under the License.
  */
-
 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;
 
@@ -45,22 +45,14 @@ import javax.servlet.http.HttpServletResponse;
 public class AdminPermissionController implements Controller, InitializingBean {
     //~ Instance fields ================================================================================================
 
-    private AclManager aclManager;
+    private AclService aclService;
     private ContactManager contactManager;
 
     //~ Methods ========================================================================================================
 
     public void afterPropertiesSet() throws Exception {
         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)
@@ -68,17 +60,17 @@ public class AdminPermissionController implements Controller, InitializingBean {
         int id = RequestUtils.getRequiredIntParameter(request, "contactId");
 
         Contact contact = contactManager.getById(new Long(id));
-        AclEntry[] acls = aclManager.getAcls(contact);
+        Acl acl = aclService.readAclById(new ObjectIdentityImpl(contact));
 
         Map model = new HashMap();
         model.put("contact", contact);
-        model.put("acls", acls);
+        model.put("acl", acl);
 
         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) {

+ 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
  * limitations under the License.
  */
-
 package sample.contact;
 
+import org.acegisecurity.acls.Permission;
+import org.acegisecurity.acls.sid.Sid;
+
 import java.util.List;
 
 
@@ -27,13 +29,13 @@ import java.util.List;
 public interface ContactManager {
     //~ 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 delete(Contact contact);
 
-    public void deletePermission(Contact contact, String recipient);
+    public void deletePermission(Contact contact, Sid recipient, Permission permission);
 
     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
  * limitations under the License.
  */
-
 package sample.contact;
 
 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;
 
@@ -45,27 +50,33 @@ import java.util.Random;
 public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
     //~ Instance fields ================================================================================================
 
-    private BasicAclExtendedDao basicAclExtendedDao;
     private ContactDao contactDao;
+    private MutableAclService mutableAclService;
     private int counter = 1000;
 
     //~ 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()) {
-            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 {
         Assert.notNull(contactDao, "contactDao required");
-        Assert.notNull(basicAclExtendedDao, "basicAclExtendedDao required");
+        Assert.notNull(mutableAclService, "mutableAclService required");
     }
 
     public void create(Contact contact) {
@@ -73,8 +84,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         contact.setId(new Long(counter++));
         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()) {
             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());
 
         // 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()) {
             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()) {
             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.addAll(contactDao.findAllRoles());
 
         return list;
     }
 
-    public BasicAclExtendedDao getBasicAclExtendedDao() {
-        return basicAclExtendedDao;
-    }
-
     public Contact getById(Long id) {
         if (logger.isDebugEnabled()) {
             logger.debug("Returning contact with id: " + id);
@@ -131,10 +150,6 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C
         return contactDao.getById(id);
     }
 
-    public ContactDao getContactDao() {
-        return contactDao;
-    }
-
     /**
      * 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) {
         this.contactDao = contactDao;
     }
 
+    public void setMutableAclService(MutableAclService mutableAclService) {
+        this.mutableAclService = mutableAclService;
+    }
+
     public void update(Contact 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
  * limitations under the License.
  */
-
 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.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 java.util.Random;
@@ -35,8 +56,10 @@ import javax.sql.DataSource;
 public class DataSourcePopulator implements InitializingBean {
     //~ Instance fields ================================================================================================
 
-    private DataSource dataSource;
+    JdbcTemplate template;
+    private MutableAclService mutableAclService;
     Random rnd = new Random();
+    TransactionTemplate tt;
     String[] firstNames = {
             "Bob", "Mary", "James", "Jane", "Kristy", "Kirsty", "Kate", "Jeni", "Angela", "Melanie", "Kent", "William",
             "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",
             "Findlay", "Robinson", "Giugni", "Lang", "Chi", "Carmichael"
         };
-    private int createEntities = 1000;
+    private int createEntities = 50;
 
     //~ Methods ========================================================================================================
 
     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(
-            "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(
-            "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(
-            "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(
-            "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(
             "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));");
         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
            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('bob','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() {
         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() {
@@ -187,6 +236,24 @@ public class DataSourcePopulator implements InitializingBean {
     }
 
     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
  * limitations under the License.
  */
-
 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;
 
@@ -44,42 +47,40 @@ import javax.servlet.http.HttpServletResponse;
 public class DeletePermissionController implements Controller, InitializingBean {
     //~ Instance fields ================================================================================================
 
-    private AclManager aclManager;
+    private AclService aclService;
     private ContactManager contactManager;
 
     //~ Methods ========================================================================================================
 
     public void afterPropertiesSet() throws Exception {
         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)
         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");
-        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));
 
-        contactManager.deletePermission(contact, recipient);
+        Sid sidObject = new PrincipalSid(sid);
+        Permission permission = BasePermission.buildFromMask(mask);
+
+        contactManager.deletePermission(contact, sidObject, permission);
 
         Map model = new HashMap();
         model.put("contact", contact);
-        model.put("recipient", recipient);
+        model.put("sid", sidObject);
+        model.put("permission", permission);
 
         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) {

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

@@ -22,14 +22,14 @@
    <!-- ~~~~~~~~~~~~~~~~~~ "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~ -->
 
    <!-- 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 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 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>
 
 
@@ -37,41 +37,53 @@
    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
 
    <!-- 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="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>
 
    <!-- 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="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>
 
    <!-- 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="aclManager"><ref local="aclManager"/></property>
-      <property name="requirePermission">
-        <list>
-          <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
-        </list>
-      </property>
    </bean>
 
    <!-- An access decision manager used by the business objects -->
@@ -89,21 +101,49 @@
 
    <!-- ========= 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 =========== -->
 
@@ -117,28 +157,31 @@
    </bean>
    
    <!-- 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>
    
    <!-- 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>
 
-
    <!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
 
    <!-- getRandomContact() is public.

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

@@ -16,7 +16,8 @@
             <value>org.hsqldb.jdbcDriver</value>
         </property>
         <property name="url">
-            <value>jdbc:hsqldb:mem:contacts</value>
+            <value>jdbc:hsqldb:mem:test</value>
+            <!-- <value>jdbc:hsqldb:hsql://localhost/acl</value> -->
         </property>
         <property name="username">
             <value>sa</value>
@@ -46,7 +47,9 @@
 	</bean>
 
    <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 id="contactDao" class="sample.contact.ContactDaoSpring">
@@ -66,7 +69,7 @@
 
    <bean id="contactManagerTarget" class="sample.contact.ContactManagerBackend">
 	   <property name="contactDao"><ref local="contactDao"/></property>
-	   <property name="basicAclExtendedDao"><ref bean="basicAclExtendedDao"/></property>
+	   <property name="mutableAclService"><ref bean="aclService"/></property>
    </bean>
 
 </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">
     	<property name="contactManager"><ref bean="contactManager"/></property>
-    	<property name="aclManager"><ref bean="aclManager"/></property>
+    	<property name="aclService"><ref bean="aclService"/></property>
  	</bean>
 
     <bean id="deletePermissionController" class="sample.contact.DeletePermissionController">
     	<property name="contactManager"><ref bean="contactManager"/></property>
-    	<property name="aclManager"><ref bean="aclManager"/></property>
+    	<property name="aclService"><ref bean="aclService"/></property>
  	</bean>
 
     <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" %>
 
 <html>
@@ -11,27 +10,17 @@
 </code>
 <P>
 <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>
       <td>
         <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>
       </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>
     </tr>
-  </c:if>
 </c:forEach>
 </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>

+ 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" %>
 
 <html>
@@ -11,8 +10,11 @@
 </code>
 <P>
 <code>
-<c:out value="${model.recipient}"/>
+<c:out value="${model.sid}"/>
 </code>
+<code>
+<c:out value="${model.permission}"/>
+</code>
 <p><a href="<c:url value="index.htm"/>">Manage</a>
 </body>
 </html>

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

@@ -18,12 +18,12 @@
   <td>
       <c:out value="${contact.email}"/>
   </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>
-  </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>
-  </authz:acl>
+  </authz:accesscontrollist>
   </tr>
 </c:forEach>
 </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
  * limitations under the License.
  */
-
 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;
 
 
 /**
- * Tests {@link
- * com.acegitech.dns.domain.DomainManager#findAllDomainsLike(String)}.
+ * Tests {@link ContactManager}.
  *
  * @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
-        
+
         List contacts = contactManager.getAll();
         assertEquals(4, contacts.size());
-        
+
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(5), contacts);
         assertContainsContact(Long.toString(6), contacts);
         assertContainsContact(Long.toString(8), contacts);
-        
+
         assertNotContainsContact(Long.toString(1), contacts);
         assertNotContainsContact(Long.toString(2), contacts);
         assertNotContainsContact(Long.toString(3), contacts);
-        
     }
 
-    public void testFindAllDomainsLikeAsMarissa() {
+    public void testMarissa() {
         makeActiveUser("marissa"); // has ROLE_SUPERVISOR
-        
-        List contacts = contactManager.getAll();        
-        
+
+        List contacts = contactManager.getAll();
+
         assertEquals(4, contacts.size());
-        
+
         assertContainsContact(Long.toString(1), contacts);
         assertContainsContact(Long.toString(2), contacts);
         assertContainsContact(Long.toString(3), 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
-        
+
         List contacts = contactManager.getAll();
-        
-        assertEquals(5, contacts.size());        
-        
+
+        assertEquals(5, contacts.size());
+
         assertContainsContact(Long.toString(4), contacts);
         assertContainsContact(Long.toString(6), contacts);
         assertContainsContact(Long.toString(7), contacts);
         assertContainsContact(Long.toString(8), 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);