Parcourir la source

SEC-239: Initial commit. Work-in-progress only.

Ben Alex il y a 19 ans
Parent
commit
5ba40705e8
38 fichiers modifiés avec 3163 ajouts et 0 suppressions
  1. 30 0
      sandbox/src/main/java/org/acegisecurity/acls/AccessControlEntry.java
  2. 206 0
      sandbox/src/main/java/org/acegisecurity/acls/Acl.java
  3. 118 0
      sandbox/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java
  4. 75 0
      sandbox/src/main/java/org/acegisecurity/acls/AclService.java
  5. 49 0
      sandbox/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java
  6. 13 0
      sandbox/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java
  7. 12 0
      sandbox/src/main/java/org/acegisecurity/acls/AuditableAcl.java
  8. 51 0
      sandbox/src/main/java/org/acegisecurity/acls/ChildrenExistException.java
  9. 49 0
      sandbox/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java
  10. 61 0
      sandbox/src/main/java/org/acegisecurity/acls/MutableAcl.java
  11. 77 0
      sandbox/src/main/java/org/acegisecurity/acls/MutableAclService.java
  12. 49 0
      sandbox/src/main/java/org/acegisecurity/acls/NotFoundException.java
  13. 37 0
      sandbox/src/main/java/org/acegisecurity/acls/OwnershipAcl.java
  14. 70 0
      sandbox/src/main/java/org/acegisecurity/acls/Permission.java
  15. 51 0
      sandbox/src/main/java/org/acegisecurity/acls/UnloadedSidException.java
  16. 124 0
      sandbox/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java
  17. 500 0
      sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java
  18. 14 0
      sandbox/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java
  19. 66 0
      sandbox/src/main/java/org/acegisecurity/acls/domain/BasePermission.java
  20. 19 0
      sandbox/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java
  21. 84 0
      sandbox/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java
  22. 18 0
      sandbox/src/main/java/org/acegisecurity/acls/jdbc/AclCache.java
  23. 517 0
      sandbox/src/main/java/org/acegisecurity/acls/jdbc/BasicLookupStrategy.java
  24. 58 0
      sandbox/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java
  25. 51 0
      sandbox/src/main/java/org/acegisecurity/acls/jdbc/JdbcAclService.java
  26. 47 0
      sandbox/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java
  27. 78 0
      sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java
  28. 157 0
      sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java
  29. 5 0
      sandbox/src/main/java/org/acegisecurity/acls/package.html
  30. 74 0
      sandbox/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java
  31. 81 0
      sandbox/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java
  32. 54 0
      sandbox/src/main/java/org/acegisecurity/acls/sid/Sid.java
  33. 41 0
      sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java
  34. 23 0
      sandbox/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java
  35. 40 0
      sandbox/src/test/java/org/acegisecurity/acls/jdbc/JdbcAclServiceTests.java
  36. 71 0
      sandbox/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml
  37. 23 0
      sandbox/src/test/java/org/acegisecurity/acls/jdbc/select.sql
  38. 70 0
      sandbox/src/test/java/org/acegisecurity/acls/jdbc/testData.sql

+ 30 - 0
sandbox/src/main/java/org/acegisecurity/acls/AccessControlEntry.java

@@ -0,0 +1,30 @@
+package org.acegisecurity.acls;
+
+import java.io.Serializable;
+
+import org.acegisecurity.acls.sid.Sid;
+
+/**
+ * Represents an individual permission assignment within an {@link Acl}. 
+ * 
+ * <p>
+ * Instances MUST be immutable, as they are returned by <code>Acl</code>
+ * and should not allow client modification.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AccessControlEntry {
+    /**
+     * Obtains an identifier that represents this ACE.
+     *
+     * @return the identifier, or <code>null</code> if unsaved
+     */
+    public Serializable getId();
+    
+	public Acl getAcl();
+	public Sid getSid();
+	public Permission getPermission();
+	public boolean isGranting();
+}

+ 206 - 0
sandbox/src/main/java/org/acegisecurity/acls/Acl.java

