001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.acl.basic;
017:
018: import org.acegisecurity.Authentication;
019:
020: import org.acegisecurity.acl.AclEntry;
021: import org.acegisecurity.acl.AclProvider;
022: import org.acegisecurity.acl.basic.cache.NullAclEntryCache;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import org.springframework.beans.factory.InitializingBean;
028:
029: import org.springframework.util.Assert;
030:
031: import java.lang.reflect.Constructor;
032:
033: import java.util.Collection;
034: import java.util.HashMap;
035: import java.util.Map;
036:
037: /**
038: * Retrieves access control lists (ACL) entries for domain object instances from a data access object (DAO).
039: * <p>
040: * This implementation will provide ACL lookup services for any object that it can determine the {@link
041: * AclObjectIdentity} for by calling the {@link #obtainIdentity(Object)} method. Subclasses can override this method
042: * if they only want the <code>BasicAclProvider</code> responding to particular domain object instances.
043: * </p>
044: * <p>
045: * <code>BasicAclProvider</code> will walk an inheritance hierarchy if a <code>BasicAclEntry</code> returned by
046: * the DAO indicates it has a parent. NB: inheritance occurs at a <i>domain instance object</i> level. It does not
047: * occur at an ACL recipient level. This means <b>all</b><code>BasicAclEntry</code>s for a given domain instance
048: * object <b>must</b> have the <b>same</b> parent identity, or <b>all</b><code>BasicAclEntry</code>s must have
049: * <code>null</code> as their parent identity.
050: * </p>
051: * <p>
052: * A cache should be used. This is provided by the {@link BasicAclEntryCache}. <code>BasicAclProvider</code> by
053: * default is setup to use the {@link NullAclEntryCache}, which performs no caching.
054: * </p>
055: * <p>To implement the {@link #getAcls(Object, Authentication)} method, <code>BasicAclProvider</code> requires a
056: * {@link EffectiveAclsResolver} to be configured against it. By default the {@link
057: * GrantedAuthorityEffectiveAclsResolver} is used.</p>
058: *
059: * @author Ben Alex
060: * @version $Id: BasicAclProvider.java 1782 2007-02-22 23:57:49Z luke_t $
061: */
062: public class BasicAclProvider implements AclProvider, InitializingBean {
063: //~ Static fields/initializers =====================================================================================
064:
065: private static final Log logger = LogFactory
066: .getLog(BasicAclProvider.class);
067:
068: /** Marker added to the cache to indicate an AclObjectIdentity has no corresponding BasicAclEntry[]s */
069: private static final String RECIPIENT_FOR_CACHE_EMPTY = "RESERVED_RECIPIENT_NOBODY";
070:
071: //~ Instance fields ================================================================================================
072:
073: /** Must be set to an appropriate data access object. Defaults to <code>null</code>. */
074: private BasicAclDao basicAclDao;
075: private BasicAclEntryCache basicAclEntryCache = new NullAclEntryCache();
076: private Class defaultAclObjectIdentityClass = NamedEntityObjectIdentity.class;
077: private Class restrictSupportToClass = null;
078: private EffectiveAclsResolver effectiveAclsResolver = new GrantedAuthorityEffectiveAclsResolver();
079:
080: //~ Methods ========================================================================================================
081:
082: public void afterPropertiesSet() {
083: Assert.notNull(basicAclDao, "basicAclDao required");
084: Assert.notNull(basicAclEntryCache,
085: "basicAclEntryCache required");
086: Assert.notNull(basicAclEntryCache,
087: "basicAclEntryCache required");
088: Assert.notNull(effectiveAclsResolver,
089: "effectiveAclsResolver required");
090: Assert.notNull(defaultAclObjectIdentityClass,
091: "defaultAclObjectIdentityClass required");
092: Assert
093: .isTrue(
094: AclObjectIdentity.class
095: .isAssignableFrom(this .defaultAclObjectIdentityClass),
096: "defaultAclObjectIdentityClass must implement AclObjectIdentity");
097:
098: try {
099: Constructor constructor = defaultAclObjectIdentityClass
100: .getConstructor(new Class[] { Object.class });
101: } catch (NoSuchMethodException nsme) {
102: throw new IllegalArgumentException(
103: "defaultAclObjectIdentityClass must provide a constructor that accepts the domain object instance!");
104: }
105: }
106:
107: public AclEntry[] getAcls(Object domainInstance) {
108: Map map = new HashMap();
109:
110: AclObjectIdentity aclIdentity = obtainIdentity(domainInstance);
111:
112: Assert.notNull(aclIdentity,
113: "domainInstance is not supported by this provider");
114:
115: if (logger.isDebugEnabled()) {
116: logger.debug("Looking up: " + aclIdentity.toString());
117: }
118:
119: BasicAclEntry[] instanceAclEntries = lookup(aclIdentity);
120:
121: // Exit if there is no ACL information or parent for this instance
122: if (instanceAclEntries == null) {
123: return null;
124: }
125:
126: // Add the leaf objects to the Map, keyed on recipient
127: for (int i = 0; i < instanceAclEntries.length; i++) {
128: if (logger.isDebugEnabled()) {
129: logger.debug("Explicit add: "
130: + instanceAclEntries[i].toString());
131: }
132:
133: map.put(instanceAclEntries[i].getRecipient(),
134: instanceAclEntries[i]);
135: }
136:
137: AclObjectIdentity parent = instanceAclEntries[0]
138: .getAclObjectParentIdentity();
139:
140: while (parent != null) {
141: BasicAclEntry[] parentAclEntries = lookup(parent);
142:
143: if (logger.isDebugEnabled()) {
144: logger.debug("Parent lookup: " + parent.toString());
145: }
146:
147: // Exit loop if parent couldn't be found (unexpected condition)
148: if (parentAclEntries == null) {
149: if (logger.isDebugEnabled()) {
150: logger
151: .debug("Parent could not be found in ACL repository");
152: }
153:
154: break;
155: }
156:
157: // Now add each _NEW_ recipient to the list
158: for (int i = 0; i < parentAclEntries.length; i++) {
159: if (!map
160: .containsKey(parentAclEntries[i].getRecipient())) {
161: if (logger.isDebugEnabled()) {
162: logger.debug("Added parent to map: "
163: + parentAclEntries[i].toString());
164: }
165:
166: map.put(parentAclEntries[i].getRecipient(),
167: parentAclEntries[i]);
168: } else {
169: if (logger.isDebugEnabled()) {
170: logger.debug("Did NOT add parent to map: "
171: + parentAclEntries[i].toString());
172: }
173: }
174: }
175:
176: // Prepare for next iteration of while loop
177: parent = parentAclEntries[0].getAclObjectParentIdentity();
178: }
179:
180: Collection collection = map.values();
181:
182: return (AclEntry[]) collection.toArray(new AclEntry[] {});
183: }
184:
185: public AclEntry[] getAcls(Object domainInstance,
186: Authentication authentication) {
187: AclEntry[] allAcls = (AclEntry[]) this .getAcls(domainInstance);
188:
189: return this .effectiveAclsResolver.resolveEffectiveAcls(allAcls,
190: authentication);
191: }
192:
193: public BasicAclDao getBasicAclDao() {
194: return basicAclDao;
195: }
196:
197: public BasicAclEntryCache getBasicAclEntryCache() {
198: return basicAclEntryCache;
199: }
200:
201: public Class getDefaultAclObjectIdentityClass() {
202: return defaultAclObjectIdentityClass;
203: }
204:
205: public EffectiveAclsResolver getEffectiveAclsResolver() {
206: return effectiveAclsResolver;
207: }
208:
209: public Class getRestrictSupportToClass() {
210: return restrictSupportToClass;
211: }
212:
213: private BasicAclEntry[] lookup(AclObjectIdentity aclObjectIdentity) {
214: BasicAclEntry[] result = basicAclEntryCache
215: .getEntriesFromCache(aclObjectIdentity);
216:
217: if (result != null) {
218: if (result[0].getRecipient().equals(
219: RECIPIENT_FOR_CACHE_EMPTY)) {
220: return null;
221: } else {
222: return result;
223: }
224: }
225:
226: result = basicAclDao.getAcls(aclObjectIdentity);
227:
228: if (result == null) {
229: SimpleAclEntry[] emptyAclEntries = { new SimpleAclEntry(
230: RECIPIENT_FOR_CACHE_EMPTY, aclObjectIdentity, null,
231: 0) };
232: basicAclEntryCache.putEntriesInCache(emptyAclEntries);
233:
234: return null;
235: }
236:
237: basicAclEntryCache.putEntriesInCache(result);
238:
239: return result;
240: }
241:
242: /**
243: * This method looks up the <code>AclObjectIdentity</code> of a passed domain object instance.<P>This
244: * implementation attempts to obtain the <code>AclObjectIdentity</code> via reflection inspection of the class for
245: * the {@link AclObjectIdentityAware} interface. If this fails, an attempt is made to construct a {@link
246: * #getDefaultAclObjectIdentityClass()} object by passing the domain instance object into its constructor.</p>
247: *
248: * @param domainInstance the domain object instance (never <code>null</code>)
249: *
250: * @return an ACL object identity, or <code>null</code> if one could not be obtained
251: */
252: protected AclObjectIdentity obtainIdentity(Object domainInstance) {
253: if (domainInstance instanceof AclObjectIdentityAware) {
254: AclObjectIdentityAware aclObjectIdentityAware = (AclObjectIdentityAware) domainInstance;
255:
256: if (logger.isDebugEnabled()) {
257: logger.debug("domainInstance: " + domainInstance
258: + " cast to AclObjectIdentityAware");
259: }
260:
261: return aclObjectIdentityAware.getAclObjectIdentity();
262: }
263:
264: try {
265: Constructor constructor = defaultAclObjectIdentityClass
266: .getConstructor(new Class[] { Object.class });
267:
268: if (logger.isDebugEnabled()) {
269: logger.debug("domainInstance: " + domainInstance
270: + " attempting to pass to constructor: "
271: + constructor);
272: }
273:
274: return (AclObjectIdentity) constructor
275: .newInstance(new Object[] { domainInstance });
276: } catch (Exception ex) {
277: if (logger.isDebugEnabled()) {
278: logger.debug("Error attempting construction of "
279: + defaultAclObjectIdentityClass + ": "
280: + ex.getMessage(), ex);
281:
282: if (ex.getCause() != null) {
283: logger.debug(
284: "Cause: " + ex.getCause().getMessage(), ex
285: .getCause());
286: }
287: }
288:
289: return null;
290: }
291: }
292:
293: public void setBasicAclDao(BasicAclDao basicAclDao) {
294: this .basicAclDao = basicAclDao;
295: }
296:
297: public void setBasicAclEntryCache(
298: BasicAclEntryCache basicAclEntryCache) {
299: this .basicAclEntryCache = basicAclEntryCache;
300: }
301:
302: /**
303: * Allows selection of the <code>AclObjectIdentity</code> class that an attempt should be made to construct
304: * if the passed object does not implement <code>AclObjectIdentityAware</code>.<P>NB: Any
305: * <code>defaultAclObjectIdentityClass</code><b>must</b> provide a public constructor that accepts an
306: * <code>Object</code>. Otherwise it is not possible for the <code>BasicAclProvider</code> to try to create the
307: * <code>AclObjectIdentity</code> instance at runtime.</p>
308: *
309: * @param defaultAclObjectIdentityClass
310: */
311: public void setDefaultAclObjectIdentityClass(
312: Class defaultAclObjectIdentityClass) {
313: this .defaultAclObjectIdentityClass = defaultAclObjectIdentityClass;
314: }
315:
316: public void setEffectiveAclsResolver(
317: EffectiveAclsResolver effectiveAclsResolver) {
318: this .effectiveAclsResolver = effectiveAclsResolver;
319: }
320:
321: /**
322: * If set to a value other than <code>null</code>, the {@link #supports(Object)} method will <b>only</b>
323: * support the indicates class. This is useful if you wish to wire multiple <code>BasicAclProvider</code>s in a
324: * list of <code>AclProviderManager.providers</code> but only have particular instances respond to particular
325: * domain object types.
326: *
327: * @param restrictSupportToClass the class to restrict this <code>BasicAclProvider</code> to service request for,
328: * or <code>null</code> (the default) if the <code>BasicAclProvider</code> should respond to every class
329: * presented
330: */
331: public void setRestrictSupportToClass(Class restrictSupportToClass) {
332: this .restrictSupportToClass = restrictSupportToClass;
333: }
334:
335: /**
336: * Indicates support for the passed object.<p>An object will only be supported if it (i) is allowed to be
337: * supported as defined by the {@link #setRestrictSupportToClass(Class)} method, <b>and</b> (ii) if an
338: * <code>AclObjectIdentity</code> is returned by {@link #obtainIdentity(Object)} for that object.</p>
339: *
340: * @param domainInstance the instance to check
341: *
342: * @return <code>true</code> if this provider supports the passed object, <code>false</code> otherwise
343: */
344: public boolean supports(Object domainInstance) {
345: if (domainInstance == null) {
346: if (logger.isDebugEnabled()) {
347: logger.debug("domainInstance is null");
348: }
349:
350: return false;
351: }
352:
353: if ((restrictSupportToClass != null)
354: && !restrictSupportToClass
355: .isAssignableFrom(domainInstance.getClass())) {
356: if (logger.isDebugEnabled()) {
357: logger.debug("domainInstance not instance of "
358: + restrictSupportToClass);
359: }
360:
361: return false;
362: }
363:
364: if (obtainIdentity(domainInstance) == null) {
365: if (logger.isDebugEnabled()) {
366: logger.debug("obtainIdentity returned null");
367: }
368:
369: return false;
370: } else {
371: if (logger.isDebugEnabled()) {
372: logger.debug("obtainIdentity returned "
373: + obtainIdentity(domainInstance));
374: }
375:
376: return true;
377: }
378: }
379: }
|