From 13d5a2dbca624555ef0b876041ee4d5f00c0990d Mon Sep 17 00:00:00 2001
From: Ben Alex
- * 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. *
* * @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(); - - while (iter.hasNext()) { - Object object = iter.next(); - - if (object != null) { - toReturnAcls.add(object); - } + if (objects.size() == 0) { + // this is an unknown object identity string + return null; } - // Return null if nothing of use found (to meet interface contract) - if (toReturnAcls.size() > 0) { - return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {}); + // Cast to an object properties holder (there should only be one record) + AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects.get(0); + + // Lookup the object's ACLs from RDBMS (guaranteed no nulls) + List acls = aclsByObjectIdentity.execute(propertiesInformation + .getForeignKeyId()); + + 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 null; + // 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 (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {}); } } @@ -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 individualBasicAclEntry from the passed
+ * AclDetailsHolders.
+ *
+ *
+ * Guarantees to never return null (exceptions are thrown in
+ * the event of any issues).
+ *
null or empty Strings prohibited for
+ * aclClass and aclObjectIdentity
+ * @param aclInformation optional information about the individual ACL
+ * record (if null only an "inheritence marker"
+ * instance is returned; if not null, it is prohibited
+ * to present null or an empty String for
+ * recipient)
+ *
+ * @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.
+ *
+ *
+ * Returns the generic AclDetailsHolder object.
+ *
+ * Guarantees to never return null (exceptions are thrown in
+ * the event of any issues).
+ *
* 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. *
*/ protected class AclsByObjectIdentityMapping extends MappingSqlQuery { protected AclsByObjectIdentityMapping(DataSource ds) { super(ds, aclsByObjectIdentityQuery); + declareParameter(new SqlParameter(Types.INTEGER)); + compile(); + } + + protected Object mapRow(ResultSet rs, int rownum) + throws SQLException { + String recipient = rs.getString(1); + int mask = rs.getInt(2); + + if ((recipient == null) || "".equals(recipient)) { + throw new IllegalArgumentException("recipient required"); + } + + return new AclDetailsHolder(recipient, mask); + } + } + + /** + * Query object to look up properties for an object identity. + * + *
+ * Returns the generic AclDetailsHolder object.
+ *
+ * Guarantees to never return null (exceptions are thrown in
+ * the event of any issues).
+ *
+ * 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. + *
+ */ + protected class ObjectPropertiesMapping extends MappingSqlQuery { + protected ObjectPropertiesMapping(DataSource ds) { + super(ds, objectPropertiesQuery); declareParameter(new SqlParameter(Types.VARCHAR)); 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); + int id = rs.getInt(1); // required + String objectIdentity = rs.getString(2); // required + String aclClass = rs.getString(3); // required + String parentObjectIdentity = rs.getString(4); // optional - // Try to create the indicated BasicAclEntry class - BasicAclEntry entry; + 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"); + } + + Class aclClazz; try { - Class aclClazz = this.getClass().getClassLoader().loadClass(aclClass); - entry = (BasicAclEntry) aclClazz.newInstance(); + aclClazz = this.getClass().getClassLoader().loadClass(aclClass); } catch (ClassNotFoundException cnf) { - logger.error(cnf); - - return null; - } catch (InstantiationException ie) { - logger.error(ie); - - return null; - } catch (IllegalAccessException iae) { - logger.error(iae); - - return null; + throw new IllegalArgumentException(cnf.getMessage()); } - // 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"); - - return null; - } - - 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. + * + *
+ * Not all properties will be set. The actual properties set will depend on
+ * which MappingSqlQuery creates the object.
+ *
+ * Does not enforce nulls or empty Strings as
+ * this is performed by the MappingSqlQuery objects (or
+ * preferably the backend RDBMS via schema constraints).
+ *
AclsByObjectIdentityMapping 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;
+ }
+ }
}
diff --git a/core/src/test/java/org/acegisecurity/PopulatedDatabase.java b/core/src/test/java/org/acegisecurity/PopulatedDatabase.java
index 00f606e2e4..b6a0ae9be9 100644
--- a/core/src/test/java/org/acegisecurity/PopulatedDatabase.java
+++ b/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 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 (2, 'net.sf.acegisecurity.acl.DomainObject:2', 1, '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 (3, 'net.sf.acegisecurity.acl.DomainObject:3', 1, 'net.sf.acegisecurity.acl.basic.SimpleAclEntry');");
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 (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:4','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:1',0,'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:5','inheritance_marker_only','net.sf.acegisecurity.acl.DomainObject:3',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:6','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'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:7','scott','some.invalid.parent:1',2,'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:8','scott','net.sf.acegisecurity.acl.DomainObject:3',1,'some.invalid.basic.acl.entry.class.name')");
+ "INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0);");
+ template.execute(
+ "INSERT INTO acl_permission VALUES (null, 2, 'marissa', 2);");
+ template.execute(
+ "INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);");
+ template.execute(
+ "INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);");
}
}
diff --git a/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java b/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java
index f9fee5ed20..2610e96ecd 100644
--- a/core/src/test/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImplTests.java
+++ b/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() {}
diff --git a/docs/reference/src/index.xml b/docs/reference/src/index.xml
index d41591cb74..69f1b771f7 100644
--- a/docs/reference/src/index.xml
+++ b/docs/reference/src/index.xml
@@ -3208,23 +3208,41 @@ public java.lang.Object getRecipient();
default database schema and some sample data will aid in understanding
its function:
-