package org.alfresco.repo.security.permissions; import java.util.Collection; import org.springframework.aop.IntroductionAdvisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultIntroductionAdvisor; import org.springframework.aop.support.DelegatingIntroductionInterceptor; /** * Interface for collection-based results that carry extra information * about the state of permission cut-offs. * * @author Derek Hulley * @since 4.0 */ public interface PermissionCheckedCollection { /** * Check if the results have been truncated by permission check limits. * * @return true - if the results (usually a collection) have been * cut off by permission check limits */ boolean isCutOff(); /** * Get the number of objects in the original (unfiltered) collection that did * not have any permission checks. * * @return number of entries from the original collection that were not checked */ int sizeUnchecked(); /** * Get the number of objects in the original (unfiltered) collection. * * @return number of entries in the original, pre-checked collection */ int sizeOriginal(); /** * Helper 'introduction' to allow simple addition of the {@link PermissionCheckedCollection} interface to * existing collections. * * @param the type of the Collection in use * * @author Derek Hulley * @since 4.0 */ @SuppressWarnings("serial") public static class PermissionCheckedCollectionMixin extends DelegatingIntroductionInterceptor implements PermissionCheckedCollection { private final boolean isCutOff; private final int sizeUnchecked; private final int sizeOriginal; private PermissionCheckedCollectionMixin(boolean isCutOff, int sizeUnchecked, int sizeOriginal) { super(); this.isCutOff = isCutOff; this.sizeUnchecked = sizeUnchecked; this.sizeOriginal = sizeOriginal; } @Override public boolean isCutOff() { return isCutOff; } @Override public int sizeUnchecked() { return sizeUnchecked; } @Override public int sizeOriginal() { return sizeOriginal; } /** * Helper method to create a {@link PermissionCheckedCollection} from an existing Collection * by applying the same values as present on a potentially permission-checked source. If the * existing checked source is NOT permission-checked, then the collection will not be * decorated. * * @param the type of the Collection * @param collection the Collection to proxy * @param checkedSource a collection that might implement {@link PermissionCheckedCollection} * @return a Collection of the same type but including the * {@link PermissionCheckedCollection} interface */ public static final Collection create( Collection collection, Collection checkedSource) { if (checkedSource instanceof PermissionCheckedCollection) { PermissionCheckedCollection source = (PermissionCheckedCollection) checkedSource; return create(collection, source.isCutOff(), source.sizeUnchecked(), source.sizeOriginal()); } else { return collection; } } /** * Helper method to create a {@link PermissionCheckedCollection} from an existing Collection * * @param the type of the Collection * @param collection the Collection to proxy * @param isCutOff true if permission checking was cut off before completion * @param sizeUnchecked number of entries from the original collection that were not checked * @param sizeOriginal number of entries in the original, pre-checked collection * @return a Collection of the same type but including the * {@link PermissionCheckedCollection} interface */ @SuppressWarnings("unchecked") public static final Collection create( Collection collection, boolean isCutOff, int sizeUnchecked, int sizeOriginal) { // Create the mixin DelegatingIntroductionInterceptor mixin = new PermissionCheckedCollectionMixin( isCutOff, sizeUnchecked, sizeOriginal ); // Create the advisor IntroductionAdvisor advisor = new DefaultIntroductionAdvisor(mixin, PermissionCheckedCollection.class); // Proxy ProxyFactory pf = new ProxyFactory(collection); pf.addAdvisor(advisor); Object proxiedObject = pf.getProxy(); // Done return (Collection) proxiedObject; } } }