فهرست منبع

Refactor ACL database tables.

Ben Alex 21 سال پیش
والد
کامیت
13d5a2dbca

+ 249 - 59
core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java

@@ -33,7 +33,6 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
 
-import java.util.Iterator;
 import java.util.List;
 import java.util.Vector;
 
@@ -46,12 +45,11 @@ import javax.sql.DataSource;
  * </p>
  * 
  * <p>
- * A default database structure is assumed (see {@link
- * #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}). This may be overridden by setting the
- * default query strings to use. If this does not provide enough flexibility,
- * another strategy would be to subclass this class and override the {@link
- * MappingSqlQuery} instance used, via the {@link #initMappingSqlQueries()}
- * extension point.
+ * A default database structure is assumed. This may be overridden by setting
+ * the default query strings to use. If this does not provide enough
+ * flexibility, another strategy would be to subclass this class and override
+ * the {@link MappingSqlQuery} instance used, via the {@link
+ * #initMappingSqlQueries()} extension point.
  * </p>
  *
  * @author Ben Alex
@@ -60,18 +58,23 @@ import javax.sql.DataSource;
 public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
     //~ Static fields/initializers =============================================
 
-    public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT OBJECT_IDENTITY, RECIPIENT, PARENT_OBJECT_IDENTITY, MASK, ACL_CLASS FROM acls WHERE object_identity = ?";
+    public static final String RECIPIENT_USED_FOR_INHERITENCE_MARKER = "___INHERITENCE_MARKER_ONLY___";
+    public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT RECIPIENT, MASK FROM acl_permission WHERE acl_object_identity = ?";
+    public static final String DEF_OBJECT_PROPERTIES_QUERY = "SELECT ID, OBJECT_IDENTITY, ACL_CLASS, PARENT.OBJECT_IDENTITY as PARENT_OBJECT_IDENTITY FROM acl_object_identity LEFT OUTER JOIN acl_object_identity as PARENT ON acl_object_identity.parent_object=parent.id WHERE parent.id=acl_object_identity.parent_object and object_identity = ?";
     private static final Log logger = LogFactory.getLog(JdbcDaoSupport.class);
 
     //~ Instance fields ========================================================
 
     private MappingSqlQuery aclsByObjectIdentity;
+    private MappingSqlQuery objectProperties;
     private String aclsByObjectIdentityQuery;
+    private String objectPropertiesQuery;
 
     //~ Constructors ===========================================================
 
     public JdbcDaoImpl() {
         aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY;
+        objectPropertiesQuery = DEF_OBJECT_PROPERTIES_QUERY;
     }
 
     //~ Methods ================================================================
@@ -114,26 +117,36 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
         String aclObjectIdentityString = neoi.getClassname() + ":"
             + neoi.getId();
 
-        // Lookup the BasicAclEntrys from RDBMS (may include null responses)
-        List acls = aclsByObjectIdentity.execute(aclObjectIdentityString);
+        // Lookup the object's main properties from the RDBMS (guaranteed no nulls)
+        List objects = objectProperties.execute(aclObjectIdentityString);
 
-        // Now prune list of null responses (to meet interface contract)
-        List toReturnAcls = new Vector();
-        Iterator iter = acls.iterator();
+        if (objects.size() == 0) {
+            // this is an unknown object identity string
+            return null;
+        }
+
+        // Cast to an object properties holder (there should only be one record)
+        AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects.get(0);
 
-        while (iter.hasNext()) {
-            Object object = iter.next();
+        // Lookup the object's ACLs from RDBMS (guaranteed no nulls)
+        List acls = aclsByObjectIdentity.execute(propertiesInformation
+                .getForeignKeyId());
 
-            if (object != null) {
-                toReturnAcls.add(object);
+        if (acls.size() == 0) {
+            // return merely an inheritence marker (as we know about the object but it has no related ACLs)
+            return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation,
+                    null)};
+        } else {
+            // return the individual ACL instances
+            AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls.toArray(new AclDetailsHolder[] {});
+            List toReturnAcls = new Vector();
+
+            for (int i = 0; i < aclHolders.length; i++) {
+                toReturnAcls.add(createBasicAclEntry(propertiesInformation,
+                        aclHolders[i]));
             }
-        }
 
-        // Return null if nothing of use found (to meet interface contract)
-        if (toReturnAcls.size() > 0) {
             return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {});
-        } else {
-            return null;
         }
     }
 
@@ -164,6 +177,18 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
         return aclsByObjectIdentityQuery;
     }
 
+    public void setObjectProperties(MappingSqlQuery objectPropertiesQuery) {
+        this.objectProperties = objectPropertiesQuery;
+    }
+
+    public void setObjectPropertiesQuery(String queryString) {
+        objectPropertiesQuery = queryString;
+    }
+
+    public String getObjectPropertiesQuery() {
+        return objectPropertiesQuery;
+    }
+
     protected void initDao() throws ApplicationContextException {
         initMappingSqlQueries();
     }
@@ -174,70 +199,151 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
      */
     protected void initMappingSqlQueries() {
         setAclsByObjectIdentity(new AclsByObjectIdentityMapping(getDataSource()));
+        setObjectProperties(new ObjectPropertiesMapping(getDataSource()));
+    }
+
+    /**
+     * Constructs an individual <code>BasicAclEntry</code> from the passed
+     * <code>AclDetailsHolder</code>s.
+     * 
+     * <P>
+     * Guarantees to never return <code>null</code> (exceptions are thrown in
+     * the event of any issues).
+     * </p>
+     *
+     * @param propertiesInformation mandatory information about which instance
+     *        to create, the object identity, and the parent object identity
+     *        (<code>null</code> or empty <code>String</code>s prohibited for
+     *        <code>aclClass</code> and <code>aclObjectIdentity</code>
+     * @param aclInformation optional information about the individual ACL
+     *        record (if <code>null</code> only an "inheritence marker"
+     *        instance is returned; if not <code>null</code>, it is prohibited
+     *        to present <code>null</code> or an empty <code>String</code> for
+     *        <code>recipient</code>)
+     *
+     * @return a fully populated instance suitable for use by external objects
+     *
+     * @throws IllegalArgumentException if the indicated ACL class could not be
+     *         created
+     */
+    private BasicAclEntry createBasicAclEntry(
+        AclDetailsHolder propertiesInformation, AclDetailsHolder aclInformation) {
+        BasicAclEntry entry;
+
+        try {
+            entry = (BasicAclEntry) propertiesInformation.getAclClass()
+                                                         .newInstance();
+        } catch (InstantiationException ie) {
+            throw new IllegalArgumentException(ie.getMessage());
+        } catch (IllegalAccessException iae) {
+            throw new IllegalArgumentException(iae.getMessage());
+        }
+
+        entry.setAclObjectIdentity(propertiesInformation.getAclObjectIdentity());
+        entry.setAclObjectParentIdentity(propertiesInformation
+            .getAclObjectParentIdentity());
+
+        if (aclInformation == null) {
+            // this is an inheritence marker instance only
+            entry.setMask(0);
+            entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
+        } else {
+            // this is an individual ACL entry
+            entry.setMask(aclInformation.getMask());
+            entry.setRecipient(aclInformation.getRecipient());
+        }
+
+        return entry;
     }
 
     //~ Inner Classes ==========================================================
 
     /**
-     * Query object to look up ACL entries.
+     * Query object to look up individual ACL entries.
+     * 
+     * <P>
+     * Returns the generic <code>AclDetailsHolder</code> object.
+     * </p>
+     * 
+     * <P>
+     * Guarantees to never return <code>null</code> (exceptions are thrown in
+     * the event of any issues).
+     * </p>
      * 
      * <P>
      * The executed SQL requires the following information be made available
-     * from the indicated placeholders: 1. OBJECT_IDENTITY, 2. RECIPIENT, 3.
-     * PARENT_OBJECT_IDENTITY, 4. MASK, and 5. ACL_CLASS
+     * from the indicated placeholders: 1. RECIPIENT, 2. MASK.
      * </p>
      */
     protected class AclsByObjectIdentityMapping extends MappingSqlQuery {
         protected AclsByObjectIdentityMapping(DataSource ds) {
             super(ds, aclsByObjectIdentityQuery);
-            declareParameter(new SqlParameter(Types.VARCHAR));
+            declareParameter(new SqlParameter(Types.INTEGER));
             compile();
         }
 
         protected Object mapRow(ResultSet rs, int rownum)
             throws SQLException {
-            String objectIdentity = rs.getString(1);
-            String recipient = rs.getString(2);
-            String parentObjectIdentity = rs.getString(3);
-            int mask = rs.getInt(4);
-            String aclClass = rs.getString(5);
-
-            // Try to create the indicated BasicAclEntry class
-            BasicAclEntry entry;
+            String recipient = rs.getString(1);
+            int mask = rs.getInt(2);
 
-            try {
-                Class aclClazz = this.getClass().getClassLoader().loadClass(aclClass);
-                entry = (BasicAclEntry) aclClazz.newInstance();
-            } catch (ClassNotFoundException cnf) {
-                logger.error(cnf);
+            if ((recipient == null) || "".equals(recipient)) {
+                throw new IllegalArgumentException("recipient required");
+            }
 
-                return null;
-            } catch (InstantiationException ie) {
-                logger.error(ie);
+            return new AclDetailsHolder(recipient, mask);
+        }
+    }
 
-                return null;
-            } catch (IllegalAccessException iae) {
-                logger.error(iae);
+    /**
+     * Query object to look up properties for an object identity.
+     * 
+     * <P>
+     * Returns the generic <code>AclDetailsHolder</code> object.
+     * </p>
+     * 
+     * <P>
+     * Guarantees to never return <code>null</code> (exceptions are thrown in
+     * the event of any issues).
+     * </p>
+     * 
+     * <P>
+     * The executed SQL requires the following information be made available
+     * from the indicated placeholders: 1. ID, 2. OBJECT_IDENTITY, 3.
+     * ACL_CLASS and 4. PARENT_OBJECT_IDENTITY.
+     * </p>
+     */
+    protected class ObjectPropertiesMapping extends MappingSqlQuery {
+        protected ObjectPropertiesMapping(DataSource ds) {
+            super(ds, objectPropertiesQuery);
+            declareParameter(new SqlParameter(Types.VARCHAR));
+            compile();
+        }
 
-                return null;
+        protected Object mapRow(ResultSet rs, int rownum)
+            throws SQLException {
+            int id = rs.getInt(1); // required
+            String objectIdentity = rs.getString(2); // required
+            String aclClass = rs.getString(3); // required
+            String parentObjectIdentity = rs.getString(4); // optional
+
+            if ((objectIdentity == null) || "".equals(objectIdentity)
+                || (aclClass == null) || "".equals(aclClass)) {
+                // shouldn't happen if DB schema defined NOT NULL columns
+                throw new IllegalArgumentException(
+                    "required DEF_OBJECT_PROPERTIES_QUERY value returned null or empty");
             }
 
-            // Now set each of the ACL's properties
-            entry.setAclObjectIdentity(buildIdentity(objectIdentity));
-            entry.setAclObjectParentIdentity(buildIdentity(parentObjectIdentity));
-            entry.setRecipient(recipient);
-            entry.setMask(mask);
-
-            if ((entry.getRecipient() == null)
-                || (entry.getAclObjectIdentity() == null)) {
-                // Problem with retrieval of ACL 
-                // (shouldn't happen if DB schema defined NOT NULL columns)
-                logger.error("recipient or aclObjectIdentity is null");
+            Class aclClazz;
 
-                return null;
+            try {
+                aclClazz = this.getClass().getClassLoader().loadClass(aclClass);
+            } catch (ClassNotFoundException cnf) {
+                throw new IllegalArgumentException(cnf.getMessage());
             }
 
-            return entry;
+            return new AclDetailsHolder(id, buildIdentity(objectIdentity),
+                buildIdentity(parentObjectIdentity), aclClazz);
         }
 
         private AclObjectIdentity buildIdentity(String identity) {
@@ -253,4 +359,88 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
             return new NamedEntityObjectIdentity(classname, id);
         }
     }
+
+    /**
+     * Used to hold details of a domain object instance's properties, or an
+     * individual ACL entry.
+     * 
+     * <P>
+     * Not all properties will be set. The actual properties set will depend on
+     * which <code>MappingSqlQuery</code> creates the object.
+     * </p>
+     * 
+     * <P>
+     * Does not enforce <code>null</code>s or empty <code>String</code>s as
+     * this is performed by the <code>MappingSqlQuery</code> objects (or
+     * preferably the backend RDBMS via schema constraints).
+     * </p>
+     */
+    private class AclDetailsHolder {
+        private AclObjectIdentity aclObjectIdentity;
+        private AclObjectIdentity aclObjectParentIdentity;
+        private Class aclClass;
+        private Object recipient;
+        private int foreignKeyId;
+        private int mask;
+
+        /**
+         * Record details of an individual ACL entry (usually from the
+         * ACL_PERMISSION table)
+         *
+         * @param recipient the recipient
+         * @param mask the integer to be masked
+         */
+        public AclDetailsHolder(Object recipient, int mask) {
+            this.recipient = recipient;
+            this.mask = mask;
+        }
+
+        /**
+         * Record details of a domain object instance's properties (usually
+         * from the ACL_OBJECT_IDENTITY table)
+         *
+         * @param foreignKeyId used by the
+         *        <code>AclsByObjectIdentityMapping</code> to locate the
+         *        individual ACL entries
+         * @param aclObjectIdentity the object identity of the domain object
+         *        instance
+         * @param aclObjectParentIdentity the object identity of the domain
+         *        object instance's parent
+         * @param aclClass the class of which a new instance which should be
+         *        created for each individual ACL entry (or an inheritence
+         *        "holder" class if there are no ACL entries)
+         */
+        public AclDetailsHolder(int foreignKeyId,
+            AclObjectIdentity aclObjectIdentity,
+            AclObjectIdentity aclObjectParentIdentity, Class aclClass) {
+            this.foreignKeyId = foreignKeyId;
+            this.aclObjectIdentity = aclObjectIdentity;
+            this.aclObjectParentIdentity = aclObjectParentIdentity;
+            this.aclClass = aclClass;
+        }
+
+        public Class getAclClass() {
+            return aclClass;
+        }
+
+        public AclObjectIdentity getAclObjectIdentity() {
+            return aclObjectIdentity;
+        }
+
+        public AclObjectIdentity getAclObjectParentIdentity() {
+            return aclObjectParentIdentity;
+        }
+
+        public int getForeignKeyId() {
+            return foreignKeyId;
+        }
+
+        public int getMask() {
+            return mask;
+        }
+
+        public Object getRecipient() {
+            return recipient;
+        }
+    }
 }

+ 20 - 10
core/src/test/java/org/acegisecurity/PopulatedDatabase.java

@@ -63,7 +63,9 @@ public class PopulatedDatabase {
         template.execute(
             "CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY)");
         template.execute(
-            "CREATE TABLE ACLS(OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,PARENT_OBJECT_IDENTITY VARCHAR_IGNORECASE(250),MASK INTEGER NOT NULL,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT PK_ACLS PRIMARY KEY(OBJECT_IDENTITY,RECIPIENT))");
+            "CREATE TABLE ACL_OBJECT_IDENTITY(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0)  NOT NULL PRIMARY KEY,OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,PARENT_OBJECT INTEGER,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT UNIQUE_OBJECT_IDENTITY UNIQUE(OBJECT_IDENTITY),CONSTRAINT SYS_FK_3 FOREIGN KEY(PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID))");
+        template.execute(
+            "CREATE TABLE ACL_PERMISSION(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0)  NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY INTEGER NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,MASK INTEGER NOT NULL,CONSTRAINT UNIQUE_RECIPIENT UNIQUE(ACL_OBJECT_IDENTITY,RECIPIENT),CONSTRAINT SYS_FK_7 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID))");
         template.execute("SET IGNORECASE TRUE");
         template.execute("INSERT INTO USERS VALUES('dianne','emu',TRUE)");
         template.execute("INSERT INTO USERS VALUES('marissa','koala',TRUE)");
@@ -81,22 +83,30 @@ public class PopulatedDatabase {
         template.execute(
             "INSERT INTO AUTHORITIES VALUES('peter','ROLE_TELLER')");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:1','ROLE_SUPERVISOR',NULL,1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_object_identity VALUES (1, 'net.sf.acegisecurity.acl.DomainObject:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (2, 'net.sf.acegisecurity.acl.DomainObject:2', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (3, 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        template.execute(
+            "INSERT INTO acl_object_identity VALUES (4, 'net.sf.acegisecurity.acl.DomainObject:4', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','marissa','net.sf.acegisecurity.acl.DomainObject:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_object_identity VALUES (5, 'net.sf.acegisecurity.acl.DomainObject:5', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','ROLE_SUPERVISOR','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_object_identity VALUES (6, 'net.sf.acegisecurity.acl.DomainObject:6', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
+        // ----- BEGIN deviation from normal sample data load script -----
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:3','scott','net.sf.acegisecurity.acl.DomainObject:1',14,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_object_identity VALUES (7, 'net.sf.acegisecurity.acl.DomainObject:7', 3, 'some.invalid.acl.entry.class');");
+        // ----- FINISH deviation from normal sample data load script -----
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:4','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1);");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:5','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:3',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0);");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:6','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:7','scott','some.invalid.parent:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')");
+            "INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);");
         template.execute(
-            "INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:8','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'some.invalid.basic.acl.entry.class.name')");
+            "INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);");
     }
 }

