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: 403   Methods: 16
NCLOC: 203   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
BasicAclProvider.java 64.8% 79.8% 100% 76.8%
coverage coverage
 1    /* Copyright 2004 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;
 17   
 18    import org.acegisecurity.Authentication;
 19    import org.acegisecurity.acl.AclEntry;
 20    import org.acegisecurity.acl.AclProvider;
 21    import org.acegisecurity.acl.basic.cache.NullAclEntryCache;
 22   
 23    import org.apache.commons.logging.Log;
 24    import org.apache.commons.logging.LogFactory;
 25   
 26    import org.springframework.beans.factory.InitializingBean;
 27    import org.springframework.util.Assert;
 28   
 29    import java.lang.reflect.Constructor;
 30   
 31    import java.util.Collection;
 32    import java.util.HashMap;
 33    import java.util.Map;
 34   
 35   
 36    /**
 37    * <P>
 38    * Retrieves access control lists (ACL) entries for domain object instances
 39    * from a data access object (DAO).
 40    * </p>
 41    *
 42    * <P>
 43    * This implementation will provide ACL lookup services for any object that it
 44    * can determine the {@link AclObjectIdentity} for by calling the {@link
 45    * #obtainIdentity(Object)} method. Subclasses can override this method if
 46    * they only want the <code>BasicAclProvider</code> responding to particular
 47    * domain object instances.
 48    * </p>
 49    *
 50    * <P>
 51    * <code>BasicAclProvider</code> will walk an inheritance hierarchy if a
 52    * <code>BasicAclEntry</code> returned by the DAO indicates it has a parent.
 53    * NB: inheritance occurs at a <I>domain instance object</I> level. It does
 54    * not occur at an ACL recipient level. This means
 55    * <B>all</B><code>BasicAclEntry</code>s for a given domain instance object
 56    * <B>must</B> have the <B>same</B> parent identity, or
 57    * <B>all</B><code>BasicAclEntry</code>s must have <code>null</code> as their
 58    * parent identity.
 59    * </p>
 60    *
 61    * <P>
 62    * A cache should be used. This is provided by the {@link BasicAclEntryCache}.
 63    * <code>BasicAclProvider</code> by default is setup to use the {@link
 64    * NullAclEntryCache}, which performs no caching.
 65    * </p>
 66    *
 67    * <P>
 68    * To implement the {@link #getAcls(Object, Authentication)} method,
 69    * <code>BasicAclProvider</code> requires a {@link EffectiveAclsResolver} to
 70    * be configured against it. By default the {@link
 71    * GrantedAuthorityEffectiveAclsResolver} is used.
 72    * </p>
 73    *
 74    * @author Ben Alex
 75    * @version $Id: BasicAclProvider.java,v 1.5 2005/11/17 00:55:47 benalex Exp $
 76    */
 77    public class BasicAclProvider implements AclProvider, InitializingBean {
 78    //~ Static fields/initializers =============================================
 79   
 80    private static final Log logger = LogFactory.getLog(BasicAclProvider.class);
 81   
 82    /**
 83    * Marker added to the cache to indicate an AclObjectIdentity has no
 84    * corresponding BasicAclEntry[]s
 85    */
 86    private static String RECIPIENT_FOR_CACHE_EMPTY = "RESERVED_RECIPIENT_NOBODY";
 87   
 88    //~ Instance fields ========================================================
 89   
 90    /**
 91    * Must be set to an appropriate data access object. Defaults to
 92    * <code>null</code>.
 93    */
 94    private BasicAclDao basicAclDao;
 95    private BasicAclEntryCache basicAclEntryCache = new NullAclEntryCache();
 96    private Class defaultAclObjectIdentityClass = NamedEntityObjectIdentity.class;
 97    private Class restrictSupportToClass = null;
 98    private EffectiveAclsResolver effectiveAclsResolver = new GrantedAuthorityEffectiveAclsResolver();
 99   
 100    //~ Methods ================================================================
 101   
 102  10 public AclEntry[] getAcls(Object domainInstance) {
 103  10 Map map = new HashMap();
 104   
 105  10 AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
 106   
 107  10 Assert.notNull(aclIdentity, "domainInstance is not supported by this provider");
 108   
 109  9 if (logger.isDebugEnabled()) {
 110  0 logger.debug("Looking up: " + aclIdentity.toString());
 111    }
 112   
 113  9 BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
 114   
 115    // Exit if there is no ACL information or parent for this instance
 116  9 if (instanceAclEntries == null) {
 117  4 return null;
 118    }
 119   
 120    // Add the leaf objects to the Map, keyed on recipient
 121  5 for (int i = 0; i < instanceAclEntries.length; i++) {
 122  5 if (logger.isDebugEnabled()) {
 123  0 logger.debug("Explicit add: "
 124    + instanceAclEntries[i].toString());
 125    }
 126   
 127  5 map.put(instanceAclEntries[i].getRecipient(), instanceAclEntries[i]);
 128    }
 129   
 130  5 AclObjectIdentity parent = instanceAclEntries[0]
 131    .getAclObjectParentIdentity();
 132   
 133  5 while (parent != null) {
 134  6 BasicAclEntry[] parentAclEntries = lookup(parent);
 135   
 136  6 if (logger.isDebugEnabled()) {
 137  0 logger.debug("Parent lookup: " + parent.toString());
 138    }
 139   
 140    // Exit loop if parent couldn't be found (unexpected condition)
 141  6 if (parentAclEntries == null) {
 142  0 if (logger.isDebugEnabled()) {
 143  0 logger.debug("Parent could not be found in ACL repository");
 144    }
 145   
 146  0 break;
 147    }
 148   
 149    // Now add each _NEW_ recipient to the list
 150  6 for (int i = 0; i < parentAclEntries.length; i++) {
 151  6 if (!map.containsKey(parentAclEntries[i].getRecipient())) {
 152  4 if (logger.isDebugEnabled()) {
 153  0 logger.debug("Added parent to map: "
 154    + parentAclEntries[i].toString());
 155    }
 156   
 157  4 map.put(parentAclEntries[i].getRecipient(),
 158    parentAclEntries[i]);
 159    } else {
 160  2 if (logger.isDebugEnabled()) {
 161  0 logger.debug("Did NOT add parent to map: "
 162    + parentAclEntries[i].toString());
 163    }
 164    }
 165    }
 166   
 167    // Prepare for next iteration of while loop
 168  6 parent = parentAclEntries[0].getAclObjectParentIdentity();
 169    }
 170   
 171  5 Collection collection = map.values();
 172   
 173  5 return (AclEntry[]) collection.toArray(new AclEntry[]{});
 174    }
 175   
 176  1 public AclEntry[] getAcls(Object domainInstance,
 177    Authentication authentication) {
 178  1 AclEntry[] allAcls = (AclEntry[]) this.getAcls(domainInstance);
 179   
 180  1 return this.effectiveAclsResolver.resolveEffectiveAcls(allAcls,
 181    authentication);
 182    }
 183   
 184  11 public void setBasicAclDao(BasicAclDao basicAclDao) {
 185  11 this.basicAclDao = basicAclDao;
 186    }
 187   
 188  1 public BasicAclDao getBasicAclDao() {
 189  1 return basicAclDao;
 190    }
 191   
 192  3 public void setBasicAclEntryCache(BasicAclEntryCache basicAclEntryCache) {
 193  3 this.basicAclEntryCache = basicAclEntryCache;
 194    }
 195   
 196  2 public BasicAclEntryCache getBasicAclEntryCache() {
 197  2 return basicAclEntryCache;
 198    }
 199   
 200    /**
 201    * Allows selection of the <code>AclObjectIdentity</code> class that an
 202    * attempt should be made to construct if the passed object does not
 203    * implement <code>AclObjectIdentityAware</code>.
 204    *
 205    * <P>
 206    * NB: Any <code>defaultAclObjectIdentityClass</code><b>must</b> provide a
 207    * public constructor that accepts an <code>Object</code>. Otherwise it is
 208    * not possible for the <code>BasicAclProvider</code> to try to create the
 209    * <code>AclObjectIdentity</code> instance at runtime.
 210    * </p>
 211    *
 212    * @param defaultAclObjectIdentityClass
 213    */
 214  4 public void setDefaultAclObjectIdentityClass(
 215    Class defaultAclObjectIdentityClass) {
 216  4 this.defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
 217    }
 218   
 219  2 public Class getDefaultAclObjectIdentityClass() {
 220  2 return defaultAclObjectIdentityClass;
 221    }
 222   
 223  2 public void setEffectiveAclsResolver(
 224    EffectiveAclsResolver effectiveAclsResolver) {
 225  2 this.effectiveAclsResolver = effectiveAclsResolver;
 226    }
 227   
 228  2 public EffectiveAclsResolver getEffectiveAclsResolver() {
 229  2 return effectiveAclsResolver;
 230    }
 231   
 232    /**
 233    * If set to a value other than <code>null</code>, the {@link
 234    * #supports(Object)} method will <b>only</b> support the indicates class.
 235    * This is useful if you wish to wire multiple
 236    * <code>BasicAclProvider</code>s in a list of
 237    * <code>AclProviderManager.providers</code> but only have particular
 238    * instances respond to particular domain object types.
 239    *
 240    * @param restrictSupportToClass the class to restrict this
 241    * <code>BasicAclProvider</code> to service request for, or
 242    * <code>null</code> (the default) if the
 243    * <code>BasicAclProvider</code> should respond to every class
 244    * presented
 245    */
 246  2 public void setRestrictSupportToClass(Class restrictSupportToClass) {
 247  2 this.restrictSupportToClass = restrictSupportToClass;
 248    }
 249   
 250  3 public Class getRestrictSupportToClass() {
 251  3 return restrictSupportToClass;
 252    }
 253   
 254  6 public void afterPropertiesSet() {
 255  6 Assert.notNull(basicAclDao, "basicAclDao required");
 256  5 Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
 257  4 Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
 258  4 Assert.notNull(effectiveAclsResolver, "effectiveAclsResolver required");
 259  3 Assert.notNull(defaultAclObjectIdentityClass, "defaultAclObjectIdentityClass required");
 260  2 Assert.isTrue(AclObjectIdentity.class.isAssignableFrom(this.defaultAclObjectIdentityClass),
 261    "defaultAclObjectIdentityClass must implement AclObjectIdentity");
 262   
 263  1 try {
 264  1 Constructor constructor = defaultAclObjectIdentityClass
 265    .getConstructor(new Class[]{Object.class});
 266    } catch (NoSuchMethodException nsme) {
 267  1 throw new IllegalArgumentException("defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!");
 268    }
 269    }
 270   
 271    /**
 272    * Indicates support for the passed object.
 273    *
 274    * <p>
 275    * An object will only be supported if it (i) is allowed to be supported as
 276    * defined by the {@link #setRestrictSupportToClass(Class)} method,
 277    * <b>and</b> (ii) if an <code>AclObjectIdentity</code> is returned by
 278    * {@link #obtainIdentity(Object)} for that object.
 279    * </p>
 280    *
 281    * @param domainInstance the instance to check
 282    *
 283    * @return <code>true</code> if this provider supports the passed object,
 284    * <code>false</code> otherwise
 285    */
 286  7 public boolean supports(Object domainInstance) {
 287  7 if (domainInstance == null) {
 288  0 if (logger.isDebugEnabled()) {
 289  0 logger.debug("domainInstance is null");
 290    }
 291   
 292  0 return false;
 293    }
 294   
 295  7 if ((restrictSupportToClass != null)
 296    && !restrictSupportToClass.isAssignableFrom(
 297    domainInstance.getClass())) {
 298  1 if (logger.isDebugEnabled()) {
 299  0 logger.debug("domainInstance not instance of "
 300    + restrictSupportToClass);
 301    }
 302   
 303  1 return false;
 304    }
 305   
 306  6 if (obtainIdentity(domainInstance) == null) {
 307  3 if (logger.isDebugEnabled()) {
 308  0 logger.debug("obtainIdentity returned null");
 309    }
 310   
 311  3 return false;
 312    } else {
 313  3 if (logger.isDebugEnabled()) {
 314  0 logger.debug("obtainIdentity returned "
 315    + obtainIdentity(domainInstance));
 316    }
 317   
 318  3 return true;
 319    }
 320    }
 321   
 322    /**
 323    * This method looks up the <code>AclObjectIdentity</code> of a passed
 324    * domain object instance.
 325    *
 326    * <P>
 327    * This implementation attempts to obtain the
 328    * <code>AclObjectIdentity</code> via reflection inspection of the class
 329    * for the {@link AclObjectIdentityAware} interface. If this fails, an
 330    * attempt is made to construct a {@link
 331    * #getDefaultAclObjectIdentityClass()} object by passing the domain
 332    * instance object into its constructor.
 333    * </p>
 334    *
 335    * @param domainInstance the domain object instance (never
 336    * <code>null</code>)
 337    *
 338    * @return an ACL object identity, or <code>null</code> if one could not be
 339    * obtained
 340    */
 341  16 protected AclObjectIdentity obtainIdentity(Object domainInstance) {
 342  16 if (domainInstance instanceof AclObjectIdentityAware) {
 343  10 AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
 344   
 345  10 if (logger.isDebugEnabled()) {
 346  0 logger.debug("domainInstance: " + domainInstance
 347    + " cast to AclObjectIdentityAware");
 348    }
 349   
 350  10 return aclObjectIdentityAware.getAclObjectIdentity();
 351    }
 352   
 353  6 try {
 354  6 Constructor constructor = defaultAclObjectIdentityClass
 355    .getConstructor(new Class[] {Object.class});
 356   
 357  6 if (logger.isDebugEnabled()) {
 358  0 logger.debug("domainInstance: " + domainInstance
 359    + " attempting to pass to constructor: " + constructor);
 360    }
 361   
 362  6 return (AclObjectIdentity) constructor.newInstance(new Object[] {domainInstance});
 363    } catch (Exception ex) {
 364  4 if (logger.isDebugEnabled()) {
 365  0 logger.debug("Error attempting construction of "
 366    + defaultAclObjectIdentityClass + ": " + ex.getMessage(), ex);
 367   
 368  0 if (ex.getCause() != null) {
 369  0 logger.debug("Cause: " + ex.getCause().getMessage(),
 370    ex.getCause());
 371    }
 372    }
 373   
 374  4 return null;
 375    }
 376    }
 377   
 378  15 private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
 379  15 BasicAclEntry[] result = basicAclEntryCache.getEntriesFromCache(aclObjectIdentity);
 380   
 381  15 if (result != null) {
 382  3 if (result[0].getRecipient().equals(RECIPIENT_FOR_CACHE_EMPTY)) {
 383  2 return null;
 384    } else {
 385  1 return result;
 386    }
 387    }
 388   
 389  12 result = basicAclDao.getAcls(aclObjectIdentity);
 390   
 391  12 if (result == null) {
 392  2 SimpleAclEntry[] emptyAclEntries = {new SimpleAclEntry(RECIPIENT_FOR_CACHE_EMPTY,
 393    aclObjectIdentity, null, 0)};
 394  2 basicAclEntryCache.putEntriesInCache(emptyAclEntries);
 395   
 396  2 return null;
 397    }
 398   
 399  10 basicAclEntryCache.putEntriesInCache(result);
 400   
 401  10 return result;
 402    }
 403    }