View Javadoc

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     public AclEntry[] getAcls(Object domainInstance) {
103         Map map = new HashMap();
104 
105         AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
106 
107         Assert.notNull(aclIdentity, "domainInstance is not supported by this provider");
108 
109         if (logger.isDebugEnabled()) {
110             logger.debug("Looking up: " + aclIdentity.toString());
111         }
112 
113         BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
114 
115         // Exit if there is no ACL information or parent for this instance
116         if (instanceAclEntries == null) {
117             return null;
118         }
119 
120         // Add the leaf objects to the Map, keyed on recipient
121         for (int i = 0; i < instanceAclEntries.length; i++) {
122             if (logger.isDebugEnabled()) {
123                 logger.debug("Explicit add: "
124                         + instanceAclEntries[i].toString());
125             }
126 
127             map.put(instanceAclEntries[i].getRecipient(), instanceAclEntries[i]);
128         }
129 
130         AclObjectIdentity parent = instanceAclEntries[0]
131                 .getAclObjectParentIdentity();
132 
133         while (parent != null) {
134             BasicAclEntry[] parentAclEntries = lookup(parent);
135 
136             if (logger.isDebugEnabled()) {
137                 logger.debug("Parent lookup: " + parent.toString());
138             }
139 
140             // Exit loop if parent couldn't be found (unexpected condition)
141             if (parentAclEntries == null) {
142                 if (logger.isDebugEnabled()) {
143                     logger.debug("Parent could not be found in ACL repository");
144                 }
145 
146                 break;
147             }
148 
149             // Now add each _NEW_ recipient to the list
150             for (int i = 0; i < parentAclEntries.length; i++) {
151                 if (!map.containsKey(parentAclEntries[i].getRecipient())) {
152                     if (logger.isDebugEnabled()) {
153                         logger.debug("Added parent to map: "
154                                 + parentAclEntries[i].toString());
155                     }
156 
157                     map.put(parentAclEntries[i].getRecipient(),
158                             parentAclEntries[i]);
159                 } else {
160                     if (logger.isDebugEnabled()) {
161                         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             parent = parentAclEntries[0].getAclObjectParentIdentity();
169         }
170 
171         Collection collection = map.values();
172 
173         return (AclEntry[]) collection.toArray(new AclEntry[]{});
174     }
175 
176     public AclEntry[] getAcls(Object domainInstance,
177         Authentication authentication) {
178         AclEntry[] allAcls = (AclEntry[]) this.getAcls(domainInstance);
179 
180         return this.effectiveAclsResolver.resolveEffectiveAcls(allAcls,
181             authentication);
182     }
183 
184     public void setBasicAclDao(BasicAclDao basicAclDao) {
185         this.basicAclDao = basicAclDao;
186     }
187 
188     public BasicAclDao getBasicAclDao() {
189         return basicAclDao;
190     }
191 
192     public void setBasicAclEntryCache(BasicAclEntryCache basicAclEntryCache) {
193         this.basicAclEntryCache = basicAclEntryCache;
194     }
195 
196     public BasicAclEntryCache getBasicAclEntryCache() {
197         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     public void setDefaultAclObjectIdentityClass(
215         Class defaultAclObjectIdentityClass) {
216         this.defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
217     }
218 
219     public Class getDefaultAclObjectIdentityClass() {
220         return defaultAclObjectIdentityClass;
221     }
222 
223     public void setEffectiveAclsResolver(
224         EffectiveAclsResolver effectiveAclsResolver) {
225         this.effectiveAclsResolver = effectiveAclsResolver;
226     }
227 
228     public EffectiveAclsResolver getEffectiveAclsResolver() {
229         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     public void setRestrictSupportToClass(Class restrictSupportToClass) {
247         this.restrictSupportToClass = restrictSupportToClass;
248     }
249 
250     public Class getRestrictSupportToClass() {
251         return restrictSupportToClass;
252     }
253 
254     public void afterPropertiesSet() {
255         Assert.notNull(basicAclDao, "basicAclDao required");
256         Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
257         Assert.notNull(basicAclEntryCache, "basicAclEntryCache required");
258         Assert.notNull(effectiveAclsResolver, "effectiveAclsResolver required");
259         Assert.notNull(defaultAclObjectIdentityClass, "defaultAclObjectIdentityClass required");
260         Assert.isTrue(AclObjectIdentity.class.isAssignableFrom(this.defaultAclObjectIdentityClass),
261                 "defaultAclObjectIdentityClass must implement AclObjectIdentity");
262 
263         try {
264             Constructor constructor = defaultAclObjectIdentityClass
265                     .getConstructor(new Class[]{Object.class});
266         } catch (NoSuchMethodException nsme) {
267             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     public boolean supports(Object domainInstance) {
287         if (domainInstance == null) {
288             if (logger.isDebugEnabled()) {
289                 logger.debug("domainInstance is null");
290             }
291 
292             return false;
293         }
294 
295         if ((restrictSupportToClass != null)
296             && !restrictSupportToClass.isAssignableFrom(
297                 domainInstance.getClass())) {
298             if (logger.isDebugEnabled()) {
299                 logger.debug("domainInstance not instance of "
300                     + restrictSupportToClass);
301             }
302 
303             return false;
304         }
305 
306         if (obtainIdentity(domainInstance) == null) {
307             if (logger.isDebugEnabled()) {
308                 logger.debug("obtainIdentity returned null");
309             }
310 
311             return false;
312         } else {
313             if (logger.isDebugEnabled()) {
314                 logger.debug("obtainIdentity returned "
315                     + obtainIdentity(domainInstance));
316             }
317 
318             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     protected AclObjectIdentity obtainIdentity(Object domainInstance) {
342         if (domainInstance instanceof AclObjectIdentityAware) {
343             AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
344 
345             if (logger.isDebugEnabled()) {
346                 logger.debug("domainInstance: " + domainInstance
347                     + " cast to AclObjectIdentityAware");
348             }
349 
350             return aclObjectIdentityAware.getAclObjectIdentity();
351         }
352 
353         try {
354             Constructor constructor = defaultAclObjectIdentityClass
355                 .getConstructor(new Class[] {Object.class});
356 
357             if (logger.isDebugEnabled()) {
358                 logger.debug("domainInstance: " + domainInstance
359                     + " attempting to pass to constructor: " + constructor);
360             }
361 
362             return (AclObjectIdentity) constructor.newInstance(new Object[] {domainInstance});
363         } catch (Exception ex) {
364             if (logger.isDebugEnabled()) {
365                 logger.debug("Error attempting construction of "
366                     + defaultAclObjectIdentityClass + ": " + ex.getMessage(), ex);
367 
368                 if (ex.getCause() != null) {
369                     logger.debug("Cause: " + ex.getCause().getMessage(),
370                         ex.getCause());
371                 }
372             }
373 
374             return null;
375         }
376     }
377 
378     private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
379         BasicAclEntry[] result = basicAclEntryCache.getEntriesFromCache(aclObjectIdentity);
380 
381         if (result != null) {
382             if (result[0].getRecipient().equals(RECIPIENT_FOR_CACHE_EMPTY)) {
383                 return null;
384             } else {
385                 return result;
386             }
387         }
388 
389         result = basicAclDao.getAcls(aclObjectIdentity);
390 
391         if (result == null) {
392             SimpleAclEntry[] emptyAclEntries = {new SimpleAclEntry(RECIPIENT_FOR_CACHE_EMPTY,
393                         aclObjectIdentity, null, 0)};
394             basicAclEntryCache.putEntriesInCache(emptyAclEntries);
395 
396             return null;
397         }
398 
399         basicAclEntryCache.putEntriesInCache(result);
400 
401         return result;
402     }
403 }