+ 39 - 10
core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java

@@ -59,7 +59,22 @@ public class JdbcDaoImplTests extends TestCase {
         junit.textui.TestRunner.run(JdbcDaoImplTests.class);
     }
 
-    public void testGetsAclsWhichExistInDatabase() throws Exception {
+    public void testExceptionThrownIfBasicAclEntryClassNotFound()
+        throws Exception {
+        JdbcDaoImpl dao = makePopulatedJdbcDao();
+        AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
+                "7");
+
+        try {
+            dao.getAcls(identity);
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGetsEntriesWhichExistInDatabaseAndHaveAcls()
+        throws Exception {
         JdbcDaoImpl dao = makePopulatedJdbcDao();
         AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
                 "2");
@@ -67,6 +82,26 @@ public class JdbcDaoImplTests extends TestCase {
         assertEquals(2, acls.length);
     }
 
+    public void testGetsEntriesWhichExistInDatabaseButHaveNoAcls()
+        throws Exception {
+        JdbcDaoImpl dao = makePopulatedJdbcDao();
+        AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
+                "5");
+        BasicAclEntry[] acls = dao.getAcls(identity);
+        assertEquals(1, acls.length);
+        assertEquals(JdbcDaoImpl.RECIPIENT_USED_FOR_INHERITENCE_MARKER,
+            acls[0].getRecipient());
+    }
+
+    public void testGetsEntriesWhichHaveNoParent() throws Exception {
+        JdbcDaoImpl dao = makePopulatedJdbcDao();
+        AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
+                "1");
+        BasicAclEntry[] acls = dao.getAcls(identity);
+        assertEquals(1, acls.length);
+        assertNull(acls[0].getAclObjectParentIdentity());
+    }
+
     public void testGettersSetters() throws Exception {
         JdbcDaoImpl dao = makePopulatedJdbcDao();
         dao.setAclsByObjectIdentity(new MockMappingSqlQuery());
@@ -74,15 +109,9 @@ public class JdbcDaoImplTests extends TestCase {
 
         dao.setAclsByObjectIdentityQuery("foo");
         assertEquals("foo", dao.getAclsByObjectIdentityQuery());
-    }
 
-    public void testNullReturnedIfBasicAclEntryClassNotFound()
-        throws Exception {
-        JdbcDaoImpl dao = makePopulatedJdbcDao();
-        AclObjectIdentity identity = new NamedEntityObjectIdentity(OBJECT_IDENTITY,
-                "8");
-        BasicAclEntry[] result = dao.getAcls(identity);
-        assertNull(result);
+        dao.setObjectPropertiesQuery("foobar");
+        assertEquals("foobar", dao.getObjectPropertiesQuery());
     }
 
     public void testNullReturnedIfEntityNotFound() throws Exception {
@@ -93,7 +122,7 @@ public class JdbcDaoImplTests extends TestCase {
         assertNull(result);
     }
 
-    public void testRejectsNonNamedEntityObjectIdentity()
+    public void testReturnsNullForUnNamedEntityObjectIdentity()
         throws Exception {
         JdbcDaoImpl dao = new JdbcDaoImpl();
         AclObjectIdentity identity = new AclObjectIdentity() {}

+ 34 - 17
docs/reference/src/index.xml

@@ -3208,23 +3208,41 @@ public java.lang.Object getRecipient();</programlisting></para>
         default database schema and some sample data will aid in understanding
         its function:</para>
 
-        <para><programlisting>CREATE TABLE acls (
-  object_identity VARCHAR_IGNORECASE(250) NOT NULL,
-  recipient VARCHAR_IGNORECASE(100) NOT NULL,
-  parent_object_identity VARCHAR_IGNORECASE(250),
-  mask INTEGER NOT NULL,
-  acl_class VARCHAR_IGNORECASE(250) NOT NULL,
-  CONSTRAINT pk_acls PRIMARY KEY(object_identity, recipient)
+        <para><programlisting>CREATE TABLE acl_object_identity (
+     id IDENTITY NOT NULL,
+     object_identity VARCHAR_IGNORECASE(250) NOT NULL,
+     parent_object INTEGER,
+     acl_class VARCHAR_IGNORECASE(250) NOT NULL,
+     CONSTRAINT unique_object_identity UNIQUE(object_identity),
+     FOREIGN KEY (parent_object) REFERENCES acl_object_identity(id)
 );
 
-INSERT INTO acls VALUES ('corp.DomainObject:1', 'ROLE_SUPERVISOR', null, 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:2', 'ROLE_SUPERVISOR', 'corp.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:2', 'marissa', 'corp.DomainObject:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:3', 'scott', 'corp.DomainObject:1', 14, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:4', 'inheritance_marker_only', 'corp.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:5', 'inheritance_marker_only', 'corp.DomainObject:3', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:6', 'scott', 'corp.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('corp.DomainObject:7', 'scott', 'some.invalid.parent:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');</programlisting></para>
+CREATE TABLE acl_permission (
+     id IDENTITY NOT NULL,
+     acl_object_identity INTEGER NOT NULL,
+     recipient VARCHAR_IGNORECASE(100) NOT NULL,
+     mask INTEGER NOT NULL,
+     CONSTRAINT unique_recipient UNIQUE(acl_object_identity, recipient),
+     FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id)
+);
+
+INSERT INTO acl_object_identity VALUES (1, 'corp.DomainObject:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (2, 'corp.DomainObject:2', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (3, 'corp.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (4, 'corp.DomainObject:4', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (5, 'corp.DomainObject:5', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (6, 'corp.DomainObject:6', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+
+INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1);
+INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0);
+INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);
+INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);
+INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);</programlisting></para>
+
+        <para>As can be seen, database-specific constraints are used
+        extensively to ensure the integrity of the ACL information. If you
+        need to use a different database (Hypersonic SQL statements are shown
+        above), you should try to implement equivalent constraints.</para>
 
         <para>The <literal>JdbcDaoImpl</literal> will only respond to requests
         for <literal>NamedEntityObjectIdentity</literal>s. It converts such
@@ -3312,8 +3330,7 @@ INSERT INTO acls VALUES ('corp.DomainObject:7', 'scott', 'some.invalid.parent:1'
 ---    5      ROLE_SUPERVISOR   Administer (from parent #3)
 ---           scott             Read, Write, Create (from parent #3)
 ---    6      ROLE_SUPERVISOR   Administer (from parent #3)
----           scott             Administer (overrides parent #3)
----    7      scott             Read (invalid parent ignored)</programlisting></para>
+---           scott             Administer (overrides parent #3)</programlisting></para>
 
         <para>So the above explains how a domain object instance has its
         <literal>AclObjectIdentity</literal> discovered, and the

+ 1 - 1
hsqldb/acegisecurity.properties

@@ -1,5 +1,5 @@
 #HSQL database
-#Wed Jul 28 02:20:39 GMT 2004
+#Sat Jul 31 02:59:13 GMT 2004
 hsqldb.script_format=0
 runtime.gc_interval=0
 sql.enforce_strict_size=false

+ 13 - 9
hsqldb/acegisecurity.script

@@ -1,7 +1,8 @@
 CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,ENABLED BOOLEAN NOT NULL)
 CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME))
 CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY)
-CREATE TABLE ACLS(OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,PARENT_OBJECT_IDENTITY VARCHAR_IGNORECASE(250),MASK INTEGER NOT NULL,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT PK_ACLS PRIMARY KEY(OBJECT_IDENTITY,RECIPIENT))
+CREATE TABLE ACL_OBJECT_IDENTITY(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0)  NOT NULL PRIMARY KEY,OBJECT_IDENTITY VARCHAR_IGNORECASE(250) NOT NULL,PARENT_OBJECT INTEGER,ACL_CLASS VARCHAR_IGNORECASE(250) NOT NULL,CONSTRAINT UNIQUE_OBJECT_IDENTITY UNIQUE(OBJECT_IDENTITY),CONSTRAINT SYS_FK_3 FOREIGN KEY(PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID))
+CREATE TABLE ACL_PERMISSION(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0)  NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY INTEGER NOT NULL,RECIPIENT VARCHAR_IGNORECASE(100) NOT NULL,MASK INTEGER NOT NULL,CONSTRAINT UNIQUE_RECIPIENT UNIQUE(ACL_OBJECT_IDENTITY,RECIPIENT),CONSTRAINT SYS_FK_7 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID))
 SET IGNORECASE TRUE
 CREATE USER SA PASSWORD "" ADMIN
 INSERT INTO USERS VALUES('dianne','emu',TRUE)
@@ -13,11 +14,14 @@ INSERT INTO AUTHORITIES VALUES('marissa','ROLE_SUPERVISOR')
 INSERT INTO AUTHORITIES VALUES('dianne','ROLE_TELLER')
 INSERT INTO AUTHORITIES VALUES('scott','ROLE_TELLER')
 INSERT INTO AUTHORITIES VALUES('peter','ROLE_TELLER')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:1','ROLE_SUPERVISOR',NULL,1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','marissa','net.sf.acegisecurity.acl.DomainObject:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:2','ROLE_SUPERVISOR','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:3','scott','net.sf.acegisecurity.acl.DomainObject:1',14,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:4','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:1',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:5','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:3',0,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:6','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
-INSERT INTO ACLS VALUES('net.sf.acegisecurity.acl.DomainObject:7','scott','some.invalid.parent:1',2,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_OBJECT_IDENTITY VALUES(1,'net.sf.acegisecurity.acl.DomainObject:1',NULL,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_OBJECT_IDENTITY VALUES(2,'net.sf.acegisecurity.acl.DomainObject:2',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_OBJECT_IDENTITY VALUES(3,'net.sf.acegisecurity.acl.DomainObject:3',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_OBJECT_IDENTITY VALUES(4,'net.sf.acegisecurity.acl.DomainObject:4',1,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_OBJECT_IDENTITY VALUES(5,'net.sf.acegisecurity.acl.DomainObject:5',3,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_OBJECT_IDENTITY VALUES(6,'net.sf.acegisecurity.acl.DomainObject:6',3,'net.sf.acegisecurity.acl.basic.SimpleAclEntry')
+INSERT INTO ACL_PERMISSION VALUES(0,1,'ROLE_SUPERVISOR',1)
+INSERT INTO ACL_PERMISSION VALUES(1,2,'ROLE_SUPERVISOR',0)
+INSERT INTO ACL_PERMISSION VALUES(2,2,'marissa',2)
+INSERT INTO ACL_PERMISSION VALUES(3,3,'scott',14)
+INSERT INTO ACL_PERMISSION VALUES(4,6,'scott',1)

+ 30 - 16
hsqldb/dbinit.txt

@@ -27,13 +27,24 @@ INSERT INTO authorities VALUES ('dianne', 'ROLE_TELLER');
 INSERT INTO authorities VALUES ('scott', 'ROLE_TELLER');
 INSERT INTO authorities VALUES ('peter', 'ROLE_TELLER');
 
-CREATE TABLE acls (
-	object_identity VARCHAR_IGNORECASE(250) NOT NULL,
-	recipient VARCHAR_IGNORECASE(100) NOT NULL,
-	parent_object_identity VARCHAR_IGNORECASE(250),
-	mask INTEGER NOT NULL,
-	acl_class VARCHAR_IGNORECASE(250) NOT NULL,
-	CONSTRAINT pk_acls PRIMARY KEY(object_identity, recipient)
+--- Indexes auto created in HSQLDB for primary keys and unique columns
+
+CREATE TABLE acl_object_identity (
+     id IDENTITY NOT NULL,
+     object_identity VARCHAR_IGNORECASE(250) NOT NULL,
+     parent_object INTEGER,
+     acl_class VARCHAR_IGNORECASE(250) NOT NULL,
+     CONSTRAINT unique_object_identity UNIQUE(object_identity),
+     FOREIGN KEY (parent_object) REFERENCES acl_object_identity(id)
+);
+
+CREATE TABLE acl_permission (
+     id IDENTITY NOT NULL,
+     acl_object_identity INTEGER NOT NULL,
+     recipient VARCHAR_IGNORECASE(100) NOT NULL,
+     mask INTEGER NOT NULL,
+     CONSTRAINT unique_recipient UNIQUE(acl_object_identity, recipient),
+     FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id)
 );
 
 --- Mask integer 0  = no permissions
@@ -56,16 +67,19 @@ CREATE TABLE acls (
 ---           scott             Read, Write, Create (from parent #3)
 ---    6      ROLE_SUPERVISOR   Administer (from parent #3)
 ---           scott             Administer (overrides parent #3)
----    7      scott             Read (invalid parent ignored)
 ---------------------------------------------------------------------
 
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:1', 'ROLE_SUPERVISOR', null, 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:2', 'ROLE_SUPERVISOR', 'net.sf.acegisecurity.acl.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:2', 'marissa', 'net.sf.acegisecurity.acl.DomainObject:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:3', 'scott', 'net.sf.acegisecurity.acl.DomainObject:1', 14, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:4', 'inheritance_marker_only', 'net.sf.acegisecurity.acl.DomainObject:1', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:5', 'inheritance_marker_only', 'net.sf.acegisecurity.acl.DomainObject:3', 0, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:6', 'scott', 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
-INSERT INTO acls VALUES ('net.sf.acegisecurity.acl.DomainObject:7', 'scott', 'some.invalid.parent:1', 2, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (1, 'net.sf.acegisecurity.acl.DomainObject:1', null, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (2, 'net.sf.acegisecurity.acl.DomainObject:2', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (3, 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (4, 'net.sf.acegisecurity.acl.DomainObject:4', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (5, 'net.sf.acegisecurity.acl.DomainObject:5', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+INSERT INTO acl_object_identity VALUES (6, 'net.sf.acegisecurity.acl.DomainObject:6', 3, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');
+
+INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1);
+INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0);
+INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);
+INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);
+INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);