@@ -0,0 +1,206 @@
+/* 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;
+
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.sid.Sid;
+
+
+/**
+ * Represents an access control list (ACL) for a domain object.
+ * 
+ * <p>
+ * An <code>Acl</code> represents all ACL entries for a given domain object. In
+ * order to avoid needing references to the domain object itself, this
+ * interface handles indirection between a domain object and an ACL object
+ * identity via the {@link
+ * org.acegisecurity.acls.objectidentity.ObjectIdentity} interface.
+ * </p>
+ * 
+ * <p>
+ * An implementation represents the {@link org.acegisecurity.acls.Permission}
+ * list applicable for some or all {@link org.acegisecurity.acls.sid.Sid}
+ * instances.
+ * </p>
+ *
+ * @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);
+}

+ 118 - 0
sandbox/src/main/java/org/acegisecurity/acls/AclFormattingUtils.java

@@ -0,0 +1,118 @@
+/* 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);
+    }
+}

+ 75 - 0
sandbox/src/main/java/org/acegisecurity/acls/AclService.java

@@ -0,0 +1,75 @@
+/* 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>)
+     */
+    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>)
+     */
+    public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException;
+}

+ 49 - 0
sandbox/src/main/java/org/acegisecurity/acls/AlreadyExistsException.java

