Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 473   Methods: 26
NCLOC: 215   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
JdbcDaoImpl.java 100% 97.6% 100% 98.3%
coverage coverage
 1    /* Copyright 2004, 2005 Acegi Technology Pty Limited
 2    *
 3    * Licensed under the Apache License, Version 2.0 (the "License");
 4    * you may not use this file except in compliance with the License.
 5    * You may obtain a copy of the License at
 6    *
 7    * http://www.apache.org/licenses/LICENSE-2.0
 8    *
 9    * Unless required by applicable law or agreed to in writing, software
 10    * distributed under the License is distributed on an "AS IS" BASIS,
 11    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    * See the License for the specific language governing permissions and
 13    * limitations under the License.
 14    */
 15   
 16    package org.acegisecurity.acl.basic.jdbc;
 17   
 18    import org.acegisecurity.acl.basic.AclObjectIdentity;
 19    import org.acegisecurity.acl.basic.BasicAclDao;
 20    import org.acegisecurity.acl.basic.BasicAclEntry;
 21    import org.acegisecurity.acl.basic.NamedEntityObjectIdentity;
 22   
 23    import org.apache.commons.logging.Log;
 24    import org.apache.commons.logging.LogFactory;
 25   
 26    import org.springframework.context.ApplicationContextException;
 27   
 28    import org.springframework.jdbc.core.SqlParameter;
 29    import org.springframework.jdbc.core.support.JdbcDaoSupport;
 30    import org.springframework.jdbc.object.MappingSqlQuery;
 31   
 32    import org.springframework.util.Assert;
 33   
 34    import java.sql.ResultSet;
 35    import java.sql.SQLException;
 36    import java.sql.Types;
 37   
 38    import java.util.List;
 39    import java.util.Vector;
 40   
 41    import javax.sql.DataSource;
 42   
 43   
 44    /**
 45    * <p>
 46    * Retrieves ACL details from a JDBC location.
 47    * </p>
 48    *
 49    * <p>
 50    * A default database structure is assumed. This may be overridden by setting
 51    * the default query strings to use. If this does not provide enough
 52    * flexibility, another strategy would be to subclass this class and override
 53    * the {@link MappingSqlQuery} instance used, via the {@link
 54    * #initMappingSqlQueries()} extension point.
 55    * </p>
 56    */
 57    public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
 58    //~ Static fields/initializers =============================================
 59   
 60    public static final String RECIPIENT_USED_FOR_INHERITENCE_MARKER = "___INHERITENCE_MARKER_ONLY___";
 61    public static final String DEF_ACLS_BY_OBJECT_IDENTITY_QUERY = "SELECT RECIPIENT, MASK FROM acl_permission WHERE acl_object_identity = ?";
 62    public static final String DEF_OBJECT_PROPERTIES_QUERY = "SELECT CHILD.ID, CHILD.OBJECT_IDENTITY, CHILD.ACL_CLASS, PARENT.OBJECT_IDENTITY as PARENT_OBJECT_IDENTITY FROM acl_object_identity as CHILD LEFT OUTER JOIN acl_object_identity as PARENT ON CHILD.parent_object=PARENT.id WHERE CHILD.object_identity = ?";
 63    private static final Log logger = LogFactory.getLog(JdbcDaoImpl.class);
 64   
 65    //~ Instance fields ========================================================
 66   
 67    protected MappingSqlQuery aclsByObjectIdentity;
 68    protected MappingSqlQuery objectProperties;
 69    private String aclsByObjectIdentityQuery;
 70    private String objectPropertiesQuery;
 71   
 72    //~ Constructors ===========================================================
 73   
 74  26 public JdbcDaoImpl() {
 75  26 aclsByObjectIdentityQuery = DEF_ACLS_BY_OBJECT_IDENTITY_QUERY;
 76  26 objectPropertiesQuery = DEF_OBJECT_PROPERTIES_QUERY;
 77    }
 78   
 79    //~ Methods ================================================================
 80   
 81    /**
 82    * Responsible for covering a <code>AclObjectIdentity</code> to a
 83    * <code>String</code> that can be located in the RDBMS.
 84    *
 85    * @param aclObjectIdentity to locate
 86    *
 87    * @return the object identity as a <code>String</code>
 88    */
 89  62 protected String convertAclObjectIdentityToString(
 90    AclObjectIdentity aclObjectIdentity) {
 91    // Ensure we can process this type of AclObjectIdentity
 92  62 Assert.isInstanceOf(NamedEntityObjectIdentity.class, aclObjectIdentity,
 93    "Only aclObjectIdentity of type NamedEntityObjectIdentity supported (was passed: "
 94    + aclObjectIdentity + ")");
 95   
 96  60 NamedEntityObjectIdentity neoi = (NamedEntityObjectIdentity) aclObjectIdentity;
 97   
 98    // Compose the String we expect to find in the RDBMS
 99  60 return neoi.getClassname() + ":" + neoi.getId();
 100    }
 101   
 102    /**
 103    * Constructs an individual <code>BasicAclEntry</code> from the passed
 104    * <code>AclDetailsHolder</code>s.
 105    *
 106    * <P>
 107    * Guarantees to never return <code>null</code> (exceptions are thrown in
 108    * the event of any issues).
 109    * </p>
 110    *
 111    * @param propertiesInformation mandatory information about which instance
 112    * to create, the object identity, and the parent object identity
 113    * (<code>null</code> or empty <code>String</code>s prohibited for
 114    * <code>aclClass</code> and <code>aclObjectIdentity</code>
 115    * @param aclInformation optional information about the individual ACL
 116    * record (if <code>null</code> only an "inheritence marker"
 117    * instance is returned; if not <code>null</code>, it is prohibited
 118    * to present <code>null</code> or an empty <code>String</code> for
 119    * <code>recipient</code>)
 120    *
 121    * @return a fully populated instance suitable for use by external objects
 122    *
 123    * @throws IllegalArgumentException if the indicated ACL class could not be
 124    * created
 125    */
 126  25 private BasicAclEntry createBasicAclEntry(
 127    AclDetailsHolder propertiesInformation, AclDetailsHolder aclInformation) {
 128  25 BasicAclEntry entry;
 129   
 130  25 try {
 131  25 entry = (BasicAclEntry) propertiesInformation.getAclClass()
 132    .newInstance();
 133    } catch (InstantiationException ie) {
 134  0 throw new IllegalArgumentException(ie.getMessage());
 135    } catch (IllegalAccessException iae) {
 136  0 throw new IllegalArgumentException(iae.getMessage());
 137    }
 138   
 139  25 entry.setAclObjectIdentity(propertiesInformation.getAclObjectIdentity());
 140  25 entry.setAclObjectParentIdentity(propertiesInformation
 141    .getAclObjectParentIdentity());
 142   
 143  25 if (aclInformation == null) {
 144    // this is an inheritence marker instance only
 145  2 entry.setMask(0);
 146  2 entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
 147    } else {
 148    // this is an individual ACL entry
 149  23 entry.setMask(aclInformation.getMask());
 150  23 entry.setRecipient(aclInformation.getRecipient());
 151    }
 152   
 153  25 return entry;
 154    }
 155   
 156    /**
 157    * Returns the ACLs associated with the requested
 158    * <code>AclObjectIdentity</code>.
 159    *
 160    * <P>
 161    * The {@link BasicAclEntry}s returned by this method will have
 162    * <code>String</code>-based recipients. This will not be a problem if
 163    * you are using the
 164    * <code>GrantedAuthorityEffectiveAclsResolver</code>, which is the
 165    * default configured against <code>BasicAclProvider</code>.
 166    * </p>
 167    *
 168    * <P>
 169    * This method will only return ACLs for requests where the
 170    * <code>AclObjectIdentity</code> is of type {@link
 171    * NamedEntityObjectIdentity}. Of course, you can subclass or replace
 172    * this class and support your own custom
 173    * <code>AclObjectIdentity</code> types.
 174    * </p>
 175    *
 176    * @param aclObjectIdentity for which ACL information is required
 177    * (cannot be <code>null</code> and must be an instance of
 178    * <code>NamedEntityObjectIdentity</code>)
 179    *
 180    * @return the ACLs that apply (without any <code>null</code>s inside
 181    * the array), or <code>null</code> if not found or if an
 182    * incompatible <code>AclObjectIdentity</code> was requested
 183    */
 184  25 public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
 185  25 String aclObjectIdentityString;
 186   
 187  25 try {
 188  25 aclObjectIdentityString = convertAclObjectIdentityToString(aclObjectIdentity);
 189    } catch (IllegalArgumentException unsupported) {
 190  1 return null; // pursuant to contract described in JavaDocs above
 191    }
 192   
 193    // Lookup the object's main properties from the RDBMS (guaranteed no nulls)
 194  24 List objects = objectProperties.execute(aclObjectIdentityString);
 195   
 196  23 if (objects.size() == 0) {
 197    // this is an unknown object identity string
 198  4 return null;
 199    }
 200   
 201    // Cast to an object properties holder (there should only be one record)
 202  19 AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects
 203    .get(0);
 204   
 205    // Lookup the object's ACLs from RDBMS (guaranteed no nulls)
 206  19 List acls = aclsByObjectIdentity.execute(propertiesInformation
 207    .getForeignKeyId());
 208   
 209  19 if (acls.size() == 0) {
 210    // return merely an inheritence marker (as we know about the object but it has no related ACLs)
 211  2 return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation,
 212    null)};
 213    } else {
 214    // return the individual ACL instances
 215  17 AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls
 216    .toArray(new AclDetailsHolder[] {});
 217  17 List toReturnAcls = new Vector();
 218   
 219  17 for (int i = 0; i < aclHolders.length; i++) {
 220  23 toReturnAcls.add(createBasicAclEntry(
 221    propertiesInformation, aclHolders[i]));
 222    }
 223   
 224  17 return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {});
 225    }
 226    }
 227   
 228  3 public MappingSqlQuery getAclsByObjectIdentity() {
 229  3 return aclsByObjectIdentity;
 230    }
 231   
 232  3 public String getAclsByObjectIdentityQuery() {
 233  3 return aclsByObjectIdentityQuery;
 234    }
 235   
 236  1 public String getObjectPropertiesQuery() {
 237  1 return objectPropertiesQuery;
 238    }
 239   
 240  25 protected void initDao() throws ApplicationContextException {
 241  25 initMappingSqlQueries();
 242    }
 243   
 244    /**
 245    * Extension point to allow other MappingSqlQuery objects to be
 246    * substituted in a subclass
 247    */
 248  25 protected void initMappingSqlQueries() {
 249  25 setAclsByObjectIdentity(new AclsByObjectIdentityMapping(
 250    getDataSource()));
 251  25 setObjectProperties(new ObjectPropertiesMapping(
 252    getDataSource()));
 253    }
 254   
 255  27 public void setAclsByObjectIdentity(
 256    MappingSqlQuery aclsByObjectIdentityQuery) {
 257  27 this.aclsByObjectIdentity = aclsByObjectIdentityQuery;
 258    }
 259   
 260    /**
 261    * Allows the default query string used to retrieve ACLs based
 262    * on object identity to be overriden, if default table or
 263    * column names need to be changed. The default query is
 264    * {@link #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}; when modifying
 265    * this query, ensure that all returned columns are mapped
 266    * back to the same column names as in the default query.
 267    *
 268    * @param queryString The query string to set
 269    */
 270  2 public void setAclsByObjectIdentityQuery(String queryString) {
 271  2 aclsByObjectIdentityQuery = queryString;
 272    }
 273   
 274  25 public void setObjectProperties(
 275    MappingSqlQuery objectPropertiesQuery) {
 276  25 this.objectProperties = objectPropertiesQuery;
 277    }
 278   
 279  1 public void setObjectPropertiesQuery(String queryString) {
 280  1 objectPropertiesQuery = queryString;
 281    }
 282   
 283    //~ Inner Classes ==========================================================
 284   
 285    /**
 286    * Used to hold details of a domain object instance's
 287    * properties, or an individual ACL entry.
 288    *
 289    * <P>
 290    * Not all properties will be set. The actual properties set
 291    * will depend on which <code>MappingSqlQuery</code> creates
 292    * the object.
 293    * </p>
 294    *
 295    * <P>
 296    * Does not enforce <code>null</code>s or empty
 297    * <code>String</code>s as this is performed by the
 298    * <code>MappingSqlQuery</code> objects (or preferably the
 299    * backend RDBMS via schema constraints).
 300    * </p>
 301    */
 302    protected final class AclDetailsHolder {
 303    private AclObjectIdentity aclObjectIdentity;
 304    private AclObjectIdentity aclObjectParentIdentity;
 305    private Class aclClass;
 306    private Object recipient;
 307    private int mask;
 308    private long foreignKeyId;
 309   
 310    /**
 311    * Record details of an individual ACL entry (usually from
 312    * the ACL_PERMISSION table)
 313    *
 314    * @param recipient the recipient
 315    * @param mask the integer to be masked
 316    */
 317  25 public AclDetailsHolder(Object recipient, int mask) {
 318  25 this.recipient = recipient;
 319  25 this.mask = mask;
 320    }
 321   
 322    /**
 323    * Record details of a domain object instance's properties
 324    * (usually from the ACL_OBJECT_IDENTITY table)
 325    *
 326    * @param foreignKeyId used by the
 327    * <code>AclsByObjectIdentityMapping</code> to
 328    * locate the individual ACL entries
 329    * @param aclObjectIdentity the object identity of the
 330    * domain object instance
 331    * @param aclObjectParentIdentity the object identity of
 332    * the domain object instance's parent
 333    * @param aclClass the class of which a new instance which
 334    * should be created for each individual ACL entry
 335    * (or an inheritence "holder" class if there are
 336    * no ACL entries)
 337    */
 338  47 public AclDetailsHolder(long foreignKeyId,
 339    AclObjectIdentity aclObjectIdentity,
 340    AclObjectIdentity aclObjectParentIdentity,
 341    Class aclClass) {
 342  47 this.foreignKeyId = foreignKeyId;
 343  47 this.aclObjectIdentity = aclObjectIdentity;
 344  47 this.aclObjectParentIdentity = aclObjectParentIdentity;
 345  47 this.aclClass = aclClass;
 346    }
 347   
 348  25 public Class getAclClass() {
 349  25 return aclClass;
 350    }
 351   
 352  25 public AclObjectIdentity getAclObjectIdentity() {
 353  25 return aclObjectIdentity;
 354    }
 355   
 356  25 public AclObjectIdentity getAclObjectParentIdentity() {
 357  25 return aclObjectParentIdentity;
 358    }
 359   
 360  53 public long getForeignKeyId() {
 361  53 return foreignKeyId;
 362    }
 363   
 364  23 public int getMask() {
 365  23 return mask;
 366    }
 367   
 368  25 public Object getRecipient() {
 369  25 return recipient;
 370    }
 371    }
 372   
 373    /**
 374    * Query object to look up individual ACL entries.
 375    *
 376    * <P>
 377    * Returns the generic <code>AclDetailsHolder</code> object.
 378    * </p>
 379    *
 380    * <P>
 381    * Guarantees to never return <code>null</code> (exceptions are
 382    * thrown in the event of any issues).
 383    * </p>
 384    *
 385    * <P>
 386    * The executed SQL requires the following information be made
 387    * available from the indicated placeholders: 1. RECIPIENT, 2.
 388    * MASK.
 389    * </p>
 390    */
 391    protected class AclsByObjectIdentityMapping
 392    extends MappingSqlQuery {
 393  25 protected AclsByObjectIdentityMapping(DataSource ds) {
 394  25 super(ds, aclsByObjectIdentityQuery);
 395  25 declareParameter(new SqlParameter(Types.BIGINT));
 396  25 compile();
 397    }
 398   
 399  25 protected Object mapRow(ResultSet rs, int rownum)
 400    throws SQLException {
 401  25 String recipient = rs.getString(1);
 402  25 int mask = rs.getInt(2);
 403  25 Assert.hasText(recipient, "recipient required");
 404   
 405  25 return new AclDetailsHolder(recipient, mask);
 406    }
 407    }
 408   
 409    /**
 410    * Query object to look up properties for an object identity.
 411    *
 412    * <P>
 413    * Returns the generic <code>AclDetailsHolder</code> object.
 414    * </p>
 415    *
 416    * <P>
 417    * Guarantees to never return <code>null</code> (exceptions are
 418    * thrown in the event of any issues).
 419    * </p>
 420    *
 421    * <P>
 422    * The executed SQL requires the following information be made
 423    * available from the indicated placeholders: 1. ID, 2.
 424    * OBJECT_IDENTITY, 3. ACL_CLASS and 4.
 425    * PARENT_OBJECT_IDENTITY.
 426    * </p>
 427    */
 428    protected class ObjectPropertiesMapping extends MappingSqlQuery {
 429  25 protected ObjectPropertiesMapping(DataSource ds) {
 430  25 super(ds, objectPropertiesQuery);
 431  25 declareParameter(new SqlParameter(Types.VARCHAR));
 432  25 compile();
 433    }
 434   
 435  94 private AclObjectIdentity buildIdentity(String identity) {
 436  94 if (identity == null) {
 437    // Must be an empty parent, so return null
 438  17 return null;
 439    }
 440   
 441  77 int delim = identity.lastIndexOf(":");
 442  77 String classname = identity.substring(0, delim);
 443  77 String id = identity.substring(delim + 1);
 444   
 445  77 return new NamedEntityObjectIdentity(classname, id);
 446    }
 447   
 448  48 protected Object mapRow(ResultSet rs, int rownum)
 449    throws SQLException {
 450  48 long id = rs.getLong(1); // required
 451  48 String objectIdentity = rs.getString(2); // required
 452  48 String aclClass = rs.getString(3); // required
 453  48 String parentObjectIdentity = rs.getString(4); // optional
 454  48 Assert.hasText(objectIdentity,
 455    "required DEF_OBJECT_PROPERTIES_QUERY value (objectIdentity) returned null or empty");
 456  48 Assert.hasText(aclClass,
 457    "required DEF_OBJECT_PROPERTIES_QUERY value (aclClass) returned null or empty");
 458   
 459  48 Class aclClazz;
 460   
 461  48 try {
 462  48 aclClazz = this.getClass().getClassLoader()
 463    .loadClass(aclClass);
 464    } catch (ClassNotFoundException cnf) {
 465  1 throw new IllegalArgumentException(cnf.getMessage());
 466    }
 467   
 468  47 return new AclDetailsHolder(id,
 469    buildIdentity(objectIdentity),
 470    buildIdentity(parentObjectIdentity), aclClazz);
 471    }
 472    }
 473    }