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