@@ -0,0 +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 <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);
+    }
+
+    /**
+     * 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);
+    }
+}

+ 13 - 0
sandbox/src/main/java/org/acegisecurity/acls/AuditableAccessControlEntry.java

@@ -0,0 +1,13 @@
+package org.acegisecurity.acls;
+
+/**
+ * Represents an ACE that provides auditing information.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AuditableAccessControlEntry extends AccessControlEntry {
+	public boolean isAuditSuccess();
+	public boolean isAuditFailure();
+}

+ 12 - 0
sandbox/src/main/java/org/acegisecurity/acls/AuditableAcl.java

@@ -0,0 +1,12 @@
+package org.acegisecurity.acls;
+
+/**
+ * A mutable ACL that provides audit capabilities.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AuditableAcl extends MutableAcl {
+	public void updateAuditing(Long aceId, boolean auditSuccess, boolean auditFailure);
+}

+ 51 - 0
sandbox/src/main/java/org/acegisecurity/acls/ChildrenExistException.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.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);
+    }
+
+    /**
+     * 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);
+    }
+}

+ 49 - 0
sandbox/src/main/java/org/acegisecurity/acls/IdentityUnavailableException.java

@@ -0,0 +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 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);
+    }
+
+    /**
+     * 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);
+    }
+}

+ 61 - 0
sandbox/src/main/java/org/acegisecurity/acls/MutableAcl.java

@@ -0,0 +1,61 @@
+/* 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;
+
+import org.acegisecurity.acls.sid.Sid;
+
+
+/**
+ * A mutable <code>Acl</code>.
+ * 
+ * <p>
+ * A mutable ACL must ensure that appropriate security checks are performed
+ * before allowing access to its methods.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface MutableAcl extends Acl {
+    //~ Methods ================================================================
+
+
+    /**
+     * Obtains an identifier that represents this <code>MutableAcl</code>.
+     *
+     * @return the identifier, or <code>null</code> if unsaved
+     */
+    public Serializable getId();
+
+	
+    public void deleteAce(Long aceId) throws NotFoundException ;
+
+    public void insertAce(Long afterAceId, Permission permission, Sid sid,
+        boolean granting) throws NotFoundException;
+
+    /**
+     * 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 void setEntriesInheriting(boolean entriesInheriting);
+}

+ 77 - 0
sandbox/src/main/java/org/acegisecurity/acls/MutableAclService.java

@@ -0,0 +1,77 @@
+/* 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;
+}

+ 49 - 0
sandbox/src/main/java/org/acegisecurity/acls/NotFoundException.java

@@ -0,0 +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 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);
+    }
+
+    /**
+     * 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);
+    }
+}

+ 37 - 0
sandbox/src/main/java/org/acegisecurity/acls/OwnershipAcl.java

@@ -0,0 +1,37 @@
+/* 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.
+ * 
+ * <p>
+ * Generally the owner of an ACL is able to call any ACL mutator method, as
+ * well as assign a new owner.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface OwnershipAcl extends MutableAcl {
+    //~ Methods ================================================================
+
+    public Sid getOwner();
+
+    public void setOwner(Sid newOwner);
+}

+ 70 - 0
sandbox/src/main/java/org/acegisecurity/acls/Permission.java

@@ -0,0 +1,70 @@
+/* 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();
+}

+ 51 - 0
sandbox/src/main/java/org/acegisecurity/acls/UnloadedSidException.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.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);
+    }
+
+    /**
+     * 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);
+    }
+}

+ 124 - 0
sandbox/src/main/java/org/acegisecurity/acls/domain/AccessControlEntryImpl.java

@@ -0,0 +1,124 @@
+package org.acegisecurity.acls.domain;
+
+import java.io.Serializable;
+
+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;
+
+/**
+ * An immutable default implementation of <code>AccessControlEntry</code>.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry {
+	private Serializable id;
+	private Acl acl;
+	private Sid sid;
+	private Permission permission;
+	private boolean granting;
+	private boolean auditSuccess = false;
+	private boolean auditFailure = false;
+	private boolean aceDirty = false;
+	
+	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 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;
+	}
+	
+	public Acl getAcl() {
+		return acl;
+	}
+	public boolean isGranting() {
+		return granting;
+	}
+	public Serializable getId() {
+		return id;
+	}
+	public Permission getPermission() {
+		return permission;
+	}
+	public Sid getSid() {
+		return sid;
+	}
+	
+	void setPermission(Permission permission) {
+		Assert.notNull(permission, "Permission required");
+		this.permission = permission;
+		this.aceDirty = true;
+	}
+	
+	void setId(Serializable id) {
+		this.id = id;
+	}
+	
+	public boolean isAuditFailure() {
+		return auditFailure;
+	}
+
+	void setAuditFailure(boolean auditFailure) {
+		this.auditFailure = auditFailure;
+		this.aceDirty = true;
+	}
+
+	public boolean isAuditSuccess() {
+		return auditSuccess;
+	}
+
+	void setAuditSuccess(boolean auditSuccess) {
+		this.auditSuccess = auditSuccess;
+		this.aceDirty = true;
+	}
+	
+	public boolean isAceDirty() {
+		return aceDirty;
+	}
+	
+
+	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();
+	}	
+}

+ 500 - 0
sandbox/src/main/java/org/acegisecurity/acls/domain/AclImpl.java

@@ -0,0 +1,500 @@
+/* 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 java.io.Serializable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+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;
+import org.acegisecurity.acls.MutableAcl;
+import org.acegisecurity.acls.NotFoundException;
+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;
+
+
+/**
+ * Base implementation of <code>Acl</code>.
+ *
+ * @author Ben Alex
+ * @version $Id
+ */
+public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
+    //~ Instance fields ========================================================
+	private static final int CHANGE_OWNERSHIP = 0;
+	private static final int CHANGE_AUDITING = 1;
+	private static final int CHANGE_GENERAL = 2;
+	
+	private GrantedAuthority gaTakeOwnership;
+	private GrantedAuthority gaModifyAuditing;
+	private GrantedAuthority gaGeneralChanges;
+	
+	private Acl parentAcl;
+    private AuditLogger auditLogger = new ConsoleAuditLogger(); // AuditableAcl
+    private List aces = new Vector();
+    private List deletedAces = new Vector();
+    private Long id;
+    private ObjectIdentity objectIdentity;
+    private Sid owner; // OwnershipAcl
+    private boolean entriesInheriting = false;
+    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 updatedAces = false; // for snapshot detection
+
+    //~ Constructors ===========================================================
+
+    /**
+     * Minimal constructor, which should be used {@link
+     * org.acegisecurity.acls.MutableAclService#createAcl(ObjectIdentity)}.
+     *
+     * @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)
+     */
+    public AclImpl(ObjectIdentity objectIdentity, Long id, GrantedAuthority[] auths) {
+    	Assert.notNull(objectIdentity, "Object Identity required");
+        Assert.notNull(id, "Id required");
+        this.objectIdentity = objectIdentity;
+        this.id = id;
+        this.setAuthorities(auths);
+    }
+    
+    /**
+     * 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).
+     *
+     * @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];
+    }
+
+    /**
+     * Full constructor, which should be used by persistence tools that do not
+     * provide field-level access features.
+     *
+     * @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 parentAcl the parent (may be <code>null</code>)
+     * @param loadedSids the loaded SIDs if only a subset were loaded (may be
+     *        <code>null</code>)
+     * @param entriesInheriting if ACEs from the parent should inherit into
+     *        this ACL
+     * @param owner the owner (required)
+     */
+    public AclImpl(ObjectIdentity objectIdentity, Long id, Acl parentAcl, GrantedAuthority[] auths,
+        Sid[] loadedSids, boolean entriesInheriting, Sid owner) {
+        Assert.notNull(objectIdentity, "Object Identity required");
+        Assert.notNull(id, "Id required");
+        Assert.notNull(owner, "Owner required");
+        this.objectIdentity = objectIdentity;
+        this.id = id;
+        setAuthorities(auths);
+        this.parentAcl = parentAcl; // may be null
+        this.loadedSids = loadedSids; // may be null
+        this.entriesInheriting = entriesInheriting;
+        this.owner = owner;
+    }
+
+    /**
+     * Private no-argument constructor for use by reflection-based persistence
+     * tools along with field-level access.
+     */
+    private AclImpl() {}
+
+    //~ Methods ================================================================
+    
+    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");
+    }
+
+    public void deleteAce(Long aceId) throws NotFoundException {
+        securityCheck(CHANGE_GENERAL);
+    	
+    	synchronized (aces) {
+        	int offset = findAceOffset(aceId);
+
+            if (offset == 1) {
+                throw new NotFoundException("Requested ACE ID not found");
+            }
+
+            aces.remove(offset);
+            deletedAces.add(aceId);
+        }
+    }
+
+    private int findAceOffset(Long aceId) {
+        Assert.notNull(aceId, "ACE ID is required");
+
+        synchronized (aces) {
+            for (int i = 0; i < aces.size(); i++) {
+                AccessControlEntry ace = (AccessControlEntry) aces.get(i);
+
+                if (ace.getId().equals(aceId)) {
+                    return i;
+                }
+            }
+        }
+
+        return -1;
+    }
+
+    public AccessControlEntry[] getEntries() {
+        // Can safely return AccessControlEntry directly, as they're immutable
+        return (AccessControlEntry[]) aces.toArray(new AccessControlEntry[] {});
+    }
+
+	public void setEntriesInheriting(boolean entriesInheriting) {
+        securityCheck(CHANGE_GENERAL);
+        this.entriesInheriting = entriesInheriting;
+        this.aclDirty = true;
+    }
+
+    public Serializable getId() {
+        return this.id;
+    }
+
+    public ObjectIdentity getObjectIdentity() {
+        return objectIdentity;
+    }
+
+    public Sid getOwner() {
+        return this.owner;
+    }
+
+    public Acl getParentAcl() {
+        return parentAcl;
+    }
+
+    public void insertAce(Long afterAceId, Permission permission, Sid sid,
+        boolean granting) throws NotFoundException {
+        securityCheck(CHANGE_GENERAL);
+        Assert.notNull(permission, "Permission required");
+        Assert.notNull(sid, "Sid required");
+
+        AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this,
+                sid, permission, granting, false, false);
+
+        synchronized (aces) {
+            if (afterAceId != null) {
+                int offset = findAceOffset(afterAceId);
+
+                if (offset == -1) {
+                    throw new NotFoundException("Requested ACE ID not found");
+                }
+
+                aces.add(offset + 1, ace);
+            } else {
+                aces.add(ace);
+            }
+       	
+        }
+
+        this.addedAces = true;
+    }
+
+    public boolean isSidLoaded(Sid[] sids) {
+        // If loadedSides is null, this indicates all SIDs were loaded
+    	// Also return true if the caller didn't specify a SID to find
+        if (this.loadedSids == null || sids == null || sids.length == 0) {
+        	return true;
+        }
+        
+        // This ACL applies to a SID subset. Iterate to check it applies
+        for (int i = 0; i < sids.length; i++) {
+        	boolean found = false;
+        	for (int y = 0; y < this.loadedSids.length; y++) {
+        		if (sids[i].equals(this.loadedSids[y])) {
+        			// this SID is OK
+        			found = true;
+        			break; // out of loadedSids for loop
+        		}
+        	}
+    		if (!found) {
+    			return false;
+    		}
+        }
+        return true;
+    }
+
+    public boolean isEntriesInheriting() {
+        return entriesInheriting;
+    }
+
+    /**
+     * 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.
+     *
+     * @param permission the exact permissions to scan for (order is important)
+     * @param sids the exact SIDs to scan for (order is important)
+     * @param administrativeMode if <code>true</code> denotes the query is for
+     *        administrative purposes and no auditing will be undertaken
+     *
+     * @return <code>true</code> if one of the permissions has been granted,
+     *         <code>false</code> if one of the permissions has been
+     *         specifically revoked
+     *
+     * @throws NotFoundException if an exact ACE for one of the permission bit
+     *         masks and SID combination could not be found
+     * @throws UnloadedSidException if the passed SIDs are unknown to this ACL
+     *         because the ACL was only loaded for a subset of SIDs
+     */
+    public boolean isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
+        throws NotFoundException, UnloadedSidException {
+        Assert.notEmpty(permission, "Permissions required");
+        Assert.notEmpty(sids, "SIDs required");
+
+        if (!this.isSidLoaded(sids)) {
+            throw new UnloadedSidException("ACL was not loaded for one or more SID");
+        }
+        
+        AccessControlEntry firstRejection = null;
+
+        for (int i = 0; i < permission.length; i++) {
+            for (int x = 0; x < sids.length; x++) {
+                // Attempt to find exact match for this permission mask and SID
+                Iterator acesIterator = aces.iterator();
+                boolean scanNextSid = true;
+
+                while (acesIterator.hasNext()) {
+                    AccessControlEntry ace = (AccessControlEntry) acesIterator
+                        .next();
+
+                    if ((ace.getPermission().getMask() == permission[i].getMask())
+                        && ace.getSid().equals(sids[x])) {
+                        // Found a matching ACE, so its authorization decision will prevail
+                        if (ace.isGranting()) {
+                            // Success
+                        	if (!administrativeMode) {
+                                auditLogger.logIfNeeded(true, ace);
+                        	}
+
+                            return true;
+                        } else {
+                            // Failure for this permission, so stop search
+                            // We will see if they have a different permission
+                            // (this permission is 100% rejected for this SID)
+                            if (firstRejection == null) {
+                                // Store first rejection for auditing reasons
+                                firstRejection = ace;
+                            }
+
+                            scanNextSid = false; // helps break the loop
+
+                            break; // exit "aceIterator" while loop
+                        }
+                    }
+                }
+
+                if (!scanNextSid) {
+                    break; // exit SID for loop (now try next permission)
+                }
+            }
+        }
+
+        if (firstRejection != null) {
+            // We found an ACE to reject the request at this point, as no
+            // other ACEs were found that granted a different permission
+            
+        	if (!administrativeMode) {
+            	auditLogger.logIfNeeded(false, firstRejection);
+        	}
+
+            return false;
+        }
+
+        // No matches have been found so far
+        if (isEntriesInheriting() && (parentAcl != null)) {
+            // We have a parent, so let them try to find a matching ACE
+        	return parentAcl.isGranted(permission, sids, false);
+        } else {
+            // We either have no parent, or we're the uppermost parent
+            throw new NotFoundException(
+                "Unable to locate a matching ACE for passed permissions and SIDs");
+        }
+    }
+
+    public void setOwner(Sid newOwner) {
+        securityCheck(CHANGE_OWNERSHIP);
+        Assert.notNull(newOwner, "Owner required");
+        this.owner = newOwner;
+        this.aclDirty = true;
+    }
+
+    public void setParent(MutableAcl newParent) {
+        securityCheck(CHANGE_GENERAL);
+        Assert.notNull(newParent, "New Parent required");
+        this.parentAcl = newParent;
+        this.aclDirty = true;
+    }
+
+    public void updateAce(Long aceId, Permission permission)
+        throws NotFoundException {
+        securityCheck(CHANGE_GENERAL);
+        synchronized (aces) {
+        	int offset = findAceOffset(aceId);
+
+            if (offset == 1) {
+                throw new NotFoundException("Requested ACE ID not found");
+            }
+
+            AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
+            ace.setPermission(permission);
+        }
+        
+        this.updatedAces = true;
+    }
+
+    public void updateAuditing(Long aceId, boolean auditSuccess,
+        boolean auditFailure) {
+        securityCheck(CHANGE_AUDITING);
+    	
+    	synchronized (aces) {
+            int offset = findAceOffset(aceId);
+
+            if (offset == 1) {
+                throw new NotFoundException("Requested ACE ID not found");
+            }
+
+            AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(offset);
+            ace.setAuditSuccess(auditSuccess);
+            ace.setAuditFailure(auditFailure);
+		}
+        this.updatedAces = true;
+    }
+    
+    /**
+     * 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 boolean isAclDirty() {
+		return aclDirty;
+	}
+
+	public String toString() {
+		StringBuffer sb = new StringBuffer();
+		sb.append("AclImpl[");
+		sb.append("id: ").append(this.id).append("; ");
+		sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
+		sb.append("owner: ").append(this.owner).append("; ");
+		Iterator iterator = this.aces.iterator();
+		int count = 0;
+		while (iterator.hasNext()) {
+			count++;
+			if (count == 1) {
+				sb.append("\r\n");
+			}
+			sb.append(iterator.next().toString()).append("\r\n");
+		}
+		sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
+		sb.append("parent: ").append(this.parentAcl == null ? "Null" : this.parentAcl.getObjectIdentity());
+		sb.append("]");
+		return sb.toString();
+	}
+
+}

+ 14 - 0
sandbox/src/main/java/org/acegisecurity/acls/domain/AuditLogger.java

@@ -0,0 +1,14 @@
+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 {
+	public void logIfNeeded(boolean granted, AccessControlEntry ace);
+}

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

@@ -0,0 +1,66 @@
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AclFormattingUtils;
+import org.acegisecurity.acls.Permission;
+
+public class BasePermission implements Permission {
+	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 ADMINISTRATION = new BasePermission(1<<3, 'A'); // 8
+	
+	private int mask;
+	private char code;
+	
+	private BasePermission(int mask, char code) {
+		this.mask = mask;
+		this.code = code;
+	}
+	
+	public boolean equals(Object arg0) {
+		if (!(arg0 instanceof BasePermission)) {
+			return false;
+		}
+		BasePermission rhs = (BasePermission) arg0;
+		return (this.mask == rhs.getMask());
+	}
+
+	/**
+	 * Dynamically creates a <code>CumulativePermission</code>
+	 * representing the active bits in the passed mask.
+	 * NB: Only uses <code>BasePermission</code>!
+	 * 
+	 * @param mask to review
+	 */
+	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(ADMINISTRATION);
+		}
+		return permission;
+	}
+	
+	public int getMask() {
+		return mask;
+	}
+
+	public String toString() {
+		return "BasePermission[" + getPattern() + "=" + mask + "]";
+	}
+
+	public String getPattern() {
+		return AclFormattingUtils.printBinary(mask, code);
+	}
+
+}

+ 19 - 0
sandbox/src/main/java/org/acegisecurity/acls/domain/ConsoleAuditLogger.java

@@ -0,0 +1,19 @@
+package org.acegisecurity.acls.domain;
+
+import org.acegisecurity.acls.AccessControlEntry;
+import org.acegisecurity.acls.AuditableAccessControlEntry;
+import org.springframework.util.Assert;
+
+public class ConsoleAuditLogger implements AuditLogger {
+	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);
+			}
+		}
+	}
+}

+ 84 - 0
sandbox/src/main/java/org/acegisecurity/acls/domain/CumulativePermission.java

@@ -0,0 +1,84 @@
+/* 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 boolean equals(Object arg0) {
+		if (!(arg0 instanceof CumulativePermission)) {
+			return false;
+		}
+		CumulativePermission rhs = (CumulativePermission) arg0;
+		return (this.mask == rhs.getMask());
+	}
+
+
+    public CumulativePermission clear() {
+        this.mask = 0;
+        this.pattern = THIRTY_TWO_RESERVED_OFF;
+
+        return this;
+    }
+
+    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 + "]";
+    }
+}

+ 18 - 0
sandbox/src/main/java/org/acegisecurity/acls/jdbc/AclCache.java

@@ -0,0 +1,18 @@
+package org.acegisecurity.acls.jdbc;
+
+import org.acegisecurity.acls.domain.AclImpl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+
+/**
+ * A caching layer for {@link JdbcAclService}.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ *
+ */
+public interface AclCache {
+	public AclImpl getFromCache(ObjectIdentity objectIdentity);
+	public AclImpl getFromCache(Long pk);
+	public void putInCache(AclImpl acl); // should walk tree as well!
+	public void evictFromCache(Long pk);
+}

+ 517 - 0
sandbox/src/main/java/org/acegisecurity/acls/jdbc/BasicLookupStrategy.java

@@ -0,0 +1,517 @@
+/* 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 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;
+
+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;
+
+
+/**
+ * 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 {
+    
+	private int batchSize = 50;
+    private AclCache aclCache;
+    private JdbcTemplate jdbcTemplate;
+    private GrantedAuthority[] auths;
+
+    //~ 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 ================================================================
+
+    public void setBatchSize(int batchSize) {
+		this.batchSize = batchSize;
+	}
+
+    /**
+     * The main method.
+     * 
+     * <p>
+     * WARNING: This implementation completely disregards the "sids" argument!
+     * Every item in the cache is expected to contain all SIDs.
+     *
+     * <p>
+     * The implementation works in batch sizes specfied by {@link #batchSize}.
+     *
+     */
+    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;
+    }
+    
+    /**
+     * 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>
+     */
+    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));
+    }
+
+    /**
+     * 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
+     */
+    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", ex);
+	    }
+	
+	    // Add the ACE if it doesn't already exist in the ACL.aces field
+	    if (!aces.contains(ace)) {
+		    aces.add(ace);
+	    }
+	}
+    
+    /**
+     * 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 inputAcl the current <code>Acl</code> that we wish to convert (this may be
+     * @return
+     */
+    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;
+    }
+
+	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();
+    }
+
+    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) {
+                getAccessibleField(clazz.getSuperclass(), protectedField);
+            } else {
+                throw new IllegalArgumentException("Couldn't find '"
+                    + protectedField + "' field");
+            }
+        }
+
+        // We have a field, so process
+        field.setAccessible(true);
+
+        return field;
+    }
+	
+    //~ Inner Classes ==========================================================
+
+    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");
+        }
+    }
+    
+    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;
+	    }
+    }
+}

+ 58 - 0
sandbox/src/main/java/org/acegisecurity/acls/jdbc/EhCacheBasedAclCache.java

@@ -0,0 +1,58 @@
+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;
+
+public class EhCacheBasedAclCache implements AclCache {
+
+	private Cache cache;
+	
+	public EhCacheBasedAclCache(Cache cache) {
+		Assert.notNull(cache, "Cache required");
+		this.cache = cache;
+	}
+	
+	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));
+	}
+
+	public void evictFromCache(Long pk) {
+		AclImpl acl = getFromCache(pk);
+		if (acl != null) {
+			cache.remove(pk);
+			cache.remove(acl.getObjectIdentity());
+		}
+	}
+
+}

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

@@ -0,0 +1,51 @@
+package org.acegisecurity.acls.jdbc;
+
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+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;
+
+/**
+ * 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.
+ * 
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class JdbcAclService implements AclService/*, MutableAclService */ {
+
+	private AclCache aclCache;
+	private JdbcTemplate template;
+	private LookupStrategy lookupStrategy;
+
+	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;
+	}
+	
+	public Map readAclsById(ObjectIdentity[] objects) {
+		return readAclsById(objects, null);
+	}
+
+	/**
+	 * Method required by interface.
+	 */
+	public Map readAclsById(ObjectIdentity[] objects, Sid[] sids) throws NotFoundException {
+		return lookupStrategy.readAclsById(objects, sids);
+	}
+	
+
+}

+ 47 - 0
sandbox/src/main/java/org/acegisecurity/acls/jdbc/LookupStrategy.java

@@ -0,0 +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.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);
+}

+ 78 - 0
sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentity.java

@@ -0,0 +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.objectidentity;
+
+import java.io.Serializable;
+
+/**
+ * Interface representing the identity of an individual domain object instance.
+ * 
+ * <P>
+ * As implementations are used as the key for caching and lookup, it is
+ * essential that implementations provide methods so that object-equality
+ * rather than reference-equality can be relied upon by caches. In other
+ * words, a cache can consider two <code>ObjectIdentity</code>s equal if
+ * <code>identity1.equals(identity2)</code>, rather than reference-equality of
+ * <code>identity1==identity2</code>.
+ * </p>
+ *
+ * @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();
+}

+ 157 - 0
sandbox/src/main/java/org/acegisecurity/acls/objectidentity/ObjectIdentityImpl.java

@@ -0,0 +1,157 @@
+/* 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;
+    }
+
+    /**
+     * Creates the <code>NamedEntityObjectIdentity</code> based on the passed
+     * object instance. The passed object must provide a <code>getId()</code>
+     * method, otherwise an exception will be thrown.
+     *
+     * @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();
+    }
+}

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

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

+ 74 - 0
sandbox/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java

@@ -0,0 +1,74 @@
+/* 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 + "]";
+    }
+}

+ 81 - 0
sandbox/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java

@@ -0,0 +1,81 @@
+/* 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 + "]";
+    }
+}

+ 54 - 0
sandbox/src/main/java/org/acegisecurity/acls/sid/Sid.java

@@ -0,0 +1,54 @@
+/* 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.
+ * 
+ * <p>
+ * This interface provides indirection between actual security objects (eg
+ * principals, roles, groups etc) and what is stored inside an
+ * <code>Acl</code>. This is because an <code>Acl</code> will not store an
+ * entire security object, but only an abstraction of it. This interface
+ * therefore provides a simple way to compare these abstracted security
+ * identities with other security identities and actual security objects.
+ * </p>
+ *
+ * @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();
+}

+ 41 - 0
sandbox/src/test/java/org/acegisecurity/acls/domain/PermissionTests.java

@@ -0,0 +1,41 @@
+package org.acegisecurity.acls.domain;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests BasePermission and CumulativePermission.
+ * 
+ * @author Ben Alex
+ * @version $Id${date}
+ *
+ */
+public class PermissionTests extends TestCase {
+	public void testStringConversion() {
+		System.out.println("R =  " + BasePermission.READ.toString());
+		assertEquals("BasePermission[...............................R=1]", BasePermission.READ.toString());
+		
+		System.out.println("A =  " + BasePermission.ADMINISTRATION.toString());
+		assertEquals("BasePermission[............................A...=8]", BasePermission.ADMINISTRATION.toString());
+		
+		System.out.println("R =  " + new CumulativePermission().set(BasePermission.READ).toString());
+		assertEquals("CumulativePermission[...............................R=1]", new CumulativePermission().set(BasePermission.READ).toString());
+		
+		System.out.println("A =  " + new CumulativePermission().set(BasePermission.ADMINISTRATION).toString());
+		assertEquals("CumulativePermission[............................A...=8]", new CumulativePermission().set(BasePermission.ADMINISTRATION).toString());
+		
+		System.out.println("RA = " + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
+		assertEquals("CumulativePermission[............................A..R=9]", new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
+		
+		System.out.println("R =  " + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).toString());
+		assertEquals("CumulativePermission[...............................R=1]", new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).toString());
+		
+		System.out.println("0 =  " + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
+		assertEquals("CumulativePermission[................................=0]", new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
+	}
+	
+	public void testExpectedIntegerValues() {
+		assertEquals(1, BasePermission.READ.getMask());
+		assertEquals(8, BasePermission.ADMINISTRATION.getMask());
+		assertEquals(9, new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask());
+	}
+}

+ 23 - 0
sandbox/src/test/java/org/acegisecurity/acls/jdbc/DatabaseSeeder.java

@@ -0,0 +1,23 @@
+package org.acegisecurity.acls.jdbc;
+
+import java.io.IOException;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.io.Resource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+
+public class DatabaseSeeder {
+	
+	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);
+	}
+
+}

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

@@ -0,0 +1,40 @@
+package org.acegisecurity.acls.jdbc;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.acegisecurity.acls.Acl;
+import org.acegisecurity.acls.objectidentity.ObjectIdentity;
+import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
+import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
+
+public class JdbcAclServiceTests extends AbstractDependencyInjectionSpringContextTests {
+
+	protected String[] getConfigLocations() {
+		return new String[] {"classpath:org/acegisecurity/acls/jdbc/applicationContext-test.xml"};
+	}
+	
+	private JdbcAclService jdbcAclService;
+	
+	public void testStub() {
+		ObjectIdentity id1 = new ObjectIdentityImpl("sample.contact.Contact", new Long(1));
+		ObjectIdentity id2 = new ObjectIdentityImpl("sample.contact.Contact", new Long(2));
+		ObjectIdentity id3 = new ObjectIdentityImpl("sample.contact.Contact", new Long(3));
+		ObjectIdentity id4 = new ObjectIdentityImpl("sample.contact.Contact", new Long(4));
+		ObjectIdentity id5 = new ObjectIdentityImpl("sample.contact.Contact", new Long(5));
+		ObjectIdentity id6 = new ObjectIdentityImpl("sample.contact.Contact", 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));
+		}
+	}
+
+	public void setJdbcAclService(JdbcAclService jdbcAclService) {
+		this.jdbcAclService = jdbcAclService;
+	}
+
+
+}

+ 71 - 0
sandbox/src/test/java/org/acegisecurity/acls/jdbc/applicationContext-test.xml

@@ -0,0 +1,71 @@
+<?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="databaseSeeder" class="org.acegisecurity.acls.jdbc.DatabaseSeeder">
+		<constructor-arg ref="dataSource"/>
+		<constructor-arg value="classpath:org/acegisecurity/acls/jdbc/testData.sql"/>
+	</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>
+			<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">
+		<constructor-arg ref="dataSource"/>
+		<constructor-arg ref="aclCache"/>
+		<constructor-arg ref="lookupStrategy"/>
+	</bean>
+
+    <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:test</value>
+        </property>
+        <property name="username">
+            <value>sa</value>
+        </property>
+        <property name="password">
+            <value></value>
+        </property>
+    </bean>
+	
+</beans>

+ 23 - 0
sandbox/src/test/java/org/acegisecurity/acls/jdbc/select.sql

@@ -0,0 +1,23 @@
+-- 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_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 (
+(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 1 
+and ACL_CLASS.CLASS = 'sample.contact.Contact')
+or 
+(ACL_OBJECT_IDENTITY.OBJECT_ID_IDENTITY = 2
+and ACL_CLASS.CLASS = 'sample.contact.Contact')
+) order by ACL_ENTRY.ACL_OBJECT_IDENTITY asc, ACL_ENTRY.ACE_ORDER asc

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

@@ -0,0 +1,70 @@
+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);