浏览代码

SEC-2915: Updated Java Code Formatting

Rob Winch 10 年之前
父节点
当前提交
ae6af5d73c
共有 100 个文件被更改,包括 9283 次插入8368 次删除
  1. 219 186
      acl/src/main/java/org/springframework/security/acls/AclEntryVoter.java
  2. 32 31
      acl/src/main/java/org/springframework/security/acls/AclPermissionCacheOptimizer.java
  3. 128 118
      acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java
  4. 95 84
      acl/src/main/java/org/springframework/security/acls/afterinvocation/AbstractAclProvider.java
  5. 96 79
      acl/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java
  6. 72 59
      acl/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProvider.java
  7. 87 83
      acl/src/main/java/org/springframework/security/acls/afterinvocation/ArrayFilterer.java
  8. 68 65
      acl/src/main/java/org/springframework/security/acls/afterinvocation/CollectionFilterer.java
  9. 20 20
      acl/src/main/java/org/springframework/security/acls/afterinvocation/Filterer.java
  10. 51 47
      acl/src/main/java/org/springframework/security/acls/domain/AbstractPermission.java
  11. 152 142
      acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java
  12. 8 7
      acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategy.java
  13. 111 102
      acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java
  14. 84 80
      acl/src/main/java/org/springframework/security/acls/domain/AclFormattingUtils.java
  15. 322 286
      acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java
  16. 3 3
      acl/src/main/java/org/springframework/security/acls/domain/AuditLogger.java
  17. 11 12
      acl/src/main/java/org/springframework/security/acls/domain/BasePermission.java
  18. 14 13
      acl/src/main/java/org/springframework/security/acls/domain/ConsoleAuditLogger.java
  19. 29 25
      acl/src/main/java/org/springframework/security/acls/domain/CumulativePermission.java
  20. 128 122
      acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionFactory.java
  21. 114 103
      acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionGrantingStrategy.java
  22. 124 113
      acl/src/main/java/org/springframework/security/acls/domain/EhCacheBasedAclCache.java
  23. 42 34
      acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java
  24. 20 19
      acl/src/main/java/org/springframework/security/acls/domain/IdentityUnavailableException.java
  25. 134 126
      acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityImpl.java
  26. 13 10
      acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImpl.java
  27. 13 14
      acl/src/main/java/org/springframework/security/acls/domain/PermissionFactory.java
  28. 55 49
      acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java
  29. 25 21
      acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java
  30. 103 95
      acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java
  31. 637 587
      acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java
  32. 96 85
      acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java
  33. 434 396
      acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java
  34. 16 15
      acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java
  35. 20 21
      acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java
  36. 161 128
      acl/src/main/java/org/springframework/security/acls/model/Acl.java
  37. 8 8
      acl/src/main/java/org/springframework/security/acls/model/AclCache.java
  38. 19 19
      acl/src/main/java/org/springframework/security/acls/model/AclDataAccessException.java
  39. 87 67
      acl/src/main/java/org/springframework/security/acls/model/AclService.java
  40. 20 20
      acl/src/main/java/org/springframework/security/acls/model/AlreadyExistsException.java
  41. 4 4
      acl/src/main/java/org/springframework/security/acls/model/AuditableAccessControlEntry.java
  42. 3 3
      acl/src/main/java/org/springframework/security/acls/model/AuditableAcl.java
  43. 20 21
      acl/src/main/java/org/springframework/security/acls/model/ChildrenExistException.java
  44. 32 34
      acl/src/main/java/org/springframework/security/acls/model/MutableAcl.java
  45. 34 35
      acl/src/main/java/org/springframework/security/acls/model/MutableAclService.java
  46. 20 20
      acl/src/main/java/org/springframework/security/acls/model/NotFoundException.java
  47. 42 39
      acl/src/main/java/org/springframework/security/acls/model/ObjectIdentity.java
  48. 12 12
      acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityGenerator.java
  49. 3 3
      acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityRetrievalStrategy.java
  50. 5 6
      acl/src/main/java/org/springframework/security/acls/model/OwnershipAcl.java
  51. 33 28
      acl/src/main/java/org/springframework/security/acls/model/Permission.java
  52. 8 7
      acl/src/main/java/org/springframework/security/acls/model/PermissionGrantingStrategy.java
  53. 22 20
      acl/src/main/java/org/springframework/security/acls/model/Sid.java
  54. 5 5
      acl/src/main/java/org/springframework/security/acls/model/SidRetrievalStrategy.java
  55. 23 22
      acl/src/main/java/org/springframework/security/acls/model/UnloadedSidException.java
  56. 128 119
      acl/src/test/java/org/springframework/security/acls/AclFormattingUtilsTests.java
  57. 34 33
      acl/src/test/java/org/springframework/security/acls/AclPermissionCacheOptimizerTests.java
  58. 15 15
      acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java
  59. 44 31
      acl/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProviderTests.java
  60. 93 68
      acl/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProviderTests.java
  61. 72 70
      acl/src/test/java/org/springframework/security/acls/domain/AccessControlImplEntryTests.java
  62. 593 509
      acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java
  63. 248 209
      acl/src/test/java/org/springframework/security/acls/domain/AclImplementationSecurityCheckTests.java
  64. 50 48
      acl/src/test/java/org/springframework/security/acls/domain/AuditLoggerTests.java
  65. 175 171
      acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityImplTests.java
  66. 28 26
      acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImplTests.java
  67. 88 70
      acl/src/test/java/org/springframework/security/acls/domain/PermissionTests.java
  68. 5 6
      acl/src/test/java/org/springframework/security/acls/domain/SpecialPermission.java
  69. 330 279
      acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java
  70. 9 9
      acl/src/test/java/org/springframework/security/acls/jdbc/DatabaseSeeder.java
  71. 220 204
      acl/src/test/java/org/springframework/security/acls/jdbc/EhCacheBasedAclCacheTests.java
  72. 25 23
      acl/src/test/java/org/springframework/security/acls/jdbc/JdbcAclServiceTests.java
  73. 513 469
      acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java
  74. 127 113
      acl/src/test/java/org/springframework/security/acls/jdbc/SpringCacheBasedAclCacheTests.java
  75. 10 10
      acl/src/test/java/org/springframework/security/acls/sid/CustomSid.java
  76. 33 30
      acl/src/test/java/org/springframework/security/acls/sid/SidRetrievalStrategyTests.java
  77. 189 174
      acl/src/test/java/org/springframework/security/acls/sid/SidTests.java
  78. 143 133
      aspects/src/test/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspectTests.java
  79. 8 7
      cas/src/main/java/org/springframework/security/cas/SamlServiceProperties.java
  80. 111 106
      cas/src/main/java/org/springframework/security/cas/ServiceProperties.java
  81. 17 17
      cas/src/main/java/org/springframework/security/cas/authentication/CasAssertionAuthenticationToken.java
  82. 226 194
      cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java
  83. 110 99
      cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java
  84. 43 39
      cas/src/main/java/org/springframework/security/cas/authentication/EhCacheBasedTicketCache.java
  85. 29 30
      cas/src/main/java/org/springframework/security/cas/authentication/NullStatelessTicketCache.java
  86. 40 34
      cas/src/main/java/org/springframework/security/cas/authentication/SpringCacheBasedTicketCache.java
  87. 68 70
      cas/src/main/java/org/springframework/security/cas/authentication/StatelessTicketCache.java
  88. 18 15
      cas/src/main/java/org/springframework/security/cas/userdetails/AbstractCasAssertionUserDetailsService.java
  89. 63 53
      cas/src/main/java/org/springframework/security/cas/userdetails/GrantedAuthorityFromAssertionAttributesUserDetailsService.java
  90. 129 111
      cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java
  91. 306 264
      cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java
  92. 121 116
      cas/src/main/java/org/springframework/security/cas/web/authentication/DefaultServiceAuthenticationDetails.java
  93. 9 10
      cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetails.java
  94. 52 44
      cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetailsSource.java
  95. 1 0
      cas/src/main/java/org/springframework/security/cas/web/authentication/package-info.java
  96. 10 8
      cas/src/test/java/org/springframework/security/cas/authentication/AbstractStatelessTicketCacheTests.java
  97. 376 362
      cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java
  98. 172 152
      cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java
  99. 53 52
      cas/src/test/java/org/springframework/security/cas/authentication/EhCacheBasedTicketCacheTests.java
  100. 12 13
      cas/src/test/java/org/springframework/security/cas/authentication/NullStatelessTicketCacheTests.java

+ 219 - 186
acl/src/main/java/org/springframework/security/acls/AclEntryVoter.java

@@ -40,201 +40,234 @@ import org.springframework.security.core.Authentication;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
-
 /**
  * <p>
- * Given a domain object instance passed as a method argument, ensures the principal has appropriate permission
- * as indicated by the {@link AclService}.
+ * Given a domain object instance passed as a method argument, ensures the principal has
+ * appropriate permission as indicated by the {@link AclService}.
  * <p>
- * The <tt>AclService</tt> is used to retrieve the access control list (ACL) permissions associated with a
- * domain object instance for the current <tt>Authentication</tt> object.
+ * The <tt>AclService</tt> is used to retrieve the access control list (ACL) permissions
+ * associated with a domain object instance for the current <tt>Authentication</tt>
+ * object.
  * <p>
- * The voter will vote if any  {@link ConfigAttribute#getAttribute()} matches the {@link #processConfigAttribute}.
- * The provider will then locate the first method argument of type {@link #processDomainObjectClass}. Assuming that
- * method argument is non-null, the provider will then lookup the ACLs from the <code>AclManager</code> and ensure the
- * principal is {@link Acl#isGranted(List,
- * List, boolean)} when presenting the {@link #requirePermission} array to that
- * method.
+ * The voter will vote if any {@link ConfigAttribute#getAttribute()} matches the
+ * {@link #processConfigAttribute}. The provider will then locate the first method
+ * argument of type {@link #processDomainObjectClass}. Assuming that method argument is
+ * non-null, the provider will then lookup the ACLs from the <code>AclManager</code> and
+ * ensure the principal is {@link Acl#isGranted(List, List, boolean)} when presenting the
+ * {@link #requirePermission} array to that method.
  * <p>
- * If the method argument is <tt>null</tt>, the voter will abstain from voting. If the method argument
- * could not be found, an {@link AuthorizationServiceException} will be thrown.
+ * If the method argument is <tt>null</tt>, the voter will abstain from voting. If the
+ * method argument could not be found, an {@link AuthorizationServiceException} will be
+ * thrown.
  * <p>
- * In practical terms users will typically setup a number of <tt>AclEntryVoter</tt>s. Each will have a
- * different {@link #setProcessDomainObjectClass processDomainObjectClass}, {@link #processConfigAttribute} and
- * {@link #requirePermission} combination. For example, a small application might employ the following instances of
+ * In practical terms users will typically setup a number of <tt>AclEntryVoter</tt>s. Each
+ * will have a different {@link #setProcessDomainObjectClass processDomainObjectClass},
+ * {@link #processConfigAttribute} and {@link #requirePermission} combination. For
+ * example, a small application might employ the following instances of
  * <tt>AclEntryVoter</tt>:
- *  <ul>
- *      <li>Process domain object class <code>BankAccount</code>, configuration attribute
- *      <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission <code>BasePermission.READ</code></li>
- *      <li>Process domain object class <code>BankAccount</code>, configuration attribute
- *      <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
- *      <code>BasePermission.CREATE</code> (allowing the principal to have <b>either</b> of these two permissions)</li>
- *      <li>Process domain object class <code>Customer</code>, configuration attribute
- *      <code>VOTE_ACL_CUSTOMER_READ</code>, require permission <code>BasePermission.READ</code></li>
- *      <li>Process domain object class <code>Customer</code>, configuration attribute
- *      <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list <code>BasePermission.WRITE</code> and
- *      <code>BasePermission.CREATE</code></li>
- *  </ul>
- *  Alternatively, you could have used a common superclass or interface for the {@link #processDomainObjectClass}
- * if both <code>BankAccount</code> and <code>Customer</code> had common parents.</p>
- *  <p>If the principal does not have sufficient permissions, the voter will vote to deny access.</p>
- *  <p>All comparisons and prefixes are case sensitive.</p>
+ * <ul>
+ * <li>Process domain object class <code>BankAccount</code>, configuration attribute
+ * <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission
+ * <code>BasePermission.READ</code></li>
+ * <li>Process domain object class <code>BankAccount</code>, configuration attribute
+ * <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list
+ * <code>BasePermission.WRITE</code> and <code>BasePermission.CREATE</code> (allowing the
+ * principal to have <b>either</b> of these two permissions)</li>
+ * <li>Process domain object class <code>Customer</code>, configuration attribute
+ * <code>VOTE_ACL_CUSTOMER_READ</code>, require permission
+ * <code>BasePermission.READ</code></li>
+ * <li>Process domain object class <code>Customer</code>, configuration attribute
+ * <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list
+ * <code>BasePermission.WRITE</code> and <code>BasePermission.CREATE</code></li>
+ * </ul>
+ * Alternatively, you could have used a common superclass or interface for the
+ * {@link #processDomainObjectClass} if both <code>BankAccount</code> and
+ * <code>Customer</code> had common parents.
+ * </p>
+ * <p>
+ * If the principal does not have sufficient permissions, the voter will vote to deny
+ * access.
+ * </p>
+ * <p>
+ * All comparisons and prefixes are case sensitive.
+ * </p>
  *
  * @author Ben Alex
  */
 public class AclEntryVoter extends AbstractAclVoter {
-    //~ Static fields/initializers =====================================================================================
-
-    private static final Log logger = LogFactory.getLog(AclEntryVoter.class);
-
-    //~ Instance fields ================================================================================================
-
-    private AclService aclService;
-    private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
-    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
-    private String internalMethod;
-    private String processConfigAttribute;
-    private List<Permission> requirePermission;
-
-    //~ Constructors ===================================================================================================
-
-    public AclEntryVoter(AclService aclService, String processConfigAttribute, Permission[] requirePermission) {
-        Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory");
-        Assert.notNull(aclService, "An AclService is mandatory");
-
-        if ((requirePermission == null) || (requirePermission.length == 0)) {
-            throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
-        }
-
-        this.aclService = aclService;
-        this.processConfigAttribute = processConfigAttribute;
-        this.requirePermission = Arrays.asList(requirePermission);
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Optionally specifies a method of the domain object that will be used to obtain a contained domain
-     * object. That contained domain object will be used for the ACL evaluation. This is useful if a domain object
-     * contains a parent that an ACL evaluation should be targeted for, instead of the child domain object (which
-     * perhaps is being created and as such does not yet have any ACL permissions)
-     *
-     * @return <code>null</code> to use the domain object, or the name of a method (that requires no arguments) that
-     *         should be invoked to obtain an <code>Object</code> which will be the domain object used for ACL
-     *         evaluation
-     */
-    protected String getInternalMethod() {
-        return internalMethod;
-    }
-
-    public void setInternalMethod(String internalMethod) {
-        this.internalMethod = internalMethod;
-    }
-
-    protected String getProcessConfigAttribute() {
-        return processConfigAttribute;
-    }
-
-    public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
-        Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required");
-        this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
-    }
-
-    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
-        Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
-        this.sidRetrievalStrategy = sidRetrievalStrategy;
-    }
-
-    public boolean supports(ConfigAttribute attribute) {
-        return (attribute.getAttribute() != null) && attribute.getAttribute().equals(getProcessConfigAttribute());
-    }
-
-    public int vote(Authentication authentication, MethodInvocation object, Collection<ConfigAttribute> attributes) {
-
-        for(ConfigAttribute attr : attributes) {
-
-            if (!this.supports(attr)) {
-                continue;
-            }
-            // Need to make an access decision on this invocation
-            // Attempt to locate the domain object instance to process
-            Object domainObject = getDomainObjectInstance(object);
-
-            // If domain object is null, vote to abstain
-            if (domainObject == null) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Voting to abstain - domainObject is null");
-                }
-
-                return ACCESS_ABSTAIN;
-            }
-
-            // Evaluate if we are required to use an inner domain object
-            if (StringUtils.hasText(internalMethod)) {
-                try {
-                    Class<?> clazz = domainObject.getClass();
-                    Method method = clazz.getMethod(internalMethod, new Class[0]);
-                    domainObject = method.invoke(domainObject);
-                } catch (NoSuchMethodException nsme) {
-                    throw new AuthorizationServiceException("Object of class '" + domainObject.getClass()
-                        + "' does not provide the requested internalMethod: " + internalMethod);
-                } catch (IllegalAccessException iae) {
-                    logger.debug("IllegalAccessException", iae);
-
-                    throw new AuthorizationServiceException("Problem invoking internalMethod: " + internalMethod
-                        + " for object: " + domainObject);
-                } catch (InvocationTargetException ite) {
-                    logger.debug("InvocationTargetException", ite);
-
-                    throw new AuthorizationServiceException("Problem invoking internalMethod: " + internalMethod
-                        + " for object: " + domainObject);
-                }
-            }
-
-            // Obtain the OID applicable to the domain object
-            ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
-
-            // Obtain the SIDs applicable to the principal
-            List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
-
-            Acl acl;
-
-            try {
-                // Lookup only ACLs for SIDs we're interested in
-                acl = aclService.readAclById(objectIdentity, sids);
-            } catch (NotFoundException nfe) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Voting to deny access - no ACLs apply for this principal");
-                }
-
-                return ACCESS_DENIED;
-            }
-
-            try {
-                if (acl.isGranted(requirePermission, sids, false)) {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Voting to grant access");
-                    }
-
-                    return ACCESS_GRANTED;
-                } else {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug(
-                            "Voting to deny access - ACLs returned, but insufficient permissions for this principal");
-                    }
-
-                    return ACCESS_DENIED;
-                }
-            } catch (NotFoundException nfe) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Voting to deny access - no ACLs apply for this principal");
-                }
-
-                return ACCESS_DENIED;
-            }
-        }
-
-        // No configuration attribute matched, so abstain
-        return ACCESS_ABSTAIN;
-    }
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	private static final Log logger = LogFactory.getLog(AclEntryVoter.class);
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private AclService aclService;
+	private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+	private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+	private String internalMethod;
+	private String processConfigAttribute;
+	private List<Permission> requirePermission;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public AclEntryVoter(AclService aclService, String processConfigAttribute,
+			Permission[] requirePermission) {
+		Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory");
+		Assert.notNull(aclService, "An AclService is mandatory");
+
+		if ((requirePermission == null) || (requirePermission.length == 0)) {
+			throw new IllegalArgumentException(
+					"One or more requirePermission entries is mandatory");
+		}
+
+		this.aclService = aclService;
+		this.processConfigAttribute = processConfigAttribute;
+		this.requirePermission = Arrays.asList(requirePermission);
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 * Optionally specifies a method of the domain object that will be used to obtain a
+	 * contained domain object. That contained domain object will be used for the ACL
+	 * evaluation. This is useful if a domain object contains a parent that an ACL
+	 * evaluation should be targeted for, instead of the child domain object (which
+	 * perhaps is being created and as such does not yet have any ACL permissions)
+	 *
+	 * @return <code>null</code> to use the domain object, or the name of a method (that
+	 * requires no arguments) that should be invoked to obtain an <code>Object</code>
+	 * which will be the domain object used for ACL evaluation
+	 */
+	protected String getInternalMethod() {
+		return internalMethod;
+	}
+
+	public void setInternalMethod(String internalMethod) {
+		this.internalMethod = internalMethod;
+	}
+
+	protected String getProcessConfigAttribute() {
+		return processConfigAttribute;
+	}
+
+	public void setObjectIdentityRetrievalStrategy(
+			ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
+		Assert.notNull(objectIdentityRetrievalStrategy,
+				"ObjectIdentityRetrievalStrategy required");
+		this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
+	}
+
+	public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+		Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
+		this.sidRetrievalStrategy = sidRetrievalStrategy;
+	}
+
+	public boolean supports(ConfigAttribute attribute) {
+		return (attribute.getAttribute() != null)
+				&& attribute.getAttribute().equals(getProcessConfigAttribute());
+	}
+
+	public int vote(Authentication authentication, MethodInvocation object,
+			Collection<ConfigAttribute> attributes) {
+
+		for (ConfigAttribute attr : attributes) {
+
+			if (!this.supports(attr)) {
+				continue;
+			}
+			// Need to make an access decision on this invocation
+			// Attempt to locate the domain object instance to process
+			Object domainObject = getDomainObjectInstance(object);
+
+			// If domain object is null, vote to abstain
+			if (domainObject == null) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("Voting to abstain - domainObject is null");
+				}
+
+				return ACCESS_ABSTAIN;
+			}
+
+			// Evaluate if we are required to use an inner domain object
+			if (StringUtils.hasText(internalMethod)) {
+				try {
+					Class<?> clazz = domainObject.getClass();
+					Method method = clazz.getMethod(internalMethod, new Class[0]);
+					domainObject = method.invoke(domainObject);
+				}
+				catch (NoSuchMethodException nsme) {
+					throw new AuthorizationServiceException("Object of class '"
+							+ domainObject.getClass()
+							+ "' does not provide the requested internalMethod: "
+							+ internalMethod);
+				}
+				catch (IllegalAccessException iae) {
+					logger.debug("IllegalAccessException", iae);
+
+					throw new AuthorizationServiceException(
+							"Problem invoking internalMethod: " + internalMethod
+									+ " for object: " + domainObject);
+				}
+				catch (InvocationTargetException ite) {
+					logger.debug("InvocationTargetException", ite);
+
+					throw new AuthorizationServiceException(
+							"Problem invoking internalMethod: " + internalMethod
+									+ " for object: " + domainObject);
+				}
+			}
+
+			// Obtain the OID applicable to the domain object
+			ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy
+					.getObjectIdentity(domainObject);
+
+			// Obtain the SIDs applicable to the principal
+			List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
+
+			Acl acl;
+
+			try {
+				// Lookup only ACLs for SIDs we're interested in
+				acl = aclService.readAclById(objectIdentity, sids);
+			}
+			catch (NotFoundException nfe) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("Voting to deny access - no ACLs apply for this principal");
+				}
+
+				return ACCESS_DENIED;
+			}
+
+			try {
+				if (acl.isGranted(requirePermission, sids, false)) {
+					if (logger.isDebugEnabled()) {
+						logger.debug("Voting to grant access");
+					}
+
+					return ACCESS_GRANTED;
+				}
+				else {
+					if (logger.isDebugEnabled()) {
+						logger.debug("Voting to deny access - ACLs returned, but insufficient permissions for this principal");
+					}
+
+					return ACCESS_DENIED;
+				}
+			}
+			catch (NotFoundException nfe) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("Voting to deny access - no ACLs apply for this principal");
+				}
+
+				return ACCESS_DENIED;
+			}
+		}
+
+		// No configuration attribute matched, so abstain
+		return ACCESS_ABSTAIN;
+	}
 }

+ 32 - 31
acl/src/main/java/org/springframework/security/acls/AclPermissionCacheOptimizer.java

@@ -23,44 +23,45 @@ import org.springframework.security.core.Authentication;
  * @since 3.1
  */
 public class AclPermissionCacheOptimizer implements PermissionCacheOptimizer {
-    private final Log logger = LogFactory.getLog(getClass());
-    private final AclService aclService;
-    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
-    private ObjectIdentityRetrievalStrategy oidRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+	private final Log logger = LogFactory.getLog(getClass());
+	private final AclService aclService;
+	private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+	private ObjectIdentityRetrievalStrategy oidRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
 
-    public AclPermissionCacheOptimizer(AclService aclService) {
-        this.aclService = aclService;
-    }
+	public AclPermissionCacheOptimizer(AclService aclService) {
+		this.aclService = aclService;
+	}
 
-    public void cachePermissionsFor(Authentication authentication, Collection<?> objects) {
-        if (objects.isEmpty()) {
-            return;
-        }
+	public void cachePermissionsFor(Authentication authentication, Collection<?> objects) {
+		if (objects.isEmpty()) {
+			return;
+		}
 
-        List<ObjectIdentity> oidsToCache = new ArrayList<ObjectIdentity>(objects.size());
+		List<ObjectIdentity> oidsToCache = new ArrayList<ObjectIdentity>(objects.size());
 
-        for (Object domainObject : objects) {
-            if (domainObject == null) {
-                continue;
-            }
-            ObjectIdentity oid = oidRetrievalStrategy.getObjectIdentity(domainObject);
-            oidsToCache.add(oid);
-        }
+		for (Object domainObject : objects) {
+			if (domainObject == null) {
+				continue;
+			}
+			ObjectIdentity oid = oidRetrievalStrategy.getObjectIdentity(domainObject);
+			oidsToCache.add(oid);
+		}
 
-        List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
+		List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Eagerly loading Acls for " + oidsToCache.size() + " objects");
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("Eagerly loading Acls for " + oidsToCache.size() + " objects");
+		}
 
-        aclService.readAclsById(oidsToCache, sids);
-    }
+		aclService.readAclsById(oidsToCache, sids);
+	}
 
-    public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
-        this.oidRetrievalStrategy = objectIdentityRetrievalStrategy;
-    }
+	public void setObjectIdentityRetrievalStrategy(
+			ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
+		this.oidRetrievalStrategy = objectIdentityRetrievalStrategy;
+	}
 
-    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
-        this.sidRetrievalStrategy = sidRetrievalStrategy;
-    }
+	public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+		this.sidRetrievalStrategy = sidRetrievalStrategy;
+	}
 }

+ 128 - 118
acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java

@@ -23,8 +23,8 @@ import org.springframework.security.acls.model.SidRetrievalStrategy;
 import org.springframework.security.core.Authentication;
 
 /**
- * Used by Spring Security's expression-based access control implementation to evaluate permissions for a particular
- * object using the ACL module. Similar in behaviour to
+ * Used by Spring Security's expression-based access control implementation to evaluate
+ * permissions for a particular object using the ACL module. Similar in behaviour to
  * {@link org.springframework.security.acls.AclEntryVoter AclEntryVoter}.
  *
  * @author Luke Taylor
@@ -32,120 +32,130 @@ import org.springframework.security.core.Authentication;
  */
 public class AclPermissionEvaluator implements PermissionEvaluator {
 
-    private final Log logger = LogFactory.getLog(getClass());
-
-    private final AclService aclService;
-    private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
-    private ObjectIdentityGenerator objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
-    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
-    private PermissionFactory permissionFactory = new DefaultPermissionFactory();
-
-    public AclPermissionEvaluator(AclService aclService) {
-        this.aclService = aclService;
-    }
-
-    /**
-     * Determines whether the user has the given permission(s) on the domain object using the ACL
-     * configuration. If the domain object is null, returns false (this can always be overridden using a null
-     * check in the expression itself).
-     */
-    public boolean hasPermission(Authentication authentication, Object domainObject, Object permission) {
-        if (domainObject == null) {
-            return false;
-        }
-
-        ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
-
-        return checkPermission(authentication, objectIdentity, permission);
-    }
-
-    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
-        ObjectIdentity objectIdentity = objectIdentityGenerator.createObjectIdentity(targetId, targetType);
-
-        return checkPermission(authentication, objectIdentity, permission);
-    }
-
-    private boolean checkPermission(Authentication authentication, ObjectIdentity oid, Object permission) {
-        // Obtain the SIDs applicable to the principal
-        List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
-        List<Permission> requiredPermission = resolvePermission(permission);
-
-        final boolean debug = logger.isDebugEnabled();
-
-        if (debug) {
-            logger.debug("Checking permission '" + permission + "' for object '" + oid + "'");
-        }
-
-        try {
-            // Lookup only ACLs for SIDs we're interested in
-            Acl acl = aclService.readAclById(oid, sids);
-
-            if (acl.isGranted(requiredPermission, sids, false)) {
-                if (debug) {
-                    logger.debug("Access is granted");
-                }
-
-                return true;
-            }
-
-            if (debug) {
-                logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal");
-            }
-
-        } catch (NotFoundException nfe) {
-            if (debug) {
-                logger.debug("Returning false - no ACLs apply for this principal");
-            }
-        }
-
-        return false;
-
-    }
-
-    List<Permission> resolvePermission(Object permission) {
-        if (permission instanceof Integer) {
-            return Arrays.asList(permissionFactory.buildFromMask(((Integer)permission).intValue()));
-        }
-
-        if (permission instanceof Permission) {
-            return Arrays.asList((Permission)permission);
-        }
-
-        if (permission instanceof Permission[]) {
-            return Arrays.asList((Permission[])permission);
-        }
-
-        if (permission instanceof String) {
-            String permString = (String)permission;
-            Permission p;
-
-            try {
-                p = permissionFactory.buildFromName(permString);
-            } catch(IllegalArgumentException notfound) {
-                p = permissionFactory.buildFromName(permString.toUpperCase());
-            }
-
-            if (p != null) {
-                return Arrays.asList(p);
-            }
-
-        }
-        throw new IllegalArgumentException("Unsupported permission: " + permission);
-    }
-
-    public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
-        this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
-    }
-
-    public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
-        this.objectIdentityGenerator = objectIdentityGenerator;
-    }
-
-    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
-        this.sidRetrievalStrategy = sidRetrievalStrategy;
-    }
-
-    public void setPermissionFactory(PermissionFactory permissionFactory) {
-        this.permissionFactory = permissionFactory;
-    }
+	private final Log logger = LogFactory.getLog(getClass());
+
+	private final AclService aclService;
+	private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+	private ObjectIdentityGenerator objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
+	private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+	private PermissionFactory permissionFactory = new DefaultPermissionFactory();
+
+	public AclPermissionEvaluator(AclService aclService) {
+		this.aclService = aclService;
+	}
+
+	/**
+	 * Determines whether the user has the given permission(s) on the domain object using
+	 * the ACL configuration. If the domain object is null, returns false (this can always
+	 * be overridden using a null check in the expression itself).
+	 */
+	public boolean hasPermission(Authentication authentication, Object domainObject,
+			Object permission) {
+		if (domainObject == null) {
+			return false;
+		}
+
+		ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy
+				.getObjectIdentity(domainObject);
+
+		return checkPermission(authentication, objectIdentity, permission);
+	}
+
+	public boolean hasPermission(Authentication authentication, Serializable targetId,
+			String targetType, Object permission) {
+		ObjectIdentity objectIdentity = objectIdentityGenerator.createObjectIdentity(
+				targetId, targetType);
+
+		return checkPermission(authentication, objectIdentity, permission);
+	}
+
+	private boolean checkPermission(Authentication authentication, ObjectIdentity oid,
+			Object permission) {
+		// Obtain the SIDs applicable to the principal
+		List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
+		List<Permission> requiredPermission = resolvePermission(permission);
+
+		final boolean debug = logger.isDebugEnabled();
+
+		if (debug) {
+			logger.debug("Checking permission '" + permission + "' for object '" + oid
+					+ "'");
+		}
+
+		try {
+			// Lookup only ACLs for SIDs we're interested in
+			Acl acl = aclService.readAclById(oid, sids);
+
+			if (acl.isGranted(requiredPermission, sids, false)) {
+				if (debug) {
+					logger.debug("Access is granted");
+				}
+
+				return true;
+			}
+
+			if (debug) {
+				logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal");
+			}
+
+		}
+		catch (NotFoundException nfe) {
+			if (debug) {
+				logger.debug("Returning false - no ACLs apply for this principal");
+			}
+		}
+
+		return false;
+
+	}
+
+	List<Permission> resolvePermission(Object permission) {
+		if (permission instanceof Integer) {
+			return Arrays.asList(permissionFactory.buildFromMask(((Integer) permission)
+					.intValue()));
+		}
+
+		if (permission instanceof Permission) {
+			return Arrays.asList((Permission) permission);
+		}
+
+		if (permission instanceof Permission[]) {
+			return Arrays.asList((Permission[]) permission);
+		}
+
+		if (permission instanceof String) {
+			String permString = (String) permission;
+			Permission p;
+
+			try {
+				p = permissionFactory.buildFromName(permString);
+			}
+			catch (IllegalArgumentException notfound) {
+				p = permissionFactory.buildFromName(permString.toUpperCase());
+			}
+
+			if (p != null) {
+				return Arrays.asList(p);
+			}
+
+		}
+		throw new IllegalArgumentException("Unsupported permission: " + permission);
+	}
+
+	public void setObjectIdentityRetrievalStrategy(
+			ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
+		this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
+	}
+
+	public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
+		this.objectIdentityGenerator = objectIdentityGenerator;
+	}
+
+	public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+		this.sidRetrievalStrategy = sidRetrievalStrategy;
+	}
+
+	public void setPermissionFactory(PermissionFactory permissionFactory) {
+		this.permissionFactory = permissionFactory;
+	}
 }

+ 95 - 84
acl/src/main/java/org/springframework/security/acls/afterinvocation/AbstractAclProvider.java

@@ -34,92 +34,103 @@ import org.springframework.security.acls.model.SidRetrievalStrategy;
 import org.springframework.security.core.Authentication;
 import org.springframework.util.Assert;
 
-
 /**
- * Abstract {@link AfterInvocationProvider} which provides commonly-used ACL-related services.
+ * Abstract {@link AfterInvocationProvider} which provides commonly-used ACL-related
+ * services.
  *
  * @author Ben Alex
-  */
+ */
 public abstract class AbstractAclProvider implements AfterInvocationProvider {
-    //~ Instance fields ================================================================================================
-
-    protected final AclService aclService;
-    protected Class<?> processDomainObjectClass = Object.class;
-    protected ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
-    protected SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
-    protected String processConfigAttribute;
-    protected final List<Permission> requirePermission;
-
-    //~ Constructors ===================================================================================================
-
-    public AbstractAclProvider(AclService aclService, String processConfigAttribute, List<Permission> requirePermission) {
-        Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
-        Assert.notNull(aclService, "An AclService is mandatory");
-
-        if (requirePermission == null || requirePermission.isEmpty()) {
-            throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
-        }
-
-        this.aclService = aclService;
-        this.processConfigAttribute = processConfigAttribute;
-        this.requirePermission = requirePermission;
-    }
-
-    //~ Methods ========================================================================================================
-
-    protected Class<?> getProcessDomainObjectClass() {
-        return processDomainObjectClass;
-    }
-
-    protected boolean hasPermission(Authentication authentication, Object domainObject) {
-        // Obtain the OID applicable to the domain object
-        ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);
-
-        // Obtain the SIDs applicable to the principal
-        List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
-
-        try {
-            // Lookup only ACLs for SIDs we're interested in
-            Acl acl = aclService.readAclById(objectIdentity, sids);
-
-            return acl.isGranted(requirePermission, sids, false);
-        } catch (NotFoundException ignore) {
-            return false;
-        }
-    }
-
-    public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
-        Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required");
-        this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
-    }
-
-    protected void setProcessConfigAttribute(String processConfigAttribute) {
-        Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
-        this.processConfigAttribute = processConfigAttribute;
-    }
-
-    public void setProcessDomainObjectClass(Class<?> processDomainObjectClass) {
-        Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null");
-        this.processDomainObjectClass = processDomainObjectClass;
-    }
-
-    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
-        Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
-        this.sidRetrievalStrategy = sidRetrievalStrategy;
-    }
-
-    public boolean supports(ConfigAttribute attribute) {
-        return processConfigAttribute.equals(attribute.getAttribute());
-    }
-
-    /**
-     * This implementation supports any type of class, because it does not query the presented secure object.
-     *
-     * @param clazz the secure object
-     *
-     * @return always <code>true</code>
-     */
-    public boolean supports(Class<?> clazz) {
-        return true;
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	protected final AclService aclService;
+	protected Class<?> processDomainObjectClass = Object.class;
+	protected ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
+	protected SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+	protected String processConfigAttribute;
+	protected final List<Permission> requirePermission;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public AbstractAclProvider(AclService aclService, String processConfigAttribute,
+			List<Permission> requirePermission) {
+		Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
+		Assert.notNull(aclService, "An AclService is mandatory");
+
+		if (requirePermission == null || requirePermission.isEmpty()) {
+			throw new IllegalArgumentException(
+					"One or more requirePermission entries is mandatory");
+		}
+
+		this.aclService = aclService;
+		this.processConfigAttribute = processConfigAttribute;
+		this.requirePermission = requirePermission;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	protected Class<?> getProcessDomainObjectClass() {
+		return processDomainObjectClass;
+	}
+
+	protected boolean hasPermission(Authentication authentication, Object domainObject) {
+		// Obtain the OID applicable to the domain object
+		ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy
+				.getObjectIdentity(domainObject);
+
+		// Obtain the SIDs applicable to the principal
+		List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
+
+		try {
+			// Lookup only ACLs for SIDs we're interested in
+			Acl acl = aclService.readAclById(objectIdentity, sids);
+
+			return acl.isGranted(requirePermission, sids, false);
+		}
+		catch (NotFoundException ignore) {
+			return false;
+		}
+	}
+
+	public void setObjectIdentityRetrievalStrategy(
+			ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
+		Assert.notNull(objectIdentityRetrievalStrategy,
+				"ObjectIdentityRetrievalStrategy required");
+		this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
+	}
+
+	protected void setProcessConfigAttribute(String processConfigAttribute) {
+		Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory");
+		this.processConfigAttribute = processConfigAttribute;
+	}
+
+	public void setProcessDomainObjectClass(Class<?> processDomainObjectClass) {
+		Assert.notNull(processDomainObjectClass,
+				"processDomainObjectClass cannot be set to null");
+		this.processDomainObjectClass = processDomainObjectClass;
+	}
+
+	public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+		Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
+		this.sidRetrievalStrategy = sidRetrievalStrategy;
+	}
+
+	public boolean supports(ConfigAttribute attribute) {
+		return processConfigAttribute.equals(attribute.getAttribute());
+	}
+
+	/**
+	 * This implementation supports any type of class, because it does not query the
+	 * presented secure object.
+	 *
+	 * @param clazz the secure object
+	 *
+	 * @return always <code>true</code>
+	 */
+	public boolean supports(Class<?> clazz) {
+		return true;
+	}
 }

+ 96 - 79
acl/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java

@@ -26,96 +26,113 @@ import org.springframework.security.acls.model.AclService;
 import org.springframework.security.acls.model.Permission;
 import org.springframework.security.core.Authentication;
 
-
 /**
  * <p>
- * Given a <code>Collection</code> of domain object instances returned from a secure object invocation, remove
- * any <code>Collection</code> elements the principal does not have appropriate permission to access as defined by the
- * {@link AclService}.
+ * Given a <code>Collection</code> of domain object instances returned from a secure
+ * object invocation, remove any <code>Collection</code> elements the principal does not
+ * have appropriate permission to access as defined by the {@link AclService}.
  * <p>
- * The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with
- * each <code>Collection</code> domain object instance element for the current <code>Authentication</code> object.
+ * The <code>AclService</code> is used to retrieve the access control list (ACL)
+ * permissions associated with each <code>Collection</code> domain object instance element
+ * for the current <code>Authentication</code> object.
  * <p>
- * This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} matches the {@link
- * #processConfigAttribute}. The provider will then lookup the ACLs from the <code>AclService</code> and ensure the
- * principal is {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean) Acl.isGranted()}
- * when presenting the {@link #requirePermission} array to that method.
+ * This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()}
+ * matches the {@link #processConfigAttribute}. The provider will then lookup the ACLs
+ * from the <code>AclService</code> and ensure the principal is
+ * {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean)
+ * Acl.isGranted()} when presenting the {@link #requirePermission} array to that method.
  * <p>
- * If the principal does not have permission, that element will not be included in the returned
- * <code>Collection</code>.
+ * If the principal does not have permission, that element will not be included in the
+ * returned <code>Collection</code>.
  * <p>
- * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code> with a {@link
- * #processConfigAttribute} of <code>AFTER_ACL_COLLECTION_READ</code> and a {@link #requirePermission} of
- * <code>BasePermission.READ</code>. These are also the defaults.
+ * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code> with a
+ * {@link #processConfigAttribute} of <code>AFTER_ACL_COLLECTION_READ</code> and a
+ * {@link #requirePermission} of <code>BasePermission.READ</code>. These are also the
+ * defaults.
  * <p>
- * If the provided <code>returnObject</code> is <code>null</code>, a <code>null</code><code>Collection</code>
- * will be returned. If the provided <code>returnObject</code> is not a <code>Collection</code>, an {@link
- * AuthorizationServiceException} will be thrown.
+ * If the provided <code>returnObject</code> is <code>null</code>, a <code>null</code>
+ * <code>Collection</code> will be returned. If the provided <code>returnObject</code> is
+ * not a <code>Collection</code>, an {@link AuthorizationServiceException} will be thrown.
  * <p>
  * All comparisons and prefixes are case sensitive.
  *
  * @author Ben Alex
  * @author Paulo Neves
  */
-public class AclEntryAfterInvocationCollectionFilteringProvider extends AbstractAclProvider {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class);
-
-    //~ Constructors ===================================================================================================
-
-    public AclEntryAfterInvocationCollectionFilteringProvider(AclService aclService, List<Permission> requirePermission) {
-        super(aclService, "AFTER_ACL_COLLECTION_READ", requirePermission);
-    }
-
-    //~ Methods ========================================================================================================
-
-    @SuppressWarnings("unchecked")
-    public Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> config,
-            Object returnedObject) throws AccessDeniedException {
-
-        if (returnedObject == null) {
-            logger.debug("Return object is null, skipping");
-
-            return null;
-        }
-
-        for (ConfigAttribute attr : config) {
-            if (!this.supports(attr)) {
-                continue;
-            }
-
-            // Need to process the Collection for this invocation
-            Filterer filterer;
-
-            if (returnedObject instanceof Collection) {
-                filterer = new CollectionFilterer((Collection) returnedObject);
-            } else if (returnedObject.getClass().isArray()) {
-                filterer = new ArrayFilterer((Object[]) returnedObject);
-            } else {
-                throw new AuthorizationServiceException("A Collection or an array (or null) was required as the "
-                        + "returnedObject, but the returnedObject was: " + returnedObject);
-            }
-
-            // Locate unauthorised Collection elements
-            for (Object domainObject : filterer) {
-                // Ignore nulls or entries which aren't instances of the configured domain object class
-                if (domainObject == null || !getProcessDomainObjectClass().isAssignableFrom(domainObject.getClass())) {
-                    continue;
-                }
-
-                if(!hasPermission(authentication, domainObject)) {
-                    filterer.remove(domainObject);
-
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Principal is NOT authorised for element: " + domainObject);
-                    }
-                }
-            }
-
-            return filterer.getFilteredObject();
-        }
-
-        return returnedObject;
-    }
+public class AclEntryAfterInvocationCollectionFilteringProvider extends
+		AbstractAclProvider {
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	protected static final Log logger = LogFactory
+			.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class);
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public AclEntryAfterInvocationCollectionFilteringProvider(AclService aclService,
+			List<Permission> requirePermission) {
+		super(aclService, "AFTER_ACL_COLLECTION_READ", requirePermission);
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	@SuppressWarnings("unchecked")
+	public Object decide(Authentication authentication, Object object,
+			Collection<ConfigAttribute> config, Object returnedObject)
+			throws AccessDeniedException {
+
+		if (returnedObject == null) {
+			logger.debug("Return object is null, skipping");
+
+			return null;
+		}
+
+		for (ConfigAttribute attr : config) {
+			if (!this.supports(attr)) {
+				continue;
+			}
+
+			// Need to process the Collection for this invocation
+			Filterer filterer;
+
+			if (returnedObject instanceof Collection) {
+				filterer = new CollectionFilterer((Collection) returnedObject);
+			}
+			else if (returnedObject.getClass().isArray()) {
+				filterer = new ArrayFilterer((Object[]) returnedObject);
+			}
+			else {
+				throw new AuthorizationServiceException(
+						"A Collection or an array (or null) was required as the "
+								+ "returnedObject, but the returnedObject was: "
+								+ returnedObject);
+			}
+
+			// Locate unauthorised Collection elements
+			for (Object domainObject : filterer) {
+				// Ignore nulls or entries which aren't instances of the configured domain
+				// object class
+				if (domainObject == null
+						|| !getProcessDomainObjectClass().isAssignableFrom(
+								domainObject.getClass())) {
+					continue;
+				}
+
+				if (!hasPermission(authentication, domainObject)) {
+					filterer.remove(domainObject);
+
+					if (logger.isDebugEnabled()) {
+						logger.debug("Principal is NOT authorised for element: "
+								+ domainObject);
+					}
+				}
+			}
+
+			return filterer.getFilteredObject();
+		}
+
+		return returnedObject;
+	}
 }

+ 72 - 59
acl/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProvider.java

@@ -29,90 +29,103 @@ import org.springframework.security.acls.model.Permission;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.SpringSecurityMessageSource;
 
-
 /**
- * Given a domain object instance returned from a secure object invocation, ensures the principal has
- * appropriate permission as defined by the {@link AclService}.
+ * Given a domain object instance returned from a secure object invocation, ensures the
+ * principal has appropriate permission as defined by the {@link AclService}.
  * <p>
- * The <code>AclService</code> is used to retrieve the access control list (ACL) permissions associated with a
- * domain object instance for the current <code>Authentication</code> object.
+ * The <code>AclService</code> is used to retrieve the access control list (ACL)
+ * permissions associated with a domain object instance for the current
+ * <code>Authentication</code> object.
  * <p>
- * This after invocation provider will fire if any  {@link ConfigAttribute#getAttribute()} matches the {@link
- * #processConfigAttribute}. The provider will then lookup the ACLs from the <tt>AclService</tt> and ensure the
- * principal is {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean)
- * Acl.isGranted(List, List, boolean)} when presenting the {@link #requirePermission} array to that method.
+ * This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()}
+ * matches the {@link #processConfigAttribute}. The provider will then lookup the ACLs
+ * from the <tt>AclService</tt> and ensure the principal is
+ * {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean)
+ * Acl.isGranted(List, List, boolean)} when presenting the {@link #requirePermission}
+ * array to that method.
  * <p>
- * Often users will set up an <code>AclEntryAfterInvocationProvider</code> with a {@link
- * #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a {@link #requirePermission} of
- * <code>BasePermission.READ</code>. These are also the defaults.
+ * Often users will set up an <code>AclEntryAfterInvocationProvider</code> with a
+ * {@link #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a
+ * {@link #requirePermission} of <code>BasePermission.READ</code>. These are also the
+ * defaults.
  * <p>
- * If the principal does not have sufficient permissions, an <code>AccessDeniedException</code> will be thrown.
+ * If the principal does not have sufficient permissions, an
+ * <code>AccessDeniedException</code> will be thrown.
  * <p>
- * If the provided <tt>returnedObject</tt> is <code>null</code>, permission will always be granted and
- * <code>null</code> will be returned.
+ * If the provided <tt>returnedObject</tt> is <code>null</code>, permission will always be
+ * granted and <code>null</code> will be returned.
  * <p>
  * All comparisons and prefixes are case sensitive.
  */
-public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements MessageSourceAware {
-    //~ Static fields/initializers =====================================================================================
+public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements
+		MessageSourceAware {
+	// ~ Static fields/initializers
+	// =====================================================================================
 
-    protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationProvider.class);
+	protected static final Log logger = LogFactory
+			.getLog(AclEntryAfterInvocationProvider.class);
 
-    //~ Instance fields ================================================================================================
+	// ~ Instance fields
+	// ================================================================================================
 
-    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
+	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
 
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    public AclEntryAfterInvocationProvider(AclService aclService, List<Permission> requirePermission) {
-        this(aclService, "AFTER_ACL_READ", requirePermission);
-    }
+	public AclEntryAfterInvocationProvider(AclService aclService,
+			List<Permission> requirePermission) {
+		this(aclService, "AFTER_ACL_READ", requirePermission);
+	}
 
-    public AclEntryAfterInvocationProvider(AclService aclService, String processConfigAttribute,
-            List<Permission> requirePermission) {
-        super(aclService, processConfigAttribute, requirePermission);
-    }
+	public AclEntryAfterInvocationProvider(AclService aclService,
+			String processConfigAttribute, List<Permission> requirePermission) {
+		super(aclService, processConfigAttribute, requirePermission);
+	}
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> config,
-            Object returnedObject) throws AccessDeniedException {
+	public Object decide(Authentication authentication, Object object,
+			Collection<ConfigAttribute> config, Object returnedObject)
+			throws AccessDeniedException {
 
-        if (returnedObject == null) {
-            // AclManager interface contract prohibits nulls
-            // As they have permission to null/nothing, grant access
-            logger.debug("Return object is null, skipping");
+		if (returnedObject == null) {
+			// AclManager interface contract prohibits nulls
+			// As they have permission to null/nothing, grant access
+			logger.debug("Return object is null, skipping");
 
-            return null;
-        }
+			return null;
+		}
 
-        if (!getProcessDomainObjectClass().isAssignableFrom(returnedObject.getClass())) {
-            logger.debug("Return object is not applicable for this provider, skipping");
+		if (!getProcessDomainObjectClass().isAssignableFrom(returnedObject.getClass())) {
+			logger.debug("Return object is not applicable for this provider, skipping");
 
-            return returnedObject;
-        }
+			return returnedObject;
+		}
 
-        for (ConfigAttribute attr : config) {
-            if (!this.supports(attr)) {
-                continue;
-            }
-            // Need to make an access decision on this invocation
+		for (ConfigAttribute attr : config) {
+			if (!this.supports(attr)) {
+				continue;
+			}
+			// Need to make an access decision on this invocation
 
-            if (hasPermission(authentication, returnedObject)) {
-                return returnedObject;
-            }
+			if (hasPermission(authentication, returnedObject)) {
+				return returnedObject;
+			}
 
-            logger.debug("Denying access");
+			logger.debug("Denying access");
 
-            throw new AccessDeniedException(messages.getMessage("AclEntryAfterInvocationProvider.noPermission",
-                    new Object[] {authentication.getName(), returnedObject},
-                    "Authentication {0} has NO permissions to the domain object {1}"));
-        }
+			throw new AccessDeniedException(messages.getMessage(
+					"AclEntryAfterInvocationProvider.noPermission", new Object[] {
+							authentication.getName(), returnedObject },
+					"Authentication {0} has NO permissions to the domain object {1}"));
+		}
 
-        return returnedObject;
-    }
+		return returnedObject;
+	}
 
-    public void setMessageSource(MessageSource messageSource) {
-        this.messages = new MessageSourceAccessor(messageSource);
-    }
+	public void setMessageSource(MessageSource messageSource) {
+		this.messages = new MessageSourceAccessor(messageSource);
+	}
 }

+ 87 - 83
acl/src/main/java/org/springframework/security/acls/afterinvocation/ArrayFilterer.java

@@ -24,7 +24,6 @@ import java.util.Set;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-
 /**
  * A filter used to filter arrays.
  *
@@ -32,86 +31,91 @@ import org.apache.commons.logging.LogFactory;
  * @author Paulo Neves
  */
 class ArrayFilterer<T> implements Filterer<T> {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log logger = LogFactory.getLog(ArrayFilterer.class);
-
-    //~ Instance fields ================================================================================================
-
-    private final Set<T> removeList;
-    private final T[] list;
-
-    //~ Constructors ===================================================================================================
-
-    ArrayFilterer(T[] list) {
-        this.list = list;
-
-        // Collect the removed objects to a HashSet so that
-        // it is fast to lookup them when a filtered array
-        // is constructed.
-        removeList = new HashSet<T>();
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     *
-     * @see org.springframework.security.acls.afterinvocation.Filterer#getFilteredObject()
-     */
-    @SuppressWarnings("unchecked")
-    public T[] getFilteredObject() {
-        // Recreate an array of same type and filter the removed objects.
-        int originalSize = list.length;
-        int sizeOfResultingList = originalSize - removeList.size();
-        T[] filtered = (T[]) Array.newInstance(list.getClass().getComponentType(), sizeOfResultingList);
-
-        for (int i = 0, j = 0; i < list.length; i++) {
-            T object = list[i];
-
-            if (!removeList.contains(object)) {
-                filtered[j] = object;
-                j++;
-            }
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Original array contained " + originalSize + " elements; now contains " + sizeOfResultingList
-                + " elements");
-        }
-
-        return filtered;
-    }
-
-    /**
-     *
-     * @see org.springframework.security.acls.afterinvocation.Filterer#iterator()
-     */
-    public Iterator<T> iterator() {
-        return new Iterator<T>() {
-            private int index = 0;
-
-            public boolean hasNext() {
-                return index < list.length;
-            }
-
-            public T next() {
-                if (!hasNext()) {
-                    throw new NoSuchElementException();
-                }
-                return list[index++];
-            }
-
-            public void remove() {
-                throw new UnsupportedOperationException();
-            }
-        };
-    }
-
-    /**
-     *
-     * @see org.springframework.security.acls.afterinvocation.Filterer#remove(java.lang.Object)
-     */
-    public void remove(T object) {
-        removeList.add(object);
-    }
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	protected static final Log logger = LogFactory.getLog(ArrayFilterer.class);
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final Set<T> removeList;
+	private final T[] list;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	ArrayFilterer(T[] list) {
+		this.list = list;
+
+		// Collect the removed objects to a HashSet so that
+		// it is fast to lookup them when a filtered array
+		// is constructed.
+		removeList = new HashSet<T>();
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 *
+	 * @see org.springframework.security.acls.afterinvocation.Filterer#getFilteredObject()
+	 */
+	@SuppressWarnings("unchecked")
+	public T[] getFilteredObject() {
+		// Recreate an array of same type and filter the removed objects.
+		int originalSize = list.length;
+		int sizeOfResultingList = originalSize - removeList.size();
+		T[] filtered = (T[]) Array.newInstance(list.getClass().getComponentType(),
+				sizeOfResultingList);
+
+		for (int i = 0, j = 0; i < list.length; i++) {
+			T object = list[i];
+
+			if (!removeList.contains(object)) {
+				filtered[j] = object;
+				j++;
+			}
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Original array contained " + originalSize
+					+ " elements; now contains " + sizeOfResultingList + " elements");
+		}
+
+		return filtered;
+	}
+
+	/**
+	 *
+	 * @see org.springframework.security.acls.afterinvocation.Filterer#iterator()
+	 */
+	public Iterator<T> iterator() {
+		return new Iterator<T>() {
+			private int index = 0;
+
+			public boolean hasNext() {
+				return index < list.length;
+			}
+
+			public T next() {
+				if (!hasNext()) {
+					throw new NoSuchElementException();
+				}
+				return list[index++];
+			}
+
+			public void remove() {
+				throw new UnsupportedOperationException();
+			}
+		};
+	}
+
+	/**
+	 *
+	 * @see org.springframework.security.acls.afterinvocation.Filterer#remove(java.lang.Object)
+	 */
+	public void remove(T object) {
+		removeList.add(object);
+	}
 }

+ 68 - 65
acl/src/main/java/org/springframework/security/acls/afterinvocation/CollectionFilterer.java

@@ -23,7 +23,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
-
 /**
  * A filter used to filter Collections.
  *
@@ -31,68 +30,72 @@ import java.util.Set;
  * @author Paulo Neves
  */
 class CollectionFilterer<T> implements Filterer<T> {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log logger = LogFactory.getLog(CollectionFilterer.class);
-
-    //~ Instance fields ================================================================================================
-
-    private final Collection<T> collection;
-
-    private final Set<T> removeList;
-
-    //~ Constructors ===================================================================================================
-
-    CollectionFilterer(Collection<T> collection) {
-        this.collection = collection;
-
-        // We create a Set of objects to be removed from the Collection,
-        // as ConcurrentModificationException prevents removal during
-        // iteration, and making a new Collection to be returned is
-        // problematic as the original Collection implementation passed
-        // to the method may not necessarily be re-constructable (as
-        // the Collection(collection) constructor is not guaranteed and
-        // manually adding may lose sort order or other capabilities)
-        removeList = new HashSet<T>();
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     *
-     * @see org.springframework.security.acls.afterinvocation.Filterer#getFilteredObject()
-     */
-    public Object getFilteredObject() {
-        // Now the Iterator has ended, remove Objects from Collection
-        Iterator<T> removeIter = removeList.iterator();
-
-        int originalSize = collection.size();
-
-        while (removeIter.hasNext()) {
-            collection.remove(removeIter.next());
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Original collection contained " + originalSize + " elements; now contains "
-                + collection.size() + " elements");
-        }
-
-        return collection;
-    }
-
-    /**
-     *
-     * @see org.springframework.security.acls.afterinvocation.Filterer#iterator()
-     */
-    public Iterator<T> iterator() {
-        return collection.iterator();
-    }
-
-    /**
-     *
-     * @see org.springframework.security.acls.afterinvocation.Filterer#remove(java.lang.Object)
-     */
-    public void remove(T object) {
-        removeList.add(object);
-    }
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	protected static final Log logger = LogFactory.getLog(CollectionFilterer.class);
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final Collection<T> collection;
+
+	private final Set<T> removeList;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	CollectionFilterer(Collection<T> collection) {
+		this.collection = collection;
+
+		// We create a Set of objects to be removed from the Collection,
+		// as ConcurrentModificationException prevents removal during
+		// iteration, and making a new Collection to be returned is
+		// problematic as the original Collection implementation passed
+		// to the method may not necessarily be re-constructable (as
+		// the Collection(collection) constructor is not guaranteed and
+		// manually adding may lose sort order or other capabilities)
+		removeList = new HashSet<T>();
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 *
+	 * @see org.springframework.security.acls.afterinvocation.Filterer#getFilteredObject()
+	 */
+	public Object getFilteredObject() {
+		// Now the Iterator has ended, remove Objects from Collection
+		Iterator<T> removeIter = removeList.iterator();
+
+		int originalSize = collection.size();
+
+		while (removeIter.hasNext()) {
+			collection.remove(removeIter.next());
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Original collection contained " + originalSize
+					+ " elements; now contains " + collection.size() + " elements");
+		}
+
+		return collection;
+	}
+
+	/**
+	 *
+	 * @see org.springframework.security.acls.afterinvocation.Filterer#iterator()
+	 */
+	public Iterator<T> iterator() {
+		return collection.iterator();
+	}
+
+	/**
+	 *
+	 * @see org.springframework.security.acls.afterinvocation.Filterer#remove(java.lang.Object)
+	 */
+	public void remove(T object) {
+		removeList.add(object);
+	}
 }

+ 20 - 20
acl/src/main/java/org/springframework/security/acls/afterinvocation/Filterer.java

@@ -17,7 +17,6 @@ package org.springframework.security.acls.afterinvocation;
 
 import java.util.Iterator;
 
-
 /**
  * Filterer strategy interface.
  *
@@ -25,26 +24,27 @@ import java.util.Iterator;
  * @author Paulo Neves
  */
 interface Filterer<T> extends Iterable<T> {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * Gets the filtered collection or array.
-     *
-     * @return the filtered collection or array
-     */
-    Object getFilteredObject();
+	/**
+	 * Gets the filtered collection or array.
+	 *
+	 * @return the filtered collection or array
+	 */
+	Object getFilteredObject();
 
-    /**
-     * Returns an iterator over the filtered collection or array.
-     *
-     * @return an Iterator
-     */
-    Iterator<T> iterator();
+	/**
+	 * Returns an iterator over the filtered collection or array.
+	 *
+	 * @return an Iterator
+	 */
+	Iterator<T> iterator();
 
-    /**
-     * Removes the the given object from the resulting list.
-     *
-     * @param object the object to be removed
-     */
-    void remove(T object);
+	/**
+	 * Removes the the given object from the resulting list.
+	 *
+	 * @param object the object to be removed
+	 */
+	void remove(T object);
 }

+ 51 - 47
acl/src/main/java/org/springframework/security/acls/domain/AbstractPermission.java

@@ -10,63 +10,67 @@ import org.springframework.security.acls.model.Permission;
  */
 public abstract class AbstractPermission implements Permission {
 
-    //~ Instance fields ================================================================================================
+	// ~ Instance fields
+	// ================================================================================================
 
-    protected final char code;
-    protected int mask;
+	protected final char code;
+	protected int mask;
 
-    //~ Constructors ===================================================================================================
-    /**
-     * Sets the permission mask and uses the '*' character to represent active bits when represented as a bit
-     * pattern string.
-     *
-     * @param mask the integer bit mask for the permission
-     */
-    protected AbstractPermission(int mask) {
-        this.mask = mask;
-        this.code = '*';
-    }
+	// ~ Constructors
+	// ===================================================================================================
+	/**
+	 * Sets the permission mask and uses the '*' character to represent active bits when
+	 * represented as a bit pattern string.
+	 *
+	 * @param mask the integer bit mask for the permission
+	 */
+	protected AbstractPermission(int mask) {
+		this.mask = mask;
+		this.code = '*';
+	}
 
-    /**
-     * Sets the permission mask and uses the specified character for active bits.
-     *
-     * @param mask the integer bit mask for the permission
-     * @param code the character to print for each active bit in the mask (see {@link Permission#getPattern()})
-     */
-    protected AbstractPermission(int mask, char code) {
-        this.mask = mask;
-        this.code = code;
-    }
+	/**
+	 * Sets the permission mask and uses the specified character for active bits.
+	 *
+	 * @param mask the integer bit mask for the permission
+	 * @param code the character to print for each active bit in the mask (see
+	 * {@link Permission#getPattern()})
+	 */
+	protected AbstractPermission(int mask, char code) {
+		this.mask = mask;
+		this.code = code;
+	}
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public final boolean equals(Object arg0) {
-        if (arg0 == null) {
-            return false;
-        }
+	public final boolean equals(Object arg0) {
+		if (arg0 == null) {
+			return false;
+		}
 
-        if (!(arg0 instanceof Permission)) {
-            return false;
-        }
+		if (!(arg0 instanceof Permission)) {
+			return false;
+		}
 
-        Permission rhs = (Permission) arg0;
+		Permission rhs = (Permission) arg0;
 
-        return (this.mask == rhs.getMask());
-    }
+		return (this.mask == rhs.getMask());
+	}
 
-    public final int getMask() {
-        return mask;
-    }
+	public final int getMask() {
+		return mask;
+	}
 
-    public String getPattern() {
-        return AclFormattingUtils.printBinary(mask, code);
-    }
+	public String getPattern() {
+		return AclFormattingUtils.printBinary(mask, code);
+	}
 
-    public final String toString() {
-        return this.getClass().getSimpleName() + "[" + getPattern() + "=" + mask + "]";
-    }
+	public final String toString() {
+		return this.getClass().getSimpleName() + "[" + getPattern() + "=" + mask + "]";
+	}
 
-    public final int hashCode() {
-        return this.mask;
-    }
+	public final int hashCode() {
+		return this.mask;
+	}
 }

+ 152 - 142
acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java

@@ -24,151 +24,161 @@ import org.springframework.util.Assert;
 
 import java.io.Serializable;
 
-
 /**
  * An immutable default implementation of <code>AccessControlEntry</code>.
  *
  * @author Ben Alex
  */
-public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry {
-    //~ Instance fields ================================================================================================
-
-    private final Acl acl;
-    private Permission permission;
-    private final Serializable id;
-    private final Sid sid;
-    private boolean auditFailure = false;
-    private boolean auditSuccess = false;
-    private final boolean granting;
-
-    //~ Constructors ===================================================================================================
-
-    public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting,
-        boolean auditSuccess, boolean auditFailure) {
-        Assert.notNull(acl, "Acl required");
-        Assert.notNull(sid, "Sid required");
-        Assert.notNull(permission, "Permission required");
-        this.id = id;
-        this.acl = acl; // can be null
-        this.sid = sid;
-        this.permission = permission;
-        this.granting = granting;
-        this.auditSuccess = auditSuccess;
-        this.auditFailure = auditFailure;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public boolean equals(Object arg0) {
-        if (!(arg0 instanceof AccessControlEntryImpl)) {
-            return false;
-        }
-
-        AccessControlEntryImpl rhs = (AccessControlEntryImpl) arg0;
-
-        if (this.acl == null) {
-            if (rhs.getAcl() != null) {
-                return false;
-            }
-            // Both this.acl and rhs.acl are null and thus equal
-        } else {
-            // this.acl is non-null
-            if (rhs.getAcl() == null) {
-                return false;
-            }
-
-            // Both this.acl and rhs.acl are non-null, so do a comparison
-            if (this.acl.getObjectIdentity() == null) {
-                if (rhs.acl.getObjectIdentity() != null) {
-                    return false;
-                }
-                // Both this.acl and rhs.acl are null and thus equal
-            } else {
-                // Both this.acl.objectIdentity and rhs.acl.objectIdentity are non-null
-                if (!this.acl.getObjectIdentity().equals(rhs.getAcl().getObjectIdentity())) {
-                    return false;
-                }
-            }
-        }
-
-        if (this.id == null) {
-            if (rhs.id != null) {
-                return false;
-            }
-            // Both this.id and rhs.id are null and thus equal
-        } else {
-            // this.id is non-null
-            if (rhs.id == null) {
-                return false;
-            }
-
-            // Both this.id and rhs.id are non-null
-            if (!this.id.equals(rhs.id)) {
-                return false;
-            }
-        }
-
-        if ((this.auditFailure != rhs.isAuditFailure()) || (this.auditSuccess != rhs.isAuditSuccess())
-            || (this.granting != rhs.isGranting())
-            || !this.permission.equals(rhs.getPermission()) || !this.sid.equals(rhs.getSid())) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public Acl getAcl() {
-        return acl;
-    }
-
-    public Serializable getId() {
-        return id;
-    }
-
-    public Permission getPermission() {
-        return permission;
-    }
-
-    public Sid getSid() {
-        return sid;
-    }
-
-    public boolean isAuditFailure() {
-        return auditFailure;
-    }
-
-    public boolean isAuditSuccess() {
-        return auditSuccess;
-    }
-
-    public boolean isGranting() {
-        return granting;
-    }
-
-    void setAuditFailure(boolean auditFailure) {
-        this.auditFailure = auditFailure;
-    }
-
-    void setAuditSuccess(boolean auditSuccess) {
-        this.auditSuccess = auditSuccess;
-    }
-
-    void setPermission(Permission permission) {
-        Assert.notNull(permission, "Permission required");
-        this.permission = permission;
-    }
-
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("AccessControlEntryImpl[");
-        sb.append("id: ").append(this.id).append("; ");
-        sb.append("granting: ").append(this.granting).append("; ");
-        sb.append("sid: ").append(this.sid).append("; ");
-        sb.append("permission: ").append(this.permission).append("; ");
-        sb.append("auditSuccess: ").append(this.auditSuccess).append("; ");
-        sb.append("auditFailure: ").append(this.auditFailure);
-        sb.append("]");
-
-        return sb.toString();
-    }
+public class AccessControlEntryImpl implements AccessControlEntry,
+		AuditableAccessControlEntry {
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final Acl acl;
+	private Permission permission;
+	private final Serializable id;
+	private final Sid sid;
+	private boolean auditFailure = false;
+	private boolean auditSuccess = false;
+	private final boolean granting;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid,
+			Permission permission, boolean granting, boolean auditSuccess,
+			boolean auditFailure) {
+		Assert.notNull(acl, "Acl required");
+		Assert.notNull(sid, "Sid required");
+		Assert.notNull(permission, "Permission required");
+		this.id = id;
+		this.acl = acl; // can be null
+		this.sid = sid;
+		this.permission = permission;
+		this.granting = granting;
+		this.auditSuccess = auditSuccess;
+		this.auditFailure = auditFailure;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public boolean equals(Object arg0) {
+		if (!(arg0 instanceof AccessControlEntryImpl)) {
+			return false;
+		}
+
+		AccessControlEntryImpl rhs = (AccessControlEntryImpl) arg0;
+
+		if (this.acl == null) {
+			if (rhs.getAcl() != null) {
+				return false;
+			}
+			// Both this.acl and rhs.acl are null and thus equal
+		}
+		else {
+			// this.acl is non-null
+			if (rhs.getAcl() == null) {
+				return false;
+			}
+
+			// Both this.acl and rhs.acl are non-null, so do a comparison
+			if (this.acl.getObjectIdentity() == null) {
+				if (rhs.acl.getObjectIdentity() != null) {
+					return false;
+				}
+				// Both this.acl and rhs.acl are null and thus equal
+			}
+			else {
+				// Both this.acl.objectIdentity and rhs.acl.objectIdentity are non-null
+				if (!this.acl.getObjectIdentity()
+						.equals(rhs.getAcl().getObjectIdentity())) {
+					return false;
+				}
+			}
+		}
+
+		if (this.id == null) {
+			if (rhs.id != null) {
+				return false;
+			}
+			// Both this.id and rhs.id are null and thus equal
+		}
+		else {
+			// this.id is non-null
+			if (rhs.id == null) {
+				return false;
+			}
+
+			// Both this.id and rhs.id are non-null
+			if (!this.id.equals(rhs.id)) {
+				return false;
+			}
+		}
+
+		if ((this.auditFailure != rhs.isAuditFailure())
+				|| (this.auditSuccess != rhs.isAuditSuccess())
+				|| (this.granting != rhs.isGranting())
+				|| !this.permission.equals(rhs.getPermission())
+				|| !this.sid.equals(rhs.getSid())) {
+			return false;
+		}
+
+		return true;
+	}
+
+	public Acl getAcl() {
+		return acl;
+	}
+
+	public Serializable getId() {
+		return id;
+	}
+
+	public Permission getPermission() {
+		return permission;
+	}
+
+	public Sid getSid() {
+		return sid;
+	}
+
+	public boolean isAuditFailure() {
+		return auditFailure;
+	}
+
+	public boolean isAuditSuccess() {
+		return auditSuccess;
+	}
+
+	public boolean isGranting() {
+		return granting;
+	}
+
+	void setAuditFailure(boolean auditFailure) {
+		this.auditFailure = auditFailure;
+	}
+
+	void setAuditSuccess(boolean auditSuccess) {
+		this.auditSuccess = auditSuccess;
+	}
+
+	void setPermission(Permission permission) {
+		Assert.notNull(permission, "Permission required");
+		this.permission = permission;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("AccessControlEntryImpl[");
+		sb.append("id: ").append(this.id).append("; ");
+		sb.append("granting: ").append(this.granting).append("; ");
+		sb.append("sid: ").append(this.sid).append("; ");
+		sb.append("permission: ").append(this.permission).append("; ");
+		sb.append("auditSuccess: ").append(this.auditSuccess).append("; ");
+		sb.append("auditFailure: ").append(this.auditFailure);
+		sb.append("]");
+
+		return sb.toString();
+	}
 }

+ 8 - 7
acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategy.java

@@ -17,7 +17,6 @@ package org.springframework.security.acls.domain;
 
 import org.springframework.security.acls.model.Acl;
 
-
 /**
  * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call
  * adminstrative methods on the <code>AclImpl</code>.
@@ -25,13 +24,15 @@ import org.springframework.security.acls.model.Acl;
  * @author Ben Alex
  */
 public interface AclAuthorizationStrategy {
-    //~ Static fields/initializers =====================================================================================
+	// ~ Static fields/initializers
+	// =====================================================================================
 
-    int CHANGE_OWNERSHIP = 0;
-    int CHANGE_AUDITING = 1;
-    int CHANGE_GENERAL = 2;
+	int CHANGE_OWNERSHIP = 0;
+	int CHANGE_AUDITING = 1;
+	int CHANGE_GENERAL = 2;
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    void securityCheck(Acl acl, int changeType);
+	void securityCheck(Acl acl, int changeType);
 }

+ 111 - 102
acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java

@@ -27,115 +27,124 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.util.Assert;
 
-
 /**
  * Default implementation of {@link AclAuthorizationStrategy}.
  * <p>
- * Permission will be granted if at least one of the following conditions is true for the current 
- * principal. 
+ * Permission will be granted if at least one of the following conditions is true for the
+ * current principal.
  * <ul>
- * <li> is the owner (as defined by the ACL). </li>
- * <li> holds the relevant system-wide {@link GrantedAuthority} injected into the 
- *      constructor. </li>
- * <li> has {@link BasePermission#ADMINISTRATION} permission (as defined by the ACL). </li>
+ * <li>is the owner (as defined by the ACL).</li>
+ * <li>holds the relevant system-wide {@link GrantedAuthority} injected into the
+ * constructor.</li>
+ * <li>has {@link BasePermission#ADMINISTRATION} permission (as defined by the ACL).</li>
  * </ul>
  *
  * @author Ben Alex
  */
 public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
-    //~ Instance fields ================================================================================================
-
-    private final GrantedAuthority gaGeneralChanges;
-    private final GrantedAuthority gaModifyAuditing;
-    private final GrantedAuthority gaTakeOwnership;
-    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
-
-    //~ Constructors ===================================================================================================
-
-    /**
-     * Constructor. The only mandatory parameter relates to the system-wide {@link GrantedAuthority} instances that
-     * can be held to always permit ACL changes.
-     *
-     * @param auths the <code>GrantedAuthority</code>s that have
-     * special permissions (index 0 is the authority needed to change
-     * ownership, index 1 is the authority needed to modify auditing details,
-     * index 2 is the authority needed to change other ACL and ACE details) (required)
-     * <p>
-     * Alternatively, a single value can be supplied for all three permissions.
-     */
-    public AclAuthorizationStrategyImpl(GrantedAuthority... auths) {
-        Assert.isTrue(auths != null && (auths.length == 3 || auths.length == 1),
-                "One or three GrantedAuthority instances required");
-        if (auths.length == 3) {
-            gaTakeOwnership = auths[0];
-            gaModifyAuditing = auths[1];
-            gaGeneralChanges = auths[2];
-        } else {
-            gaTakeOwnership = gaModifyAuditing = gaGeneralChanges = auths[0];
-        }
-    }
-
-    //~ Methods ========================================================================================================
-
-    public void securityCheck(Acl acl, int changeType) {
-        if ((SecurityContextHolder.getContext() == null)
-            || (SecurityContextHolder.getContext().getAuthentication() == null)
-            || !SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
-            throw new AccessDeniedException("Authenticated principal required to operate with ACLs");
-        }
-
-        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-
-        // Check if authorized by virtue of ACL ownership
-        Sid currentUser = createCurrentUser(authentication);
-
-        if (currentUser.equals(acl.getOwner())
-                && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
-            return;
-        }
-
-        // Not authorized by ACL ownership; try via adminstrative permissions
-        GrantedAuthority requiredAuthority;
-
-        if (changeType == CHANGE_AUDITING) {
-            requiredAuthority = this.gaModifyAuditing;
-        } else if (changeType == CHANGE_GENERAL) {
-            requiredAuthority = this.gaGeneralChanges;
-        } else if (changeType == CHANGE_OWNERSHIP) {
-            requiredAuthority = this.gaTakeOwnership;
-        } else {
-            throw new IllegalArgumentException("Unknown change type");
-        }
-
-        // Iterate this principal's authorities to determine right
-        if (authentication.getAuthorities().contains(requiredAuthority)) {
-            return;
-        }
-
-        // Try to get permission via ACEs within the ACL
-        List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
-
-        if (acl.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), sids, false)) {
-            return;
-        }
-
-        throw new AccessDeniedException(
-                "Principal does not have required ACL permissions to perform requested operation");
-    }
-
-    /**
-     * Creates a principal-like sid from the authentication information.
-     *
-     * @param authentication the authentication information that can provide principal and thus the sid's id will be
-     *                       dependant on the value inside
-     * @return a sid with the ID taken from the authentication information
-     */
-    protected Sid createCurrentUser(Authentication authentication) {
-        return new PrincipalSid(authentication);
-    }
-
-    public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
-        Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
-        this.sidRetrievalStrategy = sidRetrievalStrategy;
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final GrantedAuthority gaGeneralChanges;
+	private final GrantedAuthority gaModifyAuditing;
+	private final GrantedAuthority gaTakeOwnership;
+	private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	/**
+	 * Constructor. The only mandatory parameter relates to the system-wide
+	 * {@link GrantedAuthority} instances that can be held to always permit ACL changes.
+	 *
+	 * @param auths the <code>GrantedAuthority</code>s that have special permissions
+	 * (index 0 is the authority needed to change ownership, index 1 is the authority
+	 * needed to modify auditing details, index 2 is the authority needed to change other
+	 * ACL and ACE details) (required)
+	 * <p>
+	 * Alternatively, a single value can be supplied for all three permissions.
+	 */
+	public AclAuthorizationStrategyImpl(GrantedAuthority... auths) {
+		Assert.isTrue(auths != null && (auths.length == 3 || auths.length == 1),
+				"One or three GrantedAuthority instances required");
+		if (auths.length == 3) {
+			gaTakeOwnership = auths[0];
+			gaModifyAuditing = auths[1];
+			gaGeneralChanges = auths[2];
+		}
+		else {
+			gaTakeOwnership = gaModifyAuditing = gaGeneralChanges = auths[0];
+		}
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void securityCheck(Acl acl, int changeType) {
+		if ((SecurityContextHolder.getContext() == null)
+				|| (SecurityContextHolder.getContext().getAuthentication() == null)
+				|| !SecurityContextHolder.getContext().getAuthentication()
+						.isAuthenticated()) {
+			throw new AccessDeniedException(
+					"Authenticated principal required to operate with ACLs");
+		}
+
+		Authentication authentication = SecurityContextHolder.getContext()
+				.getAuthentication();
+
+		// Check if authorized by virtue of ACL ownership
+		Sid currentUser = createCurrentUser(authentication);
+
+		if (currentUser.equals(acl.getOwner())
+				&& ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
+			return;
+		}
+
+		// Not authorized by ACL ownership; try via adminstrative permissions
+		GrantedAuthority requiredAuthority;
+
+		if (changeType == CHANGE_AUDITING) {
+			requiredAuthority = this.gaModifyAuditing;
+		}
+		else if (changeType == CHANGE_GENERAL) {
+			requiredAuthority = this.gaGeneralChanges;
+		}
+		else if (changeType == CHANGE_OWNERSHIP) {
+			requiredAuthority = this.gaTakeOwnership;
+		}
+		else {
+			throw new IllegalArgumentException("Unknown change type");
+		}
+
+		// Iterate this principal's authorities to determine right
+		if (authentication.getAuthorities().contains(requiredAuthority)) {
+			return;
+		}
+
+		// Try to get permission via ACEs within the ACL
+		List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
+
+		if (acl.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), sids, false)) {
+			return;
+		}
+
+		throw new AccessDeniedException(
+				"Principal does not have required ACL permissions to perform requested operation");
+	}
+
+	/**
+	 * Creates a principal-like sid from the authentication information.
+	 *
+	 * @param authentication the authentication information that can provide principal and
+	 * thus the sid's id will be dependant on the value inside
+	 * @return a sid with the ID taken from the authentication information
+	 */
+	protected Sid createCurrentUser(Authentication authentication) {
+		return new PrincipalSid(authentication);
+	}
+
+	public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
+		Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
+		this.sidRetrievalStrategy = sidRetrievalStrategy;
+	}
 }

+ 84 - 80
acl/src/main/java/org/springframework/security/acls/domain/AclFormattingUtils.java

@@ -17,7 +17,6 @@ package org.springframework.security.acls.domain;
 import org.springframework.security.acls.model.Permission;
 import org.springframework.util.Assert;
 
-
 /**
  * Utility methods for displaying ACL information.
  *
@@ -25,83 +24,88 @@ import org.springframework.util.Assert;
  */
 public abstract class AclFormattingUtils {
 
-    public static String demergePatterns(String original, String removeBits) {
-        Assert.notNull(original, "Original string required");
-        Assert.notNull(removeBits, "Bits To Remove string required");
-        Assert.isTrue(original.length() == removeBits.length(),
-            "Original and Bits To Remove strings must be identical length");
-
-        char[] replacement = new char[original.length()];
-
-        for (int i = 0; i < original.length(); i++) {
-            if (removeBits.charAt(i) == Permission.RESERVED_OFF) {
-                replacement[i] = original.charAt(i);
-            } else {
-                replacement[i] = Permission.RESERVED_OFF;
-            }
-        }
-
-        return new String(replacement);
-    }
-
-    public static String mergePatterns(String original, String extraBits) {
-        Assert.notNull(original, "Original string required");
-        Assert.notNull(extraBits, "Extra Bits string required");
-        Assert.isTrue(original.length() == extraBits.length(),
-            "Original and Extra Bits strings must be identical length");
-
-        char[] replacement = new char[extraBits.length()];
-
-        for (int i = 0; i < extraBits.length(); i++) {
-            if (extraBits.charAt(i) == Permission.RESERVED_OFF) {
-                replacement[i] = original.charAt(i);
-            } else {
-                replacement[i] = extraBits.charAt(i);
-            }
-        }
-
-        return new String(replacement);
-    }
-
-    /**
-     * Returns a representation of the active bits in the presented mask, with each active bit being denoted by
-     * character '*'.
-     * <p>
-     * Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.
-     *
-     * @param i the integer bit mask to print the active bits for
-     *
-     * @return a 32-character representation of the bit mask
-     */
-    public static String printBinary(int i) {
-        return printBinary(i, '*', Permission.RESERVED_OFF);
-    }
-
-    /**
-     * Returns a representation of the active bits in the presented mask, with each active bit being denoted by
-     * the passed character.
-     * <p>
-     * Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.
-     *
-     * @param mask the integer bit mask to print the active bits for
-     * @param code the character to print when an active bit is detected
-     *
-     * @return a 32-character representation of the bit mask
-     */
-    public static String printBinary(int mask, char code) {
-        Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_ON),
-            Permission.RESERVED_ON + " is a reserved character code");
-        Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_OFF),
-            Permission.RESERVED_OFF + " is a reserved character code");
-
-        return printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF).replace(Permission.RESERVED_ON, code);
-    }
-
-    private static String printBinary(int i, char on, char off) {
-        String s = Integer.toBinaryString(i);
-        String pattern = Permission.THIRTY_TWO_RESERVED_OFF;
-        String temp2 = pattern.substring(0, pattern.length() - s.length()) + s;
-
-        return temp2.replace('0', off).replace('1', on);
-    }
+	public static String demergePatterns(String original, String removeBits) {
+		Assert.notNull(original, "Original string required");
+		Assert.notNull(removeBits, "Bits To Remove string required");
+		Assert.isTrue(original.length() == removeBits.length(),
+				"Original and Bits To Remove strings must be identical length");
+
+		char[] replacement = new char[original.length()];
+
+		for (int i = 0; i < original.length(); i++) {
+			if (removeBits.charAt(i) == Permission.RESERVED_OFF) {
+				replacement[i] = original.charAt(i);
+			}
+			else {
+				replacement[i] = Permission.RESERVED_OFF;
+			}
+		}
+
+		return new String(replacement);
+	}
+
+	public static String mergePatterns(String original, String extraBits) {
+		Assert.notNull(original, "Original string required");
+		Assert.notNull(extraBits, "Extra Bits string required");
+		Assert.isTrue(original.length() == extraBits.length(),
+				"Original and Extra Bits strings must be identical length");
+
+		char[] replacement = new char[extraBits.length()];
+
+		for (int i = 0; i < extraBits.length(); i++) {
+			if (extraBits.charAt(i) == Permission.RESERVED_OFF) {
+				replacement[i] = original.charAt(i);
+			}
+			else {
+				replacement[i] = extraBits.charAt(i);
+			}
+		}
+
+		return new String(replacement);
+	}
+
+	/**
+	 * Returns a representation of the active bits in the presented mask, with each active
+	 * bit being denoted by character '*'.
+	 * <p>
+	 * Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.
+	 *
+	 * @param i the integer bit mask to print the active bits for
+	 *
+	 * @return a 32-character representation of the bit mask
+	 */
+	public static String printBinary(int i) {
+		return printBinary(i, '*', Permission.RESERVED_OFF);
+	}
+
+	/**
+	 * Returns a representation of the active bits in the presented mask, with each active
+	 * bit being denoted by the passed character.
+	 * <p>
+	 * Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}.
+	 *
+	 * @param mask the integer bit mask to print the active bits for
+	 * @param code the character to print when an active bit is detected
+	 *
+	 * @return a 32-character representation of the bit mask
+	 */
+	public static String printBinary(int mask, char code) {
+		Assert.doesNotContain(Character.toString(code),
+				Character.toString(Permission.RESERVED_ON), Permission.RESERVED_ON
+						+ " is a reserved character code");
+		Assert.doesNotContain(Character.toString(code),
+				Character.toString(Permission.RESERVED_OFF), Permission.RESERVED_OFF
+						+ " is a reserved character code");
+
+		return printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF)
+				.replace(Permission.RESERVED_ON, code);
+	}
+
+	private static String printBinary(int i, char on, char off) {
+		String s = Integer.toBinaryString(i);
+		String pattern = Permission.THIRTY_TWO_RESERVED_OFF;
+		String temp2 = pattern.substring(0, pattern.length() - s.length()) + s;
+
+		return temp2.replace('0', off).replace('1', on);
+	}
 }

+ 322 - 286
acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java

@@ -31,297 +31,333 @@ import org.springframework.security.acls.model.Sid;
 import org.springframework.security.acls.model.UnloadedSidException;
 import org.springframework.util.Assert;
 
-
 /**
  * Base implementation of <code>Acl</code>.
  *
  * @author Ben Alex
  */
 public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
-    //~ Instance fields ================================================================================================
-
-    private Acl parentAcl;
-    private transient AclAuthorizationStrategy aclAuthorizationStrategy;
-    private transient PermissionGrantingStrategy permissionGrantingStrategy;
-    private final List<AccessControlEntry> aces = new ArrayList<AccessControlEntry>();
-    private ObjectIdentity objectIdentity;
-    private Serializable id;
-    private Sid owner; // OwnershipAcl
-    private List<Sid> loadedSids = null; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
-    private boolean entriesInheriting = true;
-
-    //~ Constructors ===================================================================================================
-
-    /**
-     * Minimal constructor, which should be used {@link
-     * org.springframework.security.acls.model.MutableAclService#createAcl(ObjectIdentity)}.
-     *
-     * @param objectIdentity the object identity this ACL relates to (required)
-     * @param id the primary key assigned to this ACL (required)
-     * @param aclAuthorizationStrategy authorization strategy (required)
-     * @param auditLogger audit logger (required)
-     */
-    public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
-                    AuditLogger auditLogger) {
-        Assert.notNull(objectIdentity, "Object Identity required");
-        Assert.notNull(id, "Id required");
-        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
-        Assert.notNull(auditLogger, "AuditLogger required");
-        this.objectIdentity = objectIdentity;
-        this.id = id;
-        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
-        this.permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger);
-    }
-
-    /**
-     * Full constructor, which should be used by persistence tools that do not
-     * provide field-level access features.
-     *
-     * @param objectIdentity the object identity this ACL relates to
-     * @param id the primary key assigned to this ACL
-     * @param aclAuthorizationStrategy authorization strategy
-     * @param grantingStrategy the {@code PermissionGrantingStrategy} which will be used by the {@code isGranted()} method
-     * @param parentAcl the parent (may be may be {@code null})
-     * @param loadedSids the loaded SIDs if only a subset were loaded (may be {@code null})
-     * @param entriesInheriting if ACEs from the parent should inherit into
-     *        this ACL
-     * @param owner the owner (required)
-     */
-    public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
-            PermissionGrantingStrategy grantingStrategy, Acl parentAcl, List<Sid> loadedSids, boolean entriesInheriting, Sid owner) {
-        Assert.notNull(objectIdentity, "Object Identity required");
-        Assert.notNull(id, "Id required");
-        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
-        Assert.notNull(owner, "Owner required");
-
-        this.objectIdentity = objectIdentity;
-        this.id = id;
-        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
-        this.parentAcl = parentAcl; // may be null
-        this.loadedSids = loadedSids; // may be null
-        this.entriesInheriting = entriesInheriting;
-        this.owner = owner;
-        this.permissionGrantingStrategy = grantingStrategy;
-    }
-
-    /**
-     * Private no-argument constructor for use by reflection-based persistence
-     * tools along with field-level access.
-     */
-    @SuppressWarnings("unused")
-    private AclImpl() {}
-
-    //~ Methods ========================================================================================================
-
-    public void deleteAce(int aceIndex) throws NotFoundException {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
-        verifyAceIndexExists(aceIndex);
-
-        synchronized (aces) {
-            this.aces.remove(aceIndex);
-        }
-    }
-
-    private void verifyAceIndexExists(int aceIndex) {
-        if (aceIndex < 0) {
-            throw new NotFoundException("aceIndex must be greater than or equal to zero");
-        }
-        if (aceIndex >= this.aces.size()) {
-            throw new NotFoundException("aceIndex must refer to an index of the AccessControlEntry list. " +
-                    "List size is " + aces.size() + ", index was " + aceIndex);
-        }
-    }
-
-    public void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) throws NotFoundException {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
-        Assert.notNull(permission, "Permission required");
-        Assert.notNull(sid, "Sid required");
-        if (atIndexLocation < 0) {
-            throw new NotFoundException("atIndexLocation must be greater than or equal to zero");
-        }
-        if (atIndexLocation > this.aces.size()) {
-            throw new NotFoundException("atIndexLocation must be less than or equal to the size of the AccessControlEntry collection");
-        }
-
-        AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid, permission, granting, false, false);
-
-        synchronized (aces) {
-            this.aces.add(atIndexLocation, ace);
-        }
-    }
-
-    public List<AccessControlEntry> getEntries() {
-        // Can safely return AccessControlEntry directly, as they're immutable outside the ACL package
-        return new ArrayList<AccessControlEntry>(aces);
-    }
-
-    public Serializable getId() {
-        return this.id;
-    }
-
-    public ObjectIdentity getObjectIdentity() {
-        return objectIdentity;
-    }
-
-    public boolean isEntriesInheriting() {
-        return entriesInheriting;
-    }
-
-    /**
-     * Delegates to the {@link PermissionGrantingStrategy}.
-     *
-     * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the ACL was only loaded for a
-     *         subset of SIDs
-     * @see DefaultPermissionGrantingStrategy
-     */
-    public boolean isGranted(List<Permission> permission, List<Sid> sids, boolean administrativeMode)
-            throws NotFoundException, UnloadedSidException {
-        Assert.notEmpty(permission, "Permissions required");
-        Assert.notEmpty(sids, "SIDs required");
-
-        if (!this.isSidLoaded(sids)) {
-            throw new UnloadedSidException("ACL was not loaded for one or more SID");
-        }
-
-        return permissionGrantingStrategy.isGranted(this, permission, sids, administrativeMode);
-    }
-
-    public boolean isSidLoaded(List<Sid> sids) {
-        // If loadedSides is null, this indicates all SIDs were loaded
-        // Also return true if the caller didn't specify a SID to find
-        if ((this.loadedSids == null) || (sids == null) || (sids.size() == 0)) {
-            return true;
-        }
-
-        // This ACL applies to a SID subset only. Iterate to check it applies.
-        for (Sid sid: sids) {
-            boolean found = false;
-
-            for (Sid loadedSid : loadedSids) {
-                if (sid.equals(loadedSid)) {
-                    // this SID is OK
-                    found = true;
-
-                    break; // out of loadedSids for loop
-                }
-            }
-
-            if (!found) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    public void setEntriesInheriting(boolean entriesInheriting) {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
-        this.entriesInheriting = entriesInheriting;
-    }
-
-    public void setOwner(Sid newOwner) {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-        Assert.notNull(newOwner, "Owner required");
-        this.owner = newOwner;
-    }
-
-    public Sid getOwner() {
-        return this.owner;
-    }
-
-    public void setParent(Acl newParent) {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
-        Assert.isTrue(newParent == null || !newParent.equals(this), "Cannot be the parent of yourself");
-        this.parentAcl = newParent;
-    }
-
-    public Acl getParentAcl() {
-        return parentAcl;
-    }
-
-    public void updateAce(int aceIndex, Permission permission)
-        throws NotFoundException {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
-        verifyAceIndexExists(aceIndex);
-
-        synchronized (aces) {
-            AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(aceIndex);
-            ace.setPermission(permission);
-        }
-    }
-
-    public void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure) {
-        aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING);
-        verifyAceIndexExists(aceIndex);
-
-        synchronized (aces) {
-            AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(aceIndex);
-            ace.setAuditSuccess(auditSuccess);
-            ace.setAuditFailure(auditFailure);
-        }
-    }
-
-    public boolean equals(Object obj) {
-        if (obj instanceof AclImpl) {
-            AclImpl rhs = (AclImpl) obj;
-            if (this.aces.equals(rhs.aces)) {
-                if ((this.parentAcl == null && rhs.parentAcl == null) || (this.parentAcl !=null && this.parentAcl.equals(rhs.parentAcl))) {
-                    if ((this.objectIdentity == null && rhs.objectIdentity == null) || (this.objectIdentity != null && this.objectIdentity.equals(rhs.objectIdentity))) {
-                        if ((this.id == null && rhs.id == null) || (this.id != null && this.id.equals(rhs.id))) {
-                            if ((this.owner == null && rhs.owner == null) || (this.owner != null && this.owner.equals(rhs.owner))) {
-                                if (this.entriesInheriting == rhs.entriesInheriting) {
-                                    if ((this.loadedSids == null && rhs.loadedSids == null)) {
-                                        return true;
-                                    }
-                                    if (this.loadedSids != null && (this.loadedSids.size() == rhs.loadedSids.size())) {
-                                        for (int i = 0; i < this.loadedSids.size(); i++) {
-                                            if (!this.loadedSids.get(i).equals(rhs.loadedSids.get(i))) {
-                                                return false;
-                                            }
-                                        }
-                                        return true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("AclImpl[");
-        sb.append("id: ").append(this.id).append("; ");
-        sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
-        sb.append("owner: ").append(this.owner).append("; ");
-
-        int count = 0;
-
-        for (AccessControlEntry ace : aces) {
-            count++;
-
-            if (count == 1) {
-                sb.append("\n");
-            }
-
-            sb.append(ace).append("\n");
-        }
-
-        if (count == 0) {
-            sb.append("no ACEs; ");
-        }
-
-        sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
-        sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString());
-        sb.append("; ");
-        sb.append("aclAuthorizationStrategy: ").append(this.aclAuthorizationStrategy).append("; ");
-        sb.append("permissionGrantingStrategy: ").append(this.permissionGrantingStrategy);
-        sb.append("]");
-
-        return sb.toString();
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private Acl parentAcl;
+	private transient AclAuthorizationStrategy aclAuthorizationStrategy;
+	private transient PermissionGrantingStrategy permissionGrantingStrategy;
+	private final List<AccessControlEntry> aces = new ArrayList<AccessControlEntry>();
+	private ObjectIdentity objectIdentity;
+	private Serializable id;
+	private Sid owner; // OwnershipAcl
+	private List<Sid> loadedSids = null; // includes all SIDs the WHERE clause covered,
+											// even if there was no ACE for a SID
+	private boolean entriesInheriting = true;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	/**
+	 * Minimal constructor, which should be used
+	 * {@link org.springframework.security.acls.model.MutableAclService#createAcl(ObjectIdentity)}
+	 * .
+	 *
+	 * @param objectIdentity the object identity this ACL relates to (required)
+	 * @param id the primary key assigned to this ACL (required)
+	 * @param aclAuthorizationStrategy authorization strategy (required)
+	 * @param auditLogger audit logger (required)
+	 */
+	public AclImpl(ObjectIdentity objectIdentity, Serializable id,
+			AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) {
+		Assert.notNull(objectIdentity, "Object Identity required");
+		Assert.notNull(id, "Id required");
+		Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+		Assert.notNull(auditLogger, "AuditLogger required");
+		this.objectIdentity = objectIdentity;
+		this.id = id;
+		this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+		this.permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(
+				auditLogger);
+	}
+
+	/**
+	 * Full constructor, which should be used by persistence tools that do not provide
+	 * field-level access features.
+	 *
+	 * @param objectIdentity the object identity this ACL relates to
+	 * @param id the primary key assigned to this ACL
+	 * @param aclAuthorizationStrategy authorization strategy
+	 * @param grantingStrategy the {@code PermissionGrantingStrategy} which will be used
+	 * by the {@code isGranted()} method
+	 * @param parentAcl the parent (may be may be {@code null})
+	 * @param loadedSids the loaded SIDs if only a subset were loaded (may be {@code null}
+	 * )
+	 * @param entriesInheriting if ACEs from the parent should inherit into this ACL
+	 * @param owner the owner (required)
+	 */
+	public AclImpl(ObjectIdentity objectIdentity, Serializable id,
+			AclAuthorizationStrategy aclAuthorizationStrategy,
+			PermissionGrantingStrategy grantingStrategy, Acl parentAcl,
+			List<Sid> loadedSids, boolean entriesInheriting, Sid owner) {
+		Assert.notNull(objectIdentity, "Object Identity required");
+		Assert.notNull(id, "Id required");
+		Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+		Assert.notNull(owner, "Owner required");
+
+		this.objectIdentity = objectIdentity;
+		this.id = id;
+		this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+		this.parentAcl = parentAcl; // may be null
+		this.loadedSids = loadedSids; // may be null
+		this.entriesInheriting = entriesInheriting;
+		this.owner = owner;
+		this.permissionGrantingStrategy = grantingStrategy;
+	}
+
+	/**
+	 * Private no-argument constructor for use by reflection-based persistence tools along
+	 * with field-level access.
+	 */
+	@SuppressWarnings("unused")
+	private AclImpl() {
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void deleteAce(int aceIndex) throws NotFoundException {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
+		verifyAceIndexExists(aceIndex);
+
+		synchronized (aces) {
+			this.aces.remove(aceIndex);
+		}
+	}
+
+	private void verifyAceIndexExists(int aceIndex) {
+		if (aceIndex < 0) {
+			throw new NotFoundException("aceIndex must be greater than or equal to zero");
+		}
+		if (aceIndex >= this.aces.size()) {
+			throw new NotFoundException(
+					"aceIndex must refer to an index of the AccessControlEntry list. "
+							+ "List size is " + aces.size() + ", index was " + aceIndex);
+		}
+	}
+
+	public void insertAce(int atIndexLocation, Permission permission, Sid sid,
+			boolean granting) throws NotFoundException {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
+		Assert.notNull(permission, "Permission required");
+		Assert.notNull(sid, "Sid required");
+		if (atIndexLocation < 0) {
+			throw new NotFoundException(
+					"atIndexLocation must be greater than or equal to zero");
+		}
+		if (atIndexLocation > this.aces.size()) {
+			throw new NotFoundException(
+					"atIndexLocation must be less than or equal to the size of the AccessControlEntry collection");
+		}
+
+		AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid,
+				permission, granting, false, false);
+
+		synchronized (aces) {
+			this.aces.add(atIndexLocation, ace);
+		}
+	}
+
+	public List<AccessControlEntry> getEntries() {
+		// Can safely return AccessControlEntry directly, as they're immutable outside the
+		// ACL package
+		return new ArrayList<AccessControlEntry>(aces);
+	}
+
+	public Serializable getId() {
+		return this.id;
+	}
+
+	public ObjectIdentity getObjectIdentity() {
+		return objectIdentity;
+	}
+
+	public boolean isEntriesInheriting() {
+		return entriesInheriting;
+	}
+
+	/**
+	 * Delegates to the {@link PermissionGrantingStrategy}.
+	 *
+	 * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the
+	 * ACL was only loaded for a subset of SIDs
+	 * @see DefaultPermissionGrantingStrategy
+	 */
+	public boolean isGranted(List<Permission> permission, List<Sid> sids,
+			boolean administrativeMode) throws NotFoundException, UnloadedSidException {
+		Assert.notEmpty(permission, "Permissions required");
+		Assert.notEmpty(sids, "SIDs required");
+
+		if (!this.isSidLoaded(sids)) {
+			throw new UnloadedSidException("ACL was not loaded for one or more SID");
+		}
+
+		return permissionGrantingStrategy.isGranted(this, permission, sids,
+				administrativeMode);
+	}
+
+	public boolean isSidLoaded(List<Sid> sids) {
+		// If loadedSides is null, this indicates all SIDs were loaded
+		// Also return true if the caller didn't specify a SID to find
+		if ((this.loadedSids == null) || (sids == null) || (sids.size() == 0)) {
+			return true;
+		}
+
+		// This ACL applies to a SID subset only. Iterate to check it applies.
+		for (Sid sid : sids) {
+			boolean found = false;
+
+			for (Sid loadedSid : loadedSids) {
+				if (sid.equals(loadedSid)) {
+					// this SID is OK
+					found = true;
+
+					break; // out of loadedSids for loop
+				}
+			}
+
+			if (!found) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	public void setEntriesInheriting(boolean entriesInheriting) {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
+		this.entriesInheriting = entriesInheriting;
+	}
+
+	public void setOwner(Sid newOwner) {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+		Assert.notNull(newOwner, "Owner required");
+		this.owner = newOwner;
+	}
+
+	public Sid getOwner() {
+		return this.owner;
+	}
+
+	public void setParent(Acl newParent) {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
+		Assert.isTrue(newParent == null || !newParent.equals(this),
+				"Cannot be the parent of yourself");
+		this.parentAcl = newParent;
+	}
+
+	public Acl getParentAcl() {
+		return parentAcl;
+	}
+
+	public void updateAce(int aceIndex, Permission permission) throws NotFoundException {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
+		verifyAceIndexExists(aceIndex);
+
+		synchronized (aces) {
+			AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(aceIndex);
+			ace.setPermission(permission);
+		}
+	}
+
+	public void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure) {
+		aclAuthorizationStrategy.securityCheck(this,
+				AclAuthorizationStrategy.CHANGE_AUDITING);
+		verifyAceIndexExists(aceIndex);
+
+		synchronized (aces) {
+			AccessControlEntryImpl ace = (AccessControlEntryImpl) aces.get(aceIndex);
+			ace.setAuditSuccess(auditSuccess);
+			ace.setAuditFailure(auditFailure);
+		}
+	}
+
+	public boolean equals(Object obj) {
+		if (obj instanceof AclImpl) {
+			AclImpl rhs = (AclImpl) obj;
+			if (this.aces.equals(rhs.aces)) {
+				if ((this.parentAcl == null && rhs.parentAcl == null)
+						|| (this.parentAcl != null && this.parentAcl
+								.equals(rhs.parentAcl))) {
+					if ((this.objectIdentity == null && rhs.objectIdentity == null)
+							|| (this.objectIdentity != null && this.objectIdentity
+									.equals(rhs.objectIdentity))) {
+						if ((this.id == null && rhs.id == null)
+								|| (this.id != null && this.id.equals(rhs.id))) {
+							if ((this.owner == null && rhs.owner == null)
+									|| (this.owner != null && this.owner
+											.equals(rhs.owner))) {
+								if (this.entriesInheriting == rhs.entriesInheriting) {
+									if ((this.loadedSids == null && rhs.loadedSids == null)) {
+										return true;
+									}
+									if (this.loadedSids != null
+											&& (this.loadedSids.size() == rhs.loadedSids
+													.size())) {
+										for (int i = 0; i < this.loadedSids.size(); i++) {
+											if (!this.loadedSids.get(i).equals(
+													rhs.loadedSids.get(i))) {
+												return false;
+											}
+										}
+										return true;
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("AclImpl[");
+		sb.append("id: ").append(this.id).append("; ");
+		sb.append("objectIdentity: ").append(this.objectIdentity).append("; ");
+		sb.append("owner: ").append(this.owner).append("; ");
+
+		int count = 0;
+
+		for (AccessControlEntry ace : aces) {
+			count++;
+
+			if (count == 1) {
+				sb.append("\n");
+			}
+
+			sb.append(ace).append("\n");
+		}
+
+		if (count == 0) {
+			sb.append("no ACEs; ");
+		}
+
+		sb.append("inheriting: ").append(this.entriesInheriting).append("; ");
+		sb.append("parent: ").append(
+				(this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity()
+						.toString());
+		sb.append("; ");
+		sb.append("aclAuthorizationStrategy: ").append(this.aclAuthorizationStrategy)
+				.append("; ");
+		sb.append("permissionGrantingStrategy: ").append(this.permissionGrantingStrategy);
+		sb.append("]");
+
+		return sb.toString();
+	}
 
 }

+ 3 - 3
acl/src/main/java/org/springframework/security/acls/domain/AuditLogger.java

@@ -16,7 +16,6 @@ package org.springframework.security.acls.domain;
 
 import org.springframework.security.acls.model.AccessControlEntry;
 
-
 /**
  * Used by <code>AclImpl</code> to log audit events.
  *
@@ -24,7 +23,8 @@ import org.springframework.security.acls.model.AccessControlEntry;
  *
  */
 public interface AuditLogger {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    void logIfNeeded(boolean granted, AccessControlEntry ace);
+	void logIfNeeded(boolean granted, AccessControlEntry ace);
 }

+ 11 - 12
acl/src/main/java/org/springframework/security/acls/domain/BasePermission.java

@@ -16,7 +16,6 @@ package org.springframework.security.acls.domain;
 
 import org.springframework.security.acls.model.Permission;
 
-
 /**
  * A set of standard permissions.
  *
@@ -28,17 +27,17 @@ import org.springframework.security.acls.model.Permission;
  * @author Ben Alex
  */
 public class BasePermission extends AbstractPermission {
-    public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
-    public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
-    public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
-    public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
-    public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16
+	public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
+	public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
+	public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
+	public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
+	public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16
 
-    protected BasePermission(int mask) {
-       super(mask);
-    }
+	protected BasePermission(int mask) {
+		super(mask);
+	}
 
-    protected BasePermission(int mask, char code) {
-        super(mask, code);
-    }
+	protected BasePermission(int mask, char code) {
+		super(mask, code);
+	}
 }

+ 14 - 13
acl/src/main/java/org/springframework/security/acls/domain/ConsoleAuditLogger.java

@@ -19,26 +19,27 @@ import org.springframework.security.acls.model.AuditableAccessControlEntry;
 
 import org.springframework.util.Assert;
 
-
 /**
  * A basic implementation of {@link AuditLogger}.
  *
  * @author Ben Alex
  */
 public class ConsoleAuditLogger implements AuditLogger {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public void logIfNeeded(boolean granted, AccessControlEntry ace) {
-        Assert.notNull(ace, "AccessControlEntry required");
+	public void logIfNeeded(boolean granted, AccessControlEntry ace) {
+		Assert.notNull(ace, "AccessControlEntry required");
 
-        if (ace instanceof AuditableAccessControlEntry) {
-            AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
+		if (ace instanceof AuditableAccessControlEntry) {
+			AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
 
-            if (granted && auditableAce.isAuditSuccess()) {
-                System.out.println("GRANTED due to ACE: " + ace);
-            } else if (!granted && auditableAce.isAuditFailure()) {
-                System.out.println("DENIED due to ACE: " + ace);
-            }
-        }
-    }
+			if (granted && auditableAce.isAuditSuccess()) {
+				System.out.println("GRANTED due to ACE: " + ace);
+			}
+			else if (!granted && auditableAce.isAuditFailure()) {
+				System.out.println("DENIED due to ACE: " + ace);
+			}
+		}
+	}
 }

+ 29 - 25
acl/src/main/java/org/springframework/security/acls/domain/CumulativePermission.java

@@ -16,45 +16,49 @@ package org.springframework.security.acls.domain;
 
 import org.springframework.security.acls.model.Permission;
 
-
 /**
- * Represents a <code>Permission</code> that is constructed at runtime from other permissions.
+ * Represents a <code>Permission</code> that is constructed at runtime from other
+ * permissions.
  *
- * <p>Methods return <code>this</code>, in order to facilitate method chaining.</p>
+ * <p>
+ * Methods return <code>this</code>, in order to facilitate method chaining.
+ * </p>
  *
  * @author Ben Alex
  */
 public class CumulativePermission extends AbstractPermission {
 
-    private String pattern = THIRTY_TWO_RESERVED_OFF;
+	private String pattern = THIRTY_TWO_RESERVED_OFF;
 
-    public CumulativePermission() {
-        super(0, ' ');
-    }
+	public CumulativePermission() {
+		super(0, ' ');
+	}
 
-    public CumulativePermission clear(Permission permission) {
-        this.mask &= ~permission.getMask();
-        this.pattern = AclFormattingUtils.demergePatterns(this.pattern, permission.getPattern());
+	public CumulativePermission clear(Permission permission) {
+		this.mask &= ~permission.getMask();
+		this.pattern = AclFormattingUtils.demergePatterns(this.pattern,
+				permission.getPattern());
 
-        return this;
-    }
+		return this;
+	}
 
-    public CumulativePermission clear() {
-        this.mask = 0;
-        this.pattern = THIRTY_TWO_RESERVED_OFF;
+	public CumulativePermission clear() {
+		this.mask = 0;
+		this.pattern = THIRTY_TWO_RESERVED_OFF;
 
-        return this;
-    }
+		return this;
+	}
 
-    public CumulativePermission set(Permission permission) {
-        this.mask |= permission.getMask();
-        this.pattern = AclFormattingUtils.mergePatterns(this.pattern, permission.getPattern());
+	public CumulativePermission set(Permission permission) {
+		this.mask |= permission.getMask();
+		this.pattern = AclFormattingUtils.mergePatterns(this.pattern,
+				permission.getPattern());
 
-        return this;
-    }
+		return this;
+	}
 
-    public String getPattern() {
-        return this.pattern;
-    }
+	public String getPattern() {
+		return this.pattern;
+	}
 
 }

+ 128 - 122
acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionFactory.java

@@ -13,8 +13,8 @@ import org.springframework.util.Assert;
 /**
  * Default implementation of {@link PermissionFactory}.
  * <p>
- * Used as a strategy by classes which wish to map integer masks and permission names to <tt>Permission</tt>
- * instances for use with the ACL implementation.
+ * Used as a strategy by classes which wish to map integer masks and permission names to
+ * <tt>Permission</tt> instances for use with the ACL implementation.
  * <p>
  * Maintains a registry of permission names and masks to <tt>Permission</tt> instances.
  *
@@ -23,125 +23,131 @@ import org.springframework.util.Assert;
  * @since 2.0.3
  */
 public class DefaultPermissionFactory implements PermissionFactory {
-    private final Map<Integer, Permission> registeredPermissionsByInteger = new HashMap<Integer, Permission>();
-    private final Map<String, Permission> registeredPermissionsByName = new HashMap<String, Permission>();
-
-    /**
-     * Registers the <tt>Permission</tt> fields from the <tt>BasePermission</tt> class.
-     */
-    public DefaultPermissionFactory() {
-        registerPublicPermissions(BasePermission.class);
-    }
-
-    /**
-     * Registers the <tt>Permission</tt> fields from the supplied class.
-     */
-    public DefaultPermissionFactory(Class<? extends Permission> permissionClass) {
-        registerPublicPermissions(permissionClass);
-    }
-
-    /**
-     * Registers a map of named <tt>Permission</tt> instances.
-     *
-     * @param namedPermissions the map of <tt>Permission</tt>s, keyed by name.
-     */
-    public DefaultPermissionFactory(Map<String, ? extends Permission> namedPermissions) {
-        for (String name : namedPermissions.keySet()) {
-            registerPermission(namedPermissions.get(name), name);
-        }
-    }
-
-    /**
-     * Registers the public static fields of type {@link Permission} for a give class.
-     * <p>
-     * These permissions will be registered under the name of the field. See {@link BasePermission}
-     * for an example.
-     *
-     * @param clazz a {@link Permission} class with public static fields to register
-     */
-    protected void registerPublicPermissions(Class<? extends Permission> clazz) {
-        Assert.notNull(clazz, "Class required");
-
-        Field[] fields = clazz.getFields();
-
-        for (Field field : fields) {
-            try {
-                Object fieldValue = field.get(null);
-
-                if (Permission.class.isAssignableFrom(fieldValue.getClass())) {
-                    // Found a Permission static field
-                    Permission perm = (Permission) fieldValue;
-                    String permissionName = field.getName();
-
-                    registerPermission(perm, permissionName);
-                }
-            } catch (Exception ignore) {
-            }
-        }
-    }
-
-    protected void registerPermission(Permission perm, String permissionName) {
-        Assert.notNull(perm, "Permission required");
-        Assert.hasText(permissionName, "Permission name required");
-
-        Integer mask = Integer.valueOf(perm.getMask());
-
-        // Ensure no existing Permission uses this integer or code
-        Assert.isTrue(!registeredPermissionsByInteger.containsKey(mask), "An existing Permission already provides mask " + mask);
-        Assert.isTrue(!registeredPermissionsByName.containsKey(permissionName), "An existing Permission already provides name '" + permissionName + "'");
-
-        // Register the new Permission
-        registeredPermissionsByInteger.put(mask, perm);
-        registeredPermissionsByName.put(permissionName, perm);
-    }
-
-    public Permission buildFromMask(int mask) {
-        if (registeredPermissionsByInteger.containsKey(Integer.valueOf(mask))) {
-            // The requested mask has an exact match against a statically-defined Permission, so return it
-            return registeredPermissionsByInteger.get(Integer.valueOf(mask));
-        }
-
-        // To get this far, we have to use a CumulativePermission
-        CumulativePermission permission = new CumulativePermission();
-
-        for (int i = 0; i < 32; i++) {
-            int permissionToCheck = 1 << i;
-
-            if ((mask & permissionToCheck) == permissionToCheck) {
-                Permission p = registeredPermissionsByInteger.get(Integer.valueOf(permissionToCheck));
-
-                if (p == null) {
-                    throw new IllegalStateException("Mask '" + permissionToCheck + "' does not have a corresponding static Permission");
-                }
-                permission.set(p);
-            }
-        }
-
-        return permission;
-    }
-
-    public Permission buildFromName(String name) {
-        Permission p = registeredPermissionsByName.get(name);
-
-        if (p == null) {
-            throw new IllegalArgumentException("Unknown permission '" + name + "'");
-        }
-
-        return p;
-    }
-
-    public List<Permission> buildFromNames(List<String> names) {
-        if ((names == null) || (names.size() == 0)) {
-            return Collections.emptyList();
-        }
-
-        List<Permission> permissions = new ArrayList<Permission>(names.size());
-
-        for (String name : names) {
-            permissions.add(buildFromName(name));
-        }
-
-        return permissions;
-    }
+	private final Map<Integer, Permission> registeredPermissionsByInteger = new HashMap<Integer, Permission>();
+	private final Map<String, Permission> registeredPermissionsByName = new HashMap<String, Permission>();
+
+	/**
+	 * Registers the <tt>Permission</tt> fields from the <tt>BasePermission</tt> class.
+	 */
+	public DefaultPermissionFactory() {
+		registerPublicPermissions(BasePermission.class);
+	}
+
+	/**
+	 * Registers the <tt>Permission</tt> fields from the supplied class.
+	 */
+	public DefaultPermissionFactory(Class<? extends Permission> permissionClass) {
+		registerPublicPermissions(permissionClass);
+	}
+
+	/**
+	 * Registers a map of named <tt>Permission</tt> instances.
+	 *
+	 * @param namedPermissions the map of <tt>Permission</tt>s, keyed by name.
+	 */
+	public DefaultPermissionFactory(Map<String, ? extends Permission> namedPermissions) {
+		for (String name : namedPermissions.keySet()) {
+			registerPermission(namedPermissions.get(name), name);
+		}
+	}
+
+	/**
+	 * Registers the public static fields of type {@link Permission} for a give class.
+	 * <p>
+	 * These permissions will be registered under the name of the field. See
+	 * {@link BasePermission} for an example.
+	 *
+	 * @param clazz a {@link Permission} class with public static fields to register
+	 */
+	protected void registerPublicPermissions(Class<? extends Permission> clazz) {
+		Assert.notNull(clazz, "Class required");
+
+		Field[] fields = clazz.getFields();
+
+		for (Field field : fields) {
+			try {
+				Object fieldValue = field.get(null);
+
+				if (Permission.class.isAssignableFrom(fieldValue.getClass())) {
+					// Found a Permission static field
+					Permission perm = (Permission) fieldValue;
+					String permissionName = field.getName();
+
+					registerPermission(perm, permissionName);
+				}
+			}
+			catch (Exception ignore) {
+			}
+		}
+	}
+
+	protected void registerPermission(Permission perm, String permissionName) {
+		Assert.notNull(perm, "Permission required");
+		Assert.hasText(permissionName, "Permission name required");
+
+		Integer mask = Integer.valueOf(perm.getMask());
+
+		// Ensure no existing Permission uses this integer or code
+		Assert.isTrue(!registeredPermissionsByInteger.containsKey(mask),
+				"An existing Permission already provides mask " + mask);
+		Assert.isTrue(!registeredPermissionsByName.containsKey(permissionName),
+				"An existing Permission already provides name '" + permissionName + "'");
+
+		// Register the new Permission
+		registeredPermissionsByInteger.put(mask, perm);
+		registeredPermissionsByName.put(permissionName, perm);
+	}
+
+	public Permission buildFromMask(int mask) {
+		if (registeredPermissionsByInteger.containsKey(Integer.valueOf(mask))) {
+			// The requested mask has an exact match against a statically-defined
+			// Permission, so return it
+			return registeredPermissionsByInteger.get(Integer.valueOf(mask));
+		}
+
+		// To get this far, we have to use a CumulativePermission
+		CumulativePermission permission = new CumulativePermission();
+
+		for (int i = 0; i < 32; i++) {
+			int permissionToCheck = 1 << i;
+
+			if ((mask & permissionToCheck) == permissionToCheck) {
+				Permission p = registeredPermissionsByInteger.get(Integer
+						.valueOf(permissionToCheck));
+
+				if (p == null) {
+					throw new IllegalStateException("Mask '" + permissionToCheck
+							+ "' does not have a corresponding static Permission");
+				}
+				permission.set(p);
+			}
+		}
+
+		return permission;
+	}
+
+	public Permission buildFromName(String name) {
+		Permission p = registeredPermissionsByName.get(name);
+
+		if (p == null) {
+			throw new IllegalArgumentException("Unknown permission '" + name + "'");
+		}
+
+		return p;
+	}
+
+	public List<Permission> buildFromNames(List<String> names) {
+		if ((names == null) || (names.size() == 0)) {
+			return Collections.emptyList();
+		}
+
+		List<Permission> permissions = new ArrayList<Permission>(names.size());
+
+		for (String name : names) {
+			permissions.add(buildFromName(name));
+		}
+
+		return permissions;
+	}
 
 }

+ 114 - 103
acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionGrantingStrategy.java

@@ -12,108 +12,119 @@ import org.springframework.util.Assert;
 
 public class DefaultPermissionGrantingStrategy implements PermissionGrantingStrategy {
 
-    private final transient AuditLogger auditLogger;
-
-    /**
-     * Creates an instance with the logger which will be used to record granting and denial of requested permissions.
-     */
-    public DefaultPermissionGrantingStrategy(AuditLogger auditLogger) {
-        Assert.notNull(auditLogger, "auditLogger cannot be null");
-        this.auditLogger = auditLogger;
-    }
-
-    /**
-     * Determines authorization. The order of the <code>permission</code> and <code>sid</code> arguments is
-     * <em>extremely important</em>! The method will iterate through each of the <code>permission</code>s in the order
-     * specified. For each iteration, all of the <code>sid</code>s will be considered, again in the order they are
-     * presented. A search will then be performed for the first {@link AccessControlEntry} object that directly
-     * matches that <code>permission:sid</code> combination. When the <em>first full match</em> is found (ie an ACE
-     * that has the SID currently being searched for and the exact permission bit mask being search for), the grant or
-     * deny flag for that ACE will prevail. If the ACE specifies to grant access, the method will return
-     * <code>true</code>. If the ACE specifies to deny access, the loop will stop and the next <code>permission</code>
-     * iteration will be performed. If each permission indicates to deny access, the first deny ACE found will be
-     * considered the reason for the failure (as it was the first match found, and is therefore the one most logically
-     * requiring changes - although not always). If absolutely no matching ACE was found at all for any permission,
-     * the parent ACL will be tried (provided that there is a parent and {@link Acl#isEntriesInheriting()} is
-     * <code>true</code>. The parent ACL will also scan its parent and so on. If ultimately no matching ACE is found,
-     * a <code>NotFoundException</code> will be thrown and the caller will need to decide how to handle the permission
-     * check. Similarly, if any of the SID arguments presented to the method were not loaded by the ACL,
-     * <code>UnloadedSidException</code> will be thrown.
-     *
-     * @param permission the exact permissions to scan for (order is important)
-     * @param sids the exact SIDs to scan for (order is important)
-     * @param administrativeMode if <code>true</code> denotes the query is for administrative purposes and no auditing
-     *        will be undertaken
-     *
-     * @return <code>true</code> if one of the permissions has been granted, <code>false</code> if one of the
-     *         permissions has been specifically revoked
-     *
-     * @throws NotFoundException if an exact ACE for one of the permission bit masks and SID combination could not be
-     *         found
-     */
-    public boolean isGranted(Acl acl, List<Permission> permission, List<Sid> sids, boolean administrativeMode)
-            throws NotFoundException {
-
-        final List<AccessControlEntry> aces = acl.getEntries();
-
-        AccessControlEntry firstRejection = null;
-
-        for (Permission p : permission) {
-            for (Sid sid: sids) {
-                // Attempt to find exact match for this permission mask and SID
-                boolean scanNextSid = true;
-
-                for (AccessControlEntry ace : aces ) {
-
-                    if ((ace.getPermission().getMask() == p.getMask()) && ace.getSid().equals(sid)) {
-                        // Found a matching ACE, so its authorization decision will prevail
-                        if (ace.isGranting()) {
-                            // Success
-                            if (!administrativeMode) {
-                                auditLogger.logIfNeeded(true, ace);
-                            }
-
-                            return true;
-                        }
-
-                        // Failure for this permission, so stop search
-                        // We will see if they have a different permission
-                        // (this permission is 100% rejected for this SID)
-                        if (firstRejection == null) {
-                            // Store first rejection for auditing reasons
-                            firstRejection = ace;
-                        }
-
-                        scanNextSid = false; // helps break the loop
-
-                        break; // exit aces loop
-                    }
-                }
-
-                if (!scanNextSid) {
-                    break; // exit SID for loop (now try next permission)
-                }
-            }
-        }
-
-        if (firstRejection != null) {
-            // We found an ACE to reject the request at this point, as no
-            // other ACEs were found that granted a different permission
-            if (!administrativeMode) {
-                auditLogger.logIfNeeded(false, firstRejection);
-            }
-
-            return false;
-        }
-
-        // No matches have been found so far
-        if (acl.isEntriesInheriting() && (acl.getParentAcl() != null)) {
-            // We have a parent, so let them try to find a matching ACE
-            return acl.getParentAcl().isGranted(permission, sids, false);
-        } else {
-            // We either have no parent, or we're the uppermost parent
-            throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs");
-        }
-    }
+	private final transient AuditLogger auditLogger;
+
+	/**
+	 * Creates an instance with the logger which will be used to record granting and
+	 * denial of requested permissions.
+	 */
+	public DefaultPermissionGrantingStrategy(AuditLogger auditLogger) {
+		Assert.notNull(auditLogger, "auditLogger cannot be null");
+		this.auditLogger = auditLogger;
+	}
+
+	/**
+	 * Determines authorization. The order of the <code>permission</code> and
+	 * <code>sid</code> arguments is <em>extremely important</em>! The method will iterate
+	 * through each of the <code>permission</code>s in the order specified. For each
+	 * iteration, all of the <code>sid</code>s will be considered, again in the order they
+	 * are presented. A search will then be performed for the first
+	 * {@link AccessControlEntry} object that directly matches that
+	 * <code>permission:sid</code> combination. When the <em>first full match</em> is
+	 * found (ie an ACE that has the SID currently being searched for and the exact
+	 * permission bit mask being search for), the grant or deny flag for that ACE will
+	 * prevail. If the ACE specifies to grant access, the method will return
+	 * <code>true</code>. If the ACE specifies to deny access, the loop will stop and the
+	 * next <code>permission</code> iteration will be performed. If each permission
+	 * indicates to deny access, the first deny ACE found will be considered the reason
+	 * for the failure (as it was the first match found, and is therefore the one most
+	 * logically requiring changes - although not always). If absolutely no matching ACE
+	 * was found at all for any permission, the parent ACL will be tried (provided that
+	 * there is a parent and {@link Acl#isEntriesInheriting()} is <code>true</code>. The
+	 * parent ACL will also scan its parent and so on. If ultimately no matching ACE is
+	 * found, a <code>NotFoundException</code> will be thrown and the caller will need to
+	 * decide how to handle the permission check. Similarly, if any of the SID arguments
+	 * presented to the method were not loaded by the ACL,
+	 * <code>UnloadedSidException</code> will be thrown.
+	 *
+	 * @param permission the exact permissions to scan for (order is important)
+	 * @param sids the exact SIDs to scan for (order is important)
+	 * @param administrativeMode if <code>true</code> denotes the query is for
+	 * administrative purposes and no auditing will be undertaken
+	 *
+	 * @return <code>true</code> if one of the permissions has been granted,
+	 * <code>false</code> if one of the permissions has been specifically revoked
+	 *
+	 * @throws NotFoundException if an exact ACE for one of the permission bit masks and
+	 * SID combination could not be found
+	 */
+	public boolean isGranted(Acl acl, List<Permission> permission, List<Sid> sids,
+			boolean administrativeMode) throws NotFoundException {
+
+		final List<AccessControlEntry> aces = acl.getEntries();
+
+		AccessControlEntry firstRejection = null;
+
+		for (Permission p : permission) {
+			for (Sid sid : sids) {
+				// Attempt to find exact match for this permission mask and SID
+				boolean scanNextSid = true;
+
+				for (AccessControlEntry ace : aces) {
+
+					if ((ace.getPermission().getMask() == p.getMask())
+							&& ace.getSid().equals(sid)) {
+						// Found a matching ACE, so its authorization decision will
+						// prevail
+						if (ace.isGranting()) {
+							// Success
+							if (!administrativeMode) {
+								auditLogger.logIfNeeded(true, ace);
+							}
+
+							return true;
+						}
+
+						// Failure for this permission, so stop search
+						// We will see if they have a different permission
+						// (this permission is 100% rejected for this SID)
+						if (firstRejection == null) {
+							// Store first rejection for auditing reasons
+							firstRejection = ace;
+						}
+
+						scanNextSid = false; // helps break the loop
+
+						break; // exit aces loop
+					}
+				}
+
+				if (!scanNextSid) {
+					break; // exit SID for loop (now try next permission)
+				}
+			}
+		}
+
+		if (firstRejection != null) {
+			// We found an ACE to reject the request at this point, as no
+			// other ACEs were found that granted a different permission
+			if (!administrativeMode) {
+				auditLogger.logIfNeeded(false, firstRejection);
+			}
+
+			return false;
+		}
+
+		// No matches have been found so far
+		if (acl.isEntriesInheriting() && (acl.getParentAcl() != null)) {
+			// We have a parent, so let them try to find a matching ACE
+			return acl.getParentAcl().isGranted(permission, sids, false);
+		}
+		else {
+			// We either have no parent, or we're the uppermost parent
+			throw new NotFoundException(
+					"Unable to locate a matching ACE for passed permissions and SIDs");
+		}
+	}
 
 }

+ 124 - 113
acl/src/main/java/org/springframework/security/acls/domain/EhCacheBasedAclCache.java

@@ -27,124 +27,135 @@ import org.springframework.security.acls.model.PermissionGrantingStrategy;
 import org.springframework.security.util.FieldUtils;
 import org.springframework.util.Assert;
 
-
 /**
  * Simple implementation of {@link AclCache} that delegates to EH-CACHE.
  * <p>
- * Designed to handle the transient fields in {@link AclImpl}. Note that this implementation assumes all
- * {@link AclImpl} instances share the same {@link PermissionGrantingStrategy} and {@link AclAuthorizationStrategy}
- * instances.
+ * Designed to handle the transient fields in {@link AclImpl}. Note that this
+ * implementation assumes all {@link AclImpl} instances share the same
+ * {@link PermissionGrantingStrategy} and {@link AclAuthorizationStrategy} instances.
  *
  * @author Ben Alex
  */
 public class EhCacheBasedAclCache implements AclCache {
-    //~ Instance fields ================================================================================================
-
-    private final Ehcache cache;
-    private PermissionGrantingStrategy permissionGrantingStrategy;
-    private AclAuthorizationStrategy aclAuthorizationStrategy;
-
-    //~ Constructors ===================================================================================================
-
-    public EhCacheBasedAclCache(Ehcache cache, PermissionGrantingStrategy permissionGrantingStrategy,
-            AclAuthorizationStrategy aclAuthorizationStrategy) {
-        Assert.notNull(cache, "Cache required");
-        Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required");
-        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
-        this.cache = cache;
-        this.permissionGrantingStrategy = permissionGrantingStrategy;
-        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public void evictFromCache(Serializable pk) {
-        Assert.notNull(pk, "Primary key (identifier) required");
-
-        MutableAcl acl = getFromCache(pk);
-
-        if (acl != null) {
-            cache.remove(acl.getId());
-            cache.remove(acl.getObjectIdentity());
-        }
-    }
-
-    public void evictFromCache(ObjectIdentity objectIdentity) {
-        Assert.notNull(objectIdentity, "ObjectIdentity required");
-
-        MutableAcl acl = getFromCache(objectIdentity);
-
-        if (acl != null) {
-            cache.remove(acl.getId());
-            cache.remove(acl.getObjectIdentity());
-        }
-    }
-
-    public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
-        Assert.notNull(objectIdentity, "ObjectIdentity required");
-
-        Element element = null;
-
-        try {
-            element = cache.get(objectIdentity);
-        } catch (CacheException ignored) {}
-
-        if (element == null) {
-            return null;
-        }
-
-        return initializeTransientFields((MutableAcl)element.getValue());
-    }
-
-    public MutableAcl getFromCache(Serializable pk) {
-        Assert.notNull(pk, "Primary key (identifier) required");
-
-        Element element = null;
-
-        try {
-            element = cache.get(pk);
-        } catch (CacheException ignored) {}
-
-        if (element == null) {
-            return null;
-        }
-
-        return initializeTransientFields((MutableAcl) element.getValue());
-    }
-
-    public void putInCache(MutableAcl acl) {
-        Assert.notNull(acl, "Acl required");
-        Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
-        Assert.notNull(acl.getId(), "ID required");
-
-        if (this.aclAuthorizationStrategy == null) {
-            if (acl instanceof AclImpl) {
-                this.aclAuthorizationStrategy = (AclAuthorizationStrategy) FieldUtils.getProtectedFieldValue("aclAuthorizationStrategy", acl);
-                this.permissionGrantingStrategy = (PermissionGrantingStrategy) FieldUtils.getProtectedFieldValue("permissionGrantingStrategy", acl);
-            }
-        }
-
-        if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
-            putInCache((MutableAcl) acl.getParentAcl());
-        }
-
-        cache.put(new Element(acl.getObjectIdentity(), acl));
-        cache.put(new Element(acl.getId(), acl));
-    }
-
-    private MutableAcl initializeTransientFields(MutableAcl value) {
-        if (value instanceof AclImpl) {
-            FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value, this.aclAuthorizationStrategy);
-            FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value, this.permissionGrantingStrategy);
-        }
-
-        if (value.getParentAcl() != null) {
-            initializeTransientFields((MutableAcl) value.getParentAcl());
-        }
-        return value;
-    }
-
-    public void clearCache() {
-        cache.removeAll();
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final Ehcache cache;
+	private PermissionGrantingStrategy permissionGrantingStrategy;
+	private AclAuthorizationStrategy aclAuthorizationStrategy;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public EhCacheBasedAclCache(Ehcache cache,
+			PermissionGrantingStrategy permissionGrantingStrategy,
+			AclAuthorizationStrategy aclAuthorizationStrategy) {
+		Assert.notNull(cache, "Cache required");
+		Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required");
+		Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+		this.cache = cache;
+		this.permissionGrantingStrategy = permissionGrantingStrategy;
+		this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void evictFromCache(Serializable pk) {
+		Assert.notNull(pk, "Primary key (identifier) required");
+
+		MutableAcl acl = getFromCache(pk);
+
+		if (acl != null) {
+			cache.remove(acl.getId());
+			cache.remove(acl.getObjectIdentity());
+		}
+	}
+
+	public void evictFromCache(ObjectIdentity objectIdentity) {
+		Assert.notNull(objectIdentity, "ObjectIdentity required");
+
+		MutableAcl acl = getFromCache(objectIdentity);
+
+		if (acl != null) {
+			cache.remove(acl.getId());
+			cache.remove(acl.getObjectIdentity());
+		}
+	}
+
+	public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
+		Assert.notNull(objectIdentity, "ObjectIdentity required");
+
+		Element element = null;
+
+		try {
+			element = cache.get(objectIdentity);
+		}
+		catch (CacheException ignored) {
+		}
+
+		if (element == null) {
+			return null;
+		}
+
+		return initializeTransientFields((MutableAcl) element.getValue());
+	}
+
+	public MutableAcl getFromCache(Serializable pk) {
+		Assert.notNull(pk, "Primary key (identifier) required");
+
+		Element element = null;
+
+		try {
+			element = cache.get(pk);
+		}
+		catch (CacheException ignored) {
+		}
+
+		if (element == null) {
+			return null;
+		}
+
+		return initializeTransientFields((MutableAcl) element.getValue());
+	}
+
+	public void putInCache(MutableAcl acl) {
+		Assert.notNull(acl, "Acl required");
+		Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
+		Assert.notNull(acl.getId(), "ID required");
+
+		if (this.aclAuthorizationStrategy == null) {
+			if (acl instanceof AclImpl) {
+				this.aclAuthorizationStrategy = (AclAuthorizationStrategy) FieldUtils
+						.getProtectedFieldValue("aclAuthorizationStrategy", acl);
+				this.permissionGrantingStrategy = (PermissionGrantingStrategy) FieldUtils
+						.getProtectedFieldValue("permissionGrantingStrategy", acl);
+			}
+		}
+
+		if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
+			putInCache((MutableAcl) acl.getParentAcl());
+		}
+
+		cache.put(new Element(acl.getObjectIdentity(), acl));
+		cache.put(new Element(acl.getId(), acl));
+	}
+
+	private MutableAcl initializeTransientFields(MutableAcl value) {
+		if (value instanceof AclImpl) {
+			FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value,
+					this.aclAuthorizationStrategy);
+			FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value,
+					this.permissionGrantingStrategy);
+		}
+
+		if (value.getParentAcl() != null) {
+			initializeTransientFields((MutableAcl) value.getParentAcl());
+		}
+		return value;
+	}
+
+	public void clearCache() {
+		cache.removeAll();
+	}
 }

+ 42 - 34
acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java

@@ -19,53 +19,61 @@ import org.springframework.security.core.GrantedAuthority;
 
 import org.springframework.util.Assert;
 
-
 /**
- * Represents a <code>GrantedAuthority</code> as a <code>Sid</code>.<p>This is a basic implementation that simply
- * uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal objects may
- * wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
+ * Represents a <code>GrantedAuthority</code> as a <code>Sid</code>.
+ * <p>
+ * This is a basic implementation that simply uses the <code>String</code>-based principal
+ * for <code>Sid</code> comparison. More complex principal objects may wish to provide an
+ * alternative <code>Sid</code> implementation that uses some other identifier.
+ * </p>
  *
  * @author Ben Alex
  */
 public class GrantedAuthoritySid implements Sid {
-    //~ Instance fields ================================================================================================
+	// ~ Instance fields
+	// ================================================================================================
 
-    private final String grantedAuthority;
+	private final String grantedAuthority;
 
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    public GrantedAuthoritySid(String grantedAuthority) {
-        Assert.hasText(grantedAuthority, "GrantedAuthority required");
-        this.grantedAuthority = grantedAuthority;
-    }
+	public GrantedAuthoritySid(String grantedAuthority) {
+		Assert.hasText(grantedAuthority, "GrantedAuthority required");
+		this.grantedAuthority = grantedAuthority;
+	}
 
-    public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
-        Assert.notNull(grantedAuthority, "GrantedAuthority required");
-        Assert.notNull(grantedAuthority.getAuthority(),
-            "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
-        this.grantedAuthority = grantedAuthority.getAuthority();
-    }
+	public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
+		Assert.notNull(grantedAuthority, "GrantedAuthority required");
+		Assert.notNull(
+				grantedAuthority.getAuthority(),
+				"This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
+		this.grantedAuthority = grantedAuthority.getAuthority();
+	}
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public boolean equals(Object object) {
-        if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
-            return false;
-        }
+	public boolean equals(Object object) {
+		if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
+			return false;
+		}
 
-        // Delegate to getGrantedAuthority() to perform actual comparison (both should be identical)
-        return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority());
-    }
+		// Delegate to getGrantedAuthority() to perform actual comparison (both should be
+		// identical)
+		return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(
+				this.getGrantedAuthority());
+	}
 
-    public int hashCode() {
-        return this.getGrantedAuthority().hashCode();
-    }
+	public int hashCode() {
+		return this.getGrantedAuthority().hashCode();
+	}
 
-    public String getGrantedAuthority() {
-        return grantedAuthority;
-    }
+	public String getGrantedAuthority() {
+		return grantedAuthority;
+	}
 
-    public String toString() {
-        return "GrantedAuthoritySid[" + this.grantedAuthority + "]";
-    }
+	public String toString() {
+		return "GrantedAuthoritySid[" + this.grantedAuthority + "]";
+	}
 }

+ 20 - 19
acl/src/main/java/org/springframework/security/acls/domain/IdentityUnavailableException.java

@@ -20,25 +20,26 @@ package org.springframework.security.acls.domain;
  * @author Ben Alex
  */
 public class IdentityUnavailableException extends RuntimeException {
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    /**
-     * Constructs an <code>IdentityUnavailableException</code> with the specified message.
-     *
-     * @param msg the detail message
-     */
-    public IdentityUnavailableException(String msg) {
-        super(msg);
-    }
+	/**
+	 * Constructs an <code>IdentityUnavailableException</code> with the specified message.
+	 *
+	 * @param msg the detail message
+	 */
+	public IdentityUnavailableException(String msg) {
+		super(msg);
+	}
 
-    /**
-     * Constructs an <code>IdentityUnavailableException</code> with the specified message
-     * and root cause.
-     *
-     * @param msg the detail message
-     * @param t root cause
-     */
-    public IdentityUnavailableException(String msg, Throwable t) {
-        super(msg, t);
-    }
+	/**
+	 * Constructs an <code>IdentityUnavailableException</code> with the specified message
+	 * and root cause.
+	 *
+	 * @param msg the detail message
+	 * @param t root cause
+	 */
+	public IdentityUnavailableException(String msg, Throwable t) {
+		super(msg, t);
+	}
 }

+ 134 - 126
acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityImpl.java

@@ -21,137 +21,145 @@ import org.springframework.security.acls.model.ObjectIdentity;
 import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
 
-
 /**
  * Simple implementation of {@link ObjectIdentity}.
  * <p>
- * Uses <code>String</code>s to store the identity of the domain object instance. Also offers a constructor that uses
- * reflection to build the identity information.
+ * Uses <code>String</code>s to store the identity of the domain object instance. Also
+ * offers a constructor that uses reflection to build the identity information.
  *
  * @author Ben Alex
  */
 public class ObjectIdentityImpl implements ObjectIdentity {
-    //~ Instance fields ================================================================================================
-
-    private final String type;
-    private Serializable identifier;
-
-    //~ Constructors ===================================================================================================
-
-    public ObjectIdentityImpl(String type, Serializable identifier) {
-        Assert.hasText(type, "Type required");
-        Assert.notNull(identifier, "identifier required");
-
-        this.identifier = identifier;
-        this.type = type;
-    }
-
-    /**
-     * Constructor which uses the name of the supplied class as the <tt>type</tt> property.
-     */
-    public ObjectIdentityImpl(Class<?> javaType, Serializable identifier) {
-        Assert.notNull(javaType, "Java Type required");
-        Assert.notNull(identifier, "identifier required");
-        this.type = javaType.getName();
-        this.identifier = identifier;
-    }
-
-    /**
-     * Creates the <code>ObjectIdentityImpl</code> based on the passed
-     * object instance. The passed object must provide a <code>getId()</code>
-     * method, otherwise an exception will be thrown.
-     * <p>
-     * The class name of the object passed will be considered the {@link #type}, so if more control is required,
-     * a different constructor should be used.
-     *
-     * @param object the domain object instance to create an identity for.
-     *
-     * @throws IdentityUnavailableException if identity could not be extracted
-     */
-    public ObjectIdentityImpl(Object object) throws IdentityUnavailableException {
-        Assert.notNull(object, "object cannot be null");
-
-        Class<?> typeClass = ClassUtils.getUserClass(object.getClass());
-        type = typeClass.getName();
-
-        Object result;
-
-        try {
-            Method method = typeClass.getMethod("getId", new Class[] {});
-            result = method.invoke(object);
-        } catch (Exception e) {
-            throw new IdentityUnavailableException("Could not extract identity from object " + object, e);
-        }
-
-        Assert.notNull(result, "getId() is required to return a non-null value");
-        Assert.isInstanceOf(Serializable.class, result, "Getter must provide a return value of type Serializable");
-        this.identifier = (Serializable) result;
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Important so caching operates properly.
-     * <p>
-     * Considers an object of the same class equal if it has the same <code>classname</code> and
-     * <code>id</code> properties.
-     * <p>
-     * Numeric identities (Integer and Long values) are considered equal if they are numerically equal. Other
-     * serializable types are evaluated using a simple equality.
-     *
-     * @param arg0 object to compare
-     *
-     * @return <code>true</code> if the presented object matches this object
-     */
-    public boolean equals(Object arg0) {
-        if (arg0 == null || !(arg0 instanceof ObjectIdentityImpl)) {
-            return false;
-        }
-
-        ObjectIdentityImpl other = (ObjectIdentityImpl) arg0;
-
-        if (identifier instanceof Number && other.identifier instanceof Number) {
-            // Integers and Longs with same value should be considered equal
-            if (((Number)identifier).longValue() != ((Number)other.identifier).longValue()) {
-                return false;
-            }
-        } else {
-            // Use plain equality for other serializable types
-            if (!identifier.equals(other.identifier)) {
-                return false;
-            }
-        }
-
-        return type.equals(other.type);
-    }
-
-    public Serializable getIdentifier() {
-        return identifier;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    /**
-     * Important so caching operates properly.
-     *
-     * @return the hash
-     */
-    public int hashCode() {
-        int code = 31;
-        code ^= this.type.hashCode();
-        code ^= this.identifier.hashCode();
-
-        return code;
-    }
-
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(this.getClass().getName()).append("[");
-        sb.append("Type: ").append(this.type);
-        sb.append("; Identifier: ").append(this.identifier).append("]");
-
-        return sb.toString();
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final String type;
+	private Serializable identifier;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public ObjectIdentityImpl(String type, Serializable identifier) {
+		Assert.hasText(type, "Type required");
+		Assert.notNull(identifier, "identifier required");
+
+		this.identifier = identifier;
+		this.type = type;
+	}
+
+	/**
+	 * Constructor which uses the name of the supplied class as the <tt>type</tt>
+	 * property.
+	 */
+	public ObjectIdentityImpl(Class<?> javaType, Serializable identifier) {
+		Assert.notNull(javaType, "Java Type required");
+		Assert.notNull(identifier, "identifier required");
+		this.type = javaType.getName();
+		this.identifier = identifier;
+	}
+
+	/**
+	 * Creates the <code>ObjectIdentityImpl</code> based on the passed object instance.
+	 * The passed object must provide a <code>getId()</code> method, otherwise an
+	 * exception will be thrown.
+	 * <p>
+	 * The class name of the object passed will be considered the {@link #type}, so if
+	 * more control is required, a different constructor should be used.
+	 *
+	 * @param object the domain object instance to create an identity for.
+	 *
+	 * @throws IdentityUnavailableException if identity could not be extracted
+	 */
+	public ObjectIdentityImpl(Object object) throws IdentityUnavailableException {
+		Assert.notNull(object, "object cannot be null");
+
+		Class<?> typeClass = ClassUtils.getUserClass(object.getClass());
+		type = typeClass.getName();
+
+		Object result;
+
+		try {
+			Method method = typeClass.getMethod("getId", new Class[] {});
+			result = method.invoke(object);
+		}
+		catch (Exception e) {
+			throw new IdentityUnavailableException(
+					"Could not extract identity from object " + object, e);
+		}
+
+		Assert.notNull(result, "getId() is required to return a non-null value");
+		Assert.isInstanceOf(Serializable.class, result,
+				"Getter must provide a return value of type Serializable");
+		this.identifier = (Serializable) result;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 * Important so caching operates properly.
+	 * <p>
+	 * Considers an object of the same class equal if it has the same
+	 * <code>classname</code> and <code>id</code> properties.
+	 * <p>
+	 * Numeric identities (Integer and Long values) are considered equal if they are
+	 * numerically equal. Other serializable types are evaluated using a simple equality.
+	 *
+	 * @param arg0 object to compare
+	 *
+	 * @return <code>true</code> if the presented object matches this object
+	 */
+	public boolean equals(Object arg0) {
+		if (arg0 == null || !(arg0 instanceof ObjectIdentityImpl)) {
+			return false;
+		}
+
+		ObjectIdentityImpl other = (ObjectIdentityImpl) arg0;
+
+		if (identifier instanceof Number && other.identifier instanceof Number) {
+			// Integers and Longs with same value should be considered equal
+			if (((Number) identifier).longValue() != ((Number) other.identifier)
+					.longValue()) {
+				return false;
+			}
+		}
+		else {
+			// Use plain equality for other serializable types
+			if (!identifier.equals(other.identifier)) {
+				return false;
+			}
+		}
+
+		return type.equals(other.type);
+	}
+
+	public Serializable getIdentifier() {
+		return identifier;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	/**
+	 * Important so caching operates properly.
+	 *
+	 * @return the hash
+	 */
+	public int hashCode() {
+		int code = 31;
+		code ^= this.type.hashCode();
+		code ^= this.identifier.hashCode();
+
+		return code;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(this.getClass().getName()).append("[");
+		sb.append("Type: ").append(this.type);
+		sb.append("; Identifier: ").append(this.identifier).append("]");
+
+		return sb.toString();
+	}
 }

+ 13 - 10
acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImpl.java

@@ -22,19 +22,22 @@ import org.springframework.security.acls.model.ObjectIdentityGenerator;
 import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy;
 
 /**
- * Basic implementation of {@link ObjectIdentityRetrievalStrategy} and <tt>ObjectIdentityGenerator</tt>
- * that uses the constructors of {@link ObjectIdentityImpl} to create the {@link ObjectIdentity}.
+ * Basic implementation of {@link ObjectIdentityRetrievalStrategy} and
+ * <tt>ObjectIdentityGenerator</tt> that uses the constructors of
+ * {@link ObjectIdentityImpl} to create the {@link ObjectIdentity}.
  *
  * @author Ben Alex
  */
-public class ObjectIdentityRetrievalStrategyImpl implements ObjectIdentityRetrievalStrategy, ObjectIdentityGenerator {
-    //~ Methods ========================================================================================================
+public class ObjectIdentityRetrievalStrategyImpl implements
+		ObjectIdentityRetrievalStrategy, ObjectIdentityGenerator {
+	// ~ Methods
+	// ========================================================================================================
 
-    public ObjectIdentity getObjectIdentity(Object domainObject) {
-        return new ObjectIdentityImpl(domainObject);
-    }
+	public ObjectIdentity getObjectIdentity(Object domainObject) {
+		return new ObjectIdentityImpl(domainObject);
+	}
 
-    public ObjectIdentity createObjectIdentity(Serializable id, String type) {
-        return new ObjectIdentityImpl(type, id);
-    }
+	public ObjectIdentity createObjectIdentity(Serializable id, String type) {
+		return new ObjectIdentityImpl(type, id);
+	}
 }

+ 13 - 14
acl/src/main/java/org/springframework/security/acls/domain/PermissionFactory.java

@@ -5,7 +5,8 @@ import java.util.List;
 import org.springframework.security.acls.model.Permission;
 
 /**
- * Provides a simple mechanism to retrieve {@link Permission} instances from integer masks.
+ * Provides a simple mechanism to retrieve {@link Permission} instances from integer
+ * masks.
  *
  * @author Ben Alex
  * @since 2.0.3
@@ -13,19 +14,17 @@ import org.springframework.security.acls.model.Permission;
  */
 public interface PermissionFactory {
 
-    /**
-     * Dynamically creates a <code>CumulativePermission</code> or <code>BasePermission</code> representing the
-     * active bits in the passed mask.
-     *
-     * @param mask to build
-     *
-     * @return a Permission representing the requested object
-     */
-    Permission buildFromMask(int mask);
+	/**
+	 * Dynamically creates a <code>CumulativePermission</code> or
+	 * <code>BasePermission</code> representing the active bits in the passed mask.
+	 *
+	 * @param mask to build
+	 *
+	 * @return a Permission representing the requested object
+	 */
+	Permission buildFromMask(int mask);
 
+	Permission buildFromName(String name);
 
-    Permission buildFromName(String name);
-
-
-    List<Permission> buildFromNames(List<String> names);
+	List<Permission> buildFromNames(List<String> names);
 }

+ 55 - 49
acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java

@@ -14,64 +14,70 @@
  */
 package org.springframework.security.acls.domain;
 
-
 import org.springframework.security.acls.model.Sid;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import org.springframework.util.Assert;
 
-
 /**
- * Represents an <code>Authentication.getPrincipal()</code> as a <code>Sid</code>.<p>This is a basic implementation
- * that simply uses the <code>String</code>-based principal for <code>Sid</code> comparison. More complex principal
- * objects may wish to provide an alternative <code>Sid</code> implementation that uses some other identifier.</p>
+ * Represents an <code>Authentication.getPrincipal()</code> as a <code>Sid</code>.
+ * <p>
+ * This is a basic implementation that simply uses the <code>String</code>-based principal
+ * for <code>Sid</code> comparison. More complex principal objects may wish to provide an
+ * alternative <code>Sid</code> implementation that uses some other identifier.
+ * </p>
  *
  * @author Ben Alex
  */
 public class PrincipalSid implements Sid {
-    //~ Instance fields ================================================================================================
-
-    private final String principal;
-
-    //~ Constructors ===================================================================================================
-
-    public PrincipalSid(String principal) {
-        Assert.hasText(principal, "Principal required");
-        this.principal = principal;
-    }
-
-    public PrincipalSid(Authentication authentication) {
-        Assert.notNull(authentication, "Authentication required");
-        Assert.notNull(authentication.getPrincipal(), "Principal required");
-
-        if (authentication.getPrincipal() instanceof UserDetails) {
-            this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
-        } else {
-            this.principal = authentication.getPrincipal().toString();
-        }
-    }
-
-    //~ Methods ========================================================================================================
-
-    public boolean equals(Object object) {
-        if ((object == null) || !(object instanceof PrincipalSid)) {
-            return false;
-        }
-
-        // Delegate to getPrincipal() to perform actual comparison (both should be identical)
-        return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal());
-    }
-
-    public int hashCode() {
-        return this.getPrincipal().hashCode();
-    }
-
-    public String getPrincipal() {
-        return principal;
-    }
-
-    public String toString() {
-        return "PrincipalSid[" + this.principal + "]";
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final String principal;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public PrincipalSid(String principal) {
+		Assert.hasText(principal, "Principal required");
+		this.principal = principal;
+	}
+
+	public PrincipalSid(Authentication authentication) {
+		Assert.notNull(authentication, "Authentication required");
+		Assert.notNull(authentication.getPrincipal(), "Principal required");
+
+		if (authentication.getPrincipal() instanceof UserDetails) {
+			this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
+		}
+		else {
+			this.principal = authentication.getPrincipal().toString();
+		}
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public boolean equals(Object object) {
+		if ((object == null) || !(object instanceof PrincipalSid)) {
+			return false;
+		}
+
+		// Delegate to getPrincipal() to perform actual comparison (both should be
+		// identical)
+		return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal());
+	}
+
+	public int hashCode() {
+		return this.getPrincipal().hashCode();
+	}
+
+	public String getPrincipal() {
+		return principal;
+	}
+
+	public String toString() {
+		return "PrincipalSid[" + this.principal + "]";
+	}
 }

+ 25 - 21
acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java

@@ -28,38 +28,42 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.util.Assert;
 
 /**
- * Basic implementation of {@link SidRetrievalStrategy} that creates a {@link Sid} for the principal, as well as
- * every granted authority the principal holds. Can optionally have a <tt>RoleHierarchy</tt> injected in order to
- * determine the extended list of authorities that the principal is assigned.
+ * Basic implementation of {@link SidRetrievalStrategy} that creates a {@link Sid} for the
+ * principal, as well as every granted authority the principal holds. Can optionally have
+ * a <tt>RoleHierarchy</tt> injected in order to determine the extended list of
+ * authorities that the principal is assigned.
  * <p>
- * The returned array will always contain the {@link PrincipalSid} before any {@link GrantedAuthoritySid} elements.
+ * The returned array will always contain the {@link PrincipalSid} before any
+ * {@link GrantedAuthoritySid} elements.
  *
  * @author Ben Alex
  */
 public class SidRetrievalStrategyImpl implements SidRetrievalStrategy {
 
-    private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
+	private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
 
-    public SidRetrievalStrategyImpl() {
-    }
+	public SidRetrievalStrategyImpl() {
+	}
 
-    public SidRetrievalStrategyImpl(RoleHierarchy roleHierarchy) {
-        Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
-        this.roleHierarchy = roleHierarchy;
-    }
+	public SidRetrievalStrategyImpl(RoleHierarchy roleHierarchy) {
+		Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
+		this.roleHierarchy = roleHierarchy;
+	}
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public List<Sid> getSids(Authentication authentication) {
-        Collection<? extends GrantedAuthority> authorities = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());
-        List<Sid> sids = new ArrayList<Sid>(authorities.size() + 1);
+	public List<Sid> getSids(Authentication authentication) {
+		Collection<? extends GrantedAuthority> authorities = roleHierarchy
+				.getReachableGrantedAuthorities(authentication.getAuthorities());
+		List<Sid> sids = new ArrayList<Sid>(authorities.size() + 1);
 
-        sids.add(new PrincipalSid(authentication));
+		sids.add(new PrincipalSid(authentication));
 
-        for (GrantedAuthority authority : authorities) {
-            sids.add(new GrantedAuthoritySid(authority));
-        }
+		for (GrantedAuthority authority : authorities) {
+			sids.add(new GrantedAuthoritySid(authority));
+		}
 
-        return sids;
-    }
+		return sids;
+	}
 }

+ 103 - 95
acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java

@@ -25,106 +25,114 @@ import org.springframework.util.Assert;
 
 import java.io.Serializable;
 
-
 /**
- * Simple implementation of {@link org.springframework.security.acls.model.AclCache} that delegates to {@link Cache} implementation.
+ * Simple implementation of {@link org.springframework.security.acls.model.AclCache} that
+ * delegates to {@link Cache} implementation.
  * <p>
- * Designed to handle the transient fields in {@link org.springframework.security.acls.domain.AclImpl}. Note that this implementation assumes all
- * {@link org.springframework.security.acls.domain.AclImpl} instances share the same {@link org.springframework.security.acls.model.PermissionGrantingStrategy} and {@link org.springframework.security.acls.domain.AclAuthorizationStrategy}
- * instances.
+ * Designed to handle the transient fields in
+ * {@link org.springframework.security.acls.domain.AclImpl}. Note that this implementation
+ * assumes all {@link org.springframework.security.acls.domain.AclImpl} instances share
+ * the same {@link org.springframework.security.acls.model.PermissionGrantingStrategy} and
+ * {@link org.springframework.security.acls.domain.AclAuthorizationStrategy} instances.
  *
  * @author Marten Deinum
  * @since 3.2
  */
 public class SpringCacheBasedAclCache implements AclCache {
-    //~ Instance fields ================================================================================================
-
-    private final Cache cache;
-    private PermissionGrantingStrategy permissionGrantingStrategy;
-    private AclAuthorizationStrategy aclAuthorizationStrategy;
-
-    //~ Constructors ===================================================================================================
-
-    public SpringCacheBasedAclCache(Cache cache, PermissionGrantingStrategy permissionGrantingStrategy,
-                                    AclAuthorizationStrategy aclAuthorizationStrategy) {
-        Assert.notNull(cache, "Cache required");
-        Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required");
-        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
-        this.cache = cache;
-        this.permissionGrantingStrategy = permissionGrantingStrategy;
-        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public void evictFromCache(Serializable pk) {
-        Assert.notNull(pk, "Primary key (identifier) required");
-
-        MutableAcl acl = getFromCache(pk);
-
-        if (acl != null) {
-            cache.evict(acl.getId());
-            cache.evict(acl.getObjectIdentity());
-        }
-    }
-
-    public void evictFromCache(ObjectIdentity objectIdentity) {
-        Assert.notNull(objectIdentity, "ObjectIdentity required");
-
-        MutableAcl acl = getFromCache(objectIdentity);
-
-        if (acl != null) {
-            cache.evict(acl.getId());
-            cache.evict(acl.getObjectIdentity());
-        }
-    }
-
-    public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
-        Assert.notNull(objectIdentity, "ObjectIdentity required");
-        return getFromCache((Object)objectIdentity);
-    }
-
-    public MutableAcl getFromCache(Serializable pk) {
-        Assert.notNull(pk, "Primary key (identifier) required");
-        return getFromCache((Object)pk);
-    }
-
-    public void putInCache(MutableAcl acl) {
-        Assert.notNull(acl, "Acl required");
-        Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
-        Assert.notNull(acl.getId(), "ID required");
-
-        if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
-            putInCache((MutableAcl) acl.getParentAcl());
-        }
-
-        cache.put(acl.getObjectIdentity(), acl);
-        cache.put(acl.getId(), acl);
-    }
-
-    private MutableAcl getFromCache(Object key) {
-        Cache.ValueWrapper element = cache.get(key);
-
-        if (element == null) {
-            return null;
-        }
-
-        return initializeTransientFields((MutableAcl) element.get());
-    }
-
-    private MutableAcl initializeTransientFields(MutableAcl value) {
-        if (value instanceof AclImpl) {
-            FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value, this.aclAuthorizationStrategy);
-            FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value, this.permissionGrantingStrategy);
-        }
-
-        if (value.getParentAcl() != null) {
-            initializeTransientFields((MutableAcl) value.getParentAcl());
-        }
-        return value;
-    }
-
-    public void clearCache() {
-        cache.clear();
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final Cache cache;
+	private PermissionGrantingStrategy permissionGrantingStrategy;
+	private AclAuthorizationStrategy aclAuthorizationStrategy;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public SpringCacheBasedAclCache(Cache cache,
+			PermissionGrantingStrategy permissionGrantingStrategy,
+			AclAuthorizationStrategy aclAuthorizationStrategy) {
+		Assert.notNull(cache, "Cache required");
+		Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required");
+		Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+		this.cache = cache;
+		this.permissionGrantingStrategy = permissionGrantingStrategy;
+		this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void evictFromCache(Serializable pk) {
+		Assert.notNull(pk, "Primary key (identifier) required");
+
+		MutableAcl acl = getFromCache(pk);
+
+		if (acl != null) {
+			cache.evict(acl.getId());
+			cache.evict(acl.getObjectIdentity());
+		}
+	}
+
+	public void evictFromCache(ObjectIdentity objectIdentity) {
+		Assert.notNull(objectIdentity, "ObjectIdentity required");
+
+		MutableAcl acl = getFromCache(objectIdentity);
+
+		if (acl != null) {
+			cache.evict(acl.getId());
+			cache.evict(acl.getObjectIdentity());
+		}
+	}
+
+	public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
+		Assert.notNull(objectIdentity, "ObjectIdentity required");
+		return getFromCache((Object) objectIdentity);
+	}
+
+	public MutableAcl getFromCache(Serializable pk) {
+		Assert.notNull(pk, "Primary key (identifier) required");
+		return getFromCache((Object) pk);
+	}
+
+	public void putInCache(MutableAcl acl) {
+		Assert.notNull(acl, "Acl required");
+		Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
+		Assert.notNull(acl.getId(), "ID required");
+
+		if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
+			putInCache((MutableAcl) acl.getParentAcl());
+		}
+
+		cache.put(acl.getObjectIdentity(), acl);
+		cache.put(acl.getId(), acl);
+	}
+
+	private MutableAcl getFromCache(Object key) {
+		Cache.ValueWrapper element = cache.get(key);
+
+		if (element == null) {
+			return null;
+		}
+
+		return initializeTransientFields((MutableAcl) element.get());
+	}
+
+	private MutableAcl initializeTransientFields(MutableAcl value) {
+		if (value instanceof AclImpl) {
+			FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value,
+					this.aclAuthorizationStrategy);
+			FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value,
+					this.permissionGrantingStrategy);
+		}
+
+		if (value.getParentAcl() != null) {
+			initializeTransientFields((MutableAcl) value.getParentAcl());
+		}
+		return value;
+	}
+
+	public void clearCache() {
+		cache.clear();
+	}
 }

+ 637 - 587
acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java

@@ -55,600 +55,650 @@ import org.springframework.security.acls.model.UnloadedSidException;
 import org.springframework.security.util.FieldUtils;
 import org.springframework.util.Assert;
 
-
 /**
  * Performs lookups in a manner that is compatible with ANSI SQL.
  * <p>
- * NB: This implementation does attempt to provide reasonably optimised lookups - within the constraints of a normalised
- * database and standard ANSI SQL features. If you are willing to sacrifice either of these constraints
- * (e.g. use a particular database feature such as hierarchical queries or materalized views, or reduce normalisation)
- * you are likely to achieve better performance. In such situations you will need to provide your own custom
- * <code>LookupStrategy</code>. This class does not support subclassing, as it is likely to change in future releases
- * and therefore subclassing is unsupported.
+ * NB: This implementation does attempt to provide reasonably optimised lookups - within
+ * the constraints of a normalised database and standard ANSI SQL features. If you are
+ * willing to sacrifice either of these constraints (e.g. use a particular database
+ * feature such as hierarchical queries or materalized views, or reduce normalisation) you
+ * are likely to achieve better performance. In such situations you will need to provide
+ * your own custom <code>LookupStrategy</code>. This class does not support subclassing,
+ * as it is likely to change in future releases and therefore subclassing is unsupported.
  * <p>
- * There are two SQL queries executed, one in the <tt>lookupPrimaryKeys</tt> method and one in
- * <tt>lookupObjectIdentities</tt>. These are built from the same select and "order by" clause, using a different
- * where clause in each case. In order to use custom schema or column names, each of these SQL clauses can be
- * customized, but they must be consistent with each other and with the expected result set
- * generated by the the default values.
+ * There are two SQL queries executed, one in the <tt>lookupPrimaryKeys</tt> method and
+ * one in <tt>lookupObjectIdentities</tt>. These are built from the same select and
+ * "order by" clause, using a different where clause in each case. In order to use custom
+ * schema or column names, each of these SQL clauses can be customized, but they must be
+ * consistent with each other and with the expected result set generated by the the
+ * default values.
  *
  * @author Ben Alex
  */
 public class BasicLookupStrategy implements LookupStrategy {
 
-    public final static String DEFAULT_SELECT_CLAUSE = "select acl_object_identity.object_id_identity, "
-        + "acl_entry.ace_order,  "
-        + "acl_object_identity.id as acl_id, "
-        + "acl_object_identity.parent_object, "
-        + "acl_object_identity.entries_inheriting, "
-        + "acl_entry.id as ace_id, "
-        + "acl_entry.mask,  "
-        + "acl_entry.granting,  "
-        + "acl_entry.audit_success, "
-        + "acl_entry.audit_failure,  "
-        + "acl_sid.principal as ace_principal, "
-        + "acl_sid.sid as ace_sid,  "
-        + "acli_sid.principal as acl_principal, "
-        + "acli_sid.sid as acl_sid, "
-        + "acl_class.class "
-        + "from acl_object_identity "
-        + "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid "
-        + "left join acl_class on acl_class.id = acl_object_identity.object_id_class   "
-        + "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity "
-        + "left join acl_sid on acl_entry.sid = acl_sid.id  "
-        + "where ( ";
-
-    private final static String DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE = "(acl_object_identity.id = ?)";
-
-    private final static String DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE = "(acl_object_identity.object_id_identity = ? and acl_class.class = ?)";
-
-    public final static String DEFAULT_ORDER_BY_CLAUSE = ") order by acl_object_identity.object_id_identity"
-        + " asc, acl_entry.ace_order asc";
-
-    //~ Instance fields ================================================================================================
-
-    private final AclAuthorizationStrategy aclAuthorizationStrategy;
-    private PermissionFactory permissionFactory = new DefaultPermissionFactory();
-    private final AclCache aclCache;
-    private final PermissionGrantingStrategy grantingStrategy;
-    private final JdbcTemplate jdbcTemplate;
-    private int batchSize = 50;
-
-    private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces");
-    private final Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class, "acl");
-
-    // SQL Customization fields
-    private String selectClause = DEFAULT_SELECT_CLAUSE;
-    private String lookupPrimaryKeysWhereClause = DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE;
-    private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE;
-    private String orderByClause = DEFAULT_ORDER_BY_CLAUSE;
-
-    //~ Constructors ===================================================================================================
-
-    /**
-     * Constructor accepting mandatory arguments
-     *
-     * @param dataSource to access the database
-     * @param aclCache the cache where fully-loaded elements can be stored
-     * @param aclAuthorizationStrategy authorization strategy (required)
-     */
-    public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
-            AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) {
-        this(dataSource, aclCache, aclAuthorizationStrategy, new DefaultPermissionGrantingStrategy(auditLogger));
-    }
-
-    /**
-     * Creates a new instance
-     *
-     * @param dataSource to access the database
-     * @param aclCache the cache where fully-loaded elements can be stored
-     * @param aclAuthorizationStrategy authorization strategy (required)
-     * @param grantingStrategy the PermissionGrantingStrategy
-     */
-    public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
-            AclAuthorizationStrategy aclAuthorizationStrategy, PermissionGrantingStrategy grantingStrategy) {
-        Assert.notNull(dataSource, "DataSource required");
-        Assert.notNull(aclCache, "AclCache required");
-        Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
-        Assert.notNull(grantingStrategy, "grantingStrategy required");
-        jdbcTemplate = new JdbcTemplate(dataSource);
-        this.aclCache = aclCache;
-        this.aclAuthorizationStrategy = aclAuthorizationStrategy;
-        this.grantingStrategy = grantingStrategy;
-        fieldAces.setAccessible(true);
-        fieldAcl.setAccessible(true);
-
-    }
-
-    //~ Methods ========================================================================================================
-
-    private String computeRepeatingSql(String repeatingSql, int requiredRepetitions) {
-        assert requiredRepetitions > 0 : "requiredRepetitions must be > 0";
-
-        final String startSql = selectClause;
-
-        final String endSql = orderByClause;
-
-        StringBuilder sqlStringBldr =
-            new StringBuilder(startSql.length() + endSql.length() + requiredRepetitions * (repeatingSql.length() + 4));
-        sqlStringBldr.append(startSql);
-
-        for (int i = 1; i <= requiredRepetitions; i++) {
-            sqlStringBldr.append(repeatingSql);
-
-            if (i != requiredRepetitions) {
-                sqlStringBldr.append(" or ");
-            }
-        }
-
-        sqlStringBldr.append(endSql);
-
-        return sqlStringBldr.toString();
-    }
-
-    @SuppressWarnings("unchecked")
-    private List<AccessControlEntryImpl> readAces(AclImpl acl) {
-        try {
-            return (List<AccessControlEntryImpl>) fieldAces.get(acl);
-        } catch (IllegalAccessException e) {
-            throw new IllegalStateException("Could not obtain AclImpl.aces field", e);
-        }
-    }
-
-    private void setAclOnAce(AccessControlEntryImpl ace, AclImpl acl) {
-        try {
-            fieldAcl.set(ace, acl);
-        } catch (IllegalAccessException e) {
-            throw new IllegalStateException("Could not or set AclImpl on AccessControlEntryImpl fields", e);
-        }
-    }
-
-    private void setAces(AclImpl acl, List<AccessControlEntryImpl> aces) {
-        try {
-            fieldAces.set(acl, aces);
-        } catch (IllegalAccessException e) {
-            throw new IllegalStateException("Could not set AclImpl entries", e);
-        }
-    }
-
-    /**
-     * Locates the primary key IDs specified in "findNow", adding AclImpl instances with StubAclParents to the
-     * "acls" Map.
-     *
-     * @param acls the AclImpls (with StubAclParents)
-     * @param findNow Long-based primary keys to retrieve
-     * @param sids
-     */
-    private void lookupPrimaryKeys(final Map<Serializable, Acl> acls, final Set<Long> findNow, final List<Sid> sids) {
-        Assert.notNull(acls, "ACLs are required");
-        Assert.notEmpty(findNow, "Items to find now required");
-
-        String sql = computeRepeatingSql(lookupPrimaryKeysWhereClause, findNow.size());
-
-        Set<Long> parentsToLookup = jdbcTemplate.query(sql,
-            new PreparedStatementSetter() {
-                public void setValues(PreparedStatement ps) throws SQLException {
-                    int i = 0;
-
-                    for (Long toFind : findNow) {
-                        i++;
-                        ps.setLong(i, toFind);
-                    }
-                }
-            }, new ProcessResultSet(acls, sids));
-
-        // Lookup the parents, now that our JdbcTemplate has released the database connection (SEC-547)
-        if (parentsToLookup.size() > 0) {
-            lookupPrimaryKeys(acls, parentsToLookup, sids);
-        }
-    }
-
-    /**
-     * The main method.
-     * <p>
-     * WARNING: This implementation completely disregards the "sids" argument! Every item in the cache is expected to
-     * contain all SIDs. If you have serious performance needs (e.g. a very large number of
-     * SIDs per object identity), you'll probably want to develop a custom {@link LookupStrategy} implementation
-     * instead.
-     * <p>
-     * The implementation works in batch sizes specified by {@link #batchSize}.
-     *
-     * @param objects the identities to lookup (required)
-     * @param sids the SIDs for which identities are required (ignored by this implementation)
-     *
-     * @return a <tt>Map</tt> where keys represent the {@link ObjectIdentity} of the located {@link Acl} and values
-     *         are the located {@link Acl} (never <tt>null</tt> although some entries may be missing; this method
-     *         should not throw {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used
-     *         to automatically create entries if required)
-     */
-    public final Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) {
-        Assert.isTrue(batchSize >= 1, "BatchSize must be >= 1");
-        Assert.notEmpty(objects, "Objects to lookup required");
-
-        // Map<ObjectIdentity,Acl>
-        Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>(); // contains FULLY loaded Acl objects
-
-        Set<ObjectIdentity> currentBatchToLoad = new HashSet<ObjectIdentity>();
-
-        for (int i = 0; i < objects.size(); i++) {
-            final ObjectIdentity oid = objects.get(i);
-            boolean aclFound = false;
-
-            // Check we don't already have this ACL in the results
-            if (result.containsKey(oid)) {
-                aclFound = true;
-            }
-
-            // Check cache for the present ACL entry
-            if (!aclFound) {
-                Acl acl = aclCache.getFromCache(oid);
-
-                // Ensure any cached element supports all the requested SIDs
-                // (they should always, as our base impl doesn't filter on SID)
-                if (acl != null) {
-                    if (acl.isSidLoaded(sids)) {
-                        result.put(acl.getObjectIdentity(), acl);
-                        aclFound = true;
-                    } else {
-                        throw new IllegalStateException(
-                            "Error: SID-filtered element detected when implementation does not perform SID filtering "
-                                    + "- have you added something to the cache manually?");
-                    }
-                }
-            }
-
-            // Load the ACL from the database
-            if (!aclFound) {
-                currentBatchToLoad.add(oid);
-            }
-
-            // Is it time to load from JDBC the currentBatchToLoad?
-            if ((currentBatchToLoad.size() == this.batchSize) || ((i + 1) == objects.size())) {
-                if (currentBatchToLoad.size() > 0) {
-                    Map<ObjectIdentity, Acl> loadedBatch = lookupObjectIdentities(currentBatchToLoad, sids);
-
-                    // Add loaded batch (all elements 100% initialized) to results
-                    result.putAll(loadedBatch);
-
-                    // Add the loaded batch to the cache
-
-                    for (Acl loadedAcl : loadedBatch.values()) {
-                        aclCache.putInCache((AclImpl) loadedAcl);
-                    }
-
-                    currentBatchToLoad.clear();
-                }
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Looks up a batch of <code>ObjectIdentity</code>s directly from the database.
-     * <p>
-     * The caller is responsible for optimization issues, such as selecting the identities to lookup, ensuring the
-     * cache doesn't contain them already, and adding the returned elements to the cache etc.
-     * <p>
-     * This subclass is required to return fully valid <code>Acl</code>s, including properly-configured
-     * parent ACLs.
-     *
-     */
-    private Map<ObjectIdentity, Acl> lookupObjectIdentities(final Collection<ObjectIdentity> objectIdentities, List<Sid> sids) {
-        Assert.notEmpty(objectIdentities, "Must provide identities to lookup");
-
-        final Map<Serializable, Acl> acls = new HashMap<Serializable, Acl>(); // contains Acls with StubAclParents
-
-        // Make the "acls" map contain all requested objectIdentities
-        // (including markers to each parent in the hierarchy)
-        String sql = computeRepeatingSql(lookupObjectIdentitiesWhereClause, objectIdentities.size());
-
-        Set<Long> parentsToLookup = jdbcTemplate.query(sql,
-            new PreparedStatementSetter() {
-                public void setValues(PreparedStatement ps) throws SQLException {
-                    int i = 0;
-                    for (ObjectIdentity oid : objectIdentities) {
-                        // Determine prepared statement values for this iteration
-                        String type = oid.getType();
-
-                        // No need to check for nulls, as guaranteed non-null by ObjectIdentity.getIdentifier() interface contract
-                        String identifier = oid.getIdentifier().toString();
-                        long id = (Long.valueOf(identifier)).longValue();
-
-                        // Inject values
-                        ps.setLong((2 * i) + 1, id);
-                        ps.setString((2 * i) + 2, type);
-                        i++;
-                    }
-                }
-            }, new ProcessResultSet(acls, sids));
-
-        // Lookup the parents, now that our JdbcTemplate has released the database connection (SEC-547)
-        if (parentsToLookup.size() > 0) {
-            lookupPrimaryKeys(acls, parentsToLookup, sids);
-        }
-
-        // Finally, convert our "acls" containing StubAclParents into true Acls
-        Map<ObjectIdentity, Acl> resultMap = new HashMap<ObjectIdentity, Acl>();
-
-        for (Acl inputAcl : acls.values()) {
-            Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl");
-            Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(), "Acl.getId() must be Long");
-
-            Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId());
-            resultMap.put(result.getObjectIdentity(), result);
-        }
-
-        return resultMap;
-    }
-
-    /**
-     * The final phase of converting the <code>Map</code> of <code>AclImpl</code> instances which contain
-     * <code>StubAclParent</code>s into proper, valid <code>AclImpl</code>s with correct ACL parents.
-     *
-     * @param inputMap the unconverted <code>AclImpl</code>s
-     * @param currentIdentity the current<code>Acl</code> that we wish to convert (this may be
-     *
-     */
-    private AclImpl convert(Map<Serializable, Acl> inputMap, Long currentIdentity) {
-        Assert.notEmpty(inputMap, "InputMap required");
-        Assert.notNull(currentIdentity, "CurrentIdentity required");
-
-        // Retrieve this Acl from the InputMap
-        Acl uncastAcl = inputMap.get(currentIdentity);
-        Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl");
-
-        AclImpl inputAcl = (AclImpl) uncastAcl;
-
-        Acl parent = inputAcl.getParentAcl();
-
-        if ((parent != null) && parent instanceof StubAclParent) {
-            // Lookup the parent
-            StubAclParent stubAclParent = (StubAclParent) parent;
-            parent = convert(inputMap, stubAclParent.getId());
-        }
-
-        // Now we have the parent (if there is one), create the true AclImpl
-        AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), (Long) inputAcl.getId(), aclAuthorizationStrategy,
-                grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner());
-
-        // Copy the "aces" from the input to the destination
-
-        // Obtain the "aces" from the input ACL
-        List<AccessControlEntryImpl> aces = readAces(inputAcl);
-
-        // Create a list in which to store the "aces" for the "result" AclImpl instance
-        List<AccessControlEntryImpl> acesNew = new ArrayList<AccessControlEntryImpl>();
-
-        // Iterate over the "aces" input and replace each nested AccessControlEntryImpl.getAcl() with the new "result" AclImpl instance
-        // This ensures StubAclParent instances are removed, as per SEC-951
-        for (AccessControlEntryImpl ace : aces) {
-            setAclOnAce(ace, result);
-            acesNew.add(ace);
-        }
-
-        // Finally, now that the "aces" have been converted to have the "result" AclImpl instance, modify the "result" AclImpl instance
-        setAces(result, acesNew);
-
-        return result;
-    }
-
-    /**
-     * Creates a particular implementation of {@link Sid} depending on the arguments.
-     *
-     * @param sid   the name of the sid representing its unique identifier. In typical ACL database schema it's
-     *                  located in table {@code acl_sid} table, {@code sid} column.
-     * @param isPrincipal whether it's a user or granted authority like role
-     * @return the instance of Sid with the {@code sidName} as an identifier
-     */
-    protected Sid createSid(boolean isPrincipal, String sid) {
-        if (isPrincipal) {
-            return new PrincipalSid(sid);
-        } else {
-            return new GrantedAuthoritySid(sid);
-        }
-    }
-
-    /**
-     * Sets the {@code PermissionFactory} instance which will be used to convert loaded permission
-     * data values to {@code Permission}s. A {@code DefaultPermissionFactory} will be used by default.
-     *
-     * @param permissionFactory
-     */
-    public final void setPermissionFactory(PermissionFactory permissionFactory) {
-        this.permissionFactory = permissionFactory;
-    }
-
-    public final void setBatchSize(int batchSize) {
-        this.batchSize = batchSize;
-    }
-
-    /**
-     * The SQL for the select clause. If customizing in order to modify
-     * column names, schema etc, the other SQL customization fields must also be set to match.
-     *
-     * @param selectClause the select clause, which defaults to {@link #DEFAULT_SELECT_CLAUSE}.
-     */
-    public final void setSelectClause(String selectClause) {
-        this.selectClause = selectClause;
-    }
-
-    /**
-     * The SQL for the where clause used in the <tt>lookupPrimaryKey</tt> method.
-     */
-    public final void setLookupPrimaryKeysWhereClause(String lookupPrimaryKeysWhereClause) {
-        this.lookupPrimaryKeysWhereClause = lookupPrimaryKeysWhereClause;
-    }
-
-    /**
-     * The SQL for the where clause used in the <tt>lookupObjectIdentities</tt> method.
-     */
-    public final void setLookupObjectIdentitiesWhereClause(String lookupObjectIdentitiesWhereClause) {
-        this.lookupObjectIdentitiesWhereClause = lookupObjectIdentitiesWhereClause;
-    }
-
-    /**
-     * The SQL for the "order by" clause used in both queries.
-     */
-    public final void setOrderByClause(String orderByClause) {
-        this.orderByClause = orderByClause;
-    }
-
-    //~ Inner Classes ==================================================================================================
-
-    private class ProcessResultSet implements ResultSetExtractor<Set<Long>> {
-        private final Map<Serializable, Acl> acls;
-        private final List<Sid> sids;
-
-        public ProcessResultSet(Map<Serializable, Acl> acls, List<Sid> sids) {
-            Assert.notNull(acls, "ACLs cannot be null");
-            this.acls = acls;
-            this.sids = sids; // can be null
-        }
-
-        /**
-         * Implementation of {@link ResultSetExtractor#extractData(ResultSet)}.
-         * Creates an {@link Acl} for each row in the {@link ResultSet} and
-         * ensures it is in member field <tt>acls</tt>.  Any {@link Acl} with
-         * a parent will have the parents id returned in a set.  The returned
-         * set of ids may requires further processing.
-         * @param rs The {@link ResultSet} to be processed
-         * @return a list of parent IDs remaining to be looked up (may be empty, but never <tt>null</tt>)
-         * @throws SQLException
-         */
-        public Set<Long> extractData(ResultSet rs) throws SQLException {
-            Set<Long> parentIdsToLookup = new HashSet<Long>(); // Set of parent_id Longs
-
-            while (rs.next()) {
-                // Convert current row into an Acl (albeit with a StubAclParent)
-                convertCurrentResultIntoObject(acls, rs);
-
-                // Figure out if this row means we need to lookup another parent
-                long parentId = rs.getLong("parent_object");
-
-                if (parentId != 0) {
-                    // See if it's already in the "acls"
-                    if (acls.containsKey(new Long(parentId))) {
-                        continue; // skip this while iteration
-                    }
-
-                    // Now try to find it in the cache
-                    MutableAcl cached = aclCache.getFromCache(new Long(parentId));
-
-                    if ((cached == null) || !cached.isSidLoaded(sids)) {
-                        parentIdsToLookup.add(new Long(parentId));
-                    } else {
-                        // Pop into the acls map, so our convert method doesn't
-                        // need to deal with an unsynchronized AclCache
-                        acls.put(cached.getId(), cached);
-                    }
-                }
-            }
-
-            // Return the parents left to lookup to the caller
-            return parentIdsToLookup;
-        }
-
-        /**
-         * Accepts the current <code>ResultSet</code> row, and converts it into an <code>AclImpl</code> that
-         * contains a <code>StubAclParent</code>
-         *
-         * @param acls the Map we should add the converted Acl to
-         * @param rs the ResultSet focused on a current row
-         *
-         * @throws SQLException if something goes wrong converting values
-         */
-        private void convertCurrentResultIntoObject(Map<Serializable, Acl> acls, ResultSet rs) throws SQLException {
-            Long id = new Long(rs.getLong("acl_id"));
-
-            // If we already have an ACL for this ID, just create the ACE
-            Acl acl = acls.get(id);
-
-            if (acl == null) {
-                // Make an AclImpl and pop it into the Map
-                ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("class"),
-                        Long.valueOf(rs.getLong("object_id_identity")));
-
-                Acl parentAcl = null;
-                long parentAclId = rs.getLong("parent_object");
-
-                if (parentAclId != 0) {
-                    parentAcl = new StubAclParent(Long.valueOf(parentAclId));
-                }
-
-                boolean entriesInheriting = rs.getBoolean("entries_inheriting");
-                Sid owner = createSid(rs.getBoolean("acl_principal"), rs.getString("acl_sid"));
-
-                acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy, grantingStrategy, parentAcl, null,
-                        entriesInheriting, owner);
-
-                acls.put(id, acl);
-            }
-
-            // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order)
-            // It is permissible to have no ACEs in an ACL (which is detected by a null ACE_SID)
-            if (rs.getString("ace_sid") != null) {
-                Long aceId = new Long(rs.getLong("ace_id"));
-                Sid recipient = createSid(rs.getBoolean("ace_principal"), rs.getString("ace_sid"));
-
-                int mask = rs.getInt("mask");
-                Permission permission = permissionFactory.buildFromMask(mask);
-                boolean granting = rs.getBoolean("granting");
-                boolean auditSuccess = rs.getBoolean("audit_success");
-                boolean auditFailure = rs.getBoolean("audit_failure");
-
-                AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting,
-                        auditSuccess, auditFailure);
-
-                //Field acesField = FieldUtils.getField(AclImpl.class, "aces");
-                List<AccessControlEntryImpl> aces = readAces((AclImpl)acl);
-
-                // Add the ACE if it doesn't already exist in the ACL.aces field
-                if (!aces.contains(ace)) {
-                    aces.add(ace);
-                }
-            }
-        }
-    }
-
-    private class StubAclParent implements Acl {
-        private final Long id;
-
-        public StubAclParent(Long id) {
-            this.id = id;
-        }
-
-        public List<AccessControlEntry> getEntries() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public Long getId() {
-            return id;
-        }
-
-        public ObjectIdentity getObjectIdentity() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public Sid getOwner() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public Acl getParentAcl() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public boolean isEntriesInheriting() {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public boolean isGranted(List<Permission> permission, List<Sid> sids, boolean administrativeMode)
-            throws NotFoundException, UnloadedSidException {
-            throw new UnsupportedOperationException("Stub only");
-        }
-
-        public boolean isSidLoaded(List<Sid> sids) {
-            throw new UnsupportedOperationException("Stub only");
-        }
-    }
+	public final static String DEFAULT_SELECT_CLAUSE = "select acl_object_identity.object_id_identity, "
+			+ "acl_entry.ace_order,  "
+			+ "acl_object_identity.id as acl_id, "
+			+ "acl_object_identity.parent_object, "
+			+ "acl_object_identity.entries_inheriting, "
+			+ "acl_entry.id as ace_id, "
+			+ "acl_entry.mask,  "
+			+ "acl_entry.granting,  "
+			+ "acl_entry.audit_success, "
+			+ "acl_entry.audit_failure,  "
+			+ "acl_sid.principal as ace_principal, "
+			+ "acl_sid.sid as ace_sid,  "
+			+ "acli_sid.principal as acl_principal, "
+			+ "acli_sid.sid as acl_sid, "
+			+ "acl_class.class "
+			+ "from acl_object_identity "
+			+ "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid "
+			+ "left join acl_class on acl_class.id = acl_object_identity.object_id_class   "
+			+ "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity "
+			+ "left join acl_sid on acl_entry.sid = acl_sid.id  " + "where ( ";
+
+	private final static String DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE = "(acl_object_identity.id = ?)";
+
+	private final static String DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE = "(acl_object_identity.object_id_identity = ? and acl_class.class = ?)";
+
+	public final static String DEFAULT_ORDER_BY_CLAUSE = ") order by acl_object_identity.object_id_identity"
+			+ " asc, acl_entry.ace_order asc";
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final AclAuthorizationStrategy aclAuthorizationStrategy;
+	private PermissionFactory permissionFactory = new DefaultPermissionFactory();
+	private final AclCache aclCache;
+	private final PermissionGrantingStrategy grantingStrategy;
+	private final JdbcTemplate jdbcTemplate;
+	private int batchSize = 50;
+
+	private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces");
+	private final Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class,
+			"acl");
+
+	// SQL Customization fields
+	private String selectClause = DEFAULT_SELECT_CLAUSE;
+	private String lookupPrimaryKeysWhereClause = DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE;
+	private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE;
+	private String orderByClause = DEFAULT_ORDER_BY_CLAUSE;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	/**
+	 * Constructor accepting mandatory arguments
+	 *
+	 * @param dataSource to access the database
+	 * @param aclCache the cache where fully-loaded elements can be stored
+	 * @param aclAuthorizationStrategy authorization strategy (required)
+	 */
+	public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
+			AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) {
+		this(dataSource, aclCache, aclAuthorizationStrategy,
+				new DefaultPermissionGrantingStrategy(auditLogger));
+	}
+
+	/**
+	 * Creates a new instance
+	 *
+	 * @param dataSource to access the database
+	 * @param aclCache the cache where fully-loaded elements can be stored
+	 * @param aclAuthorizationStrategy authorization strategy (required)
+	 * @param grantingStrategy the PermissionGrantingStrategy
+	 */
+	public BasicLookupStrategy(DataSource dataSource, AclCache aclCache,
+			AclAuthorizationStrategy aclAuthorizationStrategy,
+			PermissionGrantingStrategy grantingStrategy) {
+		Assert.notNull(dataSource, "DataSource required");
+		Assert.notNull(aclCache, "AclCache required");
+		Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
+		Assert.notNull(grantingStrategy, "grantingStrategy required");
+		jdbcTemplate = new JdbcTemplate(dataSource);
+		this.aclCache = aclCache;
+		this.aclAuthorizationStrategy = aclAuthorizationStrategy;
+		this.grantingStrategy = grantingStrategy;
+		fieldAces.setAccessible(true);
+		fieldAcl.setAccessible(true);
+
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	private String computeRepeatingSql(String repeatingSql, int requiredRepetitions) {
+		assert requiredRepetitions > 0 : "requiredRepetitions must be > 0";
+
+		final String startSql = selectClause;
+
+		final String endSql = orderByClause;
+
+		StringBuilder sqlStringBldr = new StringBuilder(startSql.length()
+				+ endSql.length() + requiredRepetitions * (repeatingSql.length() + 4));
+		sqlStringBldr.append(startSql);
+
+		for (int i = 1; i <= requiredRepetitions; i++) {
+			sqlStringBldr.append(repeatingSql);
+
+			if (i != requiredRepetitions) {
+				sqlStringBldr.append(" or ");
+			}
+		}
+
+		sqlStringBldr.append(endSql);
+
+		return sqlStringBldr.toString();
+	}
+
+	@SuppressWarnings("unchecked")
+	private List<AccessControlEntryImpl> readAces(AclImpl acl) {
+		try {
+			return (List<AccessControlEntryImpl>) fieldAces.get(acl);
+		}
+		catch (IllegalAccessException e) {
+			throw new IllegalStateException("Could not obtain AclImpl.aces field", e);
+		}
+	}
+
+	private void setAclOnAce(AccessControlEntryImpl ace, AclImpl acl) {
+		try {
+			fieldAcl.set(ace, acl);
+		}
+		catch (IllegalAccessException e) {
+			throw new IllegalStateException(
+					"Could not or set AclImpl on AccessControlEntryImpl fields", e);
+		}
+	}
+
+	private void setAces(AclImpl acl, List<AccessControlEntryImpl> aces) {
+		try {
+			fieldAces.set(acl, aces);
+		}
+		catch (IllegalAccessException e) {
+			throw new IllegalStateException("Could not set AclImpl entries", e);
+		}
+	}
+
+	/**
+	 * Locates the primary key IDs specified in "findNow", adding AclImpl instances with
+	 * StubAclParents to the "acls" Map.
+	 *
+	 * @param acls the AclImpls (with StubAclParents)
+	 * @param findNow Long-based primary keys to retrieve
+	 * @param sids
+	 */
+	private void lookupPrimaryKeys(final Map<Serializable, Acl> acls,
+			final Set<Long> findNow, final List<Sid> sids) {
+		Assert.notNull(acls, "ACLs are required");
+		Assert.notEmpty(findNow, "Items to find now required");
+
+		String sql = computeRepeatingSql(lookupPrimaryKeysWhereClause, findNow.size());
+
+		Set<Long> parentsToLookup = jdbcTemplate.query(sql,
+				new PreparedStatementSetter() {
+					public void setValues(PreparedStatement ps) throws SQLException {
+						int i = 0;
+
+						for (Long toFind : findNow) {
+							i++;
+							ps.setLong(i, toFind);
+						}
+					}
+				}, new ProcessResultSet(acls, sids));
+
+		// Lookup the parents, now that our JdbcTemplate has released the database
+		// connection (SEC-547)
+		if (parentsToLookup.size() > 0) {
+			lookupPrimaryKeys(acls, parentsToLookup, sids);
+		}
+	}
+
+	/**
+	 * The main method.
+	 * <p>
+	 * WARNING: This implementation completely disregards the "sids" argument! Every item
+	 * in the cache is expected to contain all SIDs. If you have serious performance needs
+	 * (e.g. a very large number of SIDs per object identity), you'll probably want to
+	 * develop a custom {@link LookupStrategy} implementation instead.
+	 * <p>
+	 * The implementation works in batch sizes specified by {@link #batchSize}.
+	 *
+	 * @param objects the identities to lookup (required)
+	 * @param sids the SIDs for which identities are required (ignored by this
+	 * implementation)
+	 *
+	 * @return a <tt>Map</tt> where keys represent the {@link ObjectIdentity} of the
+	 * located {@link Acl} and values are the located {@link Acl} (never <tt>null</tt>
+	 * although some entries may be missing; this method should not throw
+	 * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to
+	 * automatically create entries if required)
+	 */
+	public final Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects,
+			List<Sid> sids) {
+		Assert.isTrue(batchSize >= 1, "BatchSize must be >= 1");
+		Assert.notEmpty(objects, "Objects to lookup required");
+
+		// Map<ObjectIdentity,Acl>
+		Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>(); // contains
+																				// FULLY
+																				// loaded
+																				// Acl
+																				// objects
+
+		Set<ObjectIdentity> currentBatchToLoad = new HashSet<ObjectIdentity>();
+
+		for (int i = 0; i < objects.size(); i++) {
+			final ObjectIdentity oid = objects.get(i);
+			boolean aclFound = false;
+
+			// Check we don't already have this ACL in the results
+			if (result.containsKey(oid)) {
+				aclFound = true;
+			}
+
+			// Check cache for the present ACL entry
+			if (!aclFound) {
+				Acl acl = aclCache.getFromCache(oid);
+
+				// Ensure any cached element supports all the requested SIDs
+				// (they should always, as our base impl doesn't filter on SID)
+				if (acl != null) {
+					if (acl.isSidLoaded(sids)) {
+						result.put(acl.getObjectIdentity(), acl);
+						aclFound = true;
+					}
+					else {
+						throw new IllegalStateException(
+								"Error: SID-filtered element detected when implementation does not perform SID filtering "
+										+ "- have you added something to the cache manually?");
+					}
+				}
+			}
+
+			// Load the ACL from the database
+			if (!aclFound) {
+				currentBatchToLoad.add(oid);
+			}
+
+			// Is it time to load from JDBC the currentBatchToLoad?
+			if ((currentBatchToLoad.size() == this.batchSize)
+					|| ((i + 1) == objects.size())) {
+				if (currentBatchToLoad.size() > 0) {
+					Map<ObjectIdentity, Acl> loadedBatch = lookupObjectIdentities(
+							currentBatchToLoad, sids);
+
+					// Add loaded batch (all elements 100% initialized) to results
+					result.putAll(loadedBatch);
+
+					// Add the loaded batch to the cache
+
+					for (Acl loadedAcl : loadedBatch.values()) {
+						aclCache.putInCache((AclImpl) loadedAcl);
+					}
+
+					currentBatchToLoad.clear();
+				}
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * Looks up a batch of <code>ObjectIdentity</code>s directly from the database.
+	 * <p>
+	 * The caller is responsible for optimization issues, such as selecting the identities
+	 * to lookup, ensuring the cache doesn't contain them already, and adding the returned
+	 * elements to the cache etc.
+	 * <p>
+	 * This subclass is required to return fully valid <code>Acl</code>s, including
+	 * properly-configured parent ACLs.
+	 *
+	 */
+	private Map<ObjectIdentity, Acl> lookupObjectIdentities(
+			final Collection<ObjectIdentity> objectIdentities, List<Sid> sids) {
+		Assert.notEmpty(objectIdentities, "Must provide identities to lookup");
+
+		final Map<Serializable, Acl> acls = new HashMap<Serializable, Acl>(); // contains
+																				// Acls
+																				// with
+																				// StubAclParents
+
+		// Make the "acls" map contain all requested objectIdentities
+		// (including markers to each parent in the hierarchy)
+		String sql = computeRepeatingSql(lookupObjectIdentitiesWhereClause,
+				objectIdentities.size());
+
+		Set<Long> parentsToLookup = jdbcTemplate.query(sql,
+				new PreparedStatementSetter() {
+					public void setValues(PreparedStatement ps) throws SQLException {
+						int i = 0;
+						for (ObjectIdentity oid : objectIdentities) {
+							// Determine prepared statement values for this iteration
+							String type = oid.getType();
+
+							// No need to check for nulls, as guaranteed non-null by
+							// ObjectIdentity.getIdentifier() interface contract
+							String identifier = oid.getIdentifier().toString();
+							long id = (Long.valueOf(identifier)).longValue();
+
+							// Inject values
+							ps.setLong((2 * i) + 1, id);
+							ps.setString((2 * i) + 2, type);
+							i++;
+						}
+					}
+				}, new ProcessResultSet(acls, sids));
+
+		// Lookup the parents, now that our JdbcTemplate has released the database
+		// connection (SEC-547)
+		if (parentsToLookup.size() > 0) {
+			lookupPrimaryKeys(acls, parentsToLookup, sids);
+		}
+
+		// Finally, convert our "acls" containing StubAclParents into true Acls
+		Map<ObjectIdentity, Acl> resultMap = new HashMap<ObjectIdentity, Acl>();
+
+		for (Acl inputAcl : acls.values()) {
+			Assert.isInstanceOf(AclImpl.class, inputAcl,
+					"Map should have contained an AclImpl");
+			Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(),
+					"Acl.getId() must be Long");
+
+			Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId());
+			resultMap.put(result.getObjectIdentity(), result);
+		}
+
+		return resultMap;
+	}
+
+	/**
+	 * The final phase of converting the <code>Map</code> of <code>AclImpl</code>
+	 * instances which contain <code>StubAclParent</code>s into proper, valid
+	 * <code>AclImpl</code>s with correct ACL parents.
+	 *
+	 * @param inputMap the unconverted <code>AclImpl</code>s
+	 * @param currentIdentity the current<code>Acl</code> that we wish to convert (this
+	 * may be
+	 *
+	 */
+	private AclImpl convert(Map<Serializable, Acl> inputMap, Long currentIdentity) {
+		Assert.notEmpty(inputMap, "InputMap required");
+		Assert.notNull(currentIdentity, "CurrentIdentity required");
+
+		// Retrieve this Acl from the InputMap
+		Acl uncastAcl = inputMap.get(currentIdentity);
+		Assert.isInstanceOf(AclImpl.class, uncastAcl,
+				"The inputMap contained a non-AclImpl");
+
+		AclImpl inputAcl = (AclImpl) uncastAcl;
+
+		Acl parent = inputAcl.getParentAcl();
+
+		if ((parent != null) && parent instanceof StubAclParent) {
+			// Lookup the parent
+			StubAclParent stubAclParent = (StubAclParent) parent;
+			parent = convert(inputMap, stubAclParent.getId());
+		}
+
+		// Now we have the parent (if there is one), create the true AclImpl
+		AclImpl result = new AclImpl(inputAcl.getObjectIdentity(),
+				(Long) inputAcl.getId(), aclAuthorizationStrategy, grantingStrategy,
+				parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner());
+
+		// Copy the "aces" from the input to the destination
+
+		// Obtain the "aces" from the input ACL
+		List<AccessControlEntryImpl> aces = readAces(inputAcl);
+
+		// Create a list in which to store the "aces" for the "result" AclImpl instance
+		List<AccessControlEntryImpl> acesNew = new ArrayList<AccessControlEntryImpl>();
+
+		// Iterate over the "aces" input and replace each nested
+		// AccessControlEntryImpl.getAcl() with the new "result" AclImpl instance
+		// This ensures StubAclParent instances are removed, as per SEC-951
+		for (AccessControlEntryImpl ace : aces) {
+			setAclOnAce(ace, result);
+			acesNew.add(ace);
+		}
+
+		// Finally, now that the "aces" have been converted to have the "result" AclImpl
+		// instance, modify the "result" AclImpl instance
+		setAces(result, acesNew);
+
+		return result;
+	}
+
+	/**
+	 * Creates a particular implementation of {@link Sid} depending on the arguments.
+	 *
+	 * @param sid the name of the sid representing its unique identifier. In typical ACL
+	 * database schema it's located in table {@code acl_sid} table, {@code sid} column.
+	 * @param isPrincipal whether it's a user or granted authority like role
+	 * @return the instance of Sid with the {@code sidName} as an identifier
+	 */
+	protected Sid createSid(boolean isPrincipal, String sid) {
+		if (isPrincipal) {
+			return new PrincipalSid(sid);
+		}
+		else {
+			return new GrantedAuthoritySid(sid);
+		}
+	}
+
+	/**
+	 * Sets the {@code PermissionFactory} instance which will be used to convert loaded
+	 * permission data values to {@code Permission}s. A {@code DefaultPermissionFactory}
+	 * will be used by default.
+	 *
+	 * @param permissionFactory
+	 */
+	public final void setPermissionFactory(PermissionFactory permissionFactory) {
+		this.permissionFactory = permissionFactory;
+	}
+
+	public final void setBatchSize(int batchSize) {
+		this.batchSize = batchSize;
+	}
+
+	/**
+	 * The SQL for the select clause. If customizing in order to modify column names,
+	 * schema etc, the other SQL customization fields must also be set to match.
+	 *
+	 * @param selectClause the select clause, which defaults to
+	 * {@link #DEFAULT_SELECT_CLAUSE}.
+	 */
+	public final void setSelectClause(String selectClause) {
+		this.selectClause = selectClause;
+	}
+
+	/**
+	 * The SQL for the where clause used in the <tt>lookupPrimaryKey</tt> method.
+	 */
+	public final void setLookupPrimaryKeysWhereClause(String lookupPrimaryKeysWhereClause) {
+		this.lookupPrimaryKeysWhereClause = lookupPrimaryKeysWhereClause;
+	}
+
+	/**
+	 * The SQL for the where clause used in the <tt>lookupObjectIdentities</tt> method.
+	 */
+	public final void setLookupObjectIdentitiesWhereClause(
+			String lookupObjectIdentitiesWhereClause) {
+		this.lookupObjectIdentitiesWhereClause = lookupObjectIdentitiesWhereClause;
+	}
+
+	/**
+	 * The SQL for the "order by" clause used in both queries.
+	 */
+	public final void setOrderByClause(String orderByClause) {
+		this.orderByClause = orderByClause;
+	}
+
+	// ~ Inner Classes
+	// ==================================================================================================
+
+	private class ProcessResultSet implements ResultSetExtractor<Set<Long>> {
+		private final Map<Serializable, Acl> acls;
+		private final List<Sid> sids;
+
+		public ProcessResultSet(Map<Serializable, Acl> acls, List<Sid> sids) {
+			Assert.notNull(acls, "ACLs cannot be null");
+			this.acls = acls;
+			this.sids = sids; // can be null
+		}
+
+		/**
+		 * Implementation of {@link ResultSetExtractor#extractData(ResultSet)}. Creates an
+		 * {@link Acl} for each row in the {@link ResultSet} and ensures it is in member
+		 * field <tt>acls</tt>. Any {@link Acl} with a parent will have the parents id
+		 * returned in a set. The returned set of ids may requires further processing.
+		 * @param rs The {@link ResultSet} to be processed
+		 * @return a list of parent IDs remaining to be looked up (may be empty, but never
+		 * <tt>null</tt>)
+		 * @throws SQLException
+		 */
+		public Set<Long> extractData(ResultSet rs) throws SQLException {
+			Set<Long> parentIdsToLookup = new HashSet<Long>(); // Set of parent_id Longs
+
+			while (rs.next()) {
+				// Convert current row into an Acl (albeit with a StubAclParent)
+				convertCurrentResultIntoObject(acls, rs);
+
+				// Figure out if this row means we need to lookup another parent
+				long parentId = rs.getLong("parent_object");
+
+				if (parentId != 0) {
+					// See if it's already in the "acls"
+					if (acls.containsKey(new Long(parentId))) {
+						continue; // skip this while iteration
+					}
+
+					// Now try to find it in the cache
+					MutableAcl cached = aclCache.getFromCache(new Long(parentId));
+
+					if ((cached == null) || !cached.isSidLoaded(sids)) {
+						parentIdsToLookup.add(new Long(parentId));
+					}
+					else {
+						// Pop into the acls map, so our convert method doesn't
+						// need to deal with an unsynchronized AclCache
+						acls.put(cached.getId(), cached);
+					}
+				}
+			}
+
+			// Return the parents left to lookup to the caller
+			return parentIdsToLookup;
+		}
+
+		/**
+		 * Accepts the current <code>ResultSet</code> row, and converts it into an
+		 * <code>AclImpl</code> that contains a <code>StubAclParent</code>
+		 *
+		 * @param acls the Map we should add the converted Acl to
+		 * @param rs the ResultSet focused on a current row
+		 *
+		 * @throws SQLException if something goes wrong converting values
+		 */
+		private void convertCurrentResultIntoObject(Map<Serializable, Acl> acls,
+				ResultSet rs) throws SQLException {
+			Long id = new Long(rs.getLong("acl_id"));
+
+			// If we already have an ACL for this ID, just create the ACE
+			Acl acl = acls.get(id);
+
+			if (acl == null) {
+				// Make an AclImpl and pop it into the Map
+				ObjectIdentity objectIdentity = new ObjectIdentityImpl(
+						rs.getString("class"), Long.valueOf(rs
+								.getLong("object_id_identity")));
+
+				Acl parentAcl = null;
+				long parentAclId = rs.getLong("parent_object");
+
+				if (parentAclId != 0) {
+					parentAcl = new StubAclParent(Long.valueOf(parentAclId));
+				}
+
+				boolean entriesInheriting = rs.getBoolean("entries_inheriting");
+				Sid owner = createSid(rs.getBoolean("acl_principal"),
+						rs.getString("acl_sid"));
+
+				acl = new AclImpl(objectIdentity, id, aclAuthorizationStrategy,
+						grantingStrategy, parentAcl, null, entriesInheriting, owner);
+
+				acls.put(id, acl);
+			}
+
+			// Add an extra ACE to the ACL (ORDER BY maintains the ACE list order)
+			// It is permissible to have no ACEs in an ACL (which is detected by a null
+			// ACE_SID)
+			if (rs.getString("ace_sid") != null) {
+				Long aceId = new Long(rs.getLong("ace_id"));
+				Sid recipient = createSid(rs.getBoolean("ace_principal"),
+						rs.getString("ace_sid"));
+
+				int mask = rs.getInt("mask");
+				Permission permission = permissionFactory.buildFromMask(mask);
+				boolean granting = rs.getBoolean("granting");
+				boolean auditSuccess = rs.getBoolean("audit_success");
+				boolean auditFailure = rs.getBoolean("audit_failure");
+
+				AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl,
+						recipient, permission, granting, auditSuccess, auditFailure);
+
+				// Field acesField = FieldUtils.getField(AclImpl.class, "aces");
+				List<AccessControlEntryImpl> aces = readAces((AclImpl) acl);
+
+				// Add the ACE if it doesn't already exist in the ACL.aces field
+				if (!aces.contains(ace)) {
+					aces.add(ace);
+				}
+			}
+		}
+	}
+
+	private class StubAclParent implements Acl {
+		private final Long id;
+
+		public StubAclParent(Long id) {
+			this.id = id;
+		}
+
+		public List<AccessControlEntry> getEntries() {
+			throw new UnsupportedOperationException("Stub only");
+		}
+
+		public Long getId() {
+			return id;
+		}
+
+		public ObjectIdentity getObjectIdentity() {
+			throw new UnsupportedOperationException("Stub only");
+		}
+
+		public Sid getOwner() {
+			throw new UnsupportedOperationException("Stub only");
+		}
+
+		public Acl getParentAcl() {
+			throw new UnsupportedOperationException("Stub only");
+		}
+
+		public boolean isEntriesInheriting() {
+			throw new UnsupportedOperationException("Stub only");
+		}
+
+		public boolean isGranted(List<Permission> permission, List<Sid> sids,
+				boolean administrativeMode) throws NotFoundException,
+				UnloadedSidException {
+			throw new UnsupportedOperationException("Stub only");
+		}
+
+		public boolean isSidLoaded(List<Sid> sids) {
+			throw new UnsupportedOperationException("Stub only");
+		}
+	}
 }

+ 96 - 85
acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java

@@ -34,96 +34,107 @@ import org.springframework.security.acls.model.ObjectIdentity;
 import org.springframework.security.acls.model.Sid;
 import org.springframework.util.Assert;
 
-
 /**
  * Simple JDBC-based implementation of <code>AclService</code>.
  * <p>
- * Requires the "dirty" flags in {@link org.springframework.security.acls.domain.AclImpl} and
- * {@link org.springframework.security.acls.domain.AccessControlEntryImpl} to be set, so that the implementation can
- * detect changed parameters easily.
+ * Requires the "dirty" flags in {@link org.springframework.security.acls.domain.AclImpl}
+ * and {@link org.springframework.security.acls.domain.AccessControlEntryImpl} to be set,
+ * so that the implementation can detect changed parameters easily.
  *
  * @author Ben Alex
  */
 public class JdbcAclService implements AclService {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log log = LogFactory.getLog(JdbcAclService.class);
-    private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, class.class as class "
-        + "from acl_object_identity obj, acl_object_identity parent, acl_class class "
-        + "where obj.parent_object = parent.id and obj.object_id_class = class.id "
-        + "and parent.object_id_identity = ? and parent.object_id_class = ("
-        + "select id FROM acl_class where acl_class.class = ?)";
-
-    //~ Instance fields ================================================================================================
-
-    protected final JdbcTemplate jdbcTemplate;
-    private final LookupStrategy lookupStrategy;
-    private String findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL;
-
-    //~ Constructors ===================================================================================================
-
-    public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
-        Assert.notNull(dataSource, "DataSource required");
-        Assert.notNull(lookupStrategy, "LookupStrategy required");
-        this.jdbcTemplate = new JdbcTemplate(dataSource);
-        this.lookupStrategy = lookupStrategy;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
-        Object[] args = {parentIdentity.getIdentifier(), parentIdentity.getType()};
-        List<ObjectIdentity> objects = jdbcTemplate.query(findChildrenSql, args,
-                new RowMapper<ObjectIdentity>() {
-                    public ObjectIdentity mapRow(ResultSet rs, int rowNum) throws SQLException {
-                        String javaType = rs.getString("class");
-                        Long identifier = new Long(rs.getLong("obj_id"));
-
-                        return new ObjectIdentityImpl(javaType, identifier);
-                    }
-                });
-
-        if (objects.size() == 0) {
-            return null;
-        }
-
-        return objects;
-    }
-
-    public Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException {
-        Map<ObjectIdentity, Acl> map = readAclsById(Arrays.asList(object), sids);
-        Assert.isTrue(map.containsKey(object), "There should have been an Acl entry for ObjectIdentity " + object);
-
-        return (Acl) map.get(object);
-    }
-
-    public Acl readAclById(ObjectIdentity object) throws NotFoundException {
-        return readAclById(object, null);
-    }
-
-    public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects) throws NotFoundException {
-        return readAclsById(objects, null);
-    }
-
-    public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) throws NotFoundException {
-        Map<ObjectIdentity, Acl> result = lookupStrategy.readAclsById(objects, sids);
-
-        // Check every requested object identity was found (throw NotFoundException if needed)
-        for (ObjectIdentity oid : objects) {
-            if (!result.containsKey(oid)) {
-                throw new NotFoundException("Unable to find ACL information for object identity '" + oid + "'");
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Allows customization of the SQL query used to find child object identities.
-     *
-     * @param findChildrenSql
-     */
-    public void setFindChildrenQuery(String findChildrenSql) {
-        this.findChildrenSql = findChildrenSql;
-    }
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	protected static final Log log = LogFactory.getLog(JdbcAclService.class);
+	private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, class.class as class "
+			+ "from acl_object_identity obj, acl_object_identity parent, acl_class class "
+			+ "where obj.parent_object = parent.id and obj.object_id_class = class.id "
+			+ "and parent.object_id_identity = ? and parent.object_id_class = ("
+			+ "select id FROM acl_class where acl_class.class = ?)";
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	protected final JdbcTemplate jdbcTemplate;
+	private final LookupStrategy lookupStrategy;
+	private String findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
+		Assert.notNull(dataSource, "DataSource required");
+		Assert.notNull(lookupStrategy, "LookupStrategy required");
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+		this.lookupStrategy = lookupStrategy;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
+		Object[] args = { parentIdentity.getIdentifier(), parentIdentity.getType() };
+		List<ObjectIdentity> objects = jdbcTemplate.query(findChildrenSql, args,
+				new RowMapper<ObjectIdentity>() {
+					public ObjectIdentity mapRow(ResultSet rs, int rowNum)
+							throws SQLException {
+						String javaType = rs.getString("class");
+						Long identifier = new Long(rs.getLong("obj_id"));
+
+						return new ObjectIdentityImpl(javaType, identifier);
+					}
+				});
+
+		if (objects.size() == 0) {
+			return null;
+		}
+
+		return objects;
+	}
+
+	public Acl readAclById(ObjectIdentity object, List<Sid> sids)
+			throws NotFoundException {
+		Map<ObjectIdentity, Acl> map = readAclsById(Arrays.asList(object), sids);
+		Assert.isTrue(map.containsKey(object),
+				"There should have been an Acl entry for ObjectIdentity " + object);
+
+		return (Acl) map.get(object);
+	}
+
+	public Acl readAclById(ObjectIdentity object) throws NotFoundException {
+		return readAclById(object, null);
+	}
+
+	public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects)
+			throws NotFoundException {
+		return readAclsById(objects, null);
+	}
+
+	public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects,
+			List<Sid> sids) throws NotFoundException {
+		Map<ObjectIdentity, Acl> result = lookupStrategy.readAclsById(objects, sids);
+
+		// Check every requested object identity was found (throw NotFoundException if
+		// needed)
+		for (ObjectIdentity oid : objects) {
+			if (!result.containsKey(oid)) {
+				throw new NotFoundException(
+						"Unable to find ACL information for object identity '" + oid
+								+ "'");
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * Allows customization of the SQL query used to find child object identities.
+	 *
+	 * @param findChildrenSql
+	 */
+	public void setFindChildrenQuery(String findChildrenSql) {
+		this.findChildrenSql = findChildrenSql;
+	}
 }

+ 434 - 396
acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java

@@ -41,409 +41,447 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.util.Assert;
 
-
 /**
  * Provides a base JDBC implementation of {@link MutableAclService}.
  * <p>
- * The default settings are for HSQLDB. If you are using a different database you
- * will probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and
- * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The other queries,
- * SQL inserts and updates can also be customized to accomodate schema variations, but must produce results
- * consistent with those expected by the defaults.
+ * The default settings are for HSQLDB. If you are using a different database you will
+ * probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and
+ * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The
+ * other queries, SQL inserts and updates can also be customized to accomodate schema
+ * variations, but must produce results consistent with those expected by the defaults.
  * <p>
- * See the appendix of the Spring Security reference manual for more information on the expected schema
- * and how it is used. Information on using PostgreSQL is also included.
+ * See the appendix of the Spring Security reference manual for more information on the
+ * expected schema and how it is used. Information on using PostgreSQL is also included.
  *
  * @author Ben Alex
  * @author Johannes Zlattinger
  */
 public class JdbcMutableAclService extends JdbcAclService implements MutableAclService {
-    //~ Instance fields ================================================================================================
-
-    private boolean foreignKeysInDatabase = true;
-    private final AclCache aclCache;
-    private String deleteEntryByObjectIdentityForeignKey = "delete from acl_entry where acl_object_identity=?";
-    private String deleteObjectIdentityByPrimaryKey = "delete from acl_object_identity where id=?";
-    private String classIdentityQuery = "call identity()";
-    private String sidIdentityQuery = "call identity()";
-    private String insertClass = "insert into acl_class (class) values (?)";
-    private String insertEntry = "insert into acl_entry "
-        + "(acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)"
-        + "values (?, ?, ?, ?, ?, ?, ?)";
-    private String insertObjectIdentity = "insert into acl_object_identity "
-        + "(object_id_class, object_id_identity, owner_sid, entries_inheriting) " + "values (?, ?, ?, ?)";
-    private String insertSid = "insert into acl_sid (principal, sid) values (?, ?)";
-    private String selectClassPrimaryKey = "select id from acl_class where class=?";
-    private String selectObjectIdentityPrimaryKey = "select acl_object_identity.id from acl_object_identity, acl_class "
-        + "where acl_object_identity.object_id_class = acl_class.id and acl_class.class=? "
-        + "and acl_object_identity.object_id_identity = ?";
-    private String selectSidPrimaryKey = "select id from acl_sid where principal=? and sid=?";
-    private String updateObjectIdentity = "update acl_object_identity set "
-        + "parent_object = ?, owner_sid = ?, entries_inheriting = ?" + " where id = ?";
-
-    //~ Constructors ===================================================================================================
-
-    public JdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
-        super(dataSource, lookupStrategy);
-        Assert.notNull(aclCache, "AclCache required");
-        this.aclCache = aclCache;
-    }
-
-    //~ Methods ========================================================================================================
-
-    public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException {
-        Assert.notNull(objectIdentity, "Object Identity required");
-
-        // Check this object identity hasn't already been persisted
-        if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) {
-            throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists");
-        }
-
-        // Need to retrieve the current principal, in order to know who "owns" this ACL (can be changed later on)
-        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
-        PrincipalSid sid = new PrincipalSid(auth);
-
-        // Create the acl_object_identity row
-        createObjectIdentity(objectIdentity, sid);
-
-        // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
-        Acl acl = readAclById(objectIdentity);
-        Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned");
-
-        return (MutableAcl) acl;
-    }
-
-    /**
-     * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl object.
-     *
-     * @param acl containing the ACEs to insert
-     */
-    protected void createEntries(final MutableAcl acl) {
-        if(acl.getEntries().isEmpty()) {
-            return;
-        }
-        jdbcTemplate.batchUpdate(insertEntry,
-            new BatchPreparedStatementSetter() {
-                public int getBatchSize() {
-                    return acl.getEntries().size();
-                }
-
-                public void setValues(PreparedStatement stmt, int i) throws SQLException {
-                    AccessControlEntry entry_ = acl.getEntries().get(i);
-                    Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class");
-                    AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_;
-
-                    stmt.setLong(1, ((Long) acl.getId()).longValue());
-                    stmt.setInt(2, i);
-                    stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true).longValue());
-                    stmt.setInt(4, entry.getPermission().getMask());
-                    stmt.setBoolean(5, entry.isGranting());
-                    stmt.setBoolean(6, entry.isAuditSuccess());
-                    stmt.setBoolean(7, entry.isAuditFailure());
-                }
-            });
-    }
-
-    /**
-     * Creates an entry in the acl_object_identity table for the passed ObjectIdentity. The Sid is also
-     * necessary, as acl_object_identity has defined the sid column as non-null.
-     *
-     * @param object to represent an acl_object_identity for
-     * @param owner for the SID column (will be created if there is no acl_sid entry for this particular Sid already)
-     */
-    protected void createObjectIdentity(ObjectIdentity object, Sid owner) {
-        Long sidId = createOrRetrieveSidPrimaryKey(owner, true);
-        Long classId = createOrRetrieveClassPrimaryKey(object.getType(), true);
-        jdbcTemplate.update(insertObjectIdentity, classId, object.getIdentifier(), sidId, Boolean.TRUE);
-    }
-
-    /**
-     * Retrieves the primary key from {@code acl_class}, creating a new row if needed and the
-     * {@code allowCreate} property is {@code true}.
-     *
-     * @param type to find or create an entry for (often the fully-qualified class name)
-     * @param allowCreate true if creation is permitted if not found
-     *
-     * @return the primary key or null if not found
-     */
-    protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate) {
-        List<Long> classIds = jdbcTemplate.queryForList(selectClassPrimaryKey, new Object[] {type}, Long.class);
-
-        if (!classIds.isEmpty()) {
-            return classIds.get(0);
-        }
-
-        if (allowCreate) {
-            jdbcTemplate.update(insertClass, type);
-            Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
-                    "Transaction must be running");
-            return new Long(jdbcTemplate.queryForLong(classIdentityQuery));
-        }
-
-        return null;
-    }
-
-    /**
-     * Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate property is
-     * true.
-     *
-     * @param sid to find or create
-     * @param allowCreate true if creation is permitted if not found
-     *
-     * @return the primary key or null if not found
-     *
-     * @throws IllegalArgumentException if the <tt>Sid</tt> is not a recognized implementation.
-     */
-    protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
-        Assert.notNull(sid, "Sid required");
-
-        String sidName;
-        boolean sidIsPrincipal = true;
-
-        if (sid instanceof PrincipalSid) {
-            sidName = ((PrincipalSid) sid).getPrincipal();
-        } else if (sid instanceof GrantedAuthoritySid) {
-            sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
-            sidIsPrincipal = false;
-        } else {
-            throw new IllegalArgumentException("Unsupported implementation of Sid");
-        }
-
-        return createOrRetrieveSidPrimaryKey(sidName, sidIsPrincipal, allowCreate);
-    }
-
-    /**
-     * Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate property is
-     * true.
-     * @param sidName name of Sid to find or to create
-     * @param sidIsPrincipal whether it's a user or granted authority like role
-     * @param allowCreate true if creation is permitted if not found
-     * @return the primary key or null if not found
-     */
-    protected Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, boolean allowCreate) {
-
-        List<Long> sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey,
-                new Object[] {Boolean.valueOf(sidIsPrincipal), sidName},  Long.class);
-
-        if (!sidIds.isEmpty()) {
-            return sidIds.get(0);
-        }
-
-        if (allowCreate) {
-            jdbcTemplate.update(insertSid, Boolean.valueOf(sidIsPrincipal), sidName);
-            Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
-            return new Long(jdbcTemplate.queryForLong(sidIdentityQuery));
-        }
-
-        return null;
-    }
-
-    public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException {
-        Assert.notNull(objectIdentity, "Object Identity required");
-        Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier");
-
-        if (deleteChildren) {
-            List<ObjectIdentity> children = findChildren(objectIdentity);
-            if (children != null) {
-                for (ObjectIdentity child : children) {
-                    deleteAcl(child, true);
-                }
-            }
-        } else {
-            if (!foreignKeysInDatabase) {
-                // We need to perform a manual verification for what a FK would normally do
-                // We generally don't do this, in the interests of deadlock management
-                List<ObjectIdentity> children = findChildren(objectIdentity);
-                if (children != null) {
-                    throw new ChildrenExistException("Cannot delete '" + objectIdentity + "' (has " + children.size()
-                            + " children)");
-                }
-            }
-        }
-
-        Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity);
-
-        // Delete this ACL's ACEs in the acl_entry table
-        deleteEntries(oidPrimaryKey);
-
-        // Delete this ACL's acl_object_identity row
-        deleteObjectIdentity(oidPrimaryKey);
-
-        // Clear the cache
-        aclCache.evictFromCache(objectIdentity);
-    }
-
-    /**
-     * Deletes all ACEs defined in the acl_entry table belonging to the presented ObjectIdentity primary key.
-     *
-     * @param oidPrimaryKey the rows in acl_entry to delete
-     */
-    protected void deleteEntries(Long oidPrimaryKey) {
-        jdbcTemplate.update(deleteEntryByObjectIdentityForeignKey, oidPrimaryKey);
-    }
-
-    /**
-     * Deletes a single row from acl_object_identity that is associated with the presented ObjectIdentity primary key.
-     * <p>
-     * We do not delete any entries from acl_class, even if no classes are using that class any longer. This is a
-     * deadlock avoidance approach.
-     *
-     * @param oidPrimaryKey to delete the acl_object_identity
-     */
-    protected void deleteObjectIdentity(Long oidPrimaryKey) {
-        // Delete the acl_object_identity row
-        jdbcTemplate.update(deleteObjectIdentityByPrimaryKey, oidPrimaryKey);
-    }
-
-    /**
-     * Retrieves the primary key from the acl_object_identity table for the passed ObjectIdentity. Unlike some
-     * other methods in this implementation, this method will NOT create a row (use {@link
-     * #createObjectIdentity(ObjectIdentity, Sid)} instead).
-     *
-     * @param oid to find
-     *
-     * @return the object identity or null if not found
-     */
-    protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) {
-        try {
-            return new Long(jdbcTemplate.queryForLong(selectObjectIdentityPrimaryKey, oid.getType(), oid.getIdentifier()));
-        } catch (DataAccessException notFound) {
-            return null;
-        }
-    }
-
-    /**
-     * This implementation will simply delete all ACEs in the database and recreate them on each invocation of
-     * this method. A more comprehensive implementation might use dirty state checking, or more likely use ORM
-     * capabilities for create, update and delete operations of {@link MutableAcl}.
-     */
-    public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
-        Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier");
-
-        // Delete this ACL's ACEs in the acl_entry table
-        deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()));
-
-        // Create this ACL's ACEs in the acl_entry table
-        createEntries(acl);
-
-        // Change the mutable columns in acl_object_identity
-        updateObjectIdentity(acl);
-
-        // Clear the cache, including children
-        clearCacheIncludingChildren(acl.getObjectIdentity());
-
-        // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
-        return (MutableAcl) super.readAclById(acl.getObjectIdentity());
-    }
-
-    private void clearCacheIncludingChildren(ObjectIdentity objectIdentity) {
-        Assert.notNull(objectIdentity, "ObjectIdentity required");
-        List<ObjectIdentity> children = findChildren(objectIdentity);
-        if (children != null) {
-            for (ObjectIdentity child : children) {
-                clearCacheIncludingChildren(child);
-            }
-        }
-        aclCache.evictFromCache(objectIdentity);
-    }
-
-    /**
-     * Updates an existing acl_object_identity row, with new information presented in the passed MutableAcl
-     * object. Also will create an acl_sid entry if needed for the Sid that owns the MutableAcl.
-     *
-     * @param acl to modify (a row must already exist in acl_object_identity)
-     *
-     * @throws NotFoundException if the ACL could not be found to update.
-     */
-    protected void updateObjectIdentity(MutableAcl acl) {
-        Long parentId = null;
-
-        if (acl.getParentAcl() != null) {
-            Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl().getObjectIdentity(),
-                "Implementation only supports ObjectIdentityImpl");
-
-            ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl().getObjectIdentity();
-            parentId = retrieveObjectIdentityPrimaryKey(oii);
-        }
-
-        Assert.notNull(acl.getOwner(), "Owner is required in this implementation");
-
-        Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true);
-        int count = jdbcTemplate.update(updateObjectIdentity,
-                parentId, ownerSid, Boolean.valueOf(acl.isEntriesInheriting()), acl.getId());
-
-        if (count != 1) {
-            throw new NotFoundException("Unable to locate ACL to update");
-        }
-    }
-
-    /**
-     * Sets the query that will be used to retrieve the identity of a newly created row in the <tt>acl_class</tt>
-     * table.
-     *
-     * @param classIdentityQuery the query, which should return the identifier. Defaults to <tt>call identity()</tt>
-     */
-    public void setClassIdentityQuery(String classIdentityQuery) {
-        Assert.hasText(classIdentityQuery, "New classIdentityQuery query is required");
-        this.classIdentityQuery = classIdentityQuery;
-    }
-
-    /**
-     * Sets the query that will be used to retrieve the identity of a newly created row in the <tt>acl_sid</tt>
-     * table.
-     *
-     * @param sidIdentityQuery the query, which should return the identifier. Defaults to <tt>call identity()</tt>
-     */
-    public void setSidIdentityQuery(String sidIdentityQuery) {
-        Assert.hasText(sidIdentityQuery, "New sidIdentityQuery query is required");
-        this.sidIdentityQuery = sidIdentityQuery;
-    }
-
-    public void setDeleteEntryByObjectIdentityForeignKeySql(String deleteEntryByObjectIdentityForeignKey) {
-        this.deleteEntryByObjectIdentityForeignKey = deleteEntryByObjectIdentityForeignKey;
-    }
-
-    public void setDeleteObjectIdentityByPrimaryKeySql(String deleteObjectIdentityByPrimaryKey) {
-        this.deleteObjectIdentityByPrimaryKey = deleteObjectIdentityByPrimaryKey;
-    }
-
-    public void setInsertClassSql(String insertClass) {
-        this.insertClass = insertClass;
-    }
-
-    public void setInsertEntrySql(String insertEntry) {
-        this.insertEntry = insertEntry;
-    }
-
-    public void setInsertObjectIdentitySql(String insertObjectIdentity) {
-        this.insertObjectIdentity = insertObjectIdentity;
-    }
-
-    public void setInsertSidSql(String insertSid) {
-        this.insertSid = insertSid;
-    }
-
-    public void setClassPrimaryKeyQuery(String selectClassPrimaryKey) {
-        this.selectClassPrimaryKey = selectClassPrimaryKey;
-    }
-
-    public void setObjectIdentityPrimaryKeyQuery(String selectObjectIdentityPrimaryKey) {
-        this.selectObjectIdentityPrimaryKey = selectObjectIdentityPrimaryKey;
-    }
-
-    public void setSidPrimaryKeyQuery(String selectSidPrimaryKey) {
-        this.selectSidPrimaryKey = selectSidPrimaryKey;
-    }
-
-    public void setUpdateObjectIdentity(String updateObjectIdentity) {
-        this.updateObjectIdentity = updateObjectIdentity;
-    }
-
-    /**
-     * @param foreignKeysInDatabase if false this class will perform additional FK constrain checking, which may
-     * cause deadlocks (the default is true, so deadlocks are avoided but the database is expected to enforce FKs)
-     */
-    public void setForeignKeysInDatabase(boolean foreignKeysInDatabase) {
-        this.foreignKeysInDatabase = foreignKeysInDatabase;
-    }
+	// ~ Instance fields
+	// ================================================================================================
+
+	private boolean foreignKeysInDatabase = true;
+	private final AclCache aclCache;
+	private String deleteEntryByObjectIdentityForeignKey = "delete from acl_entry where acl_object_identity=?";
+	private String deleteObjectIdentityByPrimaryKey = "delete from acl_object_identity where id=?";
+	private String classIdentityQuery = "call identity()";
+	private String sidIdentityQuery = "call identity()";
+	private String insertClass = "insert into acl_class (class) values (?)";
+	private String insertEntry = "insert into acl_entry "
+			+ "(acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)"
+			+ "values (?, ?, ?, ?, ?, ?, ?)";
+	private String insertObjectIdentity = "insert into acl_object_identity "
+			+ "(object_id_class, object_id_identity, owner_sid, entries_inheriting) "
+			+ "values (?, ?, ?, ?)";
+	private String insertSid = "insert into acl_sid (principal, sid) values (?, ?)";
+	private String selectClassPrimaryKey = "select id from acl_class where class=?";
+	private String selectObjectIdentityPrimaryKey = "select acl_object_identity.id from acl_object_identity, acl_class "
+			+ "where acl_object_identity.object_id_class = acl_class.id and acl_class.class=? "
+			+ "and acl_object_identity.object_id_identity = ?";
+	private String selectSidPrimaryKey = "select id from acl_sid where principal=? and sid=?";
+	private String updateObjectIdentity = "update acl_object_identity set "
+			+ "parent_object = ?, owner_sid = ?, entries_inheriting = ?"
+			+ " where id = ?";
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public JdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy,
+			AclCache aclCache) {
+		super(dataSource, lookupStrategy);
+		Assert.notNull(aclCache, "AclCache required");
+		this.aclCache = aclCache;
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public MutableAcl createAcl(ObjectIdentity objectIdentity)
+			throws AlreadyExistsException {
+		Assert.notNull(objectIdentity, "Object Identity required");
+
+		// Check this object identity hasn't already been persisted
+		if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) {
+			throw new AlreadyExistsException("Object identity '" + objectIdentity
+					+ "' already exists");
+		}
+
+		// Need to retrieve the current principal, in order to know who "owns" this ACL
+		// (can be changed later on)
+		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+		PrincipalSid sid = new PrincipalSid(auth);
+
+		// Create the acl_object_identity row
+		createObjectIdentity(objectIdentity, sid);
+
+		// Retrieve the ACL via superclass (ensures cache registration, proper retrieval
+		// etc)
+		Acl acl = readAclById(objectIdentity);
+		Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned");
+
+		return (MutableAcl) acl;
+	}
+
+	/**
+	 * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl
+	 * object.
+	 *
+	 * @param acl containing the ACEs to insert
+	 */
+	protected void createEntries(final MutableAcl acl) {
+		if (acl.getEntries().isEmpty()) {
+			return;
+		}
+		jdbcTemplate.batchUpdate(insertEntry, new BatchPreparedStatementSetter() {
+			public int getBatchSize() {
+				return acl.getEntries().size();
+			}
+
+			public void setValues(PreparedStatement stmt, int i) throws SQLException {
+				AccessControlEntry entry_ = acl.getEntries().get(i);
+				Assert.isTrue(entry_ instanceof AccessControlEntryImpl,
+						"Unknown ACE class");
+				AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_;
+
+				stmt.setLong(1, ((Long) acl.getId()).longValue());
+				stmt.setInt(2, i);
+				stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true)
+						.longValue());
+				stmt.setInt(4, entry.getPermission().getMask());
+				stmt.setBoolean(5, entry.isGranting());
+				stmt.setBoolean(6, entry.isAuditSuccess());
+				stmt.setBoolean(7, entry.isAuditFailure());
+			}
+		});
+	}
+
+	/**
+	 * Creates an entry in the acl_object_identity table for the passed ObjectIdentity.
+	 * The Sid is also necessary, as acl_object_identity has defined the sid column as
+	 * non-null.
+	 *
+	 * @param object to represent an acl_object_identity for
+	 * @param owner for the SID column (will be created if there is no acl_sid entry for
+	 * this particular Sid already)
+	 */
+	protected void createObjectIdentity(ObjectIdentity object, Sid owner) {
+		Long sidId = createOrRetrieveSidPrimaryKey(owner, true);
+		Long classId = createOrRetrieveClassPrimaryKey(object.getType(), true);
+		jdbcTemplate.update(insertObjectIdentity, classId, object.getIdentifier(), sidId,
+				Boolean.TRUE);
+	}
+
+	/**
+	 * Retrieves the primary key from {@code acl_class}, creating a new row if needed and
+	 * the {@code allowCreate} property is {@code true}.
+	 *
+	 * @param type to find or create an entry for (often the fully-qualified class name)
+	 * @param allowCreate true if creation is permitted if not found
+	 *
+	 * @return the primary key or null if not found
+	 */
+	protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate) {
+		List<Long> classIds = jdbcTemplate.queryForList(selectClassPrimaryKey,
+				new Object[] { type }, Long.class);
+
+		if (!classIds.isEmpty()) {
+			return classIds.get(0);
+		}
+
+		if (allowCreate) {
+			jdbcTemplate.update(insertClass, type);
+			Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
+					"Transaction must be running");
+			return new Long(jdbcTemplate.queryForLong(classIdentityQuery));
+		}
+
+		return null;
+	}
+
+	/**
+	 * Retrieves the primary key from acl_sid, creating a new row if needed and the
+	 * allowCreate property is true.
+	 *
+	 * @param sid to find or create
+	 * @param allowCreate true if creation is permitted if not found
+	 *
+	 * @return the primary key or null if not found
+	 *
+	 * @throws IllegalArgumentException if the <tt>Sid</tt> is not a recognized
+	 * implementation.
+	 */
+	protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
+		Assert.notNull(sid, "Sid required");
+
+		String sidName;
+		boolean sidIsPrincipal = true;
+
+		if (sid instanceof PrincipalSid) {
+			sidName = ((PrincipalSid) sid).getPrincipal();
+		}
+		else if (sid instanceof GrantedAuthoritySid) {
+			sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
+			sidIsPrincipal = false;
+		}
+		else {
+			throw new IllegalArgumentException("Unsupported implementation of Sid");
+		}
+
+		return createOrRetrieveSidPrimaryKey(sidName, sidIsPrincipal, allowCreate);
+	}
+
+	/**
+	 * Retrieves the primary key from acl_sid, creating a new row if needed and the
+	 * allowCreate property is true.
+	 * @param sidName name of Sid to find or to create
+	 * @param sidIsPrincipal whether it's a user or granted authority like role
+	 * @param allowCreate true if creation is permitted if not found
+	 * @return the primary key or null if not found
+	 */
+	protected Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal,
+			boolean allowCreate) {
+
+		List<Long> sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey, new Object[] {
+				Boolean.valueOf(sidIsPrincipal), sidName }, Long.class);
+
+		if (!sidIds.isEmpty()) {
+			return sidIds.get(0);
+		}
+
+		if (allowCreate) {
+			jdbcTemplate.update(insertSid, Boolean.valueOf(sidIsPrincipal), sidName);
+			Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(),
+					"Transaction must be running");
+			return new Long(jdbcTemplate.queryForLong(sidIdentityQuery));
+		}
+
+		return null;
+	}
+
+	public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
+			throws ChildrenExistException {
+		Assert.notNull(objectIdentity, "Object Identity required");
+		Assert.notNull(objectIdentity.getIdentifier(),
+				"Object Identity doesn't provide an identifier");
+
+		if (deleteChildren) {
+			List<ObjectIdentity> children = findChildren(objectIdentity);
+			if (children != null) {
+				for (ObjectIdentity child : children) {
+					deleteAcl(child, true);
+				}
+			}
+		}
+		else {
+			if (!foreignKeysInDatabase) {
+				// We need to perform a manual verification for what a FK would normally
+				// do
+				// We generally don't do this, in the interests of deadlock management
+				List<ObjectIdentity> children = findChildren(objectIdentity);
+				if (children != null) {
+					throw new ChildrenExistException("Cannot delete '" + objectIdentity
+							+ "' (has " + children.size() + " children)");
+				}
+			}
+		}
+
+		Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity);
+
+		// Delete this ACL's ACEs in the acl_entry table
+		deleteEntries(oidPrimaryKey);
+
+		// Delete this ACL's acl_object_identity row
+		deleteObjectIdentity(oidPrimaryKey);
+
+		// Clear the cache
+		aclCache.evictFromCache(objectIdentity);
+	}
+
+	/**
+	 * Deletes all ACEs defined in the acl_entry table belonging to the presented
+	 * ObjectIdentity primary key.
+	 *
+	 * @param oidPrimaryKey the rows in acl_entry to delete
+	 */
+	protected void deleteEntries(Long oidPrimaryKey) {
+		jdbcTemplate.update(deleteEntryByObjectIdentityForeignKey, oidPrimaryKey);
+	}
+
+	/**
+	 * Deletes a single row from acl_object_identity that is associated with the presented
+	 * ObjectIdentity primary key.
+	 * <p>
+	 * We do not delete any entries from acl_class, even if no classes are using that
+	 * class any longer. This is a deadlock avoidance approach.
+	 *
+	 * @param oidPrimaryKey to delete the acl_object_identity
+	 */
+	protected void deleteObjectIdentity(Long oidPrimaryKey) {
+		// Delete the acl_object_identity row
+		jdbcTemplate.update(deleteObjectIdentityByPrimaryKey, oidPrimaryKey);
+	}
+
+	/**
+	 * Retrieves the primary key from the acl_object_identity table for the passed
+	 * ObjectIdentity. Unlike some other methods in this implementation, this method will
+	 * NOT create a row (use {@link #createObjectIdentity(ObjectIdentity, Sid)} instead).
+	 *
+	 * @param oid to find
+	 *
+	 * @return the object identity or null if not found
+	 */
+	protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) {
+		try {
+			return new Long(jdbcTemplate.queryForLong(selectObjectIdentityPrimaryKey,
+					oid.getType(), oid.getIdentifier()));
+		}
+		catch (DataAccessException notFound) {
+			return null;
+		}
+	}
+
+	/**
+	 * This implementation will simply delete all ACEs in the database and recreate them
+	 * on each invocation of this method. A more comprehensive implementation might use
+	 * dirty state checking, or more likely use ORM capabilities for create, update and
+	 * delete operations of {@link MutableAcl}.
+	 */
+	public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
+		Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier");
+
+		// Delete this ACL's ACEs in the acl_entry table
+		deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()));
+
+		// Create this ACL's ACEs in the acl_entry table
+		createEntries(acl);
+
+		// Change the mutable columns in acl_object_identity
+		updateObjectIdentity(acl);
+
+		// Clear the cache, including children
+		clearCacheIncludingChildren(acl.getObjectIdentity());
+
+		// Retrieve the ACL via superclass (ensures cache registration, proper retrieval
+		// etc)
+		return (MutableAcl) super.readAclById(acl.getObjectIdentity());
+	}
+
+	private void clearCacheIncludingChildren(ObjectIdentity objectIdentity) {
+		Assert.notNull(objectIdentity, "ObjectIdentity required");
+		List<ObjectIdentity> children = findChildren(objectIdentity);
+		if (children != null) {
+			for (ObjectIdentity child : children) {
+				clearCacheIncludingChildren(child);
+			}
+		}
+		aclCache.evictFromCache(objectIdentity);
+	}
+
+	/**
+	 * Updates an existing acl_object_identity row, with new information presented in the
+	 * passed MutableAcl object. Also will create an acl_sid entry if needed for the Sid
+	 * that owns the MutableAcl.
+	 *
+	 * @param acl to modify (a row must already exist in acl_object_identity)
+	 *
+	 * @throws NotFoundException if the ACL could not be found to update.
+	 */
+	protected void updateObjectIdentity(MutableAcl acl) {
+		Long parentId = null;
+
+		if (acl.getParentAcl() != null) {
+			Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl()
+					.getObjectIdentity(),
+					"Implementation only supports ObjectIdentityImpl");
+
+			ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl()
+					.getObjectIdentity();
+			parentId = retrieveObjectIdentityPrimaryKey(oii);
+		}
+
+		Assert.notNull(acl.getOwner(), "Owner is required in this implementation");
+
+		Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true);
+		int count = jdbcTemplate.update(updateObjectIdentity, parentId, ownerSid,
+				Boolean.valueOf(acl.isEntriesInheriting()), acl.getId());
+
+		if (count != 1) {
+			throw new NotFoundException("Unable to locate ACL to update");
+		}
+	}
+
+	/**
+	 * Sets the query that will be used to retrieve the identity of a newly created row in
+	 * the <tt>acl_class</tt> table.
+	 *
+	 * @param classIdentityQuery the query, which should return the identifier. Defaults
+	 * to <tt>call identity()</tt>
+	 */
+	public void setClassIdentityQuery(String classIdentityQuery) {
+		Assert.hasText(classIdentityQuery, "New classIdentityQuery query is required");
+		this.classIdentityQuery = classIdentityQuery;
+	}
+
+	/**
+	 * Sets the query that will be used to retrieve the identity of a newly created row in
+	 * the <tt>acl_sid</tt> table.
+	 *
+	 * @param sidIdentityQuery the query, which should return the identifier. Defaults to
+	 * <tt>call identity()</tt>
+	 */
+	public void setSidIdentityQuery(String sidIdentityQuery) {
+		Assert.hasText(sidIdentityQuery, "New sidIdentityQuery query is required");
+		this.sidIdentityQuery = sidIdentityQuery;
+	}
+
+	public void setDeleteEntryByObjectIdentityForeignKeySql(
+			String deleteEntryByObjectIdentityForeignKey) {
+		this.deleteEntryByObjectIdentityForeignKey = deleteEntryByObjectIdentityForeignKey;
+	}
+
+	public void setDeleteObjectIdentityByPrimaryKeySql(
+			String deleteObjectIdentityByPrimaryKey) {
+		this.deleteObjectIdentityByPrimaryKey = deleteObjectIdentityByPrimaryKey;
+	}
+
+	public void setInsertClassSql(String insertClass) {
+		this.insertClass = insertClass;
+	}
+
+	public void setInsertEntrySql(String insertEntry) {
+		this.insertEntry = insertEntry;
+	}
+
+	public void setInsertObjectIdentitySql(String insertObjectIdentity) {
+		this.insertObjectIdentity = insertObjectIdentity;
+	}
+
+	public void setInsertSidSql(String insertSid) {
+		this.insertSid = insertSid;
+	}
+
+	public void setClassPrimaryKeyQuery(String selectClassPrimaryKey) {
+		this.selectClassPrimaryKey = selectClassPrimaryKey;
+	}
+
+	public void setObjectIdentityPrimaryKeyQuery(String selectObjectIdentityPrimaryKey) {
+		this.selectObjectIdentityPrimaryKey = selectObjectIdentityPrimaryKey;
+	}
+
+	public void setSidPrimaryKeyQuery(String selectSidPrimaryKey) {
+		this.selectSidPrimaryKey = selectSidPrimaryKey;
+	}
+
+	public void setUpdateObjectIdentity(String updateObjectIdentity) {
+		this.updateObjectIdentity = updateObjectIdentity;
+	}
+
+	/**
+	 * @param foreignKeysInDatabase if false this class will perform additional FK
+	 * constrain checking, which may cause deadlocks (the default is true, so deadlocks
+	 * are avoided but the database is expected to enforce FKs)
+	 */
+	public void setForeignKeysInDatabase(boolean foreignKeysInDatabase) {
+		this.foreignKeysInDatabase = foreignKeysInDatabase;
+	}
 }

+ 16 - 15
acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java

@@ -22,26 +22,27 @@ import org.springframework.security.acls.model.Sid;
 import java.util.List;
 import java.util.Map;
 
-
 /**
  * Performs lookups for {@link org.springframework.security.acls.model.AclService}.
  *
  * @author Ben Alex
  */
 public interface LookupStrategy {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * Perform database-specific optimized lookup.
-     *
-     * @param objects the identities to lookup (required)
-     * @param sids the SIDs for which identities are required (may be <tt>null</tt> - implementations may elect not
-     *        to provide SID optimisations)
-     *
-     * @return a <tt>Map</tt> where keys represent the {@link ObjectIdentity} of the located {@link Acl} and values
-     *         are the located {@link Acl} (never <tt>null</tt> although some entries may be missing; this method
-     *         should not throw {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used
-     *         to automatically create entries if required)
-     */
-    Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids);
+	/**
+	 * Perform database-specific optimized lookup.
+	 *
+	 * @param objects the identities to lookup (required)
+	 * @param sids the SIDs for which identities are required (may be <tt>null</tt> -
+	 * implementations may elect not to provide SID optimisations)
+	 *
+	 * @return a <tt>Map</tt> where keys represent the {@link ObjectIdentity} of the
+	 * located {@link Acl} and values are the located {@link Acl} (never <tt>null</tt>
+	 * although some entries may be missing; this method should not throw
+	 * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to
+	 * automatically create entries if required)
+	 */
+	Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids);
 }

+ 20 - 21
acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java

@@ -14,42 +14,41 @@
  */
 package org.springframework.security.acls.model;
 
-
 import java.io.Serializable;
 
-
 /**
  * Represents an individual permission assignment within an {@link Acl}.
  *
  * <p>
- * Instances MUST be immutable, as they are returned by <code>Acl</code>
- * and should not allow client modification.
+ * Instances MUST be immutable, as they are returned by <code>Acl</code> and should not
+ * allow client modification.
  * </p>
  *
  * @author Ben Alex
  *
  */
 public interface AccessControlEntry extends Serializable {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    Acl getAcl();
+	Acl getAcl();
 
-    /**
-     * Obtains an identifier that represents this ACE.
-     *
-     * @return the identifier, or <code>null</code> if unsaved
-     */
-    Serializable getId();
+	/**
+	 * Obtains an identifier that represents this ACE.
+	 *
+	 * @return the identifier, or <code>null</code> if unsaved
+	 */
+	Serializable getId();
 
-    Permission getPermission();
+	Permission getPermission();
 
-    Sid getSid();
+	Sid getSid();
 
-    /**
-     * Indicates the a Permission is being granted to the relevant Sid. If false, indicates the permission is
-     * being revoked/blocked.
-     *
-     * @return true if being granted, false otherwise
-     */
-    boolean isGranting();
+	/**
+	 * Indicates the a Permission is being granted to the relevant Sid. If false,
+	 * indicates the permission is being revoked/blocked.
+	 *
+	 * @return true if being granted, false otherwise
+	 */
+	boolean isGranting();
 }

+ 161 - 128
acl/src/main/java/org/springframework/security/acls/model/Acl.java

@@ -14,155 +14,188 @@
  */
 package org.springframework.security.acls.model;
 
-
 import java.io.Serializable;
 import java.util.List;
 
-
 /**
  * Represents an access control list (ACL) for a domain object.
  *
  * <p>
- * An <tt>Acl</tt> represents all ACL entries for a given domain object. In
- * order to avoid needing references to the domain object itself, this
- * interface handles indirection between a domain object and an ACL object
- * identity via the {@link
- * org.springframework.security.acls.model.ObjectIdentity} interface.
+ * An <tt>Acl</tt> represents all ACL entries for a given domain object. In order to avoid
+ * needing references to the domain object itself, this interface handles indirection
+ * between a domain object and an ACL object identity via the
+ * {@link org.springframework.security.acls.model.ObjectIdentity} interface.
  * </p>
  *
  * <p>
  * Implementing classes may elect to return instances that represent
- * {@link org.springframework.security.acls.model.Permission} information for either
- * some OR all {@link org.springframework.security.acls.model.Sid}
- * instances. Therefore, an instance may NOT necessarily contain ALL <tt>Sid</tt>s
- * for a given domain object.
+ * {@link org.springframework.security.acls.model.Permission} information for either some
+ * OR all {@link org.springframework.security.acls.model.Sid} instances. Therefore, an
+ * instance may NOT necessarily contain ALL <tt>Sid</tt>s for a given domain object.
  * </p>
  *
  * @author Ben Alex
  */
 public interface Acl extends Serializable {
 
-    /**
-     * Returns all of the entries represented by the present <tt>Acl</tt>. Entries associated with
-     * the <tt>Acl</tt> parents are not returned.
-     *
-     * <p>This method is typically used for administrative purposes.</p>
-     *
-     * <p>The order that entries appear in the array is important for methods declared in the
-     * {@link MutableAcl} interface. Furthermore, some implementations MAY use ordering as
-     * part of advanced permission checking.</p>
-     *
-     * <p>Do <em>NOT</em> use this method for making authorization decisions. Instead use {@link
-     * #isGranted(List, List, boolean)}.</p>
-     *
-     * <p>This method must operate correctly even if the <tt>Acl</tt> only represents a subset of
-     * <tt>Sid</tt>s. The caller is responsible for correctly handling the result if only a subset of
-     * <tt>Sid</tt>s is represented.</p>
-     *
-     * @return the list of entries represented by the <tt>Acl</tt>, or <tt>null</tt> if there are
-     * no entries presently associated with this <tt>Acl</tt>.
-     */
-    List<AccessControlEntry> getEntries();
+	/**
+	 * Returns all of the entries represented by the present <tt>Acl</tt>. Entries
+	 * associated with the <tt>Acl</tt> parents are not returned.
+	 *
+	 * <p>
+	 * This method is typically used for administrative purposes.
+	 * </p>
+	 *
+	 * <p>
+	 * The order that entries appear in the array is important for methods declared in the
+	 * {@link MutableAcl} interface. Furthermore, some implementations MAY use ordering as
+	 * part of advanced permission checking.
+	 * </p>
+	 *
+	 * <p>
+	 * Do <em>NOT</em> use this method for making authorization decisions. Instead use
+	 * {@link #isGranted(List, List, boolean)}.
+	 * </p>
+	 *
+	 * <p>
+	 * This method must operate correctly even if the <tt>Acl</tt> only represents a
+	 * subset of <tt>Sid</tt>s. The caller is responsible for correctly handling the
+	 * result if only a subset of <tt>Sid</tt>s is represented.
+	 * </p>
+	 *
+	 * @return the list of entries represented by the <tt>Acl</tt>, or <tt>null</tt> if
+	 * there are no entries presently associated with this <tt>Acl</tt>.
+	 */
+	List<AccessControlEntry> getEntries();
 
-    /**
-     * Obtains the domain object this <tt>Acl</tt> provides entries for. This is immutable once an
-     * <tt>Acl</tt> is created.
-     *
-     * @return the object identity (never <tt>null</tt>)
-     */
-    ObjectIdentity getObjectIdentity();
+	/**
+	 * Obtains the domain object this <tt>Acl</tt> provides entries for. This is immutable
+	 * once an <tt>Acl</tt> is created.
+	 *
+	 * @return the object identity (never <tt>null</tt>)
+	 */
+	ObjectIdentity getObjectIdentity();
 
-    /**
-     * Determines the owner of the <tt>Acl</tt>. The meaning of ownership varies by implementation and is
-     * unspecified.
-     *
-     * @return the owner (may be <tt>null</tt> if the implementation does not use ownership concepts)
-     */
-    Sid getOwner();
+	/**
+	 * Determines the owner of the <tt>Acl</tt>. The meaning of ownership varies by
+	 * implementation and is unspecified.
+	 *
+	 * @return the owner (may be <tt>null</tt> if the implementation does not use
+	 * ownership concepts)
+	 */
+	Sid getOwner();
 
-    /**
-     * A domain object may have a parent for the purpose of ACL inheritance. If there is a parent, its ACL can
-     * be accessed via this method. In turn, the parent's parent (grandparent) can be accessed and so on.
-     *
-     * <p>This method solely represents the presence of a navigation hierarchy between the parent <tt>Acl</tt> and this
-     * <tt>Acl</tt>. For actual inheritance to take place, the {@link #isEntriesInheriting()} must also be
-     * <tt>true</tt>.</p>
-     *
-     * <p>This method must operate correctly even if the <tt>Acl</tt> only represents a subset of
-     * <tt>Sid</tt>s. The caller is responsible for correctly handling the result if only a subset of
-     * <tt>Sid</tt>s is represented.</p>
-     *
-     * @return the parent <tt>Acl</tt> (may be <tt>null</tt> if this <tt>Acl</tt> does not have a parent)
-     */
-    Acl getParentAcl();
+	/**
+	 * A domain object may have a parent for the purpose of ACL inheritance. If there is a
+	 * parent, its ACL can be accessed via this method. In turn, the parent's parent
+	 * (grandparent) can be accessed and so on.
+	 *
+	 * <p>
+	 * This method solely represents the presence of a navigation hierarchy between the
+	 * parent <tt>Acl</tt> and this <tt>Acl</tt>. For actual inheritance to take place,
+	 * the {@link #isEntriesInheriting()} must also be <tt>true</tt>.
+	 * </p>
+	 *
+	 * <p>
+	 * This method must operate correctly even if the <tt>Acl</tt> only represents a
+	 * subset of <tt>Sid</tt>s. The caller is responsible for correctly handling the
+	 * result if only a subset of <tt>Sid</tt>s is represented.
+	 * </p>
+	 *
+	 * @return the parent <tt>Acl</tt> (may be <tt>null</tt> if this <tt>Acl</tt> does not
+	 * have a parent)
+	 */
+	Acl getParentAcl();
 
-    /**
-     * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down into the current
-     * <tt>Acl</tt>.<p>The mere link between an <tt>Acl</tt> and a parent <tt>Acl</tt> on its own
-     * is insufficient to cause ACL entries to inherit down. This is because a domain object may wish to have entirely
-     * independent entries, but maintain the link with the parent for navigation purposes. Thus, this method denotes
-     * whether or not the navigation relationship also extends to the actual inheritance of entries.</p>
-     *
-     * @return <tt>true</tt> if parent ACL entries inherit into the current <tt>Acl</tt>
-     */
-    boolean isEntriesInheriting();
+	/**
+	 * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down
+	 * into the current <tt>Acl</tt>.
+	 * <p>
+	 * The mere link between an <tt>Acl</tt> and a parent <tt>Acl</tt> on its own is
+	 * insufficient to cause ACL entries to inherit down. This is because a domain object
+	 * may wish to have entirely independent entries, but maintain the link with the
+	 * parent for navigation purposes. Thus, this method denotes whether or not the
+	 * navigation relationship also extends to the actual inheritance of entries.
+	 * </p>
+	 *
+	 * @return <tt>true</tt> if parent ACL entries inherit into the current <tt>Acl</tt>
+	 */
+	boolean isEntriesInheriting();
 
-    /**
-     * This is the actual authorization logic method, and must be used whenever ACL authorization decisions are
-     * required.
-     *
-     * <p>An array of <tt>Sid</tt>s are presented, representing security identifies of the current
-     * principal. In addition, an array of <tt>Permission</tt>s is presented which will have one or more bits set
-     * in order to indicate the permissions needed for an affirmative authorization decision. An array is presented
-     * because holding <em>any</em> of the <tt>Permission</tt>s inside the array will be sufficient for an
-     * affirmative authorization.</p>
-     *
-     * <p>The actual approach used to make authorization decisions is left to the implementation and is not
-     * specified by this interface. For example, an implementation <em>MAY</em> search the current ACL in the order
-     * the ACL entries have been stored. If a single entry is found that has the same active bits as are shown in a
-     * passed <tt>Permission</tt>, that entry's grant or deny state may determine the authorization decision. If
-     * the case of a deny state, the deny decision will only be relevant if all other <tt>Permission</tt>s passed
-     * in the array have also been unsuccessfully searched. If no entry is found that match the bits in the current
-     * ACL, provided that {@link #isEntriesInheriting()} is <tt>true</tt>, the authorization decision may be
-     * passed to the parent ACL. If there is no matching entry, the implementation MAY throw an exception, or make a
-     * predefined authorization decision.</p>
-     *
-     * <p>This method must operate correctly even if the <tt>Acl</tt> only represents a subset of <tt>Sid</tt>s,
-     * although the implementation is permitted to throw one of the signature-defined exceptions if the method
-     * is called requesting an authorization decision for a {@link Sid} that was never loaded in this <tt>Acl</tt>.
-     * </p>
-     *
-     * @param permission the permission or permissions required (at least one entry required)
-     * @param sids the security identities held by the principal (at least one entry required)
-     * @param administrativeMode if <tt>true</tt> denotes the query is for administrative purposes and no logging
-     *        or auditing (if supported by the implementation) should be undertaken
-     *
-     * @return <tt>true</tt> if authorization is granted
-     *
-     * @throws NotFoundException MUST be thrown if an implementation cannot make an authoritative authorization
-     *         decision, usually because there is no ACL information for this particular permission and/or SID
-     * @throws UnloadedSidException thrown if the <tt>Acl</tt> does not have details for one or more of the
-     *         <tt>Sid</tt>s passed as arguments
-     */
-    boolean isGranted(List<Permission> permission, List<Sid> sids, boolean administrativeMode)
-        throws NotFoundException, UnloadedSidException;
+	/**
+	 * This is the actual authorization logic method, and must be used whenever ACL
+	 * authorization decisions are required.
+	 *
+	 * <p>
+	 * An array of <tt>Sid</tt>s are presented, representing security identifies of the
+	 * current principal. In addition, an array of <tt>Permission</tt>s is presented which
+	 * will have one or more bits set in order to indicate the permissions needed for an
+	 * affirmative authorization decision. An array is presented because holding
+	 * <em>any</em> of the <tt>Permission</tt>s inside the array will be sufficient for an
+	 * affirmative authorization.
+	 * </p>
+	 *
+	 * <p>
+	 * The actual approach used to make authorization decisions is left to the
+	 * implementation and is not specified by this interface. For example, an
+	 * implementation <em>MAY</em> search the current ACL in the order the ACL entries
+	 * have been stored. If a single entry is found that has the same active bits as are
+	 * shown in a passed <tt>Permission</tt>, that entry's grant or deny state may
+	 * determine the authorization decision. If the case of a deny state, the deny
+	 * decision will only be relevant if all other <tt>Permission</tt>s passed in the
+	 * array have also been unsuccessfully searched. If no entry is found that match the
+	 * bits in the current ACL, provided that {@link #isEntriesInheriting()} is
+	 * <tt>true</tt>, the authorization decision may be passed to the parent ACL. If there
+	 * is no matching entry, the implementation MAY throw an exception, or make a
+	 * predefined authorization decision.
+	 * </p>
+	 *
+	 * <p>
+	 * This method must operate correctly even if the <tt>Acl</tt> only represents a
+	 * subset of <tt>Sid</tt>s, although the implementation is permitted to throw one of
+	 * the signature-defined exceptions if the method is called requesting an
+	 * authorization decision for a {@link Sid} that was never loaded in this <tt>Acl</tt>
+	 * .
+	 * </p>
+	 *
+	 * @param permission the permission or permissions required (at least one entry
+	 * required)
+	 * @param sids the security identities held by the principal (at least one entry
+	 * required)
+	 * @param administrativeMode if <tt>true</tt> denotes the query is for administrative
+	 * purposes and no logging or auditing (if supported by the implementation) should be
+	 * undertaken
+	 *
+	 * @return <tt>true</tt> if authorization is granted
+	 *
+	 * @throws NotFoundException MUST be thrown if an implementation cannot make an
+	 * authoritative authorization decision, usually because there is no ACL information
+	 * for this particular permission and/or SID
+	 * @throws UnloadedSidException thrown if the <tt>Acl</tt> does not have details for
+	 * one or more of the <tt>Sid</tt>s passed as arguments
+	 */
+	boolean isGranted(List<Permission> permission, List<Sid> sids,
+			boolean administrativeMode) throws NotFoundException, UnloadedSidException;
 
-    /**
-     * For efficiency reasons an <tt>Acl</tt> may be loaded and <em>not</em> contain entries for every
-     * <tt>Sid</tt> in the system. If an <tt>Acl</tt> has been loaded and does not represent every
-     * <tt>Sid</tt>, all methods of the <tt>Acl</tt> can only be used within the limited scope of the
-     * <tt>Sid</tt> instances it actually represents.
-     * <p>
-     * It is normal to load an <tt>Acl</tt> for only particular <tt>Sid</tt>s if read-only authorization
-     * decisions are being made. However, if user interface reporting or modification of <tt>Acl</tt>s are
-     * desired, an <tt>Acl</tt> should be loaded with all <tt>Sid</tt>s. This method denotes whether or
-     * not the specified <tt>Sid</tt>s have been loaded or not.
-     * </p>
-     *
-     * @param sids one or more security identities the caller is interest in knowing whether this <tt>Sid</tt>
-     *        supports
-     *
-     * @return <tt>true</tt> if every passed <tt>Sid</tt> is represented by this <tt>Acl</tt> instance
-     */
-    boolean isSidLoaded(List<Sid> sids);
+	/**
+	 * For efficiency reasons an <tt>Acl</tt> may be loaded and <em>not</em> contain
+	 * entries for every <tt>Sid</tt> in the system. If an <tt>Acl</tt> has been loaded
+	 * and does not represent every <tt>Sid</tt>, all methods of the <tt>Acl</tt> can only
+	 * be used within the limited scope of the <tt>Sid</tt> instances it actually
+	 * represents.
+	 * <p>
+	 * It is normal to load an <tt>Acl</tt> for only particular <tt>Sid</tt>s if read-only
+	 * authorization decisions are being made. However, if user interface reporting or
+	 * modification of <tt>Acl</tt>s are desired, an <tt>Acl</tt> should be loaded with
+	 * all <tt>Sid</tt>s. This method denotes whether or not the specified <tt>Sid</tt>s
+	 * have been loaded or not.
+	 * </p>
+	 *
+	 * @param sids one or more security identities the caller is interest in knowing
+	 * whether this <tt>Sid</tt> supports
+	 *
+	 * @return <tt>true</tt> if every passed <tt>Sid</tt> is represented by this
+	 * <tt>Acl</tt> instance
+	 */
+	boolean isSidLoaded(List<Sid> sids);
 }

+ 8 - 8
acl/src/main/java/org/springframework/security/acls/model/AclCache.java

@@ -18,7 +18,6 @@ import org.springframework.security.acls.jdbc.JdbcAclService;
 
 import java.io.Serializable;
 
-
 /**
  * A caching layer for {@link JdbcAclService}.
  *
@@ -26,17 +25,18 @@ import java.io.Serializable;
  *
  */
 public interface AclCache {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    void evictFromCache(Serializable pk);
+	void evictFromCache(Serializable pk);
 
-    void evictFromCache(ObjectIdentity objectIdentity);
+	void evictFromCache(ObjectIdentity objectIdentity);
 
-    MutableAcl getFromCache(ObjectIdentity objectIdentity);
+	MutableAcl getFromCache(ObjectIdentity objectIdentity);
 
-    MutableAcl getFromCache(Serializable pk);
+	MutableAcl getFromCache(Serializable pk);
 
-    void putInCache(MutableAcl acl);
+	void putInCache(MutableAcl acl);
 
-    void clearCache();
+	void clearCache();
 }

+ 19 - 19
acl/src/main/java/org/springframework/security/acls/model/AclDataAccessException.java

@@ -8,24 +8,24 @@ package org.springframework.security.acls.model;
  */
 public abstract class AclDataAccessException extends RuntimeException {
 
-    /**
-     * Constructs an <code>AclDataAccessException</code> with the specified
-     * message and root cause.
-     *
-     * @param msg the detail message
-     * @param cause the root cause
-     */
-    public AclDataAccessException(String msg, Throwable cause) {
-        super(msg, cause);
-    }
+	/**
+	 * Constructs an <code>AclDataAccessException</code> with the specified message and
+	 * root cause.
+	 *
+	 * @param msg the detail message
+	 * @param cause the root cause
+	 */
+	public AclDataAccessException(String msg, Throwable cause) {
+		super(msg, cause);
+	}
 
-    /**
-     * Constructs an <code>AclDataAccessException</code> with the specified
-     * message and no root cause.
-     *
-     * @param msg the detail message
-     */
-    public AclDataAccessException(String msg) {
-        super(msg);
-    }
+	/**
+	 * Constructs an <code>AclDataAccessException</code> with the specified message and no
+	 * root cause.
+	 *
+	 * @param msg the detail message
+	 */
+	public AclDataAccessException(String msg) {
+		super(msg);
+	}
 }

+ 87 - 67
acl/src/main/java/org/springframework/security/acls/model/AclService.java

@@ -14,85 +14,105 @@
  */
 package org.springframework.security.acls.model;
 
-
 import java.util.List;
 import java.util.Map;
 
-
 /**
  * Provides retrieval of {@link Acl} instances.
  *
  * @author Ben Alex
  */
 public interface AclService {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * Locates all object identities that use the specified parent.  This is useful for administration tools.
-     *
-     * @param parentIdentity to locate children of
-     *
-     * @return the children (or <tt>null</tt> if none were found)
-     */
-    List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity);
+	/**
+	 * Locates all object identities that use the specified parent. This is useful for
+	 * administration tools.
+	 *
+	 * @param parentIdentity to locate children of
+	 *
+	 * @return the children (or <tt>null</tt> if none were found)
+	 */
+	List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity);
 
-    /**
-     * Same as {@link #readAclsById(List)} except it returns only a single Acl.
-     * <p>
-     * This method should not be called as it does not leverage the underlying implementation's potential ability to
-     * filter <tt>Acl</tt> entries based on a {@link Sid} parameter.</p>
-     *
-     * @param object to locate an {@link Acl} for
-     *
-     * @return the {@link Acl} for the requested {@link ObjectIdentity} (never <tt>null</tt>)
-     *
-     * @throws NotFoundException if an {@link Acl} was not found for the requested {@link ObjectIdentity}
-     */
-    Acl readAclById(ObjectIdentity object) throws NotFoundException;
+	/**
+	 * Same as {@link #readAclsById(List)} except it returns only a single Acl.
+	 * <p>
+	 * This method should not be called as it does not leverage the underlying
+	 * implementation's potential ability to filter <tt>Acl</tt> entries based on a
+	 * {@link Sid} parameter.
+	 * </p>
+	 *
+	 * @param object to locate an {@link Acl} for
+	 *
+	 * @return the {@link Acl} for the requested {@link ObjectIdentity} (never
+	 * <tt>null</tt>)
+	 *
+	 * @throws NotFoundException if an {@link Acl} was not found for the requested
+	 * {@link ObjectIdentity}
+	 */
+	Acl readAclById(ObjectIdentity object) throws NotFoundException;
 
-    /**
-     * Same as {@link #readAclsById(List, List)} except it returns only a single Acl.
-     *
-     * @param object to locate an {@link Acl} for
-     * @param sids the security identities for which  {@link Acl} information is required
-     *        (may be <tt>null</tt> to denote all entries)
-     *
-     * @return the {@link Acl} for the requested {@link ObjectIdentity} (never <tt>null</tt>)
-     *
-     * @throws NotFoundException if an {@link Acl} was not found for the requested {@link ObjectIdentity}
-     */
-    Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException;
+	/**
+	 * Same as {@link #readAclsById(List, List)} except it returns only a single Acl.
+	 *
+	 * @param object to locate an {@link Acl} for
+	 * @param sids the security identities for which {@link Acl} information is required
+	 * (may be <tt>null</tt> to denote all entries)
+	 *
+	 * @return the {@link Acl} for the requested {@link ObjectIdentity} (never
+	 * <tt>null</tt>)
+	 *
+	 * @throws NotFoundException if an {@link Acl} was not found for the requested
+	 * {@link ObjectIdentity}
+	 */
+	Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException;
 
-    /**
-     * Obtains all the <tt>Acl</tt>s that apply for the passed <tt>Object</tt>s.<p>The returned map is
-     * keyed on the passed objects, with the values being the <tt>Acl</tt> instances. Any unknown objects will not
-     * have a map key.</p>
-     *
-     * @param objects the objects to find {@link Acl} information for
-     *
-     * @return a map with exactly one element for each {@link ObjectIdentity} passed as an argument (never <tt>null</tt>)
-     *
-     * @throws NotFoundException if an {@link Acl} was not found for each requested {@link ObjectIdentity}
-     */
-    Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects) throws NotFoundException;
+	/**
+	 * Obtains all the <tt>Acl</tt>s that apply for the passed <tt>Object</tt>s.
+	 * <p>
+	 * The returned map is keyed on the passed objects, with the values being the
+	 * <tt>Acl</tt> instances. Any unknown objects will not have a map key.
+	 * </p>
+	 *
+	 * @param objects the objects to find {@link Acl} information for
+	 *
+	 * @return a map with exactly one element for each {@link ObjectIdentity} passed as an
+	 * argument (never <tt>null</tt>)
+	 *
+	 * @throws NotFoundException if an {@link Acl} was not found for each requested
+	 * {@link ObjectIdentity}
+	 */
+	Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects)
+			throws NotFoundException;
 
-    /**
-     * Obtains all the <tt>Acl</tt>s that apply for the passed <tt>Object</tt>s, but only for the
-     * security identifies passed.<p>Implementations <em>MAY</em> provide a subset of the ACLs via this method
-     * although this is NOT a requirement. This is intended to allow performance optimisations within implementations.
-     * Callers should therefore use this method in preference to the alternative overloaded version which does not
-     * have performance optimisation opportunities.</p>
-     *  <p>The returned map is keyed on the passed objects, with the values being the <tt>Acl</tt>
-     * instances. Any unknown objects (or objects for which the interested <tt>Sid</tt>s do not have entries) will
-     * not have a map key.</p>
-     *
-     * @param objects the objects to find {@link Acl} information for
-     * @param sids the security identities for which  {@link Acl} information is required
-     *        (may be <tt>null</tt> to denote all entries)
-     *
-     * @return a map with exactly one element for each {@link ObjectIdentity} passed as an argument (never <tt>null</tt>)
-     *
-     * @throws NotFoundException if an {@link Acl} was not found for each requested {@link ObjectIdentity}
-     */
-    Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) throws NotFoundException;
+	/**
+	 * Obtains all the <tt>Acl</tt>s that apply for the passed <tt>Object</tt>s, but only
+	 * for the security identifies passed.
+	 * <p>
+	 * Implementations <em>MAY</em> provide a subset of the ACLs via this method although
+	 * this is NOT a requirement. This is intended to allow performance optimisations
+	 * within implementations. Callers should therefore use this method in preference to
+	 * the alternative overloaded version which does not have performance optimisation
+	 * opportunities.
+	 * </p>
+	 * <p>
+	 * The returned map is keyed on the passed objects, with the values being the
+	 * <tt>Acl</tt> instances. Any unknown objects (or objects for which the interested
+	 * <tt>Sid</tt>s do not have entries) will not have a map key.
+	 * </p>
+	 *
+	 * @param objects the objects to find {@link Acl} information for
+	 * @param sids the security identities for which {@link Acl} information is required
+	 * (may be <tt>null</tt> to denote all entries)
+	 *
+	 * @return a map with exactly one element for each {@link ObjectIdentity} passed as an
+	 * argument (never <tt>null</tt>)
+	 *
+	 * @throws NotFoundException if an {@link Acl} was not found for each requested
+	 * {@link ObjectIdentity}
+	 */
+	Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids)
+			throws NotFoundException;
 }

+ 20 - 20
acl/src/main/java/org/springframework/security/acls/model/AlreadyExistsException.java

@@ -14,32 +14,32 @@
  */
 package org.springframework.security.acls.model;
 
-
 /**
  * Thrown if an <code>Acl</code> entry already exists for the object.
  *
  * @author Ben Alex
  */
 public class AlreadyExistsException extends AclDataAccessException {
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    /**
-     * Constructs an <code>AlreadyExistsException</code> with the specified message.
-     *
-     * @param msg the detail message
-     */
-    public AlreadyExistsException(String msg) {
-        super(msg);
-    }
+	/**
+	 * Constructs an <code>AlreadyExistsException</code> with the specified message.
+	 *
+	 * @param msg the detail message
+	 */
+	public AlreadyExistsException(String msg) {
+		super(msg);
+	}
 
-    /**
-     * Constructs an <code>AlreadyExistsException</code> with the specified message
-     * and root cause.
-     *
-     * @param msg the detail message
-     * @param t root cause
-     */
-    public AlreadyExistsException(String msg, Throwable t) {
-        super(msg, t);
-    }
+	/**
+	 * Constructs an <code>AlreadyExistsException</code> with the specified message and
+	 * root cause.
+	 *
+	 * @param msg the detail message
+	 * @param t root cause
+	 */
+	public AlreadyExistsException(String msg, Throwable t) {
+		super(msg, t);
+	}
 }

+ 4 - 4
acl/src/main/java/org/springframework/security/acls/model/AuditableAccessControlEntry.java

@@ -14,7 +14,6 @@
  */
 package org.springframework.security.acls.model;
 
-
 /**
  * Represents an ACE that provides auditing information.
  *
@@ -22,9 +21,10 @@ package org.springframework.security.acls.model;
  *
  */
 public interface AuditableAccessControlEntry extends AccessControlEntry {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    boolean isAuditFailure();
+	boolean isAuditFailure();
 
-    boolean isAuditSuccess();
+	boolean isAuditSuccess();
 }

+ 3 - 3
acl/src/main/java/org/springframework/security/acls/model/AuditableAcl.java

@@ -14,7 +14,6 @@
  */
 package org.springframework.security.acls.model;
 
-
 /**
  * A mutable ACL that provides audit capabilities.
  *
@@ -22,7 +21,8 @@ package org.springframework.security.acls.model;
  *
  */
 public interface AuditableAcl extends MutableAcl {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure);
+	void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure);
 }

+ 20 - 21
acl/src/main/java/org/springframework/security/acls/model/ChildrenExistException.java

@@ -14,33 +14,32 @@
  */
 package org.springframework.security.acls.model;
 
-
 /**
  * Thrown if an {@link Acl} cannot be deleted because children <code>Acl</code>s exist.
  *
  * @author Ben Alex
  */
 public class ChildrenExistException extends AclDataAccessException {
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    /**
-     * Constructs an <code>ChildrenExistException</code> with the specified
-     * message.
-     *
-     * @param msg the detail message
-     */
-    public ChildrenExistException(String msg) {
-        super(msg);
-    }
+	/**
+	 * Constructs an <code>ChildrenExistException</code> with the specified message.
+	 *
+	 * @param msg the detail message
+	 */
+	public ChildrenExistException(String msg) {
+		super(msg);
+	}
 
-    /**
-     * Constructs an <code>ChildrenExistException</code> with the specified
-     * message and root cause.
-     *
-     * @param msg the detail message
-     * @param t root cause
-     */
-    public ChildrenExistException(String msg, Throwable t) {
-        super(msg, t);
-    }
+	/**
+	 * Constructs an <code>ChildrenExistException</code> with the specified message and
+	 * root cause.
+	 *
+	 * @param msg the detail message
+	 * @param t root cause
+	 */
+	public ChildrenExistException(String msg, Throwable t) {
+		super(msg, t);
+	}
 }

+ 32 - 34
acl/src/main/java/org/springframework/security/acls/model/MutableAcl.java

@@ -16,52 +16,50 @@ package org.springframework.security.acls.model;
 
 import java.io.Serializable;
 
-
-
 /**
  * A mutable <tt>Acl</tt>.
  * <p>
- * A mutable ACL must ensure that appropriate security checks are performed
- * before allowing access to its methods.
+ * A mutable ACL must ensure that appropriate security checks are performed before
+ * allowing access to its methods.
  *
  * @author Ben Alex
  */
 public interface MutableAcl extends Acl {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    void deleteAce(int aceIndex) throws NotFoundException;
+	void deleteAce(int aceIndex) throws NotFoundException;
 
-    /**
-     * Obtains an identifier that represents this <tt>MutableAcl</tt>.
-     *
-     * @return the identifier, or <tt>null</tt> if unsaved
-     */
-    Serializable getId();
+	/**
+	 * Obtains an identifier that represents this <tt>MutableAcl</tt>.
+	 *
+	 * @return the identifier, or <tt>null</tt> if unsaved
+	 */
+	Serializable getId();
 
-    void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting)
-        throws NotFoundException;
+	void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting)
+			throws NotFoundException;
 
-    /**
-     * Changes the present owner to a different owner.
-     *
-     * @param newOwner the new owner (mandatory; cannot be null)
-     */
-    void setOwner(Sid newOwner);
+	/**
+	 * Changes the present owner to a different owner.
+	 *
+	 * @param newOwner the new owner (mandatory; cannot be null)
+	 */
+	void setOwner(Sid newOwner);
 
-    /**
-     * Change the value returned by {@link Acl#isEntriesInheriting()}.
-     *
-     * @param entriesInheriting the new value
-     */
-    void setEntriesInheriting(boolean entriesInheriting);
+	/**
+	 * Change the value returned by {@link Acl#isEntriesInheriting()}.
+	 *
+	 * @param entriesInheriting the new value
+	 */
+	void setEntriesInheriting(boolean entriesInheriting);
 
-    /**
-     * Changes the parent of this ACL.
-     *
-     * @param newParent the new parent
-     */
-    void setParent(Acl newParent);
+	/**
+	 * Changes the parent of this ACL.
+	 *
+	 * @param newParent the new parent
+	 */
+	void setParent(Acl newParent);
 
-    void updateAce(int aceIndex, Permission permission)
-        throws NotFoundException;
+	void updateAce(int aceIndex, Permission permission) throws NotFoundException;
 }

+ 34 - 35
acl/src/main/java/org/springframework/security/acls/model/MutableAclService.java

@@ -14,48 +14,47 @@
  */
 package org.springframework.security.acls.model;
 
-
-
 /**
  * Provides support for creating and storing <code>Acl</code> instances.
  *
  * @author Ben Alex
  */
 public interface MutableAclService extends AclService {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * Creates an empty <code>Acl</code> object in the database. It will have no entries. The returned object
-     * will then be used to add entries.
-     *
-     * @param objectIdentity the object identity to create
-     *
-     * @return an ACL object with its ID set
-     *
-     * @throws AlreadyExistsException if the passed object identity already has a record
-     */
-    MutableAcl createAcl(ObjectIdentity objectIdentity)
-        throws AlreadyExistsException;
+	/**
+	 * Creates an empty <code>Acl</code> object in the database. It will have no entries.
+	 * The returned object will then be used to add entries.
+	 *
+	 * @param objectIdentity the object identity to create
+	 *
+	 * @return an ACL object with its ID set
+	 *
+	 * @throws AlreadyExistsException if the passed object identity already has a record
+	 */
+	MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException;
 
-    /**
-     * Removes the specified entry from the database.
-     *
-     * @param objectIdentity the object identity to remove
-     * @param deleteChildren whether to cascade the delete to children
-     *
-     * @throws ChildrenExistException if the deleteChildren argument was <code>false</code> but children exist
-     */
-    void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
-        throws ChildrenExistException;
+	/**
+	 * Removes the specified entry from the database.
+	 *
+	 * @param objectIdentity the object identity to remove
+	 * @param deleteChildren whether to cascade the delete to children
+	 *
+	 * @throws ChildrenExistException if the deleteChildren argument was
+	 * <code>false</code> but children exist
+	 */
+	void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
+			throws ChildrenExistException;
 
-    /**
-     * Changes an existing <code>Acl</code> in the database.
-     *
-     * @param acl to modify
-     *
-     * @throws NotFoundException if the relevant record could not be found (did you remember to use {@link
-     *         #createAcl(ObjectIdentity)} to create the object, rather than creating it with the <code>new</code>
-     *         keyword?)
-     */
-    MutableAcl updateAcl(MutableAcl acl) throws NotFoundException;
+	/**
+	 * Changes an existing <code>Acl</code> in the database.
+	 *
+	 * @param acl to modify
+	 *
+	 * @throws NotFoundException if the relevant record could not be found (did you
+	 * remember to use {@link #createAcl(ObjectIdentity)} to create the object, rather
+	 * than creating it with the <code>new</code> keyword?)
+	 */
+	MutableAcl updateAcl(MutableAcl acl) throws NotFoundException;
 }

+ 20 - 20
acl/src/main/java/org/springframework/security/acls/model/NotFoundException.java

@@ -14,32 +14,32 @@
  */
 package org.springframework.security.acls.model;
 
-
 /**
  * Thrown if an ACL-related object cannot be found.
  *
  * @author Ben Alex
  */
 public class NotFoundException extends AclDataAccessException {
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    /**
-     * Constructs an <code>NotFoundException</code> with the specified message.
-     *
-     * @param msg the detail message
-     */
-    public NotFoundException(String msg) {
-        super(msg);
-    }
+	/**
+	 * Constructs an <code>NotFoundException</code> with the specified message.
+	 *
+	 * @param msg the detail message
+	 */
+	public NotFoundException(String msg) {
+		super(msg);
+	}
 
-    /**
-     * Constructs an <code>NotFoundException</code> with the specified message
-     * and root cause.
-     *
-     * @param msg the detail message
-     * @param t root cause
-     */
-    public NotFoundException(String msg, Throwable t) {
-        super(msg, t);
-    }
+	/**
+	 * Constructs an <code>NotFoundException</code> with the specified message and root
+	 * cause.
+	 *
+	 * @param msg the detail message
+	 * @param t root cause
+	 */
+	public NotFoundException(String msg, Throwable t) {
+		super(msg, t);
+	}
 }

+ 42 - 39
acl/src/main/java/org/springframework/security/acls/model/ObjectIdentity.java

@@ -16,56 +16,59 @@ package org.springframework.security.acls.model;
 
 import java.io.Serializable;
 
-
 /**
  * Represents the identity of an individual domain object instance.
  *
  * <p>
- * As implementations of <tt>ObjectIdentity</tt> are used as the key to represent
- * domain objects in the ACL subsystem, it is essential that implementations provide
- * methods so that object-equality rather than reference-equality can be relied upon
- * reliably. In other words, the ACL subsystem can consider two
- * <tt>ObjectIdentity</tt>s equal if <tt>identity1.equals(identity2)</tt>, rather than
- * reference-equality of <tt>identity1==identity2</tt>.
+ * As implementations of <tt>ObjectIdentity</tt> are used as the key to represent domain
+ * objects in the ACL subsystem, it is essential that implementations provide methods so
+ * that object-equality rather than reference-equality can be relied upon reliably. In
+ * other words, the ACL subsystem can consider two <tt>ObjectIdentity</tt>s equal if
+ * <tt>identity1.equals(identity2)</tt>, rather than reference-equality of
+ * <tt>identity1==identity2</tt>.
  * </p>
  *
  * @author Ben Alex
  */
 public interface ObjectIdentity extends Serializable {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * @param obj to be compared
-     *
-     * @return <tt>true</tt> if the objects are equal, <tt>false</tt> otherwise
-     * @see Object#equals(Object)
-     */
-    boolean equals(Object obj);
+	/**
+	 * @param obj to be compared
+	 *
+	 * @return <tt>true</tt> if the objects are equal, <tt>false</tt> otherwise
+	 * @see Object#equals(Object)
+	 */
+	boolean equals(Object obj);
 
-    /**
-     * Obtains the actual identifier. This identifier must not be reused to represent other domain objects with
-     * the same <tt>javaType</tt>.
-     *
-     * <p>Because ACLs are largely immutable, it is strongly recommended to use
-     * a synthetic identifier (such as a database sequence number for the primary key). Do not use an identifier with
-     * business meaning, as that business meaning may change in the future such change will cascade to the ACL
-     * subsystem data.</p>
-     *
-     * @return the identifier (unique within this <tt>type</tt>; never <tt>null</tt>)
-     */
-    Serializable getIdentifier();
+	/**
+	 * Obtains the actual identifier. This identifier must not be reused to represent
+	 * other domain objects with the same <tt>javaType</tt>.
+	 *
+	 * <p>
+	 * Because ACLs are largely immutable, it is strongly recommended to use a synthetic
+	 * identifier (such as a database sequence number for the primary key). Do not use an
+	 * identifier with business meaning, as that business meaning may change in the future
+	 * such change will cascade to the ACL subsystem data.
+	 * </p>
+	 *
+	 * @return the identifier (unique within this <tt>type</tt>; never <tt>null</tt>)
+	 */
+	Serializable getIdentifier();
 
-    /**
-     * Obtains the "type" metadata for the domain object. This will often be a Java type name (an interface or a class)
-     * &ndash; traditionally it is the name of the domain object implementation class.
-     *
-     * @return the "type" of the domain object (never <tt>null</tt>).
-     */
-    String getType();
+	/**
+	 * Obtains the "type" metadata for the domain object. This will often be a Java type
+	 * name (an interface or a class) &ndash; traditionally it is the name of the domain
+	 * object implementation class.
+	 *
+	 * @return the "type" of the domain object (never <tt>null</tt>).
+	 */
+	String getType();
 
-    /**
-     * @return a hash code representation of the <tt>ObjectIdentity</tt>
-     * @see Object#hashCode()
-     */
-    int hashCode();
+	/**
+	 * @return a hash code representation of the <tt>ObjectIdentity</tt>
+	 * @see Object#hashCode()
+	 */
+	int hashCode();
 }

+ 12 - 12
acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityGenerator.java

@@ -2,25 +2,25 @@ package org.springframework.security.acls.model;
 
 import java.io.Serializable;
 
-
 /**
- * Strategy which creates an {@link ObjectIdentity} from an object identifier (such as a primary key)
- * and type information.
+ * Strategy which creates an {@link ObjectIdentity} from an object identifier (such as a
+ * primary key) and type information.
  * <p>
- * Differs from {@link ObjectIdentityRetrievalStrategy} in that it is used in situations when the actual object
- * instance isn't available.
+ * Differs from {@link ObjectIdentityRetrievalStrategy} in that it is used in situations
+ * when the actual object instance isn't available.
  *
  * @author Luke Taylor
  * @since 3.0
  */
 public interface ObjectIdentityGenerator {
 
-    /**
-     *
-     * @param id the identifier of the domain object, not null
-     * @param type the type of the object (often a class name), not null
-     * @return the identity constructed using the supplied identifier and type information.
-     */
-    ObjectIdentity createObjectIdentity(Serializable id, String type);
+	/**
+	 *
+	 * @param id the identifier of the domain object, not null
+	 * @param type the type of the object (often a class name), not null
+	 * @return the identity constructed using the supplied identifier and type
+	 * information.
+	 */
+	ObjectIdentity createObjectIdentity(Serializable id, String type);
 
 }

+ 3 - 3
acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityRetrievalStrategy.java

@@ -15,7 +15,6 @@
 
 package org.springframework.security.acls.model;
 
-
 /**
  * Strategy interface that provides the ability to determine which {@link ObjectIdentity}
  * will be returned for a particular domain object
@@ -24,7 +23,8 @@ package org.springframework.security.acls.model;
  *
  */
 public interface ObjectIdentityRetrievalStrategy {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    ObjectIdentity getObjectIdentity(Object domainObject);
+	ObjectIdentity getObjectIdentity(Object domainObject);
 }

+ 5 - 6
acl/src/main/java/org/springframework/security/acls/model/OwnershipAcl.java

@@ -14,19 +14,18 @@
  */
 package org.springframework.security.acls.model;
 
-
-
 /**
  * A mutable ACL that provides ownership capabilities.
  *
  * <p>
- * Generally the owner of an ACL is able to call any ACL mutator method, as
- * well as assign a new owner.
+ * Generally the owner of an ACL is able to call any ACL mutator method, as well as assign
+ * a new owner.
  *
  * @author Ben Alex
  */
 public interface OwnershipAcl extends MutableAcl {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    void setOwner(Sid newOwner);
+	void setOwner(Sid newOwner);
 }

+ 33 - 28
acl/src/main/java/org/springframework/security/acls/model/Permission.java

@@ -22,36 +22,41 @@ import java.io.Serializable;
  * @author Ben Alex
  */
 public interface Permission extends Serializable {
-    //~ Static fields/initializers =====================================================================================
+	// ~ Static fields/initializers
+	// =====================================================================================
 
-    char RESERVED_ON = '~';
-    char RESERVED_OFF = '.';
-    String THIRTY_TWO_RESERVED_OFF = "................................";
+	char RESERVED_ON = '~';
+	char RESERVED_OFF = '.';
+	String THIRTY_TWO_RESERVED_OFF = "................................";
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * Returns the bits that represents the permission.
-     *
-     * @return the bits that represent the permission
-     */
-    int getMask();
+	/**
+	 * Returns the bits that represents the permission.
+	 *
+	 * @return the bits that represent the permission
+	 */
+	int getMask();
 
-    /**
-     * Returns a 32-character long bit pattern <code>String</code> representing this permission.
-     * <p>
-     * Implementations are free to format the pattern as they see fit, although under no circumstances may
-     * {@link #RESERVED_OFF} or {@link #RESERVED_ON} be used within the pattern. An exemption is in the case of
-     * {@link #RESERVED_OFF} which is used to denote a bit that is off (clear).
-     * Implementations may also elect to use {@link #RESERVED_ON} internally for computation purposes,
-     * although this method may not return any <code>String</code> containing {@link #RESERVED_ON}.
-     * <p>
-     * The returned String must be 32 characters in length.
-     * <p>
-     * This method is only used for user interface and logging purposes. It is not used in any permission
-     * calculations. Therefore, duplication of characters within the output is permitted.
-     *
-     * @return a 32-character bit pattern
-     */
-    String getPattern();
+	/**
+	 * Returns a 32-character long bit pattern <code>String</code> representing this
+	 * permission.
+	 * <p>
+	 * Implementations are free to format the pattern as they see fit, although under no
+	 * circumstances may {@link #RESERVED_OFF} or {@link #RESERVED_ON} be used within the
+	 * pattern. An exemption is in the case of {@link #RESERVED_OFF} which is used to
+	 * denote a bit that is off (clear). Implementations may also elect to use
+	 * {@link #RESERVED_ON} internally for computation purposes, although this method may
+	 * not return any <code>String</code> containing {@link #RESERVED_ON}.
+	 * <p>
+	 * The returned String must be 32 characters in length.
+	 * <p>
+	 * This method is only used for user interface and logging purposes. It is not used in
+	 * any permission calculations. Therefore, duplication of characters within the output
+	 * is permitted.
+	 *
+	 * @return a 32-character bit pattern
+	 */
+	String getPattern();
 }

+ 8 - 7
acl/src/main/java/org/springframework/security/acls/model/PermissionGrantingStrategy.java

@@ -3,18 +3,19 @@ package org.springframework.security.acls.model;
 import java.util.List;
 
 /**
- * Allow customization of the logic for determining whether a permission or permissions are granted to a particular
- * sid or sids by an {@link Acl}.
+ * Allow customization of the logic for determining whether a permission or permissions
+ * are granted to a particular sid or sids by an {@link Acl}.
  *
  * @author Luke Taylor
  * @since 3.0.2
  */
 public interface PermissionGrantingStrategy {
 
-    /**
-     * Returns true if the the supplied strategy decides that the supplied {@code Acl} grants access
-     * based on the supplied list of permissions and sids.
-     */
-    boolean isGranted(Acl acl, List<Permission> permission, List<Sid> sids, boolean administrativeMode);
+	/**
+	 * Returns true if the the supplied strategy decides that the supplied {@code Acl}
+	 * grants access based on the supplied list of permissions and sids.
+	 */
+	boolean isGranted(Acl acl, List<Permission> permission, List<Sid> sids,
+			boolean administrativeMode);
 
 }

+ 22 - 20
acl/src/main/java/org/springframework/security/acls/model/Sid.java

@@ -20,32 +20,34 @@ import java.io.Serializable;
  * A security identity recognised by the ACL system.
  *
  * <p>
- * This interface provides indirection between actual security objects (eg
- * principals, roles, groups etc) and what is stored inside an
- * <code>Acl</code>. This is because an <code>Acl</code> will not store an
- * entire security object, but only an abstraction of it. This interface
- * therefore provides a simple way to compare these abstracted security
+ * This interface provides indirection between actual security objects (eg principals,
+ * roles, groups etc) and what is stored inside an <code>Acl</code>. This is because an
+ * <code>Acl</code> will not store an entire security object, but only an abstraction of
+ * it. This interface therefore provides a simple way to compare these abstracted security
  * identities with other security identities and actual security objects.
  * </p>
  *
  * @author Ben Alex
  */
 public interface Sid extends Serializable {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
-     *
-     * @param obj to be compared
-     *
-     * @return <code>true</code> if the objects are equal, <code>false</code> otherwise
-     */
-    boolean equals(Object obj);
+	/**
+	 * Refer to the <code>java.lang.Object</code> documentation for the interface
+	 * contract.
+	 *
+	 * @param obj to be compared
+	 *
+	 * @return <code>true</code> if the objects are equal, <code>false</code> otherwise
+	 */
+	boolean equals(Object obj);
 
-    /**
-     * Refer to the <code>java.lang.Object</code> documentation for the interface contract.
-     *
-     * @return a hash code representation of this object
-     */
-    int hashCode();
+	/**
+	 * Refer to the <code>java.lang.Object</code> documentation for the interface
+	 * contract.
+	 *
+	 * @return a hash code representation of this object
+	 */
+	int hashCode();
 }

+ 5 - 5
acl/src/main/java/org/springframework/security/acls/model/SidRetrievalStrategy.java

@@ -19,15 +19,15 @@ import java.util.List;
 
 import org.springframework.security.core.Authentication;
 
-
 /**
- * Strategy interface that provides an ability to determine the {@link Sid} instances applicable
- * for an {@link Authentication}.
+ * Strategy interface that provides an ability to determine the {@link Sid} instances
+ * applicable for an {@link Authentication}.
  *
  * @author Ben Alex
  */
 public interface SidRetrievalStrategy {
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    List<Sid> getSids(Authentication authentication);
+	List<Sid> getSids(Authentication authentication);
 }

+ 23 - 22
acl/src/main/java/org/springframework/security/acls/model/UnloadedSidException.java

@@ -14,33 +14,34 @@
  */
 package org.springframework.security.acls.model;
 
-
 /**
- * Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of <code>Sid</code>s and
- * the caller has requested details for an unloaded <code>Sid</code>.
+ * Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of
+ * <code>Sid</code>s and the caller has requested details for an unloaded <code>Sid</code>
+ * .
  *
  * @author Ben Alex
  */
 public class UnloadedSidException extends AclDataAccessException {
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    /**
-     * Constructs an <code>NotFoundException</code> with the specified message.
-     *
-     * @param msg the detail message
-     */
-    public UnloadedSidException(String msg) {
-        super(msg);
-    }
+	/**
+	 * Constructs an <code>NotFoundException</code> with the specified message.
+	 *
+	 * @param msg the detail message
+	 */
+	public UnloadedSidException(String msg) {
+		super(msg);
+	}
 
-    /**
-     * Constructs an <code>NotFoundException</code> with the specified message
-     * and root cause.
-     *
-     * @param msg the detail message
-     * @param t root cause
-     */
-    public UnloadedSidException(String msg, Throwable t) {
-        super(msg, t);
-    }
+	/**
+	 * Constructs an <code>NotFoundException</code> with the specified message and root
+	 * cause.
+	 *
+	 * @param msg the detail message
+	 * @param t root cause
+	 */
+	public UnloadedSidException(String msg, Throwable t) {
+		super(msg, t);
+	}
 }

+ 128 - 119
acl/src/test/java/org/springframework/security/acls/AclFormattingUtilsTests.java

@@ -13,123 +13,132 @@ import junit.framework.TestCase;
  */
 public class AclFormattingUtilsTests extends TestCase {
 
-    //~ Methods ========================================================================================================
-
-    public final void testDemergePatternsParametersConstraints() throws Exception {
-        try {
-            AclFormattingUtils.demergePatterns(null, "SOME STRING");
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.demergePatterns("SOME STRING", null);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.demergePatterns("SOME STRING", "LONGER SOME STRING");
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.demergePatterns("SOME STRING", "SAME LENGTH");
-            Assert.assertTrue(true);
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.fail("It shouldn't have thrown IllegalArgumentException");
-        }
-    }
-
-    public final void testDemergePatterns() throws Exception {
-        String original = "...........................A...R";
-        String removeBits = "...............................R";
-        Assert.assertEquals("...........................A....", AclFormattingUtils
-                .demergePatterns(original, removeBits));
-
-        Assert.assertEquals("ABCDEF", AclFormattingUtils.demergePatterns("ABCDEF", "......"));
-        Assert.assertEquals("......", AclFormattingUtils.demergePatterns("ABCDEF", "GHIJKL"));
-    }
-
-    public final void testMergePatternsParametersConstraints() throws Exception {
-        try {
-            AclFormattingUtils.mergePatterns(null, "SOME STRING");
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.mergePatterns("SOME STRING", null);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.mergePatterns("SOME STRING", "LONGER SOME STRING");
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.mergePatterns("SOME STRING", "SAME LENGTH");
-            Assert.assertTrue(true);
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.fail("It shouldn't have thrown IllegalArgumentException");
-        }
-    }
-
-    public final void testMergePatterns() throws Exception {
-        String original = "...............................R";
-        String extraBits = "...........................A....";
-        Assert.assertEquals("...........................A...R", AclFormattingUtils
-                .mergePatterns(original, extraBits));
-
-        Assert.assertEquals("ABCDEF", AclFormattingUtils.mergePatterns("ABCDEF", "......"));
-        Assert.assertEquals("GHIJKL", AclFormattingUtils.mergePatterns("ABCDEF", "GHIJKL"));
-    }
-
-    public final void testBinaryPrints() throws Exception {
-        Assert.assertEquals("............................****", AclFormattingUtils.printBinary(15));
-
-        try {
-            AclFormattingUtils.printBinary(15, Permission.RESERVED_ON);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            AclFormattingUtils.printBinary(15, Permission.RESERVED_OFF);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.assertTrue(true);
-        }
-
-        Assert.assertEquals("............................xxxx", AclFormattingUtils.printBinary(15, 'x'));
-    }
-
-    public void testPrintBinaryNegative() {
-        Assert.assertEquals("*...............................", AclFormattingUtils.printBinary(0x80000000));
-    }
-
-    public void testPrintBinaryMinusOne() {
-        Assert.assertEquals("********************************", AclFormattingUtils.printBinary(0xffffffff));
-    }
+	// ~ Methods
+	// ========================================================================================================
+
+	public final void testDemergePatternsParametersConstraints() throws Exception {
+		try {
+			AclFormattingUtils.demergePatterns(null, "SOME STRING");
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.demergePatterns("SOME STRING", null);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.demergePatterns("SOME STRING", "LONGER SOME STRING");
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.demergePatterns("SOME STRING", "SAME LENGTH");
+			Assert.assertTrue(true);
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.fail("It shouldn't have thrown IllegalArgumentException");
+		}
+	}
+
+	public final void testDemergePatterns() throws Exception {
+		String original = "...........................A...R";
+		String removeBits = "...............................R";
+		Assert.assertEquals("...........................A....",
+				AclFormattingUtils.demergePatterns(original, removeBits));
+
+		Assert.assertEquals("ABCDEF",
+				AclFormattingUtils.demergePatterns("ABCDEF", "......"));
+		Assert.assertEquals("......",
+				AclFormattingUtils.demergePatterns("ABCDEF", "GHIJKL"));
+	}
+
+	public final void testMergePatternsParametersConstraints() throws Exception {
+		try {
+			AclFormattingUtils.mergePatterns(null, "SOME STRING");
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.mergePatterns("SOME STRING", null);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.mergePatterns("SOME STRING", "LONGER SOME STRING");
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.mergePatterns("SOME STRING", "SAME LENGTH");
+			Assert.assertTrue(true);
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.fail("It shouldn't have thrown IllegalArgumentException");
+		}
+	}
+
+	public final void testMergePatterns() throws Exception {
+		String original = "...............................R";
+		String extraBits = "...........................A....";
+		Assert.assertEquals("...........................A...R",
+				AclFormattingUtils.mergePatterns(original, extraBits));
+
+		Assert.assertEquals("ABCDEF",
+				AclFormattingUtils.mergePatterns("ABCDEF", "......"));
+		Assert.assertEquals("GHIJKL",
+				AclFormattingUtils.mergePatterns("ABCDEF", "GHIJKL"));
+	}
+
+	public final void testBinaryPrints() throws Exception {
+		Assert.assertEquals("............................****",
+				AclFormattingUtils.printBinary(15));
+
+		try {
+			AclFormattingUtils.printBinary(15, Permission.RESERVED_ON);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			AclFormattingUtils.printBinary(15, Permission.RESERVED_OFF);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.assertTrue(true);
+		}
+
+		Assert.assertEquals("............................xxxx",
+				AclFormattingUtils.printBinary(15, 'x'));
+	}
+
+	public void testPrintBinaryNegative() {
+		Assert.assertEquals("*...............................",
+				AclFormattingUtils.printBinary(0x80000000));
+	}
+
+	public void testPrintBinaryMinusOne() {
+		Assert.assertEquals("********************************",
+				AclFormattingUtils.printBinary(0xffffffff));
+	}
 }

+ 34 - 33
acl/src/test/java/org/springframework/security/acls/AclPermissionCacheOptimizerTests.java

@@ -17,40 +17,41 @@ import java.util.List;
 /**
  * @author Luke Taylor
  */
-@SuppressWarnings({"unchecked"})
+@SuppressWarnings({ "unchecked" })
 public class AclPermissionCacheOptimizerTests {
 
-    @Test
-    public void eagerlyLoadsRequiredAcls() throws Exception {
-        AclService service = mock(AclService.class);
-        AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service);
-        ObjectIdentityRetrievalStrategy oidStrat = mock(ObjectIdentityRetrievalStrategy.class);
-        SidRetrievalStrategy sidStrat = mock(SidRetrievalStrategy.class);
-        pco.setObjectIdentityRetrievalStrategy(oidStrat);
-        pco.setSidRetrievalStrategy(sidStrat);
-        Object[] dos = {new Object(), null, new Object()};
-        ObjectIdentity[] oids = {new ObjectIdentityImpl("A", "1"), new ObjectIdentityImpl("A", "2")};
-        when(oidStrat.getObjectIdentity(dos[0])).thenReturn(oids[0]);
-        when(oidStrat.getObjectIdentity(dos[2])).thenReturn(oids[1]);
-
-        pco.cachePermissionsFor(mock(Authentication.class), Arrays.asList(dos));
-
-        // AclService should be invoked with the list of required Oids
-        verify(service).readAclsById(eq(Arrays.asList(oids)), any(List.class));
-    }
-
-    @Test
-    public void ignoresEmptyCollection() {
-        AclService service = mock(AclService.class);
-        AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service);
-        ObjectIdentityRetrievalStrategy oids = mock(ObjectIdentityRetrievalStrategy.class);
-        SidRetrievalStrategy sids = mock(SidRetrievalStrategy.class);
-        pco.setObjectIdentityRetrievalStrategy(oids);
-        pco.setSidRetrievalStrategy(sids);
-
-        pco.cachePermissionsFor(mock(Authentication.class), Collections.emptyList());
-
-        verifyZeroInteractions(service, sids, oids);
-    }
+	@Test
+	public void eagerlyLoadsRequiredAcls() throws Exception {
+		AclService service = mock(AclService.class);
+		AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service);
+		ObjectIdentityRetrievalStrategy oidStrat = mock(ObjectIdentityRetrievalStrategy.class);
+		SidRetrievalStrategy sidStrat = mock(SidRetrievalStrategy.class);
+		pco.setObjectIdentityRetrievalStrategy(oidStrat);
+		pco.setSidRetrievalStrategy(sidStrat);
+		Object[] dos = { new Object(), null, new Object() };
+		ObjectIdentity[] oids = { new ObjectIdentityImpl("A", "1"),
+				new ObjectIdentityImpl("A", "2") };
+		when(oidStrat.getObjectIdentity(dos[0])).thenReturn(oids[0]);
+		when(oidStrat.getObjectIdentity(dos[2])).thenReturn(oids[1]);
+
+		pco.cachePermissionsFor(mock(Authentication.class), Arrays.asList(dos));
+
+		// AclService should be invoked with the list of required Oids
+		verify(service).readAclsById(eq(Arrays.asList(oids)), any(List.class));
+	}
+
+	@Test
+	public void ignoresEmptyCollection() {
+		AclService service = mock(AclService.class);
+		AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service);
+		ObjectIdentityRetrievalStrategy oids = mock(ObjectIdentityRetrievalStrategy.class);
+		SidRetrievalStrategy sids = mock(SidRetrievalStrategy.class);
+		pco.setObjectIdentityRetrievalStrategy(oids);
+		pco.setSidRetrievalStrategy(sids);
+
+		pco.cachePermissionsFor(mock(Authentication.class), Collections.emptyList());
+
+		verifyZeroInteractions(service, sids, oids);
+	}
 
 }

+ 15 - 15
acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java

@@ -19,21 +19,21 @@ import org.springframework.security.core.Authentication;
  */
 public class AclPermissionEvaluatorTests {
 
-    @Test
-    @SuppressWarnings("unchecked")
-    public void hasPermissionReturnsTrueIfAclGrantsPermission() throws Exception {
-        AclService service = mock(AclService.class);
-        AclPermissionEvaluator pe = new AclPermissionEvaluator(service);
-        ObjectIdentity oid = mock(ObjectIdentity.class);
-        ObjectIdentityRetrievalStrategy oidStrategy = mock(ObjectIdentityRetrievalStrategy.class);
-        when(oidStrategy.getObjectIdentity(anyObject())).thenReturn(oid);
-        pe.setObjectIdentityRetrievalStrategy(oidStrategy);
-        pe.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
-        Acl acl = mock(Acl.class);
+	@Test
+	@SuppressWarnings("unchecked")
+	public void hasPermissionReturnsTrueIfAclGrantsPermission() throws Exception {
+		AclService service = mock(AclService.class);
+		AclPermissionEvaluator pe = new AclPermissionEvaluator(service);
+		ObjectIdentity oid = mock(ObjectIdentity.class);
+		ObjectIdentityRetrievalStrategy oidStrategy = mock(ObjectIdentityRetrievalStrategy.class);
+		when(oidStrategy.getObjectIdentity(anyObject())).thenReturn(oid);
+		pe.setObjectIdentityRetrievalStrategy(oidStrategy);
+		pe.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
+		Acl acl = mock(Acl.class);
 
-        when(service.readAclById(any(ObjectIdentity.class), anyList())).thenReturn(acl);
-        when(acl.isGranted(anyList(), anyList(), eq(false))).thenReturn(true);
+		when(service.readAclById(any(ObjectIdentity.class), anyList())).thenReturn(acl);
+		when(acl.isGranted(anyList(), anyList(), eq(false))).thenReturn(true);
 
-        assertTrue(pe.hasPermission(mock(Authentication.class), new Object(), "READ"));
-    }
+		assertTrue(pe.hasPermission(mock(Authentication.class), new Object(), "READ"));
+	}
 }

+ 44 - 31
acl/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProviderTests.java

@@ -23,42 +23,55 @@ import java.util.List;
 /**
  * @author Luke Taylor
  */
-@SuppressWarnings({"unchecked"})
+@SuppressWarnings({ "unchecked" })
 public class AclEntryAfterInvocationCollectionFilteringProviderTests {
-    @Test
-    public void objectsAreRemovedIfPermissionDenied() throws Exception {
-        AclService service = mock(AclService.class);
-        Acl acl = mock(Acl.class);
-        when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenReturn(false);
-        when(service.readAclById(any(ObjectIdentity.class), any(List.class))).thenReturn(acl);
-        AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider(service, Arrays.asList(mock(Permission.class)));
-        provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class));
-        provider.setProcessDomainObjectClass(Object.class);
-        provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
+	@Test
+	public void objectsAreRemovedIfPermissionDenied() throws Exception {
+		AclService service = mock(AclService.class);
+		Acl acl = mock(Acl.class);
+		when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenReturn(
+				false);
+		when(service.readAclById(any(ObjectIdentity.class), any(List.class))).thenReturn(
+				acl);
+		AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider(
+				service, Arrays.asList(mock(Permission.class)));
+		provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class));
+		provider.setProcessDomainObjectClass(Object.class);
+		provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
 
-        Object returned = provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), new ArrayList(Arrays.asList(new Object(), new Object())));
-        assertTrue(returned instanceof List);
-        assertTrue(((List)returned).isEmpty());
-        returned = provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("UNSUPPORTED", "AFTER_ACL_COLLECTION_READ"), new Object[] {new Object(), new Object()});
-        assertTrue(returned instanceof Object[]);
-        assertTrue(((Object[])returned).length == 0);
-    }
+		Object returned = provider.decide(mock(Authentication.class), new Object(),
+				SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), new ArrayList(
+						Arrays.asList(new Object(), new Object())));
+		assertTrue(returned instanceof List);
+		assertTrue(((List) returned).isEmpty());
+		returned = provider.decide(mock(Authentication.class), new Object(),
+				SecurityConfig.createList("UNSUPPORTED", "AFTER_ACL_COLLECTION_READ"),
+				new Object[] { new Object(), new Object() });
+		assertTrue(returned instanceof Object[]);
+		assertTrue(((Object[]) returned).length == 0);
+	}
 
-    @Test
-    public void accessIsGrantedIfNoAttributesDefined() throws Exception {
-        AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider(mock(AclService.class), Arrays.asList(mock(Permission.class)));
-        Object returned = new Object();
+	@Test
+	public void accessIsGrantedIfNoAttributesDefined() throws Exception {
+		AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider(
+				mock(AclService.class), Arrays.asList(mock(Permission.class)));
+		Object returned = new Object();
 
-        assertSame(returned, provider.decide(mock(Authentication.class), new Object(), Collections.<ConfigAttribute>emptyList(), returned));
-    }
+		assertSame(
+				returned,
+				provider.decide(mock(Authentication.class), new Object(),
+						Collections.<ConfigAttribute> emptyList(), returned));
+	}
 
-    @Test
-    public void nullReturnObjectIsIgnored() throws Exception {
-        AclService service = mock(AclService.class);
-        AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider(service, Arrays.asList(mock(Permission.class)));
+	@Test
+	public void nullReturnObjectIsIgnored() throws Exception {
+		AclService service = mock(AclService.class);
+		AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider(
+				service, Arrays.asList(mock(Permission.class)));
 
-        assertNull(provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null));
-        verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class));
-    }
+		assertNull(provider.decide(mock(Authentication.class), new Object(),
+				SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null));
+		verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class));
+	}
 
 }

+ 93 - 68
acl/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProviderTests.java

@@ -19,83 +19,108 @@ import java.util.List;
 /**
  * @author Luke Taylor
  */
-@SuppressWarnings({"unchecked"})
+@SuppressWarnings({ "unchecked" })
 public class AclEntryAfterInvocationProviderTests {
 
-    @Test(expected=IllegalArgumentException.class)
-    public void rejectsMissingPermissions() throws Exception {
-        try {
-            new AclEntryAfterInvocationProvider(mock(AclService.class), null);
-            fail("Exception expected");
-        } catch (IllegalArgumentException expected) {
-        }
-        new AclEntryAfterInvocationProvider(mock(AclService.class), Collections.<Permission>emptyList());
-    }
+	@Test(expected = IllegalArgumentException.class)
+	public void rejectsMissingPermissions() throws Exception {
+		try {
+			new AclEntryAfterInvocationProvider(mock(AclService.class), null);
+			fail("Exception expected");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+		new AclEntryAfterInvocationProvider(mock(AclService.class),
+				Collections.<Permission> emptyList());
+	}
 
-    @Test
-    public void accessIsAllowedIfPermissionIsGranted() {
-        AclService service = mock(AclService.class);
-        Acl acl = mock(Acl.class);
-        when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenReturn(true);
-        when(service.readAclById(any(ObjectIdentity.class), any(List.class))).thenReturn(acl);
-        AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, Arrays.asList(mock(Permission.class)));
-        provider.setMessageSource(new SpringSecurityMessageSource());
-        provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class));
-        provider.setProcessDomainObjectClass(Object.class);
-        provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
-        Object returned = new Object();
+	@Test
+	public void accessIsAllowedIfPermissionIsGranted() {
+		AclService service = mock(AclService.class);
+		Acl acl = mock(Acl.class);
+		when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenReturn(
+				true);
+		when(service.readAclById(any(ObjectIdentity.class), any(List.class))).thenReturn(
+				acl);
+		AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(
+				service, Arrays.asList(mock(Permission.class)));
+		provider.setMessageSource(new SpringSecurityMessageSource());
+		provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class));
+		provider.setProcessDomainObjectClass(Object.class);
+		provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
+		Object returned = new Object();
 
-        assertSame(returned, provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_READ"), returned));
-    }
+		assertSame(
+				returned,
+				provider.decide(mock(Authentication.class), new Object(),
+						SecurityConfig.createList("AFTER_ACL_READ"), returned));
+	}
 
-    @Test
-    public void accessIsGrantedIfNoAttributesDefined() throws Exception {
-        AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(mock(AclService.class), Arrays.asList(mock(Permission.class)));
-        Object returned = new Object();
+	@Test
+	public void accessIsGrantedIfNoAttributesDefined() throws Exception {
+		AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(
+				mock(AclService.class), Arrays.asList(mock(Permission.class)));
+		Object returned = new Object();
 
-        assertSame(returned, provider.decide(mock(Authentication.class), new Object(), Collections.<ConfigAttribute>emptyList(), returned));
-    }
+		assertSame(
+				returned,
+				provider.decide(mock(Authentication.class), new Object(),
+						Collections.<ConfigAttribute> emptyList(), returned));
+	}
 
-    @Test
-    public void accessIsGrantedIfObjectTypeNotSupported() throws Exception {
-        AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(mock(AclService.class), Arrays.asList(mock(Permission.class)));
-        provider.setProcessDomainObjectClass(String.class);
-        // Not a String
-        Object returned = new Object();
+	@Test
+	public void accessIsGrantedIfObjectTypeNotSupported() throws Exception {
+		AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(
+				mock(AclService.class), Arrays.asList(mock(Permission.class)));
+		provider.setProcessDomainObjectClass(String.class);
+		// Not a String
+		Object returned = new Object();
 
-        assertSame(returned, provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_READ"), returned));
-    }
+		assertSame(
+				returned,
+				provider.decide(mock(Authentication.class), new Object(),
+						SecurityConfig.createList("AFTER_ACL_READ"), returned));
+	}
 
+	@Test(expected = AccessDeniedException.class)
+	public void accessIsDeniedIfPermissionIsNotGranted() {
+		AclService service = mock(AclService.class);
+		Acl acl = mock(Acl.class);
+		when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenReturn(
+				false);
+		// Try a second time with no permissions found
+		when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenThrow(
+				new NotFoundException(""));
+		when(service.readAclById(any(ObjectIdentity.class), any(List.class))).thenReturn(
+				acl);
+		AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(
+				service, Arrays.asList(mock(Permission.class)));
+		provider.setProcessConfigAttribute("MY_ATTRIBUTE");
+		provider.setMessageSource(new SpringSecurityMessageSource());
+		provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class));
+		provider.setProcessDomainObjectClass(Object.class);
+		provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
+		try {
+			provider.decide(mock(Authentication.class), new Object(),
+					SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"),
+					new Object());
+			fail();
+		}
+		catch (AccessDeniedException expected) {
+		}
+		// Second scenario with no acls found
+		provider.decide(mock(Authentication.class), new Object(),
+				SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object());
+	}
 
-    @Test(expected= AccessDeniedException.class)
-    public void accessIsDeniedIfPermissionIsNotGranted() {
-        AclService service = mock(AclService.class);
-        Acl acl = mock(Acl.class);
-        when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenReturn(false);
-        // Try a second time with no permissions found
-        when(acl.isGranted(any(List.class), any(List.class), anyBoolean())).thenThrow(new NotFoundException(""));
-        when(service.readAclById(any(ObjectIdentity.class), any(List.class))).thenReturn(acl);
-        AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, Arrays.asList(mock(Permission.class)));
-        provider.setProcessConfigAttribute("MY_ATTRIBUTE");
-        provider.setMessageSource(new SpringSecurityMessageSource());
-        provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class));
-        provider.setProcessDomainObjectClass(Object.class);
-        provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class));
-        try {
-            provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object());
-            fail();
-        } catch (AccessDeniedException expected) {
-        }
-        // Second scenario with no acls found
-        provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object());
-    }
+	@Test
+	public void nullReturnObjectIsIgnored() throws Exception {
+		AclService service = mock(AclService.class);
+		AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(
+				service, Arrays.asList(mock(Permission.class)));
 
-    @Test
-    public void nullReturnObjectIsIgnored() throws Exception {
-        AclService service = mock(AclService.class);
-        AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, Arrays.asList(mock(Permission.class)));
-
-        assertNull(provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null));
-        verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class));
-    }
+		assertNull(provider.decide(mock(Authentication.class), new Object(),
+				SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null));
+		verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class));
+	}
 }

+ 72 - 70
acl/src/test/java/org/springframework/security/acls/domain/AccessControlImplEntryTests.java

@@ -17,84 +17,86 @@ import org.springframework.security.acls.model.Sid;
  */
 public class AccessControlImplEntryTests {
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    @Test
-    public void testConstructorRequiredFields() {
-        // Check Acl field is present
-        try {
-            new AccessControlEntryImpl(null, null, new PrincipalSid("johndoe"),
-                    BasePermission.ADMINISTRATION, true, true, true);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
+	@Test
+	public void testConstructorRequiredFields() {
+		// Check Acl field is present
+		try {
+			new AccessControlEntryImpl(null, null, new PrincipalSid("johndoe"),
+					BasePermission.ADMINISTRATION, true, true, true);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
 
-        // Check Sid field is present
-        try {
-            new AccessControlEntryImpl(null, mock(Acl.class), null,
-                    BasePermission.ADMINISTRATION, true, true, true);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
+		// Check Sid field is present
+		try {
+			new AccessControlEntryImpl(null, mock(Acl.class), null,
+					BasePermission.ADMINISTRATION, true, true, true);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
 
-        // Check Permission field is present
-        try {
-            new AccessControlEntryImpl(null, mock(Acl.class), new PrincipalSid("johndoe"), null,
-                    true, true, true);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
+		// Check Permission field is present
+		try {
+			new AccessControlEntryImpl(null, mock(Acl.class),
+					new PrincipalSid("johndoe"), null, true, true, true);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
 
-    @Test
-    public void testAccessControlEntryImplGetters() {
-        Acl mockAcl = mock(Acl.class);
-        Sid sid = new PrincipalSid("johndoe");
+	@Test
+	public void testAccessControlEntryImplGetters() {
+		Acl mockAcl = mock(Acl.class);
+		Sid sid = new PrincipalSid("johndoe");
 
-        // Create a sample entry
-        AccessControlEntry ace = new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid, BasePermission.ADMINISTRATION,
-                true, true, true);
+		// Create a sample entry
+		AccessControlEntry ace = new AccessControlEntryImpl(Long.valueOf(1), mockAcl,
+				sid, BasePermission.ADMINISTRATION, true, true, true);
 
-        // and check every get() method
-        assertEquals(new Long(1), ace.getId());
-        assertEquals(mockAcl, ace.getAcl());
-        assertEquals(sid, ace.getSid());
-        assertTrue(ace.isGranting());
-        assertEquals(BasePermission.ADMINISTRATION, ace.getPermission());
-        assertTrue(((AuditableAccessControlEntry) ace).isAuditFailure());
-        assertTrue(((AuditableAccessControlEntry) ace).isAuditSuccess());
-    }
+		// and check every get() method
+		assertEquals(new Long(1), ace.getId());
+		assertEquals(mockAcl, ace.getAcl());
+		assertEquals(sid, ace.getSid());
+		assertTrue(ace.isGranting());
+		assertEquals(BasePermission.ADMINISTRATION, ace.getPermission());
+		assertTrue(((AuditableAccessControlEntry) ace).isAuditFailure());
+		assertTrue(((AuditableAccessControlEntry) ace).isAuditSuccess());
+	}
 
-    @Test
-    public void testEquals() {
-        final Acl mockAcl = mock(Acl.class);
-        final ObjectIdentity oid = mock(ObjectIdentity.class);
+	@Test
+	public void testEquals() {
+		final Acl mockAcl = mock(Acl.class);
+		final ObjectIdentity oid = mock(ObjectIdentity.class);
 
-        when(mockAcl.getObjectIdentity()).thenReturn(oid);
-        Sid sid = new PrincipalSid("johndoe");
+		when(mockAcl.getObjectIdentity()).thenReturn(oid);
+		Sid sid = new PrincipalSid("johndoe");
 
-        AccessControlEntry ace = new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid, BasePermission.ADMINISTRATION,
-                true, true, true);
+		AccessControlEntry ace = new AccessControlEntryImpl(Long.valueOf(1), mockAcl,
+				sid, BasePermission.ADMINISTRATION, true, true, true);
 
-        assertFalse(ace.equals(null));
-        assertFalse(ace.equals(Long.valueOf(100)));
-        assertTrue(ace.equals(ace));
-        assertTrue(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
-                BasePermission.ADMINISTRATION, true, true, true)));
-        assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(2), mockAcl, sid,
-                BasePermission.ADMINISTRATION, true, true, true)));
-        assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, new PrincipalSid("scott"),
-                BasePermission.ADMINISTRATION, true, true, true)));
-        assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid, BasePermission.WRITE, true,
-                true, true)));
-        assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
-                BasePermission.ADMINISTRATION, false, true, true)));
-        assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
-                BasePermission.ADMINISTRATION, true, false, true)));
-        assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
-                BasePermission.ADMINISTRATION, true, true, false)));
-    }
+		assertFalse(ace.equals(null));
+		assertFalse(ace.equals(Long.valueOf(100)));
+		assertTrue(ace.equals(ace));
+		assertTrue(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
+				BasePermission.ADMINISTRATION, true, true, true)));
+		assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(2), mockAcl, sid,
+				BasePermission.ADMINISTRATION, true, true, true)));
+		assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl,
+				new PrincipalSid("scott"), BasePermission.ADMINISTRATION, true, true,
+				true)));
+		assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
+				BasePermission.WRITE, true, true, true)));
+		assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
+				BasePermission.ADMINISTRATION, false, true, true)));
+		assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
+				BasePermission.ADMINISTRATION, true, false, true)));
+		assertFalse(ace.equals(new AccessControlEntryImpl(Long.valueOf(1), mockAcl, sid,
+				BasePermission.ADMINISTRATION, true, true, false)));
+	}
 }

+ 593 - 509
acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java

@@ -14,519 +14,603 @@ import org.springframework.security.util.FieldUtils;
 import java.lang.reflect.Field;
 import java.util.*;
 
-
 /**
  * Tests for {@link AclImpl}.
  *
  * @author Andrei Stefan
  */
 public class AclImplTests {
-    private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
-    private static final List<Permission> READ = Arrays.asList(BasePermission.READ );
-    private static final List<Permission> WRITE = Arrays.asList(BasePermission.WRITE);
-    private static final List<Permission> CREATE = Arrays.asList(BasePermission.CREATE );
-    private static final List<Permission> DELETE = Arrays.asList(BasePermission.DELETE );
-    private static final List<Sid> SCOTT = Arrays.asList((Sid)new PrincipalSid("scott"));
-    private static final List<Sid> BEN = Arrays.asList((Sid)new PrincipalSid("ben"));
-
-    Authentication auth = new TestingAuthenticationToken("joe", "ignored", "ROLE_ADMINISTRATOR");
-    AclAuthorizationStrategy authzStrategy;
-    PermissionGrantingStrategy pgs;
-    AuditLogger mockAuditLogger;
-    ObjectIdentity objectIdentity = new ObjectIdentityImpl(TARGET_CLASS, 100);
-
-    // ~ Methods ========================================================================================================
-
-    @Before
-    public void setUp() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        authzStrategy = mock(AclAuthorizationStrategy.class);
-        mockAuditLogger = mock(AuditLogger.class);
-        pgs = new DefaultPermissionGrantingStrategy(mockAuditLogger);
-        auth.setAuthenticated(true);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        SecurityContextHolder.clearContext();
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void constructorsRejectNullObjectIdentity() throws Exception {
-        try {
-            new AclImpl(null, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-            fail("Should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-        new AclImpl(null, 1, authzStrategy, mockAuditLogger);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void constructorsRejectNullId() throws Exception {
-        try {
-            new AclImpl(objectIdentity, null, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-            fail("Should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-        new AclImpl(objectIdentity, null, authzStrategy, mockAuditLogger);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test(expected=IllegalArgumentException.class)
-    public void constructorsRejectNullAclAuthzStrategy() throws Exception {
-        try {
-            new AclImpl(objectIdentity, 1, null, new DefaultPermissionGrantingStrategy(mockAuditLogger), null, null, true, new PrincipalSid("joe"));
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-        new AclImpl(objectIdentity, 1, null, mockAuditLogger);
-    }
-
-    @Test
-    public void insertAceRejectsNullParameters() throws Exception {
-        MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid(
-                "joe"));
-        try {
-            acl.insertAce(0, null, new GrantedAuthoritySid("ROLE_IGNORED"), true);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-        try {
-            acl.insertAce(0, BasePermission.READ, null, true);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void insertAceAddsElementAtCorrectIndex() throws Exception {
-        MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        MockAclService service = new MockAclService();
-
-        // Insert one permission
-        acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
-        service.updateAcl(acl);
-        // Check it was successfully added
-        assertEquals(1, acl.getEntries().size());
-        assertEquals(acl.getEntries().get(0).getAcl(), acl);
-        assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.READ);
-        assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid("ROLE_TEST1"));
-
-        // Add a second permission
-        acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
-        service.updateAcl(acl);
-        // Check it was added on the last position
-        assertEquals(2, acl.getEntries().size());
-        assertEquals(acl.getEntries().get(1).getAcl(), acl);
-        assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.READ);
-        assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
-
-        // Add a third permission, after the first one
-        acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_TEST3"), false);
-        service.updateAcl(acl);
-        assertEquals(3, acl.getEntries().size());
-        // Check the third entry was added between the two existent ones
-        assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.READ);
-        assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid("ROLE_TEST1"));
-        assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.WRITE);
-        assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid("ROLE_TEST3"));
-        assertEquals(acl.getEntries().get(2).getPermission(), BasePermission.READ);
-        assertEquals(acl.getEntries().get(2).getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
-    }
-
-    @Test(expected=NotFoundException.class)
-    public void insertAceFailsForNonExistentElement() throws Exception {
-        MutableAcl acl = new AclImpl(objectIdentity,1, authzStrategy, pgs, null, null, true, new PrincipalSid(
-                "joe"));
-        MockAclService service = new MockAclService();
-
-        // Insert one permission
-        acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
-        service.updateAcl(acl);
-
-        acl.insertAce(55, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
-    }
-
-    @Test
-    public void deleteAceKeepsInitialOrdering() throws Exception {
-        MutableAcl acl = new AclImpl(objectIdentity,1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        MockAclService service = new MockAclService();
-
-        // Add several permissions
-        acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
-        acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
-        acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST3"), true);
-        service.updateAcl(acl);
-
-        // Delete first permission and check the order of the remaining permissions is kept
-        acl.deleteAce(0);
-        assertEquals(2, acl.getEntries().size());
-        assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
-        assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid("ROLE_TEST3"));
-
-        // Add one more permission and remove the permission in the middle
-        acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST4"), true);
-        service.updateAcl(acl);
-        acl.deleteAce(1);
-        assertEquals(2, acl.getEntries().size());
-        assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid("ROLE_TEST2"));
-        assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid("ROLE_TEST4"));
-
-        // Remove remaining permissions
-        acl.deleteAce(1);
-        acl.deleteAce(0);
-        assertEquals(0, acl.getEntries().size());
-    }
-
-    @Test
-    public void deleteAceFailsForNonExistentElement() throws Exception {
-        AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
-        MutableAcl acl = new AclImpl(objectIdentity, (1), strategy, pgs, null, null, true, new PrincipalSid(
-                "joe"));
-        try {
-            acl.deleteAce(99);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-        }
-    }
-
-    @Test
-    public void isGrantingRejectsEmptyParameters() throws Exception {
-        MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        Sid ben = new PrincipalSid("ben");
-        try {
-            acl.isGranted(new ArrayList<Permission>(0), Arrays.asList(ben) , false);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-        try {
-            acl.isGranted(READ, new ArrayList<Sid>(0), false);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void isGrantingGrantsAccessForAclWithNoParent() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL","ROLE_GUEST");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        ObjectIdentity rootOid = new ObjectIdentityImpl(TARGET_CLASS, 100);
-
-        // Create an ACL which owner is not the authenticated principal
-        MutableAcl rootAcl = new AclImpl(rootOid, 1, authzStrategy, pgs, null, null, false, new PrincipalSid("joe"));
-
-        // Grant some permissions
-        rootAcl.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), false);
-        rootAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("scott"), true);
-        rootAcl.insertAce(2, BasePermission.WRITE, new PrincipalSid("rod"), false);
-        rootAcl.insertAce(3, BasePermission.WRITE, new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), true);
-
-        // Check permissions granting
-        List<Permission> permissions = Arrays.asList(BasePermission.READ, BasePermission.CREATE);
-        List<Sid> sids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_GUEST"));
-        assertFalse(rootAcl.isGranted(permissions, sids, false));
-        try {
-            rootAcl.isGranted(permissions, SCOTT, false);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-        }
-        assertTrue(rootAcl.isGranted(WRITE, SCOTT, false));
-        assertFalse(rootAcl.isGranted(WRITE,
-                Arrays.asList(new PrincipalSid("rod"), new GrantedAuthoritySid("WRITE_ACCESS_ROLE")), false));
-        assertTrue(rootAcl.isGranted(WRITE, Arrays.asList(new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), new PrincipalSid("rod")), false));
-        try {
-            // Change the type of the Sid and check the granting process
-            rootAcl.isGranted(WRITE, Arrays.asList(new GrantedAuthoritySid("rod"), new PrincipalSid("WRITE_ACCESS_ROLE")), false);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-        }
-    }
-
-    @Test
-    public void isGrantingGrantsAccessForInheritableAcls() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("ben", "ignored","ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100);
-        ObjectIdentity parentOid1 = new ObjectIdentityImpl(TARGET_CLASS, 101);
-        ObjectIdentity parentOid2 = new ObjectIdentityImpl(TARGET_CLASS, 102);
-        ObjectIdentity childOid1 = new ObjectIdentityImpl(TARGET_CLASS, 103);
-        ObjectIdentity childOid2 = new ObjectIdentityImpl(TARGET_CLASS, 104);
-
-        // Create ACLs
-        PrincipalSid joe = new PrincipalSid("joe");
-        MutableAcl grandParentAcl = new AclImpl(grandParentOid, 1, authzStrategy, pgs, null, null, false, joe);
-        MutableAcl parentAcl1 = new AclImpl(parentOid1, 2, authzStrategy, pgs, null, null, true, joe);
-        MutableAcl parentAcl2 = new AclImpl(parentOid2, 3, authzStrategy, pgs, null, null, true, joe);
-        MutableAcl childAcl1 = new AclImpl(childOid1, 4, authzStrategy, pgs, null, null, true, joe);
-        MutableAcl childAcl2 = new AclImpl(childOid2, 4, authzStrategy, pgs, null, null, false, joe);
-
-        // Create hierarchies
-        childAcl2.setParent(childAcl1);
-        childAcl1.setParent(parentAcl1);
-        parentAcl2.setParent(grandParentAcl);
-        parentAcl1.setParent(grandParentAcl);
-
-        // Add some permissions
-        grandParentAcl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        grandParentAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("ben"), true);
-        grandParentAcl.insertAce(2, BasePermission.DELETE, new PrincipalSid("ben"), false);
-        grandParentAcl.insertAce(3, BasePermission.DELETE, new PrincipalSid("scott"), true);
-        parentAcl1.insertAce(0, BasePermission.READ, new PrincipalSid("scott"), true);
-        parentAcl1.insertAce(1, BasePermission.DELETE, new PrincipalSid("scott"), false);
-        parentAcl2.insertAce(0, BasePermission.CREATE, new PrincipalSid("ben"), true);
-        childAcl1.insertAce(0, BasePermission.CREATE, new PrincipalSid("scott"), true);
-
-        // Check granting process for parent1
-        assertTrue(parentAcl1.isGranted(READ, SCOTT, false));
-        assertTrue(parentAcl1.isGranted(READ, Arrays.asList((Sid)new GrantedAuthoritySid("ROLE_USER_READ")), false));
-        assertTrue(parentAcl1.isGranted(WRITE, BEN, false));
-        assertFalse(parentAcl1.isGranted(DELETE, BEN, false));
-        assertFalse(parentAcl1.isGranted(DELETE, SCOTT, false));
-
-        // Check granting process for parent2
-        assertTrue(parentAcl2.isGranted(CREATE, BEN, false));
-        assertTrue(parentAcl2.isGranted(WRITE, BEN, false));
-        assertFalse(parentAcl2.isGranted(DELETE, BEN, false));
-
-        // Check granting process for child1
-        assertTrue(childAcl1.isGranted(CREATE, SCOTT,
-                false));
-        assertTrue(childAcl1.isGranted(READ, Arrays.asList((Sid)new GrantedAuthoritySid("ROLE_USER_READ")), false));
-        assertFalse(childAcl1.isGranted(DELETE, BEN, false));
-
-        // Check granting process for child2 (doesn't inherit the permissions from its parent)
-        try {
-            assertTrue(childAcl2.isGranted(CREATE, SCOTT, false));
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-        try {
-            assertTrue(childAcl2.isGranted(CREATE, Arrays.asList((Sid)new PrincipalSid("joe")), false));
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-    }
-
-    @Test
-    public void updatedAceValuesAreCorrectlyReflectedInAcl() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("ben", "ignored","ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, false, new PrincipalSid("joe"));
-        MockAclService service = new MockAclService();
-
-        acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        acl.insertAce(2, BasePermission.CREATE, new PrincipalSid("ben"), true);
-        service.updateAcl(acl);
-
-        assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.READ);
-        assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.WRITE);
-        assertEquals(acl.getEntries().get(2).getPermission(), BasePermission.CREATE);
-
-        // Change each permission
-        acl.updateAce(0, BasePermission.CREATE);
-        acl.updateAce(1, BasePermission.DELETE);
-        acl.updateAce(2, BasePermission.READ);
-
-        // Check the change was successfully made
-        assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.CREATE);
-        assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.DELETE);
-        assertEquals(acl.getEntries().get(2).getPermission(), BasePermission.READ);
-    }
-
-    @Test
-    public void auditableEntryFlagsAreUpdatedCorrectly() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_AUDITING", "ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, false, new PrincipalSid("joe"));
-        MockAclService service = new MockAclService();
-
-        acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        service.updateAcl(acl);
-
-        assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditFailure());
-        assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditFailure());
-        assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditSuccess());
-        assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditSuccess());
-
-        // Change each permission
-        ((AuditableAcl) acl).updateAuditing(0, true, true);
-        ((AuditableAcl) acl).updateAuditing(1, true, true);
-
-        // Check the change was successfuly made
-        assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditFailure());
-        assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditFailure());
-        assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditSuccess());
-        assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditSuccess());
-    }
-
-    @Test
-    public void gettersAndSettersAreConsistent() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, (100));
-        ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, (101));
-        MutableAcl acl = new AclImpl(identity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        MutableAcl parentAcl = new AclImpl(identity2, 2, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        MockAclService service = new MockAclService();
-        acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true);
-        service.updateAcl(acl);
-
-        assertEquals(acl.getId(), 1);
-        assertEquals(acl.getObjectIdentity(), identity);
-        assertEquals(acl.getOwner(), new PrincipalSid("joe"));
-        assertNull(acl.getParentAcl());
-        assertTrue(acl.isEntriesInheriting());
-        assertEquals(2, acl.getEntries().size());
-
-        acl.setParent(parentAcl);
-        assertEquals(acl.getParentAcl(), parentAcl);
-
-        acl.setEntriesInheriting(false);
-        assertFalse(acl.isEntriesInheriting());
-
-        acl.setOwner(new PrincipalSid("ben"));
-        assertEquals(acl.getOwner(), new PrincipalSid("ben"));
-    }
-
-    @Test
-    public void isSidLoadedBehavesAsExpected() throws Exception {
-        List<Sid> loadedSids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_IGNORED"));
-        MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, loadedSids, true,
-                new PrincipalSid("joe"));
-
-        assertTrue(acl.isSidLoaded(loadedSids));
-        assertTrue(acl.isSidLoaded(Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new PrincipalSid("ben"))));
-        assertTrue(acl.isSidLoaded(Arrays.asList((Sid)new GrantedAuthoritySid("ROLE_IGNORED"))));
-        assertTrue(acl.isSidLoaded(BEN));
-        assertTrue(acl.isSidLoaded(null));
-        assertTrue(acl.isSidLoaded(new ArrayList<Sid>(0)));
-        assertTrue(acl.isSidLoaded(Arrays.asList((Sid)new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_IGNORED"))));
-        assertFalse(acl.isSidLoaded(Arrays.asList((Sid)new GrantedAuthoritySid("ROLE_GENERAL"), new GrantedAuthoritySid("ROLE_IGNORED"))));
-        assertFalse(acl.isSidLoaded(Arrays.asList((Sid)new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_GENERAL"))));
-    }
-
-    @Test(expected=NotFoundException.class)
-    public void insertAceRaisesNotFoundExceptionForIndexLessThanZero() throws Exception {
-        AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        acl.insertAce(-1, mock(Permission.class), mock(Sid.class), true);
-    }
-
-    @Test(expected=NotFoundException.class)
-    public void deleteAceRaisesNotFoundExceptionForIndexLessThanZero() throws Exception {
-        AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        acl.deleteAce(-1);
-    }
-
-    @Test(expected=NotFoundException.class)
-    public void insertAceRaisesNotFoundExceptionForIndexGreaterThanSize() throws Exception {
-        AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        // Insert at zero, OK.
-        acl.insertAce(0, mock(Permission.class), mock(Sid.class), true);
-        // Size is now 1
-        acl.insertAce(2, mock(Permission.class), mock(Sid.class), true);
-    }
-
-    // SEC-1151
-    @Test(expected=NotFoundException.class)
-    public void deleteAceRaisesNotFoundExceptionForIndexEqualToSize() throws Exception {
-        AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null, true, new PrincipalSid("joe"));
-        acl.insertAce(0, mock(Permission.class), mock(Sid.class), true);
-        // Size is now 1
-        acl.deleteAce(1);
-    }
-
-    // SEC-1795
-    @Test
-    public void changingParentIsSuccessful() throws Exception {
-        AclImpl parentAcl = new AclImpl(objectIdentity, 1L, authzStrategy, mockAuditLogger);
-        AclImpl childAcl = new AclImpl(objectIdentity, 2L, authzStrategy, mockAuditLogger);
-        AclImpl changeParentAcl = new AclImpl(objectIdentity, 3L, authzStrategy, mockAuditLogger);
-
-        childAcl.setParent(parentAcl);
-        childAcl.setParent(changeParentAcl);
-    }
-
-    //~ Inner Classes ==================================================================================================
-
-    private class MockAclService implements MutableAclService {
-        public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException {
-            return null;
-        }
-
-        public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException {
-        }
-
-        /*
-         * Mock implementation that populates the aces list with fully initialized AccessControlEntries
-         * @see org.springframework.security.acls.MutableAclService#updateAcl(org.springframework.security.acls.MutableAcl)
-         */
-        @SuppressWarnings("unchecked")
-        public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
-            List<AccessControlEntry> oldAces = acl.getEntries();
-            Field acesField = FieldUtils.getField(AclImpl.class, "aces");
-            acesField.setAccessible(true);
-            List newAces;
-            try {
-                newAces = (List) acesField.get(acl);
-                newAces.clear();
-
-                for (int i = 0; i < oldAces.size(); i++) {
-                    AccessControlEntry ac = oldAces.get(i);
-                    // Just give an ID to all this acl's aces, rest of the fields are just copied
-                    newAces.add(new AccessControlEntryImpl((i + 1), ac.getAcl(), ac.getSid(), ac.getPermission(), ac
-                            .isGranting(), ((AuditableAccessControlEntry) ac).isAuditSuccess(),
-                            ((AuditableAccessControlEntry) ac).isAuditFailure()));
-                }
-            }
-            catch (IllegalAccessException e) {
-                e.printStackTrace();
-            }
-
-            return acl;
-        }
-
-        public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
-            return null;
-        }
-
-        public Acl readAclById(ObjectIdentity object) throws NotFoundException {
-            return null;
-        }
-
-        public Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException {
-            return null;
-        }
-
-        public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects) throws NotFoundException {
-            return null;
-        }
-
-        public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) throws NotFoundException {
-            return null;
-        }
-    }
+	private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
+	private static final List<Permission> READ = Arrays.asList(BasePermission.READ);
+	private static final List<Permission> WRITE = Arrays.asList(BasePermission.WRITE);
+	private static final List<Permission> CREATE = Arrays.asList(BasePermission.CREATE);
+	private static final List<Permission> DELETE = Arrays.asList(BasePermission.DELETE);
+	private static final List<Sid> SCOTT = Arrays.asList((Sid) new PrincipalSid("scott"));
+	private static final List<Sid> BEN = Arrays.asList((Sid) new PrincipalSid("ben"));
+
+	Authentication auth = new TestingAuthenticationToken("joe", "ignored",
+			"ROLE_ADMINISTRATOR");
+	AclAuthorizationStrategy authzStrategy;
+	PermissionGrantingStrategy pgs;
+	AuditLogger mockAuditLogger;
+	ObjectIdentity objectIdentity = new ObjectIdentityImpl(TARGET_CLASS, 100);
+
+	// ~ Methods
+	// ========================================================================================================
+
+	@Before
+	public void setUp() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		authzStrategy = mock(AclAuthorizationStrategy.class);
+		mockAuditLogger = mock(AuditLogger.class);
+		pgs = new DefaultPermissionGrantingStrategy(mockAuditLogger);
+		auth.setAuthenticated(true);
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		SecurityContextHolder.clearContext();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorsRejectNullObjectIdentity() throws Exception {
+		try {
+			new AclImpl(null, 1, authzStrategy, pgs, null, null, true, new PrincipalSid(
+					"joe"));
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+		new AclImpl(null, 1, authzStrategy, mockAuditLogger);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorsRejectNullId() throws Exception {
+		try {
+			new AclImpl(objectIdentity, null, authzStrategy, pgs, null, null, true,
+					new PrincipalSid("joe"));
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+		new AclImpl(objectIdentity, null, authzStrategy, mockAuditLogger);
+	}
+
+	@SuppressWarnings("deprecation")
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorsRejectNullAclAuthzStrategy() throws Exception {
+		try {
+			new AclImpl(objectIdentity, 1, null, new DefaultPermissionGrantingStrategy(
+					mockAuditLogger), null, null, true, new PrincipalSid("joe"));
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+		new AclImpl(objectIdentity, 1, null, mockAuditLogger);
+	}
+
+	@Test
+	public void insertAceRejectsNullParameters() throws Exception {
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		try {
+			acl.insertAce(0, null, new GrantedAuthoritySid("ROLE_IGNORED"), true);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+		try {
+			acl.insertAce(0, BasePermission.READ, null, true);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test
+	public void insertAceAddsElementAtCorrectIndex() throws Exception {
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		MockAclService service = new MockAclService();
+
+		// Insert one permission
+		acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
+		service.updateAcl(acl);
+		// Check it was successfully added
+		assertEquals(1, acl.getEntries().size());
+		assertEquals(acl.getEntries().get(0).getAcl(), acl);
+		assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.READ);
+		assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST1"));
+
+		// Add a second permission
+		acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
+		service.updateAcl(acl);
+		// Check it was added on the last position
+		assertEquals(2, acl.getEntries().size());
+		assertEquals(acl.getEntries().get(1).getAcl(), acl);
+		assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.READ);
+		assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST2"));
+
+		// Add a third permission, after the first one
+		acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_TEST3"),
+				false);
+		service.updateAcl(acl);
+		assertEquals(3, acl.getEntries().size());
+		// Check the third entry was added between the two existent ones
+		assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.READ);
+		assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST1"));
+		assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.WRITE);
+		assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST3"));
+		assertEquals(acl.getEntries().get(2).getPermission(), BasePermission.READ);
+		assertEquals(acl.getEntries().get(2).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST2"));
+	}
+
+	@Test(expected = NotFoundException.class)
+	public void insertAceFailsForNonExistentElement() throws Exception {
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		MockAclService service = new MockAclService();
+
+		// Insert one permission
+		acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
+		service.updateAcl(acl);
+
+		acl.insertAce(55, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"),
+				true);
+	}
+
+	@Test
+	public void deleteAceKeepsInitialOrdering() throws Exception {
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		MockAclService service = new MockAclService();
+
+		// Add several permissions
+		acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true);
+		acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true);
+		acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST3"), true);
+		service.updateAcl(acl);
+
+		// Delete first permission and check the order of the remaining permissions is
+		// kept
+		acl.deleteAce(0);
+		assertEquals(2, acl.getEntries().size());
+		assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST2"));
+		assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST3"));
+
+		// Add one more permission and remove the permission in the middle
+		acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST4"), true);
+		service.updateAcl(acl);
+		acl.deleteAce(1);
+		assertEquals(2, acl.getEntries().size());
+		assertEquals(acl.getEntries().get(0).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST2"));
+		assertEquals(acl.getEntries().get(1).getSid(), new GrantedAuthoritySid(
+				"ROLE_TEST4"));
+
+		// Remove remaining permissions
+		acl.deleteAce(1);
+		acl.deleteAce(0);
+		assertEquals(0, acl.getEntries().size());
+	}
+
+	@Test
+	public void deleteAceFailsForNonExistentElement() throws Exception {
+		AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
+		MutableAcl acl = new AclImpl(objectIdentity, (1), strategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		try {
+			acl.deleteAce(99);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+		}
+	}
+
+	@Test
+	public void isGrantingRejectsEmptyParameters() throws Exception {
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		Sid ben = new PrincipalSid("ben");
+		try {
+			acl.isGranted(new ArrayList<Permission>(0), Arrays.asList(ben), false);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+		try {
+			acl.isGranted(READ, new ArrayList<Sid>(0), false);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test
+	public void isGrantingGrantsAccessForAclWithNoParent() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_GENERAL", "ROLE_GUEST");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		ObjectIdentity rootOid = new ObjectIdentityImpl(TARGET_CLASS, 100);
+
+		// Create an ACL which owner is not the authenticated principal
+		MutableAcl rootAcl = new AclImpl(rootOid, 1, authzStrategy, pgs, null, null,
+				false, new PrincipalSid("joe"));
+
+		// Grant some permissions
+		rootAcl.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), false);
+		rootAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("scott"), true);
+		rootAcl.insertAce(2, BasePermission.WRITE, new PrincipalSid("rod"), false);
+		rootAcl.insertAce(3, BasePermission.WRITE, new GrantedAuthoritySid(
+				"WRITE_ACCESS_ROLE"), true);
+
+		// Check permissions granting
+		List<Permission> permissions = Arrays.asList(BasePermission.READ,
+				BasePermission.CREATE);
+		List<Sid> sids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid(
+				"ROLE_GUEST"));
+		assertFalse(rootAcl.isGranted(permissions, sids, false));
+		try {
+			rootAcl.isGranted(permissions, SCOTT, false);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+		}
+		assertTrue(rootAcl.isGranted(WRITE, SCOTT, false));
+		assertFalse(rootAcl.isGranted(WRITE, Arrays.asList(new PrincipalSid("rod"),
+				new GrantedAuthoritySid("WRITE_ACCESS_ROLE")), false));
+		assertTrue(rootAcl.isGranted(WRITE, Arrays.asList(new GrantedAuthoritySid(
+				"WRITE_ACCESS_ROLE"), new PrincipalSid("rod")), false));
+		try {
+			// Change the type of the Sid and check the granting process
+			rootAcl.isGranted(WRITE, Arrays.asList(new GrantedAuthoritySid("rod"),
+					new PrincipalSid("WRITE_ACCESS_ROLE")), false);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+		}
+	}
+
+	@Test
+	public void isGrantingGrantsAccessForInheritableAcls() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100);
+		ObjectIdentity parentOid1 = new ObjectIdentityImpl(TARGET_CLASS, 101);
+		ObjectIdentity parentOid2 = new ObjectIdentityImpl(TARGET_CLASS, 102);
+		ObjectIdentity childOid1 = new ObjectIdentityImpl(TARGET_CLASS, 103);
+		ObjectIdentity childOid2 = new ObjectIdentityImpl(TARGET_CLASS, 104);
+
+		// Create ACLs
+		PrincipalSid joe = new PrincipalSid("joe");
+		MutableAcl grandParentAcl = new AclImpl(grandParentOid, 1, authzStrategy, pgs,
+				null, null, false, joe);
+		MutableAcl parentAcl1 = new AclImpl(parentOid1, 2, authzStrategy, pgs, null,
+				null, true, joe);
+		MutableAcl parentAcl2 = new AclImpl(parentOid2, 3, authzStrategy, pgs, null,
+				null, true, joe);
+		MutableAcl childAcl1 = new AclImpl(childOid1, 4, authzStrategy, pgs, null, null,
+				true, joe);
+		MutableAcl childAcl2 = new AclImpl(childOid2, 4, authzStrategy, pgs, null, null,
+				false, joe);
+
+		// Create hierarchies
+		childAcl2.setParent(childAcl1);
+		childAcl1.setParent(parentAcl1);
+		parentAcl2.setParent(grandParentAcl);
+		parentAcl1.setParent(grandParentAcl);
+
+		// Add some permissions
+		grandParentAcl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid(
+				"ROLE_USER_READ"), true);
+		grandParentAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("ben"), true);
+		grandParentAcl
+				.insertAce(2, BasePermission.DELETE, new PrincipalSid("ben"), false);
+		grandParentAcl.insertAce(3, BasePermission.DELETE, new PrincipalSid("scott"),
+				true);
+		parentAcl1.insertAce(0, BasePermission.READ, new PrincipalSid("scott"), true);
+		parentAcl1.insertAce(1, BasePermission.DELETE, new PrincipalSid("scott"), false);
+		parentAcl2.insertAce(0, BasePermission.CREATE, new PrincipalSid("ben"), true);
+		childAcl1.insertAce(0, BasePermission.CREATE, new PrincipalSid("scott"), true);
+
+		// Check granting process for parent1
+		assertTrue(parentAcl1.isGranted(READ, SCOTT, false));
+		assertTrue(parentAcl1.isGranted(READ,
+				Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_USER_READ")), false));
+		assertTrue(parentAcl1.isGranted(WRITE, BEN, false));
+		assertFalse(parentAcl1.isGranted(DELETE, BEN, false));
+		assertFalse(parentAcl1.isGranted(DELETE, SCOTT, false));
+
+		// Check granting process for parent2
+		assertTrue(parentAcl2.isGranted(CREATE, BEN, false));
+		assertTrue(parentAcl2.isGranted(WRITE, BEN, false));
+		assertFalse(parentAcl2.isGranted(DELETE, BEN, false));
+
+		// Check granting process for child1
+		assertTrue(childAcl1.isGranted(CREATE, SCOTT, false));
+		assertTrue(childAcl1.isGranted(READ,
+				Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_USER_READ")), false));
+		assertFalse(childAcl1.isGranted(DELETE, BEN, false));
+
+		// Check granting process for child2 (doesn't inherit the permissions from its
+		// parent)
+		try {
+			assertTrue(childAcl2.isGranted(CREATE, SCOTT, false));
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+		try {
+			assertTrue(childAcl2.isGranted(CREATE,
+					Arrays.asList((Sid) new PrincipalSid("joe")), false));
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+	}
+
+	@Test
+	public void updatedAceValuesAreCorrectlyReflectedInAcl() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				false, new PrincipalSid("joe"));
+		MockAclService service = new MockAclService();
+
+		acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"),
+				true);
+		acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"),
+				true);
+		acl.insertAce(2, BasePermission.CREATE, new PrincipalSid("ben"), true);
+		service.updateAcl(acl);
+
+		assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.READ);
+		assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.WRITE);
+		assertEquals(acl.getEntries().get(2).getPermission(), BasePermission.CREATE);
+
+		// Change each permission
+		acl.updateAce(0, BasePermission.CREATE);
+		acl.updateAce(1, BasePermission.DELETE);
+		acl.updateAce(2, BasePermission.READ);
+
+		// Check the change was successfully made
+		assertEquals(acl.getEntries().get(0).getPermission(), BasePermission.CREATE);
+		assertEquals(acl.getEntries().get(1).getPermission(), BasePermission.DELETE);
+		assertEquals(acl.getEntries().get(2).getPermission(), BasePermission.READ);
+	}
+
+	@Test
+	public void auditableEntryFlagsAreUpdatedCorrectly() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_AUDITING", "ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				false, new PrincipalSid("joe"));
+		MockAclService service = new MockAclService();
+
+		acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"),
+				true);
+		acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"),
+				true);
+		service.updateAcl(acl);
+
+		assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(0))
+				.isAuditFailure());
+		assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(1))
+				.isAuditFailure());
+		assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(0))
+				.isAuditSuccess());
+		assertFalse(((AuditableAccessControlEntry) acl.getEntries().get(1))
+				.isAuditSuccess());
+
+		// Change each permission
+		((AuditableAcl) acl).updateAuditing(0, true, true);
+		((AuditableAcl) acl).updateAuditing(1, true, true);
+
+		// Check the change was successfuly made
+		assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(0))
+				.isAuditFailure());
+		assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(1))
+				.isAuditFailure());
+		assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(0))
+				.isAuditSuccess());
+		assertTrue(((AuditableAccessControlEntry) acl.getEntries().get(1))
+				.isAuditSuccess());
+	}
+
+	@Test
+	public void gettersAndSettersAreConsistent() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, (100));
+		ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, (101));
+		MutableAcl acl = new AclImpl(identity, 1, authzStrategy, pgs, null, null, true,
+				new PrincipalSid("joe"));
+		MutableAcl parentAcl = new AclImpl(identity2, 2, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		MockAclService service = new MockAclService();
+		acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"),
+				true);
+		acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"),
+				true);
+		service.updateAcl(acl);
+
+		assertEquals(acl.getId(), 1);
+		assertEquals(acl.getObjectIdentity(), identity);
+		assertEquals(acl.getOwner(), new PrincipalSid("joe"));
+		assertNull(acl.getParentAcl());
+		assertTrue(acl.isEntriesInheriting());
+		assertEquals(2, acl.getEntries().size());
+
+		acl.setParent(parentAcl);
+		assertEquals(acl.getParentAcl(), parentAcl);
+
+		acl.setEntriesInheriting(false);
+		assertFalse(acl.isEntriesInheriting());
+
+		acl.setOwner(new PrincipalSid("ben"));
+		assertEquals(acl.getOwner(), new PrincipalSid("ben"));
+	}
+
+	@Test
+	public void isSidLoadedBehavesAsExpected() throws Exception {
+		List<Sid> loadedSids = Arrays.asList(new PrincipalSid("ben"),
+				new GrantedAuthoritySid("ROLE_IGNORED"));
+		MutableAcl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null,
+				loadedSids, true, new PrincipalSid("joe"));
+
+		assertTrue(acl.isSidLoaded(loadedSids));
+		assertTrue(acl.isSidLoaded(Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"),
+				new PrincipalSid("ben"))));
+		assertTrue(acl.isSidLoaded(Arrays.asList((Sid) new GrantedAuthoritySid(
+				"ROLE_IGNORED"))));
+		assertTrue(acl.isSidLoaded(BEN));
+		assertTrue(acl.isSidLoaded(null));
+		assertTrue(acl.isSidLoaded(new ArrayList<Sid>(0)));
+		assertTrue(acl.isSidLoaded(Arrays.asList((Sid) new GrantedAuthoritySid(
+				"ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_IGNORED"))));
+		assertFalse(acl.isSidLoaded(Arrays.asList((Sid) new GrantedAuthoritySid(
+				"ROLE_GENERAL"), new GrantedAuthoritySid("ROLE_IGNORED"))));
+		assertFalse(acl.isSidLoaded(Arrays.asList((Sid) new GrantedAuthoritySid(
+				"ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_GENERAL"))));
+	}
+
+	@Test(expected = NotFoundException.class)
+	public void insertAceRaisesNotFoundExceptionForIndexLessThanZero() throws Exception {
+		AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		acl.insertAce(-1, mock(Permission.class), mock(Sid.class), true);
+	}
+
+	@Test(expected = NotFoundException.class)
+	public void deleteAceRaisesNotFoundExceptionForIndexLessThanZero() throws Exception {
+		AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		acl.deleteAce(-1);
+	}
+
+	@Test(expected = NotFoundException.class)
+	public void insertAceRaisesNotFoundExceptionForIndexGreaterThanSize()
+			throws Exception {
+		AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		// Insert at zero, OK.
+		acl.insertAce(0, mock(Permission.class), mock(Sid.class), true);
+		// Size is now 1
+		acl.insertAce(2, mock(Permission.class), mock(Sid.class), true);
+	}
+
+	// SEC-1151
+	@Test(expected = NotFoundException.class)
+	public void deleteAceRaisesNotFoundExceptionForIndexEqualToSize() throws Exception {
+		AclImpl acl = new AclImpl(objectIdentity, 1, authzStrategy, pgs, null, null,
+				true, new PrincipalSid("joe"));
+		acl.insertAce(0, mock(Permission.class), mock(Sid.class), true);
+		// Size is now 1
+		acl.deleteAce(1);
+	}
+
+	// SEC-1795
+	@Test
+	public void changingParentIsSuccessful() throws Exception {
+		AclImpl parentAcl = new AclImpl(objectIdentity, 1L, authzStrategy,
+				mockAuditLogger);
+		AclImpl childAcl = new AclImpl(objectIdentity, 2L, authzStrategy, mockAuditLogger);
+		AclImpl changeParentAcl = new AclImpl(objectIdentity, 3L, authzStrategy,
+				mockAuditLogger);
+
+		childAcl.setParent(parentAcl);
+		childAcl.setParent(changeParentAcl);
+	}
+
+	// ~ Inner Classes
+	// ==================================================================================================
+
+	private class MockAclService implements MutableAclService {
+		public MutableAcl createAcl(ObjectIdentity objectIdentity)
+				throws AlreadyExistsException {
+			return null;
+		}
+
+		public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)
+				throws ChildrenExistException {
+		}
+
+		/*
+		 * Mock implementation that populates the aces list with fully initialized
+		 * AccessControlEntries
+		 * 
+		 * @see
+		 * org.springframework.security.acls.MutableAclService#updateAcl(org.springframework
+		 * .security.acls.MutableAcl)
+		 */
+		@SuppressWarnings("unchecked")
+		public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
+			List<AccessControlEntry> oldAces = acl.getEntries();
+			Field acesField = FieldUtils.getField(AclImpl.class, "aces");
+			acesField.setAccessible(true);
+			List newAces;
+			try {
+				newAces = (List) acesField.get(acl);
+				newAces.clear();
+
+				for (int i = 0; i < oldAces.size(); i++) {
+					AccessControlEntry ac = oldAces.get(i);
+					// Just give an ID to all this acl's aces, rest of the fields are just
+					// copied
+					newAces.add(new AccessControlEntryImpl((i + 1), ac.getAcl(), ac
+							.getSid(), ac.getPermission(), ac.isGranting(),
+							((AuditableAccessControlEntry) ac).isAuditSuccess(),
+							((AuditableAccessControlEntry) ac).isAuditFailure()));
+				}
+			}
+			catch (IllegalAccessException e) {
+				e.printStackTrace();
+			}
+
+			return acl;
+		}
+
+		public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
+			return null;
+		}
+
+		public Acl readAclById(ObjectIdentity object) throws NotFoundException {
+			return null;
+		}
+
+		public Acl readAclById(ObjectIdentity object, List<Sid> sids)
+				throws NotFoundException {
+			return null;
+		}
+
+		public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects)
+				throws NotFoundException {
+			return null;
+		}
+
+		public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects,
+				List<Sid> sids) throws NotFoundException {
+			return null;
+		}
+	}
 }

+ 248 - 209
acl/src/test/java/org/springframework/security/acls/domain/AclImplementationSecurityCheckTests.java

@@ -14,245 +14,284 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 
 /**
- * Test class for {@link AclAuthorizationStrategyImpl} and {@link AclImpl}
- * security checks.
+ * Test class for {@link AclAuthorizationStrategyImpl} and {@link AclImpl} security
+ * checks.
  *
  * @author Andrei Stefan
  */
 public class AclImplementationSecurityCheckTests {
-    private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
+	private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    @Before
-    public void setUp() throws Exception {
-        SecurityContextHolder.clearContext();
-    }
+	@Before
+	public void setUp() throws Exception {
+		SecurityContextHolder.clearContext();
+	}
 
-    @After
-    public void tearDown() throws Exception {
-        SecurityContextHolder.clearContext();
-    }
+	@After
+	public void tearDown() throws Exception {
+		SecurityContextHolder.clearContext();
+	}
 
-    @Test
-    public void testSecurityCheckNoACEs() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("user", "password","ROLE_GENERAL","ROLE_AUDITING","ROLE_OWNERSHIP");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
+	@Test
+	public void testSecurityCheckNoACEs() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("user", "password",
+				"ROLE_GENERAL", "ROLE_AUDITING", "ROLE_OWNERSHIP");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
 
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
 
-        Acl acl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
+		Acl acl = new AclImpl(identity, new Long(1), aclAuthorizationStrategy,
+				new ConsoleAuditLogger());
 
-        aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL);
-        aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING);
-        aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+		aclAuthorizationStrategy.securityCheck(acl,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
+		aclAuthorizationStrategy.securityCheck(acl,
+				AclAuthorizationStrategy.CHANGE_AUDITING);
+		aclAuthorizationStrategy.securityCheck(acl,
+				AclAuthorizationStrategy.CHANGE_OWNERSHIP);
 
-        // Create another authorization strategy
-        AclAuthorizationStrategy aclAuthorizationStrategy2 = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority("ROLE_TWO"),
-                new SimpleGrantedAuthority("ROLE_THREE"));
-        Acl acl2 = new AclImpl(identity, new Long(1), aclAuthorizationStrategy2, new ConsoleAuditLogger());
-        // Check access in case the principal has no authorization rights
-        try {
-            aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_GENERAL);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-        }
-        try {
-            aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_AUDITING);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-        }
-        try {
-            aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-        }
-    }
+		// Create another authorization strategy
+		AclAuthorizationStrategy aclAuthorizationStrategy2 = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority(
+						"ROLE_TWO"), new SimpleGrantedAuthority("ROLE_THREE"));
+		Acl acl2 = new AclImpl(identity, new Long(1), aclAuthorizationStrategy2,
+				new ConsoleAuditLogger());
+		// Check access in case the principal has no authorization rights
+		try {
+			aclAuthorizationStrategy2.securityCheck(acl2,
+					AclAuthorizationStrategy.CHANGE_GENERAL);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+		}
+		try {
+			aclAuthorizationStrategy2.securityCheck(acl2,
+					AclAuthorizationStrategy.CHANGE_AUDITING);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+		}
+		try {
+			aclAuthorizationStrategy2.securityCheck(acl2,
+					AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+		}
+	}
 
-    @Test
-    public void testSecurityCheckWithMultipleACEs() throws Exception {
-        // Create a simple authentication with ROLE_GENERAL
-        Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
+	@Test
+	public void testSecurityCheckWithMultipleACEs() throws Exception {
+		// Create a simple authentication with ROLE_GENERAL
+		Authentication auth = new TestingAuthenticationToken("user", "password",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
 
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
-        // Authorization strategy will require a different role for each access
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		// Authorization strategy will require a different role for each access
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
 
-        // Let's give the principal the ADMINISTRATION permission, without
-        // granting access
-        MutableAcl aclFirstDeny = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
-        aclFirstDeny.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false);
+		// Let's give the principal the ADMINISTRATION permission, without
+		// granting access
+		MutableAcl aclFirstDeny = new AclImpl(identity, new Long(1),
+				aclAuthorizationStrategy, new ConsoleAuditLogger());
+		aclFirstDeny.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth),
+				false);
 
-        // The CHANGE_GENERAL test should pass as the principal has ROLE_GENERAL
-        aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_GENERAL);
+		// The CHANGE_GENERAL test should pass as the principal has ROLE_GENERAL
+		aclAuthorizationStrategy.securityCheck(aclFirstDeny,
+				AclAuthorizationStrategy.CHANGE_GENERAL);
 
-        // The CHANGE_AUDITING and CHANGE_OWNERSHIP should fail since the
-        // principal doesn't have these authorities,
-        // nor granting access
-        try {
-            aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING);
-            fail("It should have thrown AccessDeniedException");
-        }
-        catch (AccessDeniedException expected) {
-        }
-        try {
-            aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-            fail("It should have thrown AccessDeniedException");
-        }
-        catch (AccessDeniedException expected) {
-        }
+		// The CHANGE_AUDITING and CHANGE_OWNERSHIP should fail since the
+		// principal doesn't have these authorities,
+		// nor granting access
+		try {
+			aclAuthorizationStrategy.securityCheck(aclFirstDeny,
+					AclAuthorizationStrategy.CHANGE_AUDITING);
+			fail("It should have thrown AccessDeniedException");
+		}
+		catch (AccessDeniedException expected) {
+		}
+		try {
+			aclAuthorizationStrategy.securityCheck(aclFirstDeny,
+					AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+			fail("It should have thrown AccessDeniedException");
+		}
+		catch (AccessDeniedException expected) {
+		}
 
-        // Add granting access to this principal
-        aclFirstDeny.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
-        // and try again for CHANGE_AUDITING - the first ACE's granting flag
-        // (false) will deny this access
-        try {
-            aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING);
-            fail("It should have thrown AccessDeniedException");
-        }
-        catch (AccessDeniedException expected) {
-        }
+		// Add granting access to this principal
+		aclFirstDeny.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth),
+				true);
+		// and try again for CHANGE_AUDITING - the first ACE's granting flag
+		// (false) will deny this access
+		try {
+			aclAuthorizationStrategy.securityCheck(aclFirstDeny,
+					AclAuthorizationStrategy.CHANGE_AUDITING);
+			fail("It should have thrown AccessDeniedException");
+		}
+		catch (AccessDeniedException expected) {
+		}
 
-        // Create another ACL and give the principal the ADMINISTRATION
-        // permission, with granting access
-        MutableAcl aclFirstAllow = new AclImpl(identity, new Long(1), aclAuthorizationStrategy,
-                new ConsoleAuditLogger());
-        aclFirstAllow.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
+		// Create another ACL and give the principal the ADMINISTRATION
+		// permission, with granting access
+		MutableAcl aclFirstAllow = new AclImpl(identity, new Long(1),
+				aclAuthorizationStrategy, new ConsoleAuditLogger());
+		aclFirstAllow.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth),
+				true);
 
-        // The CHANGE_AUDITING test should pass as there is one ACE with
-        // granting access
+		// The CHANGE_AUDITING test should pass as there is one ACE with
+		// granting access
 
-        aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING);
+		aclAuthorizationStrategy.securityCheck(aclFirstAllow,
+				AclAuthorizationStrategy.CHANGE_AUDITING);
 
-        // Add a deny ACE and test again for CHANGE_AUDITING
-        aclFirstAllow.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false);
-        try {
-            aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING);
-            assertTrue(true);
-        }
-        catch (AccessDeniedException notExpected) {
-            fail("It shouldn't have thrown AccessDeniedException");
-        }
+		// Add a deny ACE and test again for CHANGE_AUDITING
+		aclFirstAllow.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth),
+				false);
+		try {
+			aclAuthorizationStrategy.securityCheck(aclFirstAllow,
+					AclAuthorizationStrategy.CHANGE_AUDITING);
+			assertTrue(true);
+		}
+		catch (AccessDeniedException notExpected) {
+			fail("It shouldn't have thrown AccessDeniedException");
+		}
 
-        // Create an ACL with no ACE
-        MutableAcl aclNoACE = new AclImpl(identity, new Long(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
-        try {
-            aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_AUDITING);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-        // and still grant access for CHANGE_GENERAL
-        try {
-            aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_GENERAL);
-            assertTrue(true);
-        }
-        catch (NotFoundException expected) {
-            fail("It shouldn't have thrown NotFoundException");
-        }
-    }
+		// Create an ACL with no ACE
+		MutableAcl aclNoACE = new AclImpl(identity, new Long(1),
+				aclAuthorizationStrategy, new ConsoleAuditLogger());
+		try {
+			aclAuthorizationStrategy.securityCheck(aclNoACE,
+					AclAuthorizationStrategy.CHANGE_AUDITING);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+		// and still grant access for CHANGE_GENERAL
+		try {
+			aclAuthorizationStrategy.securityCheck(aclNoACE,
+					AclAuthorizationStrategy.CHANGE_GENERAL);
+			assertTrue(true);
+		}
+		catch (NotFoundException expected) {
+			fail("It shouldn't have thrown NotFoundException");
+		}
+	}
 
-    @Test
-    public void testSecurityCheckWithInheritableACEs() throws Exception {
-        // Create a simple authentication with ROLE_GENERAL
-        Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
+	@Test
+	public void testSecurityCheckWithInheritableACEs() throws Exception {
+		// Create a simple authentication with ROLE_GENERAL
+		Authentication auth = new TestingAuthenticationToken("user", "password",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
 
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100);
-        // Authorization strategy will require a different role for each access
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority("ROLE_TWO"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100);
+		// Authorization strategy will require a different role for each access
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority(
+						"ROLE_TWO"), new SimpleGrantedAuthority("ROLE_GENERAL"));
 
-        // Let's give the principal an ADMINISTRATION permission, with granting
-        // access
-        MutableAcl parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger());
-        parentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
-        MutableAcl childAcl = new AclImpl(identity, 2, aclAuthorizationStrategy, new ConsoleAuditLogger());
+		// Let's give the principal an ADMINISTRATION permission, with granting
+		// access
+		MutableAcl parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy,
+				new ConsoleAuditLogger());
+		parentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth),
+				true);
+		MutableAcl childAcl = new AclImpl(identity, 2, aclAuthorizationStrategy,
+				new ConsoleAuditLogger());
 
-        // Check against the 'child' acl, which doesn't offer any authorization
-        // rights on CHANGE_OWNERSHIP
-        try {
-            aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-            assertTrue(true);
-        }
+		// Check against the 'child' acl, which doesn't offer any authorization
+		// rights on CHANGE_OWNERSHIP
+		try {
+			aclAuthorizationStrategy.securityCheck(childAcl,
+					AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
 
-        // Link the child with its parent and test again against the
-        // CHANGE_OWNERSHIP right
-        childAcl.setParent(parentAcl);
-        childAcl.setEntriesInheriting(true);
-        try {
-            aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-            assertTrue(true);
-        }
-        catch (NotFoundException expected) {
-            fail("It shouldn't have thrown NotFoundException");
-        }
+		// Link the child with its parent and test again against the
+		// CHANGE_OWNERSHIP right
+		childAcl.setParent(parentAcl);
+		childAcl.setEntriesInheriting(true);
+		try {
+			aclAuthorizationStrategy.securityCheck(childAcl,
+					AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+			assertTrue(true);
+		}
+		catch (NotFoundException expected) {
+			fail("It shouldn't have thrown NotFoundException");
+		}
 
-        // Create a root parent and link it to the middle parent
-        MutableAcl rootParentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy,
-                new ConsoleAuditLogger());
-        parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger());
-        rootParentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true);
-        parentAcl.setEntriesInheriting(true);
-        parentAcl.setParent(rootParentAcl);
-        childAcl.setParent(parentAcl);
-        try {
-            aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-            assertTrue(true);
-        }
-        catch (NotFoundException expected) {
-            fail("It shouldn't have thrown NotFoundException");
-        }
-    }
+		// Create a root parent and link it to the middle parent
+		MutableAcl rootParentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy,
+				new ConsoleAuditLogger());
+		parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy,
+				new ConsoleAuditLogger());
+		rootParentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth),
+				true);
+		parentAcl.setEntriesInheriting(true);
+		parentAcl.setParent(rootParentAcl);
+		childAcl.setParent(parentAcl);
+		try {
+			aclAuthorizationStrategy.securityCheck(childAcl,
+					AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+			assertTrue(true);
+		}
+		catch (NotFoundException expected) {
+			fail("It shouldn't have thrown NotFoundException");
+		}
+	}
 
-    @Test
-    public void testSecurityCheckPrincipalOwner() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_ONE");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
+	@Test
+	public void testSecurityCheckPrincipalOwner() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("user", "password",
+				"ROLE_ONE");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
 
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100);
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100);
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
 
-        Acl acl = new AclImpl(identity, 1, aclAuthorizationStrategy, new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), null, null,
-                false, new PrincipalSid(auth));
-        try {
-            aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL);
-        }
-        catch (AccessDeniedException notExpected) {
-            fail("It shouldn't have thrown AccessDeniedException");
-        }
-        try {
-            aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING);
-            fail("It shouldn't have thrown AccessDeniedException");
-        }
-        catch (NotFoundException expected) {
-        }
-        try {
-            aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP);
-        }
-        catch (AccessDeniedException notExpected) {
-            fail("It shouldn't have thrown AccessDeniedException");
-        }
-    }
+		Acl acl = new AclImpl(identity, 1, aclAuthorizationStrategy,
+				new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), null,
+				null, false, new PrincipalSid(auth));
+		try {
+			aclAuthorizationStrategy.securityCheck(acl,
+					AclAuthorizationStrategy.CHANGE_GENERAL);
+		}
+		catch (AccessDeniedException notExpected) {
+			fail("It shouldn't have thrown AccessDeniedException");
+		}
+		try {
+			aclAuthorizationStrategy.securityCheck(acl,
+					AclAuthorizationStrategy.CHANGE_AUDITING);
+			fail("It shouldn't have thrown AccessDeniedException");
+		}
+		catch (NotFoundException expected) {
+		}
+		try {
+			aclAuthorizationStrategy.securityCheck(acl,
+					AclAuthorizationStrategy.CHANGE_OWNERSHIP);
+		}
+		catch (AccessDeniedException notExpected) {
+			fail("It shouldn't have thrown AccessDeniedException");
+		}
+	}
 }

+ 50 - 48
acl/src/test/java/org/springframework/security/acls/domain/AuditLoggerTests.java

@@ -18,61 +18,63 @@ import org.springframework.security.acls.model.AuditableAccessControlEntry;
  * @author Andrei Stefan
  */
 public class AuditLoggerTests {
-    //~ Instance fields ================================================================================================
-    private PrintStream console;
-    private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-    private ConsoleAuditLogger logger;
-    private AuditableAccessControlEntry ace;
+	// ~ Instance fields
+	// ================================================================================================
+	private PrintStream console;
+	private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+	private ConsoleAuditLogger logger;
+	private AuditableAccessControlEntry ace;
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    @Before
-    public void setUp() throws Exception {
-        logger = new ConsoleAuditLogger();
-        ace = mock(AuditableAccessControlEntry.class);
-        console = System.out;
-        System.setOut(new PrintStream(bytes));
-    }
+	@Before
+	public void setUp() throws Exception {
+		logger = new ConsoleAuditLogger();
+		ace = mock(AuditableAccessControlEntry.class);
+		console = System.out;
+		System.setOut(new PrintStream(bytes));
+	}
 
-    @After
-    public void tearDown() throws Exception {
-        System.setOut(console);
-        bytes.reset();
-    }
+	@After
+	public void tearDown() throws Exception {
+		System.setOut(console);
+		bytes.reset();
+	}
 
-    @Test
-    public void nonAuditableAceIsIgnored() {
-        AccessControlEntry ace = mock(AccessControlEntry.class);
-        logger.logIfNeeded(true, ace);
-        assertEquals(0, bytes.size());
-    }
+	@Test
+	public void nonAuditableAceIsIgnored() {
+		AccessControlEntry ace = mock(AccessControlEntry.class);
+		logger.logIfNeeded(true, ace);
+		assertEquals(0, bytes.size());
+	}
 
-    @Test
-    public void successIsNotLoggedIfAceDoesntRequireSuccessAudit() throws Exception {
-        when(ace.isAuditSuccess()).thenReturn(false);
-        logger.logIfNeeded(true, ace);
-        assertEquals(0, bytes.size());
-    }
+	@Test
+	public void successIsNotLoggedIfAceDoesntRequireSuccessAudit() throws Exception {
+		when(ace.isAuditSuccess()).thenReturn(false);
+		logger.logIfNeeded(true, ace);
+		assertEquals(0, bytes.size());
+	}
 
-    @Test
-    public void successIsLoggedIfAceRequiresSuccessAudit() throws Exception {
-        when(ace.isAuditSuccess()).thenReturn(true);
+	@Test
+	public void successIsLoggedIfAceRequiresSuccessAudit() throws Exception {
+		when(ace.isAuditSuccess()).thenReturn(true);
 
-        logger.logIfNeeded(true, ace);
-        assertTrue(bytes.toString().startsWith("GRANTED due to ACE"));
-    }
+		logger.logIfNeeded(true, ace);
+		assertTrue(bytes.toString().startsWith("GRANTED due to ACE"));
+	}
 
-    @Test
-    public void failureIsntLoggedIfAceDoesntRequireFailureAudit() throws Exception {
-        when(ace.isAuditFailure()).thenReturn(false);
-        logger.logIfNeeded(false, ace);
-        assertEquals(0, bytes.size());
-    }
+	@Test
+	public void failureIsntLoggedIfAceDoesntRequireFailureAudit() throws Exception {
+		when(ace.isAuditFailure()).thenReturn(false);
+		logger.logIfNeeded(false, ace);
+		assertEquals(0, bytes.size());
+	}
 
-    @Test
-    public void failureIsLoggedIfAceRequiresFailureAudit() throws Exception {
-        when(ace.isAuditFailure()).thenReturn(true);
-        logger.logIfNeeded(false, ace);
-        assertTrue(bytes.toString().startsWith("DENIED due to ACE"));
-    }
+	@Test
+	public void failureIsLoggedIfAceRequiresFailureAudit() throws Exception {
+		when(ace.isAuditFailure()).thenReturn(true);
+		logger.logIfNeeded(false, ace);
+		assertTrue(bytes.toString().startsWith("DENIED due to ACE"));
+	}
 }

+ 175 - 171
acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityImplTests.java

@@ -15,175 +15,179 @@ import org.springframework.security.acls.model.ObjectIdentity;
 @SuppressWarnings("unused")
 public class ObjectIdentityImplTests {
 
-    private static final String DOMAIN_CLASS =
-        "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockIdDomainObject";
-
-    //~ Methods ========================================================================================================
-
-    @Test
-    public void constructorsRespectRequiredFields() throws Exception {
-        // Check one-argument constructor required field
-        try {
-            new ObjectIdentityImpl(null);
-            fail("It should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        // Check String-Serializable constructor required field
-        try {
-            new ObjectIdentityImpl("", Long.valueOf(1));
-            fail("It should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        // Check Serializable parameter is not null
-        try {
-            new ObjectIdentityImpl(DOMAIN_CLASS, null);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-
-        // The correct way of using String-Serializable constructor
-        try {
-            new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1));
-        }
-        catch (IllegalArgumentException notExpected) {
-            fail("It shouldn't have thrown IllegalArgumentException");
-        }
-
-        // Check the Class-Serializable constructor
-        try {
-            new ObjectIdentityImpl(MockIdDomainObject.class, null);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void gettersReturnExpectedValues() throws Exception {
-        ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1));
-        assertEquals(Long.valueOf(1), obj.getIdentifier());
-        assertEquals(MockIdDomainObject.class.getName(), obj.getType());
-    }
-
-    @Test
-    public void testGetIdMethodConstraints() throws Exception {
-        // Check the getId() method is present
-        try {
-            new ObjectIdentityImpl("A_STRING_OBJECT");
-            fail("It should have thrown IdentityUnavailableException");
-        }
-        catch (IdentityUnavailableException expected) {
-
-        }
-
-        // getId() should return a non-null value
-        MockIdDomainObject mockId = new MockIdDomainObject();
-        try {
-            new ObjectIdentityImpl(mockId);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-
-        }
-
-        // getId() should return a Serializable object
-        mockId.setId(new MockIdDomainObject());
-        try {
-            new ObjectIdentityImpl(mockId);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-
-        // getId() should return a Serializable object
-        mockId.setId(new Long(100));
-        try {
-            new ObjectIdentityImpl(mockId);
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void constructorRejectsInvalidTypeParameter() throws Exception {
-        new ObjectIdentityImpl("", Long.valueOf(1));
-    }
-
-    @Test
-    public void testEquals() throws Exception {
-        ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1));
-        MockIdDomainObject mockObj = new MockIdDomainObject();
-        mockObj.setId(Long.valueOf(1));
-
-        String string = "SOME_STRING";
-        assertNotSame(obj, string);
-        assertFalse(obj.equals(null));
-        assertFalse(obj.equals("DIFFERENT_OBJECT_TYPE"));
-        assertFalse(obj.equals(new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(2))));
-        assertFalse(obj.equals(new ObjectIdentityImpl(
-                "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockOtherIdDomainObject",
-                Long.valueOf(1))));
-        assertEquals(new ObjectIdentityImpl(DOMAIN_CLASS,Long.valueOf(1)), obj);
-        assertEquals(obj, new ObjectIdentityImpl(mockObj));
-    }
-
-    @Test
-    public void hashcodeIsDifferentForDifferentJavaTypes() throws Exception {
-        ObjectIdentity obj = new ObjectIdentityImpl(Object.class, Long.valueOf(1));
-        ObjectIdentity obj2 = new ObjectIdentityImpl(String.class, Long.valueOf(1));
-        assertFalse(obj.hashCode() == obj2.hashCode());
-    }
-
-    @Test
-    public void longAndIntegerIdsWithSameValueAreEqualAndHaveSameHashcode() {
-        ObjectIdentity obj = new ObjectIdentityImpl(Object.class, new Long(5));
-        ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, Integer.valueOf(5));
-
-        assertEquals(obj, obj2);
-        assertEquals(obj.hashCode(), obj2.hashCode());
-    }
-
-    @Test
-    public void equalStringIdsAreEqualAndHaveSameHashcode() throws Exception {
-        ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000");
-        ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, "1000");
-        assertEquals(obj, obj2);
-        assertEquals(obj.hashCode(), obj2.hashCode());
-    }
-
-    @Test
-    public void stringAndNumericIdsAreNotEqual() throws Exception {
-        ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000");
-        ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, Long.valueOf(1000));
-        assertFalse(obj.equals(obj2));
-    }
-
-    //~ Inner Classes ==================================================================================================
-
-    private class MockIdDomainObject {
-        private Object id;
-
-        public Object getId() {
-            return id;
-        }
-
-        public void setId(Object id) {
-            this.id = id;
-        }
-    }
-
-    private class MockOtherIdDomainObject {
-        private Object id;
-
-        public Object getId() {
-            return id;
-        }
-
-        public void setId(Object id) {
-            this.id = id;
-        }
-    }
+	private static final String DOMAIN_CLASS = "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockIdDomainObject";
+
+	// ~ Methods
+	// ========================================================================================================
+
+	@Test
+	public void constructorsRespectRequiredFields() throws Exception {
+		// Check one-argument constructor required field
+		try {
+			new ObjectIdentityImpl(null);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		// Check String-Serializable constructor required field
+		try {
+			new ObjectIdentityImpl("", Long.valueOf(1));
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		// Check Serializable parameter is not null
+		try {
+			new ObjectIdentityImpl(DOMAIN_CLASS, null);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		// The correct way of using String-Serializable constructor
+		try {
+			new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1));
+		}
+		catch (IllegalArgumentException notExpected) {
+			fail("It shouldn't have thrown IllegalArgumentException");
+		}
+
+		// Check the Class-Serializable constructor
+		try {
+			new ObjectIdentityImpl(MockIdDomainObject.class, null);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test
+	public void gettersReturnExpectedValues() throws Exception {
+		ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1));
+		assertEquals(Long.valueOf(1), obj.getIdentifier());
+		assertEquals(MockIdDomainObject.class.getName(), obj.getType());
+	}
+
+	@Test
+	public void testGetIdMethodConstraints() throws Exception {
+		// Check the getId() method is present
+		try {
+			new ObjectIdentityImpl("A_STRING_OBJECT");
+			fail("It should have thrown IdentityUnavailableException");
+		}
+		catch (IdentityUnavailableException expected) {
+
+		}
+
+		// getId() should return a non-null value
+		MockIdDomainObject mockId = new MockIdDomainObject();
+		try {
+			new ObjectIdentityImpl(mockId);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+
+		}
+
+		// getId() should return a Serializable object
+		mockId.setId(new MockIdDomainObject());
+		try {
+			new ObjectIdentityImpl(mockId);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		// getId() should return a Serializable object
+		mockId.setId(new Long(100));
+		try {
+			new ObjectIdentityImpl(mockId);
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorRejectsInvalidTypeParameter() throws Exception {
+		new ObjectIdentityImpl("", Long.valueOf(1));
+	}
+
+	@Test
+	public void testEquals() throws Exception {
+		ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1));
+		MockIdDomainObject mockObj = new MockIdDomainObject();
+		mockObj.setId(Long.valueOf(1));
+
+		String string = "SOME_STRING";
+		assertNotSame(obj, string);
+		assertFalse(obj.equals(null));
+		assertFalse(obj.equals("DIFFERENT_OBJECT_TYPE"));
+		assertFalse(obj.equals(new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(2))));
+		assertFalse(obj
+				.equals(new ObjectIdentityImpl(
+						"org.springframework.security.acls.domain.ObjectIdentityImplTests$MockOtherIdDomainObject",
+						Long.valueOf(1))));
+		assertEquals(new ObjectIdentityImpl(DOMAIN_CLASS, Long.valueOf(1)), obj);
+		assertEquals(obj, new ObjectIdentityImpl(mockObj));
+	}
+
+	@Test
+	public void hashcodeIsDifferentForDifferentJavaTypes() throws Exception {
+		ObjectIdentity obj = new ObjectIdentityImpl(Object.class, Long.valueOf(1));
+		ObjectIdentity obj2 = new ObjectIdentityImpl(String.class, Long.valueOf(1));
+		assertFalse(obj.hashCode() == obj2.hashCode());
+	}
+
+	@Test
+	public void longAndIntegerIdsWithSameValueAreEqualAndHaveSameHashcode() {
+		ObjectIdentity obj = new ObjectIdentityImpl(Object.class, new Long(5));
+		ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, Integer.valueOf(5));
+
+		assertEquals(obj, obj2);
+		assertEquals(obj.hashCode(), obj2.hashCode());
+	}
+
+	@Test
+	public void equalStringIdsAreEqualAndHaveSameHashcode() throws Exception {
+		ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000");
+		ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, "1000");
+		assertEquals(obj, obj2);
+		assertEquals(obj.hashCode(), obj2.hashCode());
+	}
+
+	@Test
+	public void stringAndNumericIdsAreNotEqual() throws Exception {
+		ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000");
+		ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, Long.valueOf(1000));
+		assertFalse(obj.equals(obj2));
+	}
+
+	// ~ Inner Classes
+	// ==================================================================================================
+
+	private class MockIdDomainObject {
+		private Object id;
+
+		public Object getId() {
+			return id;
+		}
+
+		public void setId(Object id) {
+			this.id = id;
+		}
+	}
+
+	private class MockOtherIdDomainObject {
+		private Object id;
+
+		public Object getId() {
+			return id;
+		}
+
+		public void setId(Object id) {
+			this.id = id;
+		}
+	}
 }

+ 28 - 26
acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImplTests.java

@@ -13,30 +13,32 @@ import junit.framework.TestCase;
  * @author Andrei Stefan
  */
 public class ObjectIdentityRetrievalStrategyImplTests extends TestCase {
-    //~ Methods ========================================================================================================
-
-    public void testObjectIdentityCreation() throws Exception {
-        MockIdDomainObject domain = new MockIdDomainObject();
-        domain.setId(Integer.valueOf(1));
-
-        ObjectIdentityRetrievalStrategy retStrategy = new ObjectIdentityRetrievalStrategyImpl();
-        ObjectIdentity identity = retStrategy.getObjectIdentity(domain);
-
-        assertNotNull(identity);
-        assertEquals(identity, new ObjectIdentityImpl(domain));
-    }
-
-    //~ Inner Classes ==================================================================================================
-    @SuppressWarnings("unused")
-    private class MockIdDomainObject {
-        private Object id;
-
-        public Object getId() {
-            return id;
-        }
-
-        public void setId(Object id) {
-            this.id = id;
-        }
-    }
+	// ~ Methods
+	// ========================================================================================================
+
+	public void testObjectIdentityCreation() throws Exception {
+		MockIdDomainObject domain = new MockIdDomainObject();
+		domain.setId(Integer.valueOf(1));
+
+		ObjectIdentityRetrievalStrategy retStrategy = new ObjectIdentityRetrievalStrategyImpl();
+		ObjectIdentity identity = retStrategy.getObjectIdentity(domain);
+
+		assertNotNull(identity);
+		assertEquals(identity, new ObjectIdentityImpl(domain));
+	}
+
+	// ~ Inner Classes
+	// ==================================================================================================
+	@SuppressWarnings("unused")
+	private class MockIdDomainObject {
+		private Object id;
+
+		public Object getId() {
+			return id;
+		}
+
+		public void setId(Object id) {
+			this.id = id;
+		}
+	}
 }

+ 88 - 70
acl/src/test/java/org/springframework/security/acls/domain/PermissionTests.java

@@ -20,7 +20,6 @@ import org.junit.Before;
 import org.junit.Test;
 import org.springframework.security.acls.model.Permission;
 
-
 /**
  * Tests classes associated with Permission.
  *
@@ -28,73 +27,92 @@ import org.springframework.security.acls.model.Permission;
  */
 public class PermissionTests {
 
-    private DefaultPermissionFactory permissionFactory;
-
-    @Before
-    public void createPermissionfactory() {
-        permissionFactory = new DefaultPermissionFactory();
-    }
-
-    @Test
-    public void basePermissionTest() {
-        Permission p = permissionFactory.buildFromName("WRITE");
-        assertNotNull(p);
-    }
-
-    @Test
-    public void expectedIntegerValues() {
-        assertEquals(1, BasePermission.READ.getMask());
-        assertEquals(16, BasePermission.ADMINISTRATION.getMask());
-        assertEquals(7,
-                new CumulativePermission().set(BasePermission.READ).set(BasePermission.WRITE).set(BasePermission.CREATE)
-                        .getMask());
-        assertEquals(17,
-                new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask());
-    }
-
-    @Test
-    public void fromInteger() {
-        Permission permission = permissionFactory.buildFromMask(7);
-        System.out.println("7 =  " + permission.toString());
-        permission = permissionFactory.buildFromMask(4);
-        System.out.println("4 =  " + permission.toString());
-    }
-
-    @Test
-    public void stringConversion() {
-        permissionFactory.registerPublicPermissions(SpecialPermission.class);
-
-        System.out.println("R =  " + BasePermission.READ.toString());
-        assertEquals("BasePermission[...............................R=1]", BasePermission.READ.toString());
-
-        System.out.println("A =  " + BasePermission.ADMINISTRATION.toString());
-        assertEquals("BasePermission[...........................A....=16]", BasePermission.ADMINISTRATION.toString());
-
-        System.out.println("R =  " + new CumulativePermission().set(BasePermission.READ).toString());
-        assertEquals("CumulativePermission[...............................R=1]",
-                new CumulativePermission().set(BasePermission.READ).toString());
-
-        System.out.println("A =  " + new CumulativePermission().set(SpecialPermission.ENTER).set(BasePermission.ADMINISTRATION).toString());
-        assertEquals("CumulativePermission[..........................EA....=48]",
-                new CumulativePermission().set(SpecialPermission.ENTER).set(BasePermission.ADMINISTRATION).toString());
-
-        System.out.println("RA = "
-                + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
-        assertEquals("CumulativePermission[...........................A...R=17]",
-                new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString());
-
-        System.out.println("R =  "
-                + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
-                .clear(BasePermission.ADMINISTRATION).toString());
-        assertEquals("CumulativePermission[...............................R=1]",
-                new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
-                        .clear(BasePermission.ADMINISTRATION).toString());
-
-        System.out.println("0 =  "
-                + new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
-                .clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
-        assertEquals("CumulativePermission[................................=0]",
-                new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ)
-                        .clear(BasePermission.ADMINISTRATION).clear(BasePermission.READ).toString());
-    }
+	private DefaultPermissionFactory permissionFactory;
+
+	@Before
+	public void createPermissionfactory() {
+		permissionFactory = new DefaultPermissionFactory();
+	}
+
+	@Test
+	public void basePermissionTest() {
+		Permission p = permissionFactory.buildFromName("WRITE");
+		assertNotNull(p);
+	}
+
+	@Test
+	public void expectedIntegerValues() {
+		assertEquals(1, BasePermission.READ.getMask());
+		assertEquals(16, BasePermission.ADMINISTRATION.getMask());
+		assertEquals(
+				7,
+				new CumulativePermission().set(BasePermission.READ)
+						.set(BasePermission.WRITE).set(BasePermission.CREATE).getMask());
+		assertEquals(
+				17,
+				new CumulativePermission().set(BasePermission.READ)
+						.set(BasePermission.ADMINISTRATION).getMask());
+	}
+
+	@Test
+	public void fromInteger() {
+		Permission permission = permissionFactory.buildFromMask(7);
+		System.out.println("7 =  " + permission.toString());
+		permission = permissionFactory.buildFromMask(4);
+		System.out.println("4 =  " + permission.toString());
+	}
+
+	@Test
+	public void stringConversion() {
+		permissionFactory.registerPublicPermissions(SpecialPermission.class);
+
+		System.out.println("R =  " + BasePermission.READ.toString());
+		assertEquals("BasePermission[...............................R=1]",
+				BasePermission.READ.toString());
+
+		System.out.println("A =  " + BasePermission.ADMINISTRATION.toString());
+		assertEquals("BasePermission[...........................A....=16]",
+				BasePermission.ADMINISTRATION.toString());
+
+		System.out.println("R =  "
+				+ new CumulativePermission().set(BasePermission.READ).toString());
+		assertEquals("CumulativePermission[...............................R=1]",
+				new CumulativePermission().set(BasePermission.READ).toString());
+
+		System.out.println("A =  "
+				+ new CumulativePermission().set(SpecialPermission.ENTER)
+						.set(BasePermission.ADMINISTRATION).toString());
+		assertEquals(
+				"CumulativePermission[..........................EA....=48]",
+				new CumulativePermission().set(SpecialPermission.ENTER)
+						.set(BasePermission.ADMINISTRATION).toString());
+
+		System.out.println("RA = "
+				+ new CumulativePermission().set(BasePermission.ADMINISTRATION)
+						.set(BasePermission.READ).toString());
+		assertEquals(
+				"CumulativePermission[...........................A...R=17]",
+				new CumulativePermission().set(BasePermission.ADMINISTRATION)
+						.set(BasePermission.READ).toString());
+
+		System.out.println("R =  "
+				+ new CumulativePermission().set(BasePermission.ADMINISTRATION)
+						.set(BasePermission.READ).clear(BasePermission.ADMINISTRATION)
+						.toString());
+		assertEquals(
+				"CumulativePermission[...............................R=1]",
+				new CumulativePermission().set(BasePermission.ADMINISTRATION)
+						.set(BasePermission.READ).clear(BasePermission.ADMINISTRATION)
+						.toString());
+
+		System.out.println("0 =  "
+				+ new CumulativePermission().set(BasePermission.ADMINISTRATION)
+						.set(BasePermission.READ).clear(BasePermission.ADMINISTRATION)
+						.clear(BasePermission.READ).toString());
+		assertEquals(
+				"CumulativePermission[................................=0]",
+				new CumulativePermission().set(BasePermission.ADMINISTRATION)
+						.set(BasePermission.READ).clear(BasePermission.ADMINISTRATION)
+						.clear(BasePermission.READ).toString());
+	}
 }

+ 5 - 6
acl/src/test/java/org/springframework/security/acls/domain/SpecialPermission.java

@@ -16,17 +16,16 @@ package org.springframework.security.acls.domain;
 
 import org.springframework.security.acls.model.Permission;
 
-
 /**
  * A test permission.
  *
  * @author Ben Alex
  */
 public class SpecialPermission extends BasePermission {
-    public static final Permission ENTER = new SpecialPermission(1 << 5, 'E'); // 32
-    public static final Permission LEAVE = new SpecialPermission(1 << 6, 'L');
+	public static final Permission ENTER = new SpecialPermission(1 << 5, 'E'); // 32
+	public static final Permission LEAVE = new SpecialPermission(1 << 6, 'L');
 
-    protected SpecialPermission(int mask, char code) {
-        super(mask, code);
-    }
+	protected SpecialPermission(int mask, char code) {
+		super(mask, code);
+	}
 }

+ 330 - 279
acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java

@@ -29,284 +29,335 @@ import java.util.*;
  */
 public class BasicLookupStrategyTests {
 
-    private static final Sid BEN_SID = new PrincipalSid("ben");
-    private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
-
-    //~ Instance fields ================================================================================================
-
-    private static JdbcTemplate jdbcTemplate;
-    private BasicLookupStrategy strategy;
-    private static SingleConnectionDataSource dataSource;
-    private static CacheManager cacheManager;
-
-    //~ Methods ========================================================================================================
-    @BeforeClass
-    public static void initCacheManaer() {
-        cacheManager = CacheManager.create();
-        cacheManager.addCache(new Cache("basiclookuptestcache", 500, false, false, 30, 30));
-    }
-
-    @BeforeClass
-    public static void createDatabase() throws Exception {
-        dataSource = new SingleConnectionDataSource("jdbc:hsqldb:mem:lookupstrategytest", "sa", "", true);
-        dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
-        jdbcTemplate = new JdbcTemplate(dataSource);
-
-        Resource resource = new ClassPathResource("createAclSchema.sql");
-        String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
-        jdbcTemplate.execute(sql);
-    }
-
-    @AfterClass
-    public static void dropDatabase() throws Exception {
-        dataSource.destroy();
-    }
-
-    @AfterClass
-    public static void shutdownCacheManager() {
-        cacheManager.removalAll();
-        cacheManager.shutdown();
-    }
-
-    @Before
-    public void populateDatabase() {
-        String query = "INSERT INTO acl_sid(ID,PRINCIPAL,SID) VALUES (1,1,'ben');"
-                + "INSERT INTO acl_class(ID,CLASS) VALUES (2,'" + TARGET_CLASS + "');"
-                + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (1,2,100,null,1,1);"
-                + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (2,2,101,1,1,1);"
-                + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (3,2,102,2,1,1);"
-                + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (1,1,0,1,1,1,0,0);"
-                + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (2,1,1,1,2,0,0,0);"
-                + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (3,2,0,1,8,1,0,0);"
-                + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (4,3,0,1,8,0,0,0);";
-        jdbcTemplate.execute(query);
-    }
-
-    @Before
-    public void initializeBeans() {
-        EhCacheBasedAclCache cache = new EhCacheBasedAclCache(getCache(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
-        AclAuthorizationStrategy authorizationStrategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
-        strategy = new BasicLookupStrategy(dataSource, cache, authorizationStrategy,
-                new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()));
-        strategy.setPermissionFactory(new DefaultPermissionFactory());
-    }
-
-    @After
-    public void emptyDatabase() {
-        String query = "DELETE FROM acl_entry;" + "DELETE FROM acl_object_identity WHERE ID = 7;"
-                + "DELETE FROM acl_object_identity WHERE ID = 6;" + "DELETE FROM acl_object_identity WHERE ID = 5;"
-                + "DELETE FROM acl_object_identity WHERE ID = 4;" + "DELETE FROM acl_object_identity WHERE ID = 3;"
-                + "DELETE FROM acl_object_identity WHERE ID = 2;" + "DELETE FROM acl_object_identity WHERE ID = 1;"
-                + "DELETE FROM acl_class;" + "DELETE FROM acl_sid;";
-        jdbcTemplate.execute(query);
-    }
-
-    private Ehcache getCache() {
-        Ehcache cache = cacheManager.getCache("basiclookuptestcache");
-        cache.removeAll();
-        return cache;
-    }
-
-    @Test
-    public void testAclsRetrievalWithDefaultBatchSize() throws Exception {
-        ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
-        ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(101));
-        // Deliberately use an integer for the child, to reproduce bug report in SEC-819
-        ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(102));
-
-        Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-        checkEntries(topParentOid, middleParentOid, childOid, map);
-    }
-
-    @Test
-    public void testAclsRetrievalFromCacheOnly() throws Exception {
-        ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(100));
-        ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(101));
-        ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
-
-        // Objects were put in cache
-        strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-
-        // Let's empty the database to force acls retrieval from cache
-        emptyDatabase();
-        Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-
-        checkEntries(topParentOid, middleParentOid, childOid, map);
-    }
-
-    @Test
-    public void testAclsRetrievalWithCustomBatchSize() throws Exception {
-        ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
-        ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(101));
-        ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
-
-        // Set a batch size to allow multiple database queries in order to retrieve all acls
-        this.strategy.setBatchSize(1);
-        Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null);
-        checkEntries(topParentOid, middleParentOid, childOid, map);
-    }
-
-    private void checkEntries(ObjectIdentity topParentOid, ObjectIdentity middleParentOid, ObjectIdentity childOid,
-            Map<ObjectIdentity, Acl> map) throws Exception {
-        Assert.assertEquals(3, map.size());
-
-        MutableAcl topParent = (MutableAcl) map.get(topParentOid);
-        MutableAcl middleParent = (MutableAcl) map.get(middleParentOid);
-        MutableAcl child = (MutableAcl) map.get(childOid);
-
-        // Check the retrieved versions has IDs
-        Assert.assertNotNull(topParent.getId());
-        Assert.assertNotNull(middleParent.getId());
-        Assert.assertNotNull(child.getId());
-
-        // Check their parents were correctly retrieved
-        Assert.assertNull(topParent.getParentAcl());
-        Assert.assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
-        Assert.assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
-
-        // Check their ACEs were correctly retrieved
-        Assert.assertEquals(2, topParent.getEntries().size());
-        Assert.assertEquals(1, middleParent.getEntries().size());
-        Assert.assertEquals(1, child.getEntries().size());
-
-        // Check object identities were correctly retrieved
-        Assert.assertEquals(topParentOid, topParent.getObjectIdentity());
-        Assert.assertEquals(middleParentOid, middleParent.getObjectIdentity());
-        Assert.assertEquals(childOid, child.getObjectIdentity());
-
-        // Check each entry
-        Assert.assertTrue(topParent.isEntriesInheriting());
-        Assert.assertEquals(topParent.getId(), Long.valueOf(1));
-        Assert.assertEquals(topParent.getOwner(), new PrincipalSid("ben"));
-        Assert.assertEquals(topParent.getEntries().get(0).getId(), Long.valueOf(1));
-        Assert.assertEquals(topParent.getEntries().get(0).getPermission(), BasePermission.READ);
-        Assert.assertEquals(topParent.getEntries().get(0).getSid(), new PrincipalSid("ben"));
-        Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditFailure());
-        Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditSuccess());
-        Assert.assertTrue((topParent.getEntries().get(0)).isGranting());
-
-        Assert.assertEquals(topParent.getEntries().get(1).getId(), Long.valueOf(2));
-        Assert.assertEquals(topParent.getEntries().get(1).getPermission(), BasePermission.WRITE);
-        Assert.assertEquals(topParent.getEntries().get(1).getSid(), new PrincipalSid("ben"));
-        Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditFailure());
-        Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditSuccess());
-        Assert.assertFalse(topParent.getEntries().get(1).isGranting());
-
-        Assert.assertTrue(middleParent.isEntriesInheriting());
-        Assert.assertEquals(middleParent.getId(), Long.valueOf(2));
-        Assert.assertEquals(middleParent.getOwner(), new PrincipalSid("ben"));
-        Assert.assertEquals(middleParent.getEntries().get(0).getId(), Long.valueOf(3));
-        Assert.assertEquals(middleParent.getEntries().get(0).getPermission(), BasePermission.DELETE);
-        Assert.assertEquals(middleParent.getEntries().get(0).getSid(), new PrincipalSid("ben"));
-        Assert.assertFalse(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditFailure());
-        Assert.assertFalse(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditSuccess());
-        Assert.assertTrue(middleParent.getEntries().get(0).isGranting());
-
-        Assert.assertTrue(child.isEntriesInheriting());
-        Assert.assertEquals(child.getId(), Long.valueOf(3));
-        Assert.assertEquals(child.getOwner(), new PrincipalSid("ben"));
-        Assert.assertEquals(child.getEntries().get(0).getId(), Long.valueOf(4));
-        Assert.assertEquals(child.getEntries().get(0).getPermission(), BasePermission.DELETE);
-        Assert.assertEquals(child.getEntries().get(0).getSid(), new PrincipalSid("ben"));
-        Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditFailure());
-        Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditSuccess());
-        Assert.assertFalse((child.getEntries().get(0)).isGranting());
-    }
-
-    @Test
-    public void testAllParentsAreRetrievedWhenChildIsLoaded() throws Exception {
-        String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,103,1,1,1);";
-        jdbcTemplate.execute(query);
-
-        ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
-        ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
-        ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102));
-        ObjectIdentity middleParent2Oid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(103));
-
-        // Retrieve the child
-        Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(Arrays.asList(childOid), null);
-
-        // Check that the child and all its parents were retrieved
-        Assert.assertNotNull(map.get(childOid));
-        Assert.assertEquals(childOid, map.get(childOid).getObjectIdentity());
-        Assert.assertNotNull(map.get(middleParentOid));
-        Assert.assertEquals(middleParentOid, map.get(middleParentOid).getObjectIdentity());
-        Assert.assertNotNull(map.get(topParentOid));
-        Assert.assertEquals(topParentOid, map.get(topParentOid).getObjectIdentity());
-
-        // The second parent shouldn't have been retrieved
-        Assert.assertNull(map.get(middleParent2Oid));
-    }
-
-    /**
-     * Test created from SEC-590.
-     */
-    @Test
-    public void testReadAllObjectIdentitiesWhenLastElementIsAlreadyCached() throws Exception {
-        String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,1,1);"
-                + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (5,2,105,4,1,1);"
-                + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,106,4,1,1);"
-                + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (7,2,107,5,1,1);"
-                + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (5,4,0,1,1,1,0,0)";
-        jdbcTemplate.execute(query);
-
-        ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
-        ObjectIdentity parent1Oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(105));
-        ObjectIdentity parent2Oid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(106));
-        ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(107));
-
-        // First lookup only child, thus populating the cache with grandParent, parent1 and child
-        List<Permission> checkPermission = Arrays.asList(BasePermission.READ);
-        List<Sid> sids = Arrays.asList(BEN_SID);
-        List<ObjectIdentity> childOids = Arrays.asList(childOid);
-
-        strategy.setBatchSize(6);
-        Map<ObjectIdentity, Acl> foundAcls = strategy.readAclsById(childOids, sids);
-
-        Acl foundChildAcl = foundAcls.get(childOid);
-        Assert.assertNotNull(foundChildAcl);
-        Assert.assertTrue(foundChildAcl.isGranted(checkPermission, sids, false));
-
-        // Search for object identities has to be done in the following order: last element have to be one which
-        // is already in cache and the element before it must not be stored in cache
-        List<ObjectIdentity> allOids = Arrays.asList(grandParentOid, parent1Oid, parent2Oid, childOid);
-        try {
-            foundAcls = strategy.readAclsById(allOids, sids);
-            Assert.assertTrue(true);
-        } catch (NotFoundException notExpected) {
-            Assert.fail("It shouldn't have thrown NotFoundException");
-        }
-
-        Acl foundParent2Acl = foundAcls.get(parent2Oid);
-        Assert.assertNotNull(foundParent2Acl);
-        Assert.assertTrue(foundParent2Acl.isGranted(checkPermission, sids, false));
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void nullOwnerIsNotSupported() {
-        String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,null,1);";
-
-        jdbcTemplate.execute(query);
-
-        ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
-
-        strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
-    }
-
-    @Test
-    public void testCreatePrincipalSid() {
-        Sid result = strategy.createSid(true, "sid");
-
-        Assert.assertEquals(PrincipalSid.class, result.getClass());
-        Assert.assertEquals("sid", ((PrincipalSid)result).getPrincipal());
-    }
-
-    @Test
-    public void testCreateGrantedAuthority() {
-        Sid result = strategy.createSid(false, "sid");
-
-        Assert.assertEquals(GrantedAuthoritySid.class, result.getClass());
-        Assert.assertEquals("sid", ((GrantedAuthoritySid)result).getGrantedAuthority());
-    }
+	private static final Sid BEN_SID = new PrincipalSid("ben");
+	private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private static JdbcTemplate jdbcTemplate;
+	private BasicLookupStrategy strategy;
+	private static SingleConnectionDataSource dataSource;
+	private static CacheManager cacheManager;
+
+	// ~ Methods
+	// ========================================================================================================
+	@BeforeClass
+	public static void initCacheManaer() {
+		cacheManager = CacheManager.create();
+		cacheManager
+				.addCache(new Cache("basiclookuptestcache", 500, false, false, 30, 30));
+	}
+
+	@BeforeClass
+	public static void createDatabase() throws Exception {
+		dataSource = new SingleConnectionDataSource("jdbc:hsqldb:mem:lookupstrategytest",
+				"sa", "", true);
+		dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
+		jdbcTemplate = new JdbcTemplate(dataSource);
+
+		Resource resource = new ClassPathResource("createAclSchema.sql");
+		String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
+		jdbcTemplate.execute(sql);
+	}
+
+	@AfterClass
+	public static void dropDatabase() throws Exception {
+		dataSource.destroy();
+	}
+
+	@AfterClass
+	public static void shutdownCacheManager() {
+		cacheManager.removalAll();
+		cacheManager.shutdown();
+	}
+
+	@Before
+	public void populateDatabase() {
+		String query = "INSERT INTO acl_sid(ID,PRINCIPAL,SID) VALUES (1,1,'ben');"
+				+ "INSERT INTO acl_class(ID,CLASS) VALUES (2,'"
+				+ TARGET_CLASS
+				+ "');"
+				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (1,2,100,null,1,1);"
+				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (2,2,101,1,1,1);"
+				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (3,2,102,2,1,1);"
+				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (1,1,0,1,1,1,0,0);"
+				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (2,1,1,1,2,0,0,0);"
+				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (3,2,0,1,8,1,0,0);"
+				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (4,3,0,1,8,0,0,0);";
+		jdbcTemplate.execute(query);
+	}
+
+	@Before
+	public void initializeBeans() {
+		EhCacheBasedAclCache cache = new EhCacheBasedAclCache(getCache(),
+				new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
+				new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
+		AclAuthorizationStrategy authorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
+		strategy = new BasicLookupStrategy(dataSource, cache, authorizationStrategy,
+				new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()));
+		strategy.setPermissionFactory(new DefaultPermissionFactory());
+	}
+
+	@After
+	public void emptyDatabase() {
+		String query = "DELETE FROM acl_entry;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 7;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 6;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 5;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 4;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 3;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 2;"
+				+ "DELETE FROM acl_object_identity WHERE ID = 1;"
+				+ "DELETE FROM acl_class;" + "DELETE FROM acl_sid;";
+		jdbcTemplate.execute(query);
+	}
+
+	private Ehcache getCache() {
+		Ehcache cache = cacheManager.getCache("basiclookuptestcache");
+		cache.removeAll();
+		return cache;
+	}
+
+	@Test
+	public void testAclsRetrievalWithDefaultBatchSize() throws Exception {
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(
+				101));
+		// Deliberately use an integer for the child, to reproduce bug report in SEC-819
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Integer.valueOf(102));
+
+		Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(
+				Arrays.asList(topParentOid, middleParentOid, childOid), null);
+		checkEntries(topParentOid, middleParentOid, childOid, map);
+	}
+
+	@Test
+	public void testAclsRetrievalFromCacheOnly() throws Exception {
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Integer.valueOf(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(
+				101));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
+
+		// Objects were put in cache
+		strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid),
+				null);
+
+		// Let's empty the database to force acls retrieval from cache
+		emptyDatabase();
+		Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(
+				Arrays.asList(topParentOid, middleParentOid, childOid), null);
+
+		checkEntries(topParentOid, middleParentOid, childOid, map);
+	}
+
+	@Test
+	public void testAclsRetrievalWithCustomBatchSize() throws Exception {
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Integer.valueOf(101));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, new Long(102));
+
+		// Set a batch size to allow multiple database queries in order to retrieve all
+		// acls
+		this.strategy.setBatchSize(1);
+		Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(
+				Arrays.asList(topParentOid, middleParentOid, childOid), null);
+		checkEntries(topParentOid, middleParentOid, childOid, map);
+	}
+
+	private void checkEntries(ObjectIdentity topParentOid,
+			ObjectIdentity middleParentOid, ObjectIdentity childOid,
+			Map<ObjectIdentity, Acl> map) throws Exception {
+		Assert.assertEquals(3, map.size());
+
+		MutableAcl topParent = (MutableAcl) map.get(topParentOid);
+		MutableAcl middleParent = (MutableAcl) map.get(middleParentOid);
+		MutableAcl child = (MutableAcl) map.get(childOid);
+
+		// Check the retrieved versions has IDs
+		Assert.assertNotNull(topParent.getId());
+		Assert.assertNotNull(middleParent.getId());
+		Assert.assertNotNull(child.getId());
+
+		// Check their parents were correctly retrieved
+		Assert.assertNull(topParent.getParentAcl());
+		Assert.assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
+		Assert.assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
+
+		// Check their ACEs were correctly retrieved
+		Assert.assertEquals(2, topParent.getEntries().size());
+		Assert.assertEquals(1, middleParent.getEntries().size());
+		Assert.assertEquals(1, child.getEntries().size());
+
+		// Check object identities were correctly retrieved
+		Assert.assertEquals(topParentOid, topParent.getObjectIdentity());
+		Assert.assertEquals(middleParentOid, middleParent.getObjectIdentity());
+		Assert.assertEquals(childOid, child.getObjectIdentity());
+
+		// Check each entry
+		Assert.assertTrue(topParent.isEntriesInheriting());
+		Assert.assertEquals(topParent.getId(), Long.valueOf(1));
+		Assert.assertEquals(topParent.getOwner(), new PrincipalSid("ben"));
+		Assert.assertEquals(topParent.getEntries().get(0).getId(), Long.valueOf(1));
+		Assert.assertEquals(topParent.getEntries().get(0).getPermission(),
+				BasePermission.READ);
+		Assert.assertEquals(topParent.getEntries().get(0).getSid(), new PrincipalSid(
+				"ben"));
+		Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(0))
+				.isAuditFailure());
+		Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(0))
+				.isAuditSuccess());
+		Assert.assertTrue((topParent.getEntries().get(0)).isGranting());
+
+		Assert.assertEquals(topParent.getEntries().get(1).getId(), Long.valueOf(2));
+		Assert.assertEquals(topParent.getEntries().get(1).getPermission(),
+				BasePermission.WRITE);
+		Assert.assertEquals(topParent.getEntries().get(1).getSid(), new PrincipalSid(
+				"ben"));
+		Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(1))
+				.isAuditFailure());
+		Assert.assertFalse(((AuditableAccessControlEntry) topParent.getEntries().get(1))
+				.isAuditSuccess());
+		Assert.assertFalse(topParent.getEntries().get(1).isGranting());
+
+		Assert.assertTrue(middleParent.isEntriesInheriting());
+		Assert.assertEquals(middleParent.getId(), Long.valueOf(2));
+		Assert.assertEquals(middleParent.getOwner(), new PrincipalSid("ben"));
+		Assert.assertEquals(middleParent.getEntries().get(0).getId(), Long.valueOf(3));
+		Assert.assertEquals(middleParent.getEntries().get(0).getPermission(),
+				BasePermission.DELETE);
+		Assert.assertEquals(middleParent.getEntries().get(0).getSid(), new PrincipalSid(
+				"ben"));
+		Assert.assertFalse(((AuditableAccessControlEntry) middleParent.getEntries()
+				.get(0)).isAuditFailure());
+		Assert.assertFalse(((AuditableAccessControlEntry) middleParent.getEntries()
+				.get(0)).isAuditSuccess());
+		Assert.assertTrue(middleParent.getEntries().get(0).isGranting());
+
+		Assert.assertTrue(child.isEntriesInheriting());
+		Assert.assertEquals(child.getId(), Long.valueOf(3));
+		Assert.assertEquals(child.getOwner(), new PrincipalSid("ben"));
+		Assert.assertEquals(child.getEntries().get(0).getId(), Long.valueOf(4));
+		Assert.assertEquals(child.getEntries().get(0).getPermission(),
+				BasePermission.DELETE);
+		Assert.assertEquals(child.getEntries().get(0).getSid(), new PrincipalSid("ben"));
+		Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries().get(0))
+				.isAuditFailure());
+		Assert.assertFalse(((AuditableAccessControlEntry) child.getEntries().get(0))
+				.isAuditSuccess());
+		Assert.assertFalse((child.getEntries().get(0)).isGranting());
+	}
+
+	@Test
+	public void testAllParentsAreRetrievedWhenChildIsLoaded() throws Exception {
+		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,103,1,1,1);";
+		jdbcTemplate.execute(query);
+
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(100));
+		ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(101));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102));
+		ObjectIdentity middleParent2Oid = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(103));
+
+		// Retrieve the child
+		Map<ObjectIdentity, Acl> map = this.strategy.readAclsById(
+				Arrays.asList(childOid), null);
+
+		// Check that the child and all its parents were retrieved
+		Assert.assertNotNull(map.get(childOid));
+		Assert.assertEquals(childOid, map.get(childOid).getObjectIdentity());
+		Assert.assertNotNull(map.get(middleParentOid));
+		Assert.assertEquals(middleParentOid, map.get(middleParentOid).getObjectIdentity());
+		Assert.assertNotNull(map.get(topParentOid));
+		Assert.assertEquals(topParentOid, map.get(topParentOid).getObjectIdentity());
+
+		// The second parent shouldn't have been retrieved
+		Assert.assertNull(map.get(middleParent2Oid));
+	}
+
+	/**
+	 * Test created from SEC-590.
+	 */
+	@Test
+	public void testReadAllObjectIdentitiesWhenLastElementIsAlreadyCached()
+			throws Exception {
+		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,1,1);"
+				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (5,2,105,4,1,1);"
+				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,106,4,1,1);"
+				+ "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (7,2,107,5,1,1);"
+				+ "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (5,4,0,1,1,1,0,0)";
+		jdbcTemplate.execute(query);
+
+		ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+				new Long(104));
+		ObjectIdentity parent1Oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(105));
+		ObjectIdentity parent2Oid = new ObjectIdentityImpl(TARGET_CLASS,
+				Integer.valueOf(106));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Integer.valueOf(107));
+
+		// First lookup only child, thus populating the cache with grandParent, parent1
+		// and child
+		List<Permission> checkPermission = Arrays.asList(BasePermission.READ);
+		List<Sid> sids = Arrays.asList(BEN_SID);
+		List<ObjectIdentity> childOids = Arrays.asList(childOid);
+
+		strategy.setBatchSize(6);
+		Map<ObjectIdentity, Acl> foundAcls = strategy.readAclsById(childOids, sids);
+
+		Acl foundChildAcl = foundAcls.get(childOid);
+		Assert.assertNotNull(foundChildAcl);
+		Assert.assertTrue(foundChildAcl.isGranted(checkPermission, sids, false));
+
+		// Search for object identities has to be done in the following order: last
+		// element have to be one which
+		// is already in cache and the element before it must not be stored in cache
+		List<ObjectIdentity> allOids = Arrays.asList(grandParentOid, parent1Oid,
+				parent2Oid, childOid);
+		try {
+			foundAcls = strategy.readAclsById(allOids, sids);
+			Assert.assertTrue(true);
+		}
+		catch (NotFoundException notExpected) {
+			Assert.fail("It shouldn't have thrown NotFoundException");
+		}
+
+		Acl foundParent2Acl = foundAcls.get(parent2Oid);
+		Assert.assertNotNull(foundParent2Acl);
+		Assert.assertTrue(foundParent2Acl.isGranted(checkPermission, sids, false));
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void nullOwnerIsNotSupported() {
+		String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,2,104,null,null,1);";
+
+		jdbcTemplate.execute(query);
+
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, new Long(104));
+
+		strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID));
+	}
+
+	@Test
+	public void testCreatePrincipalSid() {
+		Sid result = strategy.createSid(true, "sid");
+
+		Assert.assertEquals(PrincipalSid.class, result.getClass());
+		Assert.assertEquals("sid", ((PrincipalSid) result).getPrincipal());
+	}
+
+	@Test
+	public void testCreateGrantedAuthority() {
+		Sid result = strategy.createSid(false, "sid");
+
+		Assert.assertEquals(GrantedAuthoritySid.class, result.getClass());
+		Assert.assertEquals("sid", ((GrantedAuthoritySid) result).getGrantedAuthority());
+	}
 
 }

+ 9 - 9
acl/src/test/java/org/springframework/security/acls/jdbc/DatabaseSeeder.java

@@ -25,21 +25,21 @@ import java.io.IOException;
 
 import javax.sql.DataSource;
 
-
 /**
  * Seeds the database for {@link JdbcMutableAclServiceTests}.
  *
  * @author Ben Alex
  */
 public class DatabaseSeeder {
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    public DatabaseSeeder(DataSource dataSource, Resource resource) throws IOException {
-        Assert.notNull(dataSource, "dataSource required");
-        Assert.notNull(resource, "resource required");
+	public DatabaseSeeder(DataSource dataSource, Resource resource) throws IOException {
+		Assert.notNull(dataSource, "dataSource required");
+		Assert.notNull(resource, "resource required");
 
-        JdbcTemplate template = new JdbcTemplate(dataSource);
-        String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
-        template.execute(sql);
-    }
+		JdbcTemplate template = new JdbcTemplate(dataSource);
+		String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
+		template.execute(sql);
+	}
 }

+ 220 - 204
acl/src/test/java/org/springframework/security/acls/jdbc/EhCacheBasedAclCacheTests.java

@@ -48,223 +48,239 @@ import org.springframework.util.ReflectionUtils;
  */
 @RunWith(MockitoJUnitRunner.class)
 public class EhCacheBasedAclCacheTests {
-    private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
-
-    @Mock
-    private Ehcache cache;
-    @Captor
-    private ArgumentCaptor<Element> element;
-
-    private EhCacheBasedAclCache myCache;
-
-    private MutableAcl acl;
-
-    @Before
-    public void setup() {
-        myCache = new EhCacheBasedAclCache(cache, new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
-
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
-
-        acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy, new ConsoleAuditLogger());
-    }
-
-    @After
-    public void cleanup() {
-        SecurityContextHolder.clearContext();
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void constructorRejectsNullParameters() throws Exception {
-        new EhCacheBasedAclCache(null, new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
-    }
-
-    @Test
-    public void methodsRejectNullParameters() throws Exception {
-        try {
-            Serializable id = null;
-            myCache.evictFromCache(id);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            ObjectIdentity obj = null;
-            myCache.evictFromCache(obj);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            Serializable id = null;
-            myCache.getFromCache(id);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            ObjectIdentity obj = null;
-            myCache.getFromCache(obj);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            MutableAcl acl = null;
-            myCache.putInCache(acl);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-    }
-
-    // SEC-527
-    @Test
-    public void testDiskSerializationOfMutableAclObjectInstance() throws Exception {
-        // Serialization test
-        File file = File.createTempFile("SEC_TEST", ".object");
-        FileOutputStream fos = new FileOutputStream(file);
-        ObjectOutputStream oos = new ObjectOutputStream(fos);
-        oos.writeObject(acl);
-        oos.close();
-
-        FileInputStream fis = new FileInputStream(file);
-        ObjectInputStream ois = new ObjectInputStream(fis);
-        MutableAcl retrieved = (MutableAcl) ois.readObject();
-        ois.close();
-
-        assertEquals(acl, retrieved);
-
-        Object retrieved1 = FieldUtils.getProtectedFieldValue("aclAuthorizationStrategy", retrieved);
-        assertEquals(null, retrieved1);
-
-        Object retrieved2 = FieldUtils.getProtectedFieldValue("permissionGrantingStrategy", retrieved);
-        assertEquals(null, retrieved2);
-    }
-
-    @Test
-    public void clearCache() throws Exception {
-        myCache.clearCache();
-
-        verify(cache).removeAll();
-    }
-
-    @Test
-    public void putInCache() throws Exception {
-        myCache.putInCache(acl);
-
-        verify(cache, times(2)).put(element.capture());
-        assertThat(element.getValue().getKey()).isEqualTo(acl.getId());
-        assertThat(element.getValue().getObjectValue()).isEqualTo(acl);
-        assertThat(element.getAllValues().get(0).getKey()).isEqualTo(acl.getObjectIdentity());
-        assertThat(element.getAllValues().get(0).getObjectValue()).isEqualTo(acl);
-    }
-
-    @Test
-    public void putInCacheAclWithParent() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-
-        ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(2));
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
-        MutableAcl parentAcl = new AclImpl(identityParent, Long.valueOf(2), aclAuthorizationStrategy, new ConsoleAuditLogger());
-        acl.setParent(parentAcl);
-
-        myCache.putInCache(acl);
-
-        verify(cache, times(4)).put(element.capture());
-
-        List<Element> allValues = element.getAllValues();
-
-        assertThat(allValues.get(0).getKey()).isEqualTo(parentAcl.getObjectIdentity());
-        assertThat(allValues.get(0).getObjectValue()).isEqualTo(parentAcl);
-
-        assertThat(allValues.get(1).getKey()).isEqualTo(parentAcl.getId());
-        assertThat(allValues.get(1).getObjectValue()).isEqualTo(parentAcl);
-
+	private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
+
+	@Mock
+	private Ehcache cache;
+	@Captor
+	private ArgumentCaptor<Element> element;
+
+	private EhCacheBasedAclCache myCache;
+
+	private MutableAcl acl;
+
+	@Before
+	public void setup() {
+		myCache = new EhCacheBasedAclCache(cache, new DefaultPermissionGrantingStrategy(
+				new ConsoleAuditLogger()), new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_USER")));
+
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
+
+		acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy,
+				new ConsoleAuditLogger());
+	}
+
+	@After
+	public void cleanup() {
+		SecurityContextHolder.clearContext();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorRejectsNullParameters() throws Exception {
+		new EhCacheBasedAclCache(null, new DefaultPermissionGrantingStrategy(
+				new ConsoleAuditLogger()), new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_USER")));
+	}
+
+	@Test
+	public void methodsRejectNullParameters() throws Exception {
+		try {
+			Serializable id = null;
+			myCache.evictFromCache(id);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+
+		try {
+			ObjectIdentity obj = null;
+			myCache.evictFromCache(obj);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+
+		try {
+			Serializable id = null;
+			myCache.getFromCache(id);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+
+		try {
+			ObjectIdentity obj = null;
+			myCache.getFromCache(obj);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+
+		try {
+			MutableAcl acl = null;
+			myCache.putInCache(acl);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+	}
+
+	// SEC-527
+	@Test
+	public void testDiskSerializationOfMutableAclObjectInstance() throws Exception {
+		// Serialization test
+		File file = File.createTempFile("SEC_TEST", ".object");
+		FileOutputStream fos = new FileOutputStream(file);
+		ObjectOutputStream oos = new ObjectOutputStream(fos);
+		oos.writeObject(acl);
+		oos.close();
+
+		FileInputStream fis = new FileInputStream(file);
+		ObjectInputStream ois = new ObjectInputStream(fis);
+		MutableAcl retrieved = (MutableAcl) ois.readObject();
+		ois.close();
+
+		assertEquals(acl, retrieved);
+
+		Object retrieved1 = FieldUtils.getProtectedFieldValue("aclAuthorizationStrategy",
+				retrieved);
+		assertEquals(null, retrieved1);
+
+		Object retrieved2 = FieldUtils.getProtectedFieldValue(
+				"permissionGrantingStrategy", retrieved);
+		assertEquals(null, retrieved2);
+	}
+
+	@Test
+	public void clearCache() throws Exception {
+		myCache.clearCache();
+
+		verify(cache).removeAll();
+	}
+
+	@Test
+	public void putInCache() throws Exception {
+		myCache.putInCache(acl);
+
+		verify(cache, times(2)).put(element.capture());
+		assertThat(element.getValue().getKey()).isEqualTo(acl.getId());
+		assertThat(element.getValue().getObjectValue()).isEqualTo(acl);
+		assertThat(element.getAllValues().get(0).getKey()).isEqualTo(
+				acl.getObjectIdentity());
+		assertThat(element.getAllValues().get(0).getObjectValue()).isEqualTo(acl);
+	}
+
+	@Test
+	public void putInCacheAclWithParent() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("user", "password",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+
+		ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(2));
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
+		MutableAcl parentAcl = new AclImpl(identityParent, Long.valueOf(2),
+				aclAuthorizationStrategy, new ConsoleAuditLogger());
+		acl.setParent(parentAcl);
+
+		myCache.putInCache(acl);
+
+		verify(cache, times(4)).put(element.capture());
+
+		List<Element> allValues = element.getAllValues();
+
+		assertThat(allValues.get(0).getKey()).isEqualTo(parentAcl.getObjectIdentity());
+		assertThat(allValues.get(0).getObjectValue()).isEqualTo(parentAcl);
+
+		assertThat(allValues.get(1).getKey()).isEqualTo(parentAcl.getId());
+		assertThat(allValues.get(1).getObjectValue()).isEqualTo(parentAcl);
+
+		assertThat(allValues.get(2).getKey()).isEqualTo(acl.getObjectIdentity());
+		assertThat(allValues.get(2).getObjectValue()).isEqualTo(acl);
+
+		assertThat(allValues.get(3).getKey()).isEqualTo(acl.getId());
+		assertThat(allValues.get(3).getObjectValue()).isEqualTo(acl);
+	}
+
+	@Test
+	public void getFromCacheSerializable() throws Exception {
+		when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(), acl));
+
+		assertThat(myCache.getFromCache(acl.getId())).isEqualTo(acl);
+	}
+
+	@Test
+	public void getFromCacheSerializablePopulatesTransient() throws Exception {
+		when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(), acl));
+
+		myCache.putInCache(acl);
+
+		ReflectionTestUtils.setField(acl, "permissionGrantingStrategy", null);
+		ReflectionTestUtils.setField(acl, "aclAuthorizationStrategy", null);
 
-        assertThat(allValues.get(2).getKey()).isEqualTo(acl.getObjectIdentity());
-        assertThat(allValues.get(2).getObjectValue()).isEqualTo(acl);
+		MutableAcl fromCache = myCache.getFromCache(acl.getId());
 
-        assertThat(allValues.get(3).getKey()).isEqualTo(acl.getId());
-        assertThat(allValues.get(3).getObjectValue()).isEqualTo(acl);
-    }
+		assertThat(ReflectionTestUtils.getField(fromCache, "aclAuthorizationStrategy"))
+				.isNotNull();
+		assertThat(ReflectionTestUtils.getField(fromCache, "permissionGrantingStrategy"))
+				.isNotNull();
+	}
 
-    @Test
-    public void getFromCacheSerializable() throws Exception {
-        when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(),acl));
+	@Test
+	public void getFromCacheObjectIdentity() throws Exception {
+		when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(), acl));
 
-        assertThat(myCache.getFromCache(acl.getId())).isEqualTo(acl);
-    }
+		assertThat(myCache.getFromCache(acl.getId())).isEqualTo(acl);
+	}
 
-    @Test
-    public void getFromCacheSerializablePopulatesTransient() throws Exception {
-        when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(),acl));
+	@Test
+	public void getFromCacheObjectIdentityPopulatesTransient() throws Exception {
+		when(cache.get(acl.getObjectIdentity()))
+				.thenReturn(new Element(acl.getId(), acl));
 
-        myCache.putInCache(acl);
+		myCache.putInCache(acl);
 
-        ReflectionTestUtils.setField(acl, "permissionGrantingStrategy", null);
-        ReflectionTestUtils.setField(acl, "aclAuthorizationStrategy", null);
+		ReflectionTestUtils.setField(acl, "permissionGrantingStrategy", null);
+		ReflectionTestUtils.setField(acl, "aclAuthorizationStrategy", null);
 
-        MutableAcl fromCache = myCache.getFromCache(acl.getId());
+		MutableAcl fromCache = myCache.getFromCache(acl.getObjectIdentity());
 
-        assertThat(ReflectionTestUtils.getField(fromCache, "aclAuthorizationStrategy")).isNotNull();
-        assertThat(ReflectionTestUtils.getField(fromCache, "permissionGrantingStrategy")).isNotNull();
-    }
+		assertThat(ReflectionTestUtils.getField(fromCache, "aclAuthorizationStrategy"))
+				.isNotNull();
+		assertThat(ReflectionTestUtils.getField(fromCache, "permissionGrantingStrategy"))
+				.isNotNull();
+	}
 
-    @Test
-    public void getFromCacheObjectIdentity() throws Exception {
-        when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(),acl));
+	@Test
+	public void evictCacheSerializable() throws Exception {
+		when(cache.get(acl.getObjectIdentity()))
+				.thenReturn(new Element(acl.getId(), acl));
 
-        assertThat(myCache.getFromCache(acl.getId())).isEqualTo(acl);
-    }
+		myCache.evictFromCache(acl.getObjectIdentity());
 
-    @Test
-    public void getFromCacheObjectIdentityPopulatesTransient() throws Exception {
-        when(cache.get(acl.getObjectIdentity())).thenReturn(new Element(acl.getId(),acl));
+		verify(cache).remove(acl.getId());
+		verify(cache).remove(acl.getObjectIdentity());
+	}
 
-        myCache.putInCache(acl);
+	@Test
+	public void evictCacheObjectIdentity() throws Exception {
+		when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(), acl));
 
-        ReflectionTestUtils.setField(acl, "permissionGrantingStrategy", null);
-        ReflectionTestUtils.setField(acl, "aclAuthorizationStrategy", null);
+		myCache.evictFromCache(acl.getId());
 
-        MutableAcl fromCache = myCache.getFromCache(acl.getObjectIdentity());
-
-        assertThat(ReflectionTestUtils.getField(fromCache, "aclAuthorizationStrategy")).isNotNull();
-        assertThat(ReflectionTestUtils.getField(fromCache, "permissionGrantingStrategy")).isNotNull();
-    }
-
-    @Test
-    public void evictCacheSerializable() throws Exception {
-        when(cache.get(acl.getObjectIdentity())).thenReturn(new Element(acl.getId(),acl));
-
-        myCache.evictFromCache(acl.getObjectIdentity());
-
-        verify(cache).remove(acl.getId());
-        verify(cache).remove(acl.getObjectIdentity());
-    }
-
-    @Test
-    public void evictCacheObjectIdentity() throws Exception {
-        when(cache.get(acl.getId())).thenReturn(new Element(acl.getId(),acl));
-
-        myCache.evictFromCache(acl.getId());
-
-        verify(cache).remove(acl.getId());
-        verify(cache).remove(acl.getObjectIdentity());
-    }
+		verify(cache).remove(acl.getId());
+		verify(cache).remove(acl.getObjectIdentity());
+	}
 }

+ 25 - 23
acl/src/test/java/org/springframework/security/acls/jdbc/JdbcAclServiceTests.java

@@ -24,27 +24,29 @@ import org.springframework.security.acls.model.Sid;
 
 @RunWith(MockitoJUnitRunner.class)
 public class JdbcAclServiceTests {
-    @Mock
-    private DataSource dataSource;
-
-    @Mock
-    private LookupStrategy lookupStrategy;
-
-    private JdbcAclService aclService;
-
-    @Before
-    public void setUp() {
-        aclService = new JdbcAclService(dataSource, lookupStrategy);
-    }
-
-    // SEC-1898
-    @Test(expected = NotFoundException.class)
-    public void readAclByIdMissingAcl() {
-        Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>();
-        when(lookupStrategy.readAclsById(anyListOf(ObjectIdentity.class), anyListOf(Sid.class))).thenReturn(result);
-        ObjectIdentity objectIdentity = new ObjectIdentityImpl(Object.class, 1);
-        List<Sid> sids = Arrays.<Sid> asList(new PrincipalSid("user"));
-
-        aclService.readAclById(objectIdentity, sids);
-    }
+	@Mock
+	private DataSource dataSource;
+
+	@Mock
+	private LookupStrategy lookupStrategy;
+
+	private JdbcAclService aclService;
+
+	@Before
+	public void setUp() {
+		aclService = new JdbcAclService(dataSource, lookupStrategy);
+	}
+
+	// SEC-1898
+	@Test(expected = NotFoundException.class)
+	public void readAclByIdMissingAcl() {
+		Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>();
+		when(
+				lookupStrategy.readAclsById(anyListOf(ObjectIdentity.class),
+						anyListOf(Sid.class))).thenReturn(result);
+		ObjectIdentity objectIdentity = new ObjectIdentityImpl(Object.class, 1);
+		List<Sid> sids = Arrays.<Sid> asList(new PrincipalSid("user"));
+
+		aclService.readAclById(objectIdentity, sids);
+	}
 }

+ 513 - 469
acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java

@@ -60,474 +60,518 @@ import org.springframework.transaction.annotation.Transactional;
  * @author Ben Alex
  * @author Andrei Stefan
  */
-@ContextConfiguration(locations={"/jdbcMutableAclServiceTests-context.xml"})
-public class JdbcMutableAclServiceTests extends AbstractTransactionalJUnit4SpringContextTests {
-    //~ Constant fields ================================================================================================
-
-    private static final String TARGET_CLASS = TargetObject.class.getName();
-
-    private final Authentication auth = new TestingAuthenticationToken("ben", "ignored","ROLE_ADMINISTRATOR");
-
-    public static final String SELECT_ALL_CLASSES = "SELECT * FROM acl_class WHERE class = ?";
-
-    //~ Instance fields ================================================================================================
-
-    private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
-    private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
-    private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102));
-
-    @Autowired
-    private JdbcMutableAclService jdbcMutableAclService;
-    @Autowired
-    private AclCache aclCache;
-    @Autowired
-    private LookupStrategy lookupStrategy;
-    @Autowired
-    private DataSource dataSource;
-    @Autowired
-    private JdbcTemplate jdbcTemplate;
-
-    //~ Methods ========================================================================================================
-
-    @BeforeTransaction
-    public void createTables() throws Exception {
-        try {
-            new DatabaseSeeder(dataSource, new ClassPathResource("createAclSchema.sql"));
-//            new DatabaseSeeder(dataSource, new ClassPathResource("createAclSchemaPostgres.sql"));
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw e;
-        }
-    }
-
-    @AfterTransaction
-    public void clearContextAndData() throws Exception {
-        SecurityContextHolder.clearContext();
-        jdbcTemplate.execute("drop table acl_entry");
-        jdbcTemplate.execute("drop table acl_object_identity");
-        jdbcTemplate.execute("drop table acl_class");
-        jdbcTemplate.execute("drop table acl_sid");
-        aclCache.clearCache();
-    }
-
-    @Test
-    @Transactional
-    public void testLifecycle() {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-
-        MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
-        MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
-        MutableAcl child = jdbcMutableAclService.createAcl(childOid);
-
-        // Specify the inheritance hierarchy
-        middleParent.setParent(topParent);
-        child.setParent(middleParent);
-
-        // Now let's add a couple of permissions
-        topParent.insertAce(0, BasePermission.READ, new PrincipalSid(auth), true);
-        topParent.insertAce(1, BasePermission.WRITE, new PrincipalSid(auth), false);
-        middleParent.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), true);
-        child.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), false);
-
-        // Explicitly save the changed ACL
-        jdbcMutableAclService.updateAcl(topParent);
-        jdbcMutableAclService.updateAcl(middleParent);
-        jdbcMutableAclService.updateAcl(child);
-
-        // Let's check if we can read them back correctly
-        Map<ObjectIdentity, Acl> map = jdbcMutableAclService.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid));
-        assertEquals(3, map.size());
-
-        // Replace our current objects with their retrieved versions
-        topParent = (MutableAcl) map.get(topParentOid);
-        middleParent = (MutableAcl) map.get(middleParentOid);
-        child = (MutableAcl) map.get(childOid);
-
-        // Check the retrieved versions has IDs
-        assertNotNull(topParent.getId());
-        assertNotNull(middleParent.getId());
-        assertNotNull(child.getId());
-
-        // Check their parents were correctly persisted
-        assertNull(topParent.getParentAcl());
-        assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
-        assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
-
-        // Check their ACEs were correctly persisted
-        assertEquals(2, topParent.getEntries().size());
-        assertEquals(1, middleParent.getEntries().size());
-        assertEquals(1, child.getEntries().size());
-
-        // Check the retrieved rights are correct
-        List<Permission> read = Arrays.asList(BasePermission.READ);
-        List<Permission> write = Arrays.asList(BasePermission.WRITE);
-        List<Permission> delete = Arrays.asList(BasePermission.DELETE);
-        List<Sid> pSid = Arrays.asList((Sid)new PrincipalSid(auth));
-
-
-        assertTrue(topParent.isGranted(read, pSid, false));
-        assertFalse(topParent.isGranted(write, pSid, false));
-        assertTrue(middleParent.isGranted(delete, pSid, false));
-        assertFalse(child.isGranted(delete, pSid, false));
-
-        try {
-            child.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), pSid, false);
-            fail("Should have thrown NotFoundException");
-        } catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-
-        // Now check the inherited rights (when not explicitly overridden) also look OK
-        assertTrue(child.isGranted(read, pSid, false));
-        assertFalse(child.isGranted(write, pSid, false));
-        assertFalse(child.isGranted(delete, pSid, false));
-
-        // Next change the child so it doesn't inherit permissions from above
-        child.setEntriesInheriting(false);
-        jdbcMutableAclService.updateAcl(child);
-        child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
-        assertFalse(child.isEntriesInheriting());
-
-        // Check the child permissions no longer inherit
-        assertFalse(child.isGranted(delete, pSid, true));
-
-        try {
-            child.isGranted(read, pSid, true);
-            fail("Should have thrown NotFoundException");
-        } catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            child.isGranted(write, pSid, true);
-            fail("Should have thrown NotFoundException");
-        } catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-
-        // Let's add an identical permission to the child, but it'll appear AFTER the current permission, so has no impact
-        child.insertAce(1, BasePermission.DELETE, new PrincipalSid(auth), true);
-
-        // Let's also add another permission to the child
-        child.insertAce(2, BasePermission.CREATE, new PrincipalSid(auth), true);
-
-        // Save the changed child
-        jdbcMutableAclService.updateAcl(child);
-        child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
-        assertEquals(3, child.getEntries().size());
-
-        // Output permissions
-        for (int i = 0; i < child.getEntries().size(); i++) {
-            System.out.println(child.getEntries().get(i));
-        }
-
-        // Check the permissions are as they should be
-        assertFalse(child.isGranted(delete, pSid, true)); // as earlier permission overrode
-        assertTrue(child.isGranted(Arrays.asList(BasePermission.CREATE), pSid, true));
-
-        // Now check the first ACE (index 0) really is DELETE for our Sid and is non-granting
-        AccessControlEntry entry = child.getEntries().get(0);
-        assertEquals(BasePermission.DELETE.getMask(), entry.getPermission().getMask());
-        assertEquals(new PrincipalSid(auth), entry.getSid());
-        assertFalse(entry.isGranting());
-        assertNotNull(entry.getId());
-
-        // Now delete that first ACE
-        child.deleteAce(0);
-
-        // Save and check it worked
-        child = jdbcMutableAclService.updateAcl(child);
-        assertEquals(2, child.getEntries().size());
-        assertTrue(child.isGranted(delete, pSid, false));
-
-        SecurityContextHolder.clearContext();
-    }
-
-    /**
-     * Test method that demonstrates eviction failure from cache - SEC-676
-     */
-    @Test
-    @Transactional
-    public void deleteAclAlsoDeletesChildren() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-
-        jdbcMutableAclService.createAcl(topParentOid);
-        MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
-        MutableAcl child = jdbcMutableAclService.createAcl(childOid);
-        child.setParent(middleParent);
-        jdbcMutableAclService.updateAcl(middleParent);
-        jdbcMutableAclService.updateAcl(child);
-        // Check the childOid really is a child of middleParentOid
-        Acl childAcl = jdbcMutableAclService.readAclById(childOid);
-
-        assertEquals(middleParentOid, childAcl.getParentAcl().getObjectIdentity());
-
-        // Delete the mid-parent and test if the child was deleted, as well
-        jdbcMutableAclService.deleteAcl(middleParentOid, true);
-
-        try {
-            jdbcMutableAclService.readAclById(middleParentOid);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-        try {
-            jdbcMutableAclService.readAclById(childOid);
-            fail("It should have thrown NotFoundException");
-        }
-        catch (NotFoundException expected) {
-            assertTrue(true);
-        }
-
-        Acl acl = jdbcMutableAclService.readAclById(topParentOid);
-        assertNotNull(acl);
-        assertEquals(((MutableAcl) acl).getObjectIdentity(), topParentOid);
-    }
-
-    @Test
-    public void constructorRejectsNullParameters() throws Exception {
-        try {
-            new JdbcMutableAclService(null, lookupStrategy, aclCache);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new JdbcMutableAclService(dataSource, null, aclCache);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new JdbcMutableAclService(dataSource, lookupStrategy, null);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void createAclRejectsNullParameter() throws Exception {
-        try {
-            jdbcMutableAclService.createAcl(null);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    @Transactional
-    public void createAclForADuplicateDomainObject() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        ObjectIdentity duplicateOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
-        jdbcMutableAclService.createAcl(duplicateOid);
-        // Try to add the same object second time
-        try {
-            jdbcMutableAclService.createAcl(duplicateOid);
-            fail("It should have thrown AlreadyExistsException");
-        }
-        catch (AlreadyExistsException expected) {
-        }
-    }
-
-    @Test
-    @Transactional
-    public void deleteAclRejectsNullParameters() throws Exception {
-        try {
-            jdbcMutableAclService.deleteAcl(null, true);
-            fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    @Transactional
-    public void deleteAclWithChildrenThrowsException() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        MutableAcl parent = jdbcMutableAclService.createAcl(topParentOid);
-        MutableAcl child = jdbcMutableAclService.createAcl(middleParentOid);
-
-        // Specify the inheritance hierarchy
-        child.setParent(parent);
-        jdbcMutableAclService.updateAcl(child);
-
-        try {
-            jdbcMutableAclService.setForeignKeysInDatabase(false); // switch on FK checking in the class, not database
-            jdbcMutableAclService.deleteAcl(topParentOid, false);
-            fail("It should have thrown ChildrenExistException");
-        }
-        catch (ChildrenExistException expected) {
-        } finally {
-            jdbcMutableAclService.setForeignKeysInDatabase(true); // restore to the default
-        }
-    }
-
-    @Test
-    @Transactional
-    public void deleteAclRemovesRowsFromDatabase() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        MutableAcl child = jdbcMutableAclService.createAcl(childOid);
-        child.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), false);
-        jdbcMutableAclService.updateAcl(child);
-
-        // Remove the child and check all related database rows were removed accordingly
-        jdbcMutableAclService.deleteAcl(childOid, false);
-        assertEquals(1, jdbcTemplate.queryForList(SELECT_ALL_CLASSES, new Object[] {TARGET_CLASS} ).size());
-        assertEquals(0, jdbcTemplate.queryForList("select * from acl_object_identity").size());
-        assertEquals(0, jdbcTemplate.queryForList("select * from acl_entry").size());
-
-        // Check the cache
-        assertNull(aclCache.getFromCache(childOid));
-        assertNull(aclCache.getFromCache(Long.valueOf(102)));
-    }
-
-    /** SEC-1107 */
-    @Test
-    @Transactional
-    public void identityWithIntegerIdIsSupportedByCreateAcl() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(101));
-        jdbcMutableAclService.createAcl(oid);
-
-        assertNotNull(jdbcMutableAclService.readAclById(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101))));
-    }
-
-    /**
-     * SEC-655
-     */
-    @Test
-    @Transactional
-    public void childrenAreClearedFromCacheWhenParentIsUpdated() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("ben", "ignored","ROLE_ADMINISTRATOR");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-
-        ObjectIdentity parentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(104));
-        ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(105));
-
-        MutableAcl parent = jdbcMutableAclService.createAcl(parentOid);
-        MutableAcl child = jdbcMutableAclService.createAcl(childOid);
-
-        child.setParent(parent);
-        jdbcMutableAclService.updateAcl(child);
-
-        parent = (AclImpl) jdbcMutableAclService.readAclById(parentOid);
-        parent.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), true);
-        jdbcMutableAclService.updateAcl(parent);
-
-        parent = (AclImpl) jdbcMutableAclService.readAclById(parentOid);
-        parent.insertAce(1, BasePermission.READ, new PrincipalSid("scott"), true);
-        jdbcMutableAclService.updateAcl(parent);
-
-        child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
-        parent = (MutableAcl) child.getParentAcl();
-
-        assertEquals("Fails because child has a stale reference to its parent", 2, parent.getEntries().size());
-        assertEquals(1, parent.getEntries().get(0).getPermission().getMask());
-        assertEquals(new PrincipalSid("ben"), parent.getEntries().get(0).getSid());
-        assertEquals(1, parent.getEntries().get(1).getPermission().getMask());
-        assertEquals(new PrincipalSid("scott"), parent.getEntries().get(1).getSid());
-    }
-
-    /**
-     * SEC-655
-     */
-    @Test
-    @Transactional
-    public void childrenAreClearedFromCacheWhenParentisUpdated2() throws Exception {
-        Authentication auth = new TestingAuthenticationToken("system", "secret","ROLE_IGNORED");
-        SecurityContextHolder.getContext().setAuthentication(auth);
-        ObjectIdentityImpl rootObject = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(1));
-
-        MutableAcl parent = jdbcMutableAclService.createAcl(rootObject);
-        MutableAcl child = jdbcMutableAclService.createAcl(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(2)));
-        child.setParent(parent);
-        jdbcMutableAclService.updateAcl(child);
-
-        parent.insertAce(0, BasePermission.ADMINISTRATION, new GrantedAuthoritySid("ROLE_ADMINISTRATOR"), true);
-        jdbcMutableAclService.updateAcl(parent);
-
-        parent.insertAce(1, BasePermission.DELETE, new PrincipalSid("terry"), true);
-        jdbcMutableAclService.updateAcl(parent);
-
-        child = (MutableAcl) jdbcMutableAclService.readAclById(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(2)));
-
-        parent = (MutableAcl) child.getParentAcl();
-
-        assertEquals(2, parent.getEntries().size());
-        assertEquals(16, parent.getEntries().get(0).getPermission().getMask());
-        assertEquals(new GrantedAuthoritySid("ROLE_ADMINISTRATOR"), parent.getEntries().get(0).getSid());
-        assertEquals(8, parent.getEntries().get(1).getPermission().getMask());
-        assertEquals(new PrincipalSid("terry"), parent.getEntries().get(1).getSid());
-    }
-
-    @Test
-    @Transactional
-    public void cumulativePermissions() {
-       Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR");
-       auth.setAuthenticated(true);
-       SecurityContextHolder.getContext().setAuthentication(auth);
-
-       ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(110));
-       MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
-
-       // Add an ACE permission entry
-       Permission cm = new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION);
-       assertEquals(17, cm.getMask());
-       Sid benSid = new PrincipalSid(auth);
-       topParent.insertAce(0, cm, benSid, true);
-       assertEquals(1, topParent.getEntries().size());
-
-       // Explicitly save the changed ACL
-       topParent = jdbcMutableAclService.updateAcl(topParent);
-
-       // Check the mask was retrieved correctly
-       assertEquals(17, topParent.getEntries().get(0).getPermission().getMask());
-       assertTrue(topParent.isGranted(Arrays.asList(cm), Arrays.asList(benSid), true));
-
-       SecurityContextHolder.clearContext();
-    }
-    @Test
-    public void testProcessingCustomSid() {
-        CustomJdbcMutableAclService customJdbcMutableAclService = spy(new CustomJdbcMutableAclService(dataSource,
-                lookupStrategy, aclCache));
-        CustomSid customSid = new CustomSid("Custom sid");
-        when(customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid", false, false)).thenReturn(1L);
-
-        Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(customSid, false);
-
-        assertEquals(result, new Long(1L));
-    }
-
-    /**
-     * This class needed to show how to extend {@link JdbcMutableAclService} for processing
-     * custom {@link Sid} implementations
-     */
-    private class CustomJdbcMutableAclService extends JdbcMutableAclService {
-
-        private CustomJdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
-            super(dataSource, lookupStrategy, aclCache);
-        }
-
-        @Override
-        protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
-            String sidName;
-            boolean isPrincipal = false;
-            if (sid instanceof CustomSid) {
-                sidName = ((CustomSid)sid).getSid();
-            } else if (sid instanceof GrantedAuthoritySid) {
-                sidName = ((GrantedAuthoritySid)sid).getGrantedAuthority();
-            } else {
-                sidName = ((PrincipalSid)sid).getPrincipal();
-                isPrincipal = true;
-            }
-            return createOrRetrieveSidPrimaryKey(sidName, isPrincipal, allowCreate);
-        }
-    }
+@ContextConfiguration(locations = { "/jdbcMutableAclServiceTests-context.xml" })
+public class JdbcMutableAclServiceTests extends
+		AbstractTransactionalJUnit4SpringContextTests {
+	// ~ Constant fields
+	// ================================================================================================
+
+	private static final String TARGET_CLASS = TargetObject.class.getName();
+
+	private final Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+			"ROLE_ADMINISTRATOR");
+
+	public static final String SELECT_ALL_CLASSES = "SELECT * FROM acl_class WHERE class = ?";
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+			Long.valueOf(100));
+	private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+			Long.valueOf(101));
+	private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS,
+			Long.valueOf(102));
+
+	@Autowired
+	private JdbcMutableAclService jdbcMutableAclService;
+	@Autowired
+	private AclCache aclCache;
+	@Autowired
+	private LookupStrategy lookupStrategy;
+	@Autowired
+	private DataSource dataSource;
+	@Autowired
+	private JdbcTemplate jdbcTemplate;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	@BeforeTransaction
+	public void createTables() throws Exception {
+		try {
+			new DatabaseSeeder(dataSource, new ClassPathResource("createAclSchema.sql"));
+			// new DatabaseSeeder(dataSource, new
+			// ClassPathResource("createAclSchemaPostgres.sql"));
+		}
+		catch (Exception e) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	@AfterTransaction
+	public void clearContextAndData() throws Exception {
+		SecurityContextHolder.clearContext();
+		jdbcTemplate.execute("drop table acl_entry");
+		jdbcTemplate.execute("drop table acl_object_identity");
+		jdbcTemplate.execute("drop table acl_class");
+		jdbcTemplate.execute("drop table acl_sid");
+		aclCache.clearCache();
+	}
+
+	@Test
+	@Transactional
+	public void testLifecycle() {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+
+		MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
+		MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
+		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+
+		// Specify the inheritance hierarchy
+		middleParent.setParent(topParent);
+		child.setParent(middleParent);
+
+		// Now let's add a couple of permissions
+		topParent.insertAce(0, BasePermission.READ, new PrincipalSid(auth), true);
+		topParent.insertAce(1, BasePermission.WRITE, new PrincipalSid(auth), false);
+		middleParent.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), true);
+		child.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), false);
+
+		// Explicitly save the changed ACL
+		jdbcMutableAclService.updateAcl(topParent);
+		jdbcMutableAclService.updateAcl(middleParent);
+		jdbcMutableAclService.updateAcl(child);
+
+		// Let's check if we can read them back correctly
+		Map<ObjectIdentity, Acl> map = jdbcMutableAclService.readAclsById(Arrays.asList(
+				topParentOid, middleParentOid, childOid));
+		assertEquals(3, map.size());
+
+		// Replace our current objects with their retrieved versions
+		topParent = (MutableAcl) map.get(topParentOid);
+		middleParent = (MutableAcl) map.get(middleParentOid);
+		child = (MutableAcl) map.get(childOid);
+
+		// Check the retrieved versions has IDs
+		assertNotNull(topParent.getId());
+		assertNotNull(middleParent.getId());
+		assertNotNull(child.getId());
+
+		// Check their parents were correctly persisted
+		assertNull(topParent.getParentAcl());
+		assertEquals(topParentOid, middleParent.getParentAcl().getObjectIdentity());
+		assertEquals(middleParentOid, child.getParentAcl().getObjectIdentity());
+
+		// Check their ACEs were correctly persisted
+		assertEquals(2, topParent.getEntries().size());
+		assertEquals(1, middleParent.getEntries().size());
+		assertEquals(1, child.getEntries().size());
+
+		// Check the retrieved rights are correct
+		List<Permission> read = Arrays.asList(BasePermission.READ);
+		List<Permission> write = Arrays.asList(BasePermission.WRITE);
+		List<Permission> delete = Arrays.asList(BasePermission.DELETE);
+		List<Sid> pSid = Arrays.asList((Sid) new PrincipalSid(auth));
+
+		assertTrue(topParent.isGranted(read, pSid, false));
+		assertFalse(topParent.isGranted(write, pSid, false));
+		assertTrue(middleParent.isGranted(delete, pSid, false));
+		assertFalse(child.isGranted(delete, pSid, false));
+
+		try {
+			child.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), pSid, false);
+			fail("Should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+
+		// Now check the inherited rights (when not explicitly overridden) also look OK
+		assertTrue(child.isGranted(read, pSid, false));
+		assertFalse(child.isGranted(write, pSid, false));
+		assertFalse(child.isGranted(delete, pSid, false));
+
+		// Next change the child so it doesn't inherit permissions from above
+		child.setEntriesInheriting(false);
+		jdbcMutableAclService.updateAcl(child);
+		child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+		assertFalse(child.isEntriesInheriting());
+
+		// Check the child permissions no longer inherit
+		assertFalse(child.isGranted(delete, pSid, true));
+
+		try {
+			child.isGranted(read, pSid, true);
+			fail("Should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+
+		try {
+			child.isGranted(write, pSid, true);
+			fail("Should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+
+		// Let's add an identical permission to the child, but it'll appear AFTER the
+		// current permission, so has no impact
+		child.insertAce(1, BasePermission.DELETE, new PrincipalSid(auth), true);
+
+		// Let's also add another permission to the child
+		child.insertAce(2, BasePermission.CREATE, new PrincipalSid(auth), true);
+
+		// Save the changed child
+		jdbcMutableAclService.updateAcl(child);
+		child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+		assertEquals(3, child.getEntries().size());
+
+		// Output permissions
+		for (int i = 0; i < child.getEntries().size(); i++) {
+			System.out.println(child.getEntries().get(i));
+		}
+
+		// Check the permissions are as they should be
+		assertFalse(child.isGranted(delete, pSid, true)); // as earlier permission
+															// overrode
+		assertTrue(child.isGranted(Arrays.asList(BasePermission.CREATE), pSid, true));
+
+		// Now check the first ACE (index 0) really is DELETE for our Sid and is
+		// non-granting
+		AccessControlEntry entry = child.getEntries().get(0);
+		assertEquals(BasePermission.DELETE.getMask(), entry.getPermission().getMask());
+		assertEquals(new PrincipalSid(auth), entry.getSid());
+		assertFalse(entry.isGranting());
+		assertNotNull(entry.getId());
+
+		// Now delete that first ACE
+		child.deleteAce(0);
+
+		// Save and check it worked
+		child = jdbcMutableAclService.updateAcl(child);
+		assertEquals(2, child.getEntries().size());
+		assertTrue(child.isGranted(delete, pSid, false));
+
+		SecurityContextHolder.clearContext();
+	}
+
+	/**
+	 * Test method that demonstrates eviction failure from cache - SEC-676
+	 */
+	@Test
+	@Transactional
+	public void deleteAclAlsoDeletesChildren() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+
+		jdbcMutableAclService.createAcl(topParentOid);
+		MutableAcl middleParent = jdbcMutableAclService.createAcl(middleParentOid);
+		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+		child.setParent(middleParent);
+		jdbcMutableAclService.updateAcl(middleParent);
+		jdbcMutableAclService.updateAcl(child);
+		// Check the childOid really is a child of middleParentOid
+		Acl childAcl = jdbcMutableAclService.readAclById(childOid);
+
+		assertEquals(middleParentOid, childAcl.getParentAcl().getObjectIdentity());
+
+		// Delete the mid-parent and test if the child was deleted, as well
+		jdbcMutableAclService.deleteAcl(middleParentOid, true);
+
+		try {
+			jdbcMutableAclService.readAclById(middleParentOid);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+		try {
+			jdbcMutableAclService.readAclById(childOid);
+			fail("It should have thrown NotFoundException");
+		}
+		catch (NotFoundException expected) {
+			assertTrue(true);
+		}
+
+		Acl acl = jdbcMutableAclService.readAclById(topParentOid);
+		assertNotNull(acl);
+		assertEquals(((MutableAcl) acl).getObjectIdentity(), topParentOid);
+	}
+
+	@Test
+	public void constructorRejectsNullParameters() throws Exception {
+		try {
+			new JdbcMutableAclService(null, lookupStrategy, aclCache);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new JdbcMutableAclService(dataSource, null, aclCache);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new JdbcMutableAclService(dataSource, lookupStrategy, null);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test
+	public void createAclRejectsNullParameter() throws Exception {
+		try {
+			jdbcMutableAclService.createAcl(null);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test
+	@Transactional
+	public void createAclForADuplicateDomainObject() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		ObjectIdentity duplicateOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(100));
+		jdbcMutableAclService.createAcl(duplicateOid);
+		// Try to add the same object second time
+		try {
+			jdbcMutableAclService.createAcl(duplicateOid);
+			fail("It should have thrown AlreadyExistsException");
+		}
+		catch (AlreadyExistsException expected) {
+		}
+	}
+
+	@Test
+	@Transactional
+	public void deleteAclRejectsNullParameters() throws Exception {
+		try {
+			jdbcMutableAclService.deleteAcl(null, true);
+			fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	@Test
+	@Transactional
+	public void deleteAclWithChildrenThrowsException() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		MutableAcl parent = jdbcMutableAclService.createAcl(topParentOid);
+		MutableAcl child = jdbcMutableAclService.createAcl(middleParentOid);
+
+		// Specify the inheritance hierarchy
+		child.setParent(parent);
+		jdbcMutableAclService.updateAcl(child);
+
+		try {
+			jdbcMutableAclService.setForeignKeysInDatabase(false); // switch on FK
+																	// checking in the
+																	// class, not database
+			jdbcMutableAclService.deleteAcl(topParentOid, false);
+			fail("It should have thrown ChildrenExistException");
+		}
+		catch (ChildrenExistException expected) {
+		}
+		finally {
+			jdbcMutableAclService.setForeignKeysInDatabase(true); // restore to the
+																	// default
+		}
+	}
+
+	@Test
+	@Transactional
+	public void deleteAclRemovesRowsFromDatabase() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+		child.insertAce(0, BasePermission.DELETE, new PrincipalSid(auth), false);
+		jdbcMutableAclService.updateAcl(child);
+
+		// Remove the child and check all related database rows were removed accordingly
+		jdbcMutableAclService.deleteAcl(childOid, false);
+		assertEquals(
+				1,
+				jdbcTemplate.queryForList(SELECT_ALL_CLASSES,
+						new Object[] { TARGET_CLASS }).size());
+		assertEquals(0, jdbcTemplate.queryForList("select * from acl_object_identity")
+				.size());
+		assertEquals(0, jdbcTemplate.queryForList("select * from acl_entry").size());
+
+		// Check the cache
+		assertNull(aclCache.getFromCache(childOid));
+		assertNull(aclCache.getFromCache(Long.valueOf(102)));
+	}
+
+	/** SEC-1107 */
+	@Test
+	@Transactional
+	public void identityWithIntegerIdIsSupportedByCreateAcl() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, Integer.valueOf(101));
+		jdbcMutableAclService.createAcl(oid);
+
+		assertNotNull(jdbcMutableAclService.readAclById(new ObjectIdentityImpl(
+				TARGET_CLASS, Long.valueOf(101))));
+	}
+
+	/**
+	 * SEC-655
+	 */
+	@Test
+	@Transactional
+	public void childrenAreClearedFromCacheWhenParentIsUpdated() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_ADMINISTRATOR");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+
+		ObjectIdentity parentOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(104));
+		ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(105));
+
+		MutableAcl parent = jdbcMutableAclService.createAcl(parentOid);
+		MutableAcl child = jdbcMutableAclService.createAcl(childOid);
+
+		child.setParent(parent);
+		jdbcMutableAclService.updateAcl(child);
+
+		parent = (AclImpl) jdbcMutableAclService.readAclById(parentOid);
+		parent.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), true);
+		jdbcMutableAclService.updateAcl(parent);
+
+		parent = (AclImpl) jdbcMutableAclService.readAclById(parentOid);
+		parent.insertAce(1, BasePermission.READ, new PrincipalSid("scott"), true);
+		jdbcMutableAclService.updateAcl(parent);
+
+		child = (MutableAcl) jdbcMutableAclService.readAclById(childOid);
+		parent = (MutableAcl) child.getParentAcl();
+
+		assertEquals("Fails because child has a stale reference to its parent", 2, parent
+				.getEntries().size());
+		assertEquals(1, parent.getEntries().get(0).getPermission().getMask());
+		assertEquals(new PrincipalSid("ben"), parent.getEntries().get(0).getSid());
+		assertEquals(1, parent.getEntries().get(1).getPermission().getMask());
+		assertEquals(new PrincipalSid("scott"), parent.getEntries().get(1).getSid());
+	}
+
+	/**
+	 * SEC-655
+	 */
+	@Test
+	@Transactional
+	public void childrenAreClearedFromCacheWhenParentisUpdated2() throws Exception {
+		Authentication auth = new TestingAuthenticationToken("system", "secret",
+				"ROLE_IGNORED");
+		SecurityContextHolder.getContext().setAuthentication(auth);
+		ObjectIdentityImpl rootObject = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(1));
+
+		MutableAcl parent = jdbcMutableAclService.createAcl(rootObject);
+		MutableAcl child = jdbcMutableAclService.createAcl(new ObjectIdentityImpl(
+				TARGET_CLASS, Long.valueOf(2)));
+		child.setParent(parent);
+		jdbcMutableAclService.updateAcl(child);
+
+		parent.insertAce(0, BasePermission.ADMINISTRATION, new GrantedAuthoritySid(
+				"ROLE_ADMINISTRATOR"), true);
+		jdbcMutableAclService.updateAcl(parent);
+
+		parent.insertAce(1, BasePermission.DELETE, new PrincipalSid("terry"), true);
+		jdbcMutableAclService.updateAcl(parent);
+
+		child = (MutableAcl) jdbcMutableAclService.readAclById(new ObjectIdentityImpl(
+				TARGET_CLASS, Long.valueOf(2)));
+
+		parent = (MutableAcl) child.getParentAcl();
+
+		assertEquals(2, parent.getEntries().size());
+		assertEquals(16, parent.getEntries().get(0).getPermission().getMask());
+		assertEquals(new GrantedAuthoritySid("ROLE_ADMINISTRATOR"), parent.getEntries()
+				.get(0).getSid());
+		assertEquals(8, parent.getEntries().get(1).getPermission().getMask());
+		assertEquals(new PrincipalSid("terry"), parent.getEntries().get(1).getSid());
+	}
+
+	@Test
+	@Transactional
+	public void cumulativePermissions() {
+		Authentication auth = new TestingAuthenticationToken("ben", "ignored",
+				"ROLE_ADMINISTRATOR");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+
+		ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(110));
+		MutableAcl topParent = jdbcMutableAclService.createAcl(topParentOid);
+
+		// Add an ACE permission entry
+		Permission cm = new CumulativePermission().set(BasePermission.READ).set(
+				BasePermission.ADMINISTRATION);
+		assertEquals(17, cm.getMask());
+		Sid benSid = new PrincipalSid(auth);
+		topParent.insertAce(0, cm, benSid, true);
+		assertEquals(1, topParent.getEntries().size());
+
+		// Explicitly save the changed ACL
+		topParent = jdbcMutableAclService.updateAcl(topParent);
+
+		// Check the mask was retrieved correctly
+		assertEquals(17, topParent.getEntries().get(0).getPermission().getMask());
+		assertTrue(topParent.isGranted(Arrays.asList(cm), Arrays.asList(benSid), true));
+
+		SecurityContextHolder.clearContext();
+	}
+
+	@Test
+	public void testProcessingCustomSid() {
+		CustomJdbcMutableAclService customJdbcMutableAclService = spy(new CustomJdbcMutableAclService(
+				dataSource, lookupStrategy, aclCache));
+		CustomSid customSid = new CustomSid("Custom sid");
+		when(
+				customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid",
+						false, false)).thenReturn(1L);
+
+		Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(
+				customSid, false);
+
+		assertEquals(result, new Long(1L));
+	}
+
+	/**
+	 * This class needed to show how to extend {@link JdbcMutableAclService} for
+	 * processing custom {@link Sid} implementations
+	 */
+	private class CustomJdbcMutableAclService extends JdbcMutableAclService {
+
+		private CustomJdbcMutableAclService(DataSource dataSource,
+				LookupStrategy lookupStrategy, AclCache aclCache) {
+			super(dataSource, lookupStrategy, aclCache);
+		}
+
+		@Override
+		protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
+			String sidName;
+			boolean isPrincipal = false;
+			if (sid instanceof CustomSid) {
+				sidName = ((CustomSid) sid).getSid();
+			}
+			else if (sid instanceof GrantedAuthoritySid) {
+				sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
+			}
+			else {
+				sidName = ((PrincipalSid) sid).getPrincipal();
+				isPrincipal = true;
+			}
+			return createOrRetrieveSidPrimaryKey(sidName, isPrincipal, allowCreate);
+		}
+	}
 
 }

+ 127 - 113
acl/src/test/java/org/springframework/security/acls/jdbc/SpringCacheBasedAclCacheTests.java

@@ -26,117 +26,131 @@ import static org.junit.Assert.*;
  * @author Marten Deinum
  */
 public class SpringCacheBasedAclCacheTests {
-    private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
-
-    private static CacheManager cacheManager;
-
-    @BeforeClass
-    public static void initCacheManaer() {
-        cacheManager = new ConcurrentMapCacheManager();
-        // Use disk caching immediately (to test for serialization issue reported in SEC-527)
-        cacheManager.getCache("springcasebasedacltests");
-    }
-
-    @After
-    public void clearContext() {
-        SecurityContextHolder.clearContext();
-    }
-
-    private Cache getCache() {
-        Cache cache = cacheManager.getCache("springcasebasedacltests");
-        cache.clear();
-        return cache;
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void constructorRejectsNullParameters() throws Exception {
-        new SpringCacheBasedAclCache(null, null, null);
-    }
-
-    @SuppressWarnings("rawtypes")
-    @Test
-    public void cacheOperationsAclWithoutParent() throws Exception {
-        Cache cache = getCache();
-        Map realCache = (Map) cache.getNativeCache();
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
-        AuditLogger auditLogger = new ConsoleAuditLogger();
-
-        PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger);
-        SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, aclAuthorizationStrategy);
-        MutableAcl acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy, auditLogger);
-
-        assertEquals(0, realCache.size());
-        myCache.putInCache(acl);
-
-        // Check we can get from cache the same objects we put in
-        assertEquals(myCache.getFromCache(Long.valueOf(1)), acl);
-        assertEquals(myCache.getFromCache(identity), acl);
-
-        // Put another object in cache
-        ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
-        MutableAcl acl2 = new AclImpl(identity2, Long.valueOf(2), aclAuthorizationStrategy, new ConsoleAuditLogger());
-
-        myCache.putInCache(acl2);
-
-        // Try to evict an entry that doesn't exist
-        myCache.evictFromCache(Long.valueOf(3));
-        myCache.evictFromCache(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102)));
-        assertEquals(realCache.size(), 4);
-
-        myCache.evictFromCache(Long.valueOf(1));
-        assertEquals(realCache.size(), 2);
-
-        // Check the second object inserted
-        assertEquals(myCache.getFromCache(Long.valueOf(2)), acl2);
-        assertEquals(myCache.getFromCache(identity2), acl2);
-
-        myCache.evictFromCache(identity2);
-        assertEquals(realCache.size(), 0);
-    }
-
-    @SuppressWarnings("rawtypes")
-    @Test
-    public void cacheOperationsAclWithParent() throws Exception {
-        Cache cache = getCache();
-        Map realCache = (Map) cache.getNativeCache();
-
-        Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
-        auth.setAuthenticated(true);
-        SecurityContextHolder.getContext().setAuthentication(auth);
-
-        ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(1));
-        ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(2));
-        AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
-                new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
-                new SimpleGrantedAuthority("ROLE_GENERAL"));
-        AuditLogger auditLogger = new ConsoleAuditLogger();
-
-        PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger);
-        SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, aclAuthorizationStrategy);
-
-        MutableAcl acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy, auditLogger);
-        MutableAcl parentAcl = new AclImpl(identityParent, Long.valueOf(2), aclAuthorizationStrategy, auditLogger);
-
-        acl.setParent(parentAcl);
-
-        assertEquals(0, realCache.size());
-        myCache.putInCache(acl);
-        assertEquals(realCache.size(), 4);
-
-        // Check we can get from cache the same objects we put in
-        AclImpl aclFromCache = (AclImpl) myCache.getFromCache(Long.valueOf(1));
-        assertEquals(acl, aclFromCache);
-        // SEC-951 check transient fields are set on parent
-        assertNotNull(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "aclAuthorizationStrategy"));
-        assertNotNull(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "permissionGrantingStrategy"));
-        assertEquals(acl, myCache.getFromCache(identity));
-        assertNotNull(FieldUtils.getFieldValue(aclFromCache, "aclAuthorizationStrategy"));
-        AclImpl parentAclFromCache = (AclImpl) myCache.getFromCache(Long.valueOf(2));
-        assertEquals(parentAcl, parentAclFromCache);
-        assertNotNull(FieldUtils.getFieldValue(parentAclFromCache, "aclAuthorizationStrategy"));
-        assertEquals(parentAcl, myCache.getFromCache(identityParent));
-    }
+	private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
+
+	private static CacheManager cacheManager;
+
+	@BeforeClass
+	public static void initCacheManaer() {
+		cacheManager = new ConcurrentMapCacheManager();
+		// Use disk caching immediately (to test for serialization issue reported in
+		// SEC-527)
+		cacheManager.getCache("springcasebasedacltests");
+	}
+
+	@After
+	public void clearContext() {
+		SecurityContextHolder.clearContext();
+	}
+
+	private Cache getCache() {
+		Cache cache = cacheManager.getCache("springcasebasedacltests");
+		cache.clear();
+		return cache;
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorRejectsNullParameters() throws Exception {
+		new SpringCacheBasedAclCache(null, null, null);
+	}
+
+	@SuppressWarnings("rawtypes")
+	@Test
+	public void cacheOperationsAclWithoutParent() throws Exception {
+		Cache cache = getCache();
+		Map realCache = (Map) cache.getNativeCache();
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(100));
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
+		AuditLogger auditLogger = new ConsoleAuditLogger();
+
+		PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(
+				auditLogger);
+		SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache,
+				permissionGrantingStrategy, aclAuthorizationStrategy);
+		MutableAcl acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy,
+				auditLogger);
+
+		assertEquals(0, realCache.size());
+		myCache.putInCache(acl);
+
+		// Check we can get from cache the same objects we put in
+		assertEquals(myCache.getFromCache(Long.valueOf(1)), acl);
+		assertEquals(myCache.getFromCache(identity), acl);
+
+		// Put another object in cache
+		ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(101));
+		MutableAcl acl2 = new AclImpl(identity2, Long.valueOf(2),
+				aclAuthorizationStrategy, new ConsoleAuditLogger());
+
+		myCache.putInCache(acl2);
+
+		// Try to evict an entry that doesn't exist
+		myCache.evictFromCache(Long.valueOf(3));
+		myCache.evictFromCache(new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(102)));
+		assertEquals(realCache.size(), 4);
+
+		myCache.evictFromCache(Long.valueOf(1));
+		assertEquals(realCache.size(), 2);
+
+		// Check the second object inserted
+		assertEquals(myCache.getFromCache(Long.valueOf(2)), acl2);
+		assertEquals(myCache.getFromCache(identity2), acl2);
+
+		myCache.evictFromCache(identity2);
+		assertEquals(realCache.size(), 0);
+	}
+
+	@SuppressWarnings("rawtypes")
+	@Test
+	public void cacheOperationsAclWithParent() throws Exception {
+		Cache cache = getCache();
+		Map realCache = (Map) cache.getNativeCache();
+
+		Authentication auth = new TestingAuthenticationToken("user", "password",
+				"ROLE_GENERAL");
+		auth.setAuthenticated(true);
+		SecurityContextHolder.getContext().setAuthentication(auth);
+
+		ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, Long.valueOf(1));
+		ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS,
+				Long.valueOf(2));
+		AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
+				new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority(
+						"ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL"));
+		AuditLogger auditLogger = new ConsoleAuditLogger();
+
+		PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(
+				auditLogger);
+		SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache,
+				permissionGrantingStrategy, aclAuthorizationStrategy);
+
+		MutableAcl acl = new AclImpl(identity, Long.valueOf(1), aclAuthorizationStrategy,
+				auditLogger);
+		MutableAcl parentAcl = new AclImpl(identityParent, Long.valueOf(2),
+				aclAuthorizationStrategy, auditLogger);
+
+		acl.setParent(parentAcl);
+
+		assertEquals(0, realCache.size());
+		myCache.putInCache(acl);
+		assertEquals(realCache.size(), 4);
+
+		// Check we can get from cache the same objects we put in
+		AclImpl aclFromCache = (AclImpl) myCache.getFromCache(Long.valueOf(1));
+		assertEquals(acl, aclFromCache);
+		// SEC-951 check transient fields are set on parent
+		assertNotNull(FieldUtils.getFieldValue(aclFromCache.getParentAcl(),
+				"aclAuthorizationStrategy"));
+		assertNotNull(FieldUtils.getFieldValue(aclFromCache.getParentAcl(),
+				"permissionGrantingStrategy"));
+		assertEquals(acl, myCache.getFromCache(identity));
+		assertNotNull(FieldUtils.getFieldValue(aclFromCache, "aclAuthorizationStrategy"));
+		AclImpl parentAclFromCache = (AclImpl) myCache.getFromCache(Long.valueOf(2));
+		assertEquals(parentAcl, parentAclFromCache);
+		assertNotNull(FieldUtils.getFieldValue(parentAclFromCache,
+				"aclAuthorizationStrategy"));
+		assertEquals(parentAcl, myCache.getFromCache(identityParent));
+	}
 }

+ 10 - 10
acl/src/test/java/org/springframework/security/acls/sid/CustomSid.java

@@ -8,17 +8,17 @@ import org.springframework.security.acls.model.Sid;
  */
 public class CustomSid implements Sid {
 
-    private String sid;
+	private String sid;
 
-    public CustomSid(String sid) {
-        this.sid = sid;
-    }
+	public CustomSid(String sid) {
+		this.sid = sid;
+	}
 
-    public String getSid() {
-        return sid;
-    }
+	public String getSid() {
+		return sid;
+	}
 
-    public void setSid(String sid) {
-        this.sid = sid;
-    }
+	public void setSid(String sid) {
+		this.sid = sid;
+	}
 }

+ 33 - 30
acl/src/test/java/org/springframework/security/acls/sid/SidRetrievalStrategyTests.java

@@ -27,41 +27,44 @@ import org.springframework.security.core.authority.AuthorityUtils;
  */
 @SuppressWarnings("unchecked")
 public class SidRetrievalStrategyTests {
-    Authentication authentication = new TestingAuthenticationToken("scott", "password", "A", "B", "C");
+	Authentication authentication = new TestingAuthenticationToken("scott", "password",
+			"A", "B", "C");
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    @Test
-    public void correctSidsAreRetrieved() throws Exception {
-        SidRetrievalStrategy retrStrategy = new SidRetrievalStrategyImpl();
-        List<Sid> sids = retrStrategy.getSids(authentication);
+	@Test
+	public void correctSidsAreRetrieved() throws Exception {
+		SidRetrievalStrategy retrStrategy = new SidRetrievalStrategyImpl();
+		List<Sid> sids = retrStrategy.getSids(authentication);
 
-        assertNotNull(sids);
-        assertEquals(4, sids.size());
-        assertNotNull(sids.get(0));
-        assertTrue(sids.get(0) instanceof PrincipalSid);
+		assertNotNull(sids);
+		assertEquals(4, sids.size());
+		assertNotNull(sids.get(0));
+		assertTrue(sids.get(0) instanceof PrincipalSid);
 
-        for (int i = 1; i < sids.size(); i++) {
-            assertTrue(sids.get(i) instanceof GrantedAuthoritySid);
-        }
+		for (int i = 1; i < sids.size(); i++) {
+			assertTrue(sids.get(i) instanceof GrantedAuthoritySid);
+		}
 
-        assertEquals("scott", ((PrincipalSid) sids.get(0)).getPrincipal());
-        assertEquals("A", ((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority());
-        assertEquals("B", ((GrantedAuthoritySid) sids.get(2)).getGrantedAuthority());
-        assertEquals("C", ((GrantedAuthoritySid) sids.get(3)).getGrantedAuthority());
-    }
+		assertEquals("scott", ((PrincipalSid) sids.get(0)).getPrincipal());
+		assertEquals("A", ((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority());
+		assertEquals("B", ((GrantedAuthoritySid) sids.get(2)).getGrantedAuthority());
+		assertEquals("C", ((GrantedAuthoritySid) sids.get(3)).getGrantedAuthority());
+	}
 
-    @Test
-    public void roleHierarchyIsUsedWhenSet() throws Exception {
-        RoleHierarchy rh =  mock(RoleHierarchy.class);
-        List rhAuthorities = AuthorityUtils.createAuthorityList("D");
-        when(rh.getReachableGrantedAuthorities(anyCollection())).thenReturn(rhAuthorities);
-        SidRetrievalStrategy strat = new SidRetrievalStrategyImpl(rh);
+	@Test
+	public void roleHierarchyIsUsedWhenSet() throws Exception {
+		RoleHierarchy rh = mock(RoleHierarchy.class);
+		List rhAuthorities = AuthorityUtils.createAuthorityList("D");
+		when(rh.getReachableGrantedAuthorities(anyCollection()))
+				.thenReturn(rhAuthorities);
+		SidRetrievalStrategy strat = new SidRetrievalStrategyImpl(rh);
 
-        List<Sid> sids = strat.getSids(authentication);
-        assertEquals(2, sids.size());
-        assertNotNull(sids.get(0));
-        assertTrue(sids.get(0) instanceof PrincipalSid);
-        assertEquals("D", ((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority());
-    }
+		List<Sid> sids = strat.getSids(authentication);
+		assertEquals(2, sids.size());
+		assertNotNull(sids.get(0));
+		assertTrue(sids.get(0) instanceof PrincipalSid);
+		assertEquals("D", ((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority());
+	}
 }

+ 189 - 174
acl/src/test/java/org/springframework/security/acls/sid/SidTests.java

@@ -12,178 +12,193 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
 
 public class SidTests extends TestCase {
 
-    //~ Methods ========================================================================================================
-
-    public void testPrincipalSidConstructorsRequiredFields() throws Exception {
-        // Check one String-argument constructor
-        try {
-            String string = null;
-            new PrincipalSid(string);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            new PrincipalSid("");
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            new PrincipalSid("johndoe");
-            Assert.assertTrue(true);
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.fail("It shouldn't have thrown IllegalArgumentException");
-        }
-
-        // Check one Authentication-argument constructor
-        try {
-            Authentication authentication = null;
-            new PrincipalSid(authentication);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            Authentication authentication = new TestingAuthenticationToken(null, "password");
-            new PrincipalSid(authentication);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            Authentication authentication = new TestingAuthenticationToken("johndoe", "password");
-            new PrincipalSid(authentication);
-            Assert.assertTrue(true);
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.fail("It shouldn't have thrown IllegalArgumentException");
-        }
-    }
-
-    public void testGrantedAuthoritySidConstructorsRequiredFields() throws Exception {
-        // Check one String-argument constructor
-        try {
-            String string = null;
-            new GrantedAuthoritySid(string);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            new GrantedAuthoritySid("");
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            new GrantedAuthoritySid("ROLE_TEST");
-            Assert.assertTrue(true);
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.fail("It shouldn't have thrown IllegalArgumentException");
-        }
-
-        // Check one GrantedAuthority-argument constructor
-        try {
-            GrantedAuthority ga = null;
-            new GrantedAuthoritySid(ga);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            GrantedAuthority ga = new SimpleGrantedAuthority(null);
-            new GrantedAuthoritySid(ga);
-            Assert.fail("It should have thrown IllegalArgumentException");
-        }
-        catch (IllegalArgumentException expected) {
-            Assert.assertTrue(true);
-        }
-
-        try {
-            GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
-            new GrantedAuthoritySid(ga);
-            Assert.assertTrue(true);
-        }
-        catch (IllegalArgumentException notExpected) {
-            Assert.fail("It shouldn't have thrown IllegalArgumentException");
-        }
-    }
-
-    public void testPrincipalSidEquals() throws Exception {
-        Authentication authentication = new TestingAuthenticationToken("johndoe", "password");
-        Sid principalSid = new PrincipalSid(authentication);
-
-        Assert.assertFalse(principalSid.equals(null));
-        Assert.assertFalse(principalSid.equals("DIFFERENT_TYPE_OBJECT"));
-        Assert.assertTrue(principalSid.equals(principalSid));
-        Assert.assertTrue(principalSid.equals(new PrincipalSid(authentication)));
-        Assert.assertTrue(principalSid.equals(new PrincipalSid(new TestingAuthenticationToken("johndoe", null))));
-        Assert.assertFalse(principalSid.equals(new PrincipalSid(new TestingAuthenticationToken("scott", null))));
-        Assert.assertTrue(principalSid.equals(new PrincipalSid("johndoe")));
-        Assert.assertFalse(principalSid.equals(new PrincipalSid("scott")));
-    }
-
-    public void testGrantedAuthoritySidEquals() throws Exception {
-        GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
-        Sid gaSid = new GrantedAuthoritySid(ga);
-
-        Assert.assertFalse(gaSid.equals(null));
-        Assert.assertFalse(gaSid.equals("DIFFERENT_TYPE_OBJECT"));
-        Assert.assertTrue(gaSid.equals(gaSid));
-        Assert.assertTrue(gaSid.equals(new GrantedAuthoritySid(ga)));
-        Assert.assertTrue(gaSid.equals(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST"))));
-        Assert.assertFalse(gaSid.equals(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_NOT_EQUAL"))));
-        Assert.assertTrue(gaSid.equals(new GrantedAuthoritySid("ROLE_TEST")));
-        Assert.assertFalse(gaSid.equals(new GrantedAuthoritySid("ROLE_NOT_EQUAL")));
-    }
-
-    public void testPrincipalSidHashCode() throws Exception {
-        Authentication authentication = new TestingAuthenticationToken("johndoe", "password");
-        Sid principalSid = new PrincipalSid(authentication);
-
-        Assert.assertTrue(principalSid.hashCode() == "johndoe".hashCode());
-        Assert.assertTrue(principalSid.hashCode() == new PrincipalSid("johndoe").hashCode());
-        Assert.assertTrue(principalSid.hashCode() != new PrincipalSid("scott").hashCode());
-        Assert.assertTrue(principalSid.hashCode() != new PrincipalSid(new TestingAuthenticationToken("scott", "password")).hashCode());
-    }
-
-    public void testGrantedAuthoritySidHashCode() throws Exception {
-        GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
-        Sid gaSid = new GrantedAuthoritySid(ga);
-
-        Assert.assertTrue(gaSid.hashCode() == "ROLE_TEST".hashCode());
-        Assert.assertTrue(gaSid.hashCode() == new GrantedAuthoritySid("ROLE_TEST").hashCode());
-        Assert.assertTrue(gaSid.hashCode() != new GrantedAuthoritySid("ROLE_TEST_2").hashCode());
-        Assert.assertTrue(gaSid.hashCode() != new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST_2")).hashCode());
-    }
-
-    public void testGetters() throws Exception {
-        Authentication authentication = new TestingAuthenticationToken("johndoe", "password");
-        PrincipalSid principalSid = new PrincipalSid(authentication);
-        GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
-        GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga);
-
-        Assert.assertTrue("johndoe".equals(principalSid.getPrincipal()));
-        Assert.assertFalse("scott".equals(principalSid.getPrincipal()));
-
-        Assert.assertTrue("ROLE_TEST".equals(gaSid.getGrantedAuthority()));
-        Assert.assertFalse("ROLE_TEST2".equals(gaSid.getGrantedAuthority()));
-    }
+	// ~ Methods
+	// ========================================================================================================
+
+	public void testPrincipalSidConstructorsRequiredFields() throws Exception {
+		// Check one String-argument constructor
+		try {
+			String string = null;
+			new PrincipalSid(string);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			new PrincipalSid("");
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			new PrincipalSid("johndoe");
+			Assert.assertTrue(true);
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.fail("It shouldn't have thrown IllegalArgumentException");
+		}
+
+		// Check one Authentication-argument constructor
+		try {
+			Authentication authentication = null;
+			new PrincipalSid(authentication);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			Authentication authentication = new TestingAuthenticationToken(null,
+					"password");
+			new PrincipalSid(authentication);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			Authentication authentication = new TestingAuthenticationToken("johndoe",
+					"password");
+			new PrincipalSid(authentication);
+			Assert.assertTrue(true);
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.fail("It shouldn't have thrown IllegalArgumentException");
+		}
+	}
+
+	public void testGrantedAuthoritySidConstructorsRequiredFields() throws Exception {
+		// Check one String-argument constructor
+		try {
+			String string = null;
+			new GrantedAuthoritySid(string);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			new GrantedAuthoritySid("");
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			new GrantedAuthoritySid("ROLE_TEST");
+			Assert.assertTrue(true);
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.fail("It shouldn't have thrown IllegalArgumentException");
+		}
+
+		// Check one GrantedAuthority-argument constructor
+		try {
+			GrantedAuthority ga = null;
+			new GrantedAuthoritySid(ga);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			GrantedAuthority ga = new SimpleGrantedAuthority(null);
+			new GrantedAuthoritySid(ga);
+			Assert.fail("It should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			Assert.assertTrue(true);
+		}
+
+		try {
+			GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
+			new GrantedAuthoritySid(ga);
+			Assert.assertTrue(true);
+		}
+		catch (IllegalArgumentException notExpected) {
+			Assert.fail("It shouldn't have thrown IllegalArgumentException");
+		}
+	}
+
+	public void testPrincipalSidEquals() throws Exception {
+		Authentication authentication = new TestingAuthenticationToken("johndoe",
+				"password");
+		Sid principalSid = new PrincipalSid(authentication);
+
+		Assert.assertFalse(principalSid.equals(null));
+		Assert.assertFalse(principalSid.equals("DIFFERENT_TYPE_OBJECT"));
+		Assert.assertTrue(principalSid.equals(principalSid));
+		Assert.assertTrue(principalSid.equals(new PrincipalSid(authentication)));
+		Assert.assertTrue(principalSid.equals(new PrincipalSid(
+				new TestingAuthenticationToken("johndoe", null))));
+		Assert.assertFalse(principalSid.equals(new PrincipalSid(
+				new TestingAuthenticationToken("scott", null))));
+		Assert.assertTrue(principalSid.equals(new PrincipalSid("johndoe")));
+		Assert.assertFalse(principalSid.equals(new PrincipalSid("scott")));
+	}
+
+	public void testGrantedAuthoritySidEquals() throws Exception {
+		GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
+		Sid gaSid = new GrantedAuthoritySid(ga);
+
+		Assert.assertFalse(gaSid.equals(null));
+		Assert.assertFalse(gaSid.equals("DIFFERENT_TYPE_OBJECT"));
+		Assert.assertTrue(gaSid.equals(gaSid));
+		Assert.assertTrue(gaSid.equals(new GrantedAuthoritySid(ga)));
+		Assert.assertTrue(gaSid.equals(new GrantedAuthoritySid(
+				new SimpleGrantedAuthority("ROLE_TEST"))));
+		Assert.assertFalse(gaSid.equals(new GrantedAuthoritySid(
+				new SimpleGrantedAuthority("ROLE_NOT_EQUAL"))));
+		Assert.assertTrue(gaSid.equals(new GrantedAuthoritySid("ROLE_TEST")));
+		Assert.assertFalse(gaSid.equals(new GrantedAuthoritySid("ROLE_NOT_EQUAL")));
+	}
+
+	public void testPrincipalSidHashCode() throws Exception {
+		Authentication authentication = new TestingAuthenticationToken("johndoe",
+				"password");
+		Sid principalSid = new PrincipalSid(authentication);
+
+		Assert.assertTrue(principalSid.hashCode() == "johndoe".hashCode());
+		Assert.assertTrue(principalSid.hashCode() == new PrincipalSid("johndoe")
+				.hashCode());
+		Assert.assertTrue(principalSid.hashCode() != new PrincipalSid("scott").hashCode());
+		Assert.assertTrue(principalSid.hashCode() != new PrincipalSid(
+				new TestingAuthenticationToken("scott", "password")).hashCode());
+	}
+
+	public void testGrantedAuthoritySidHashCode() throws Exception {
+		GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
+		Sid gaSid = new GrantedAuthoritySid(ga);
+
+		Assert.assertTrue(gaSid.hashCode() == "ROLE_TEST".hashCode());
+		Assert.assertTrue(gaSid.hashCode() == new GrantedAuthoritySid("ROLE_TEST")
+				.hashCode());
+		Assert.assertTrue(gaSid.hashCode() != new GrantedAuthoritySid("ROLE_TEST_2")
+				.hashCode());
+		Assert.assertTrue(gaSid.hashCode() != new GrantedAuthoritySid(
+				new SimpleGrantedAuthority("ROLE_TEST_2")).hashCode());
+	}
+
+	public void testGetters() throws Exception {
+		Authentication authentication = new TestingAuthenticationToken("johndoe",
+				"password");
+		PrincipalSid principalSid = new PrincipalSid(authentication);
+		GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
+		GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga);
+
+		Assert.assertTrue("johndoe".equals(principalSid.getPrincipal()));
+		Assert.assertFalse("scott".equals(principalSid.getPrincipal()));
+
+		Assert.assertTrue("ROLE_TEST".equals(gaSid.getGrantedAuthority()));
+		Assert.assertFalse("ROLE_TEST2".equals(gaSid.getGrantedAuthority()));
+	}
 }

+ 143 - 133
aspects/src/test/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspectTests.java

@@ -40,150 +40,160 @@ import org.springframework.security.core.context.SecurityContextHolder;
  * @since 3.0.3
  */
 public class AnnotationSecurityAspectTests {
-    private AffirmativeBased adm;
-    private @Mock AuthenticationManager authman;
-    private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A");
-//    private TestingAuthenticationToken bob = new TestingAuthenticationToken("bob", "", "ROLE_B");
-    private AspectJMethodSecurityInterceptor interceptor;
-    private SecuredImpl secured = new SecuredImpl();
-    private SecuredImplSubclass securedSub = new SecuredImplSubclass();
-    private PrePostSecured prePostSecured = new PrePostSecured();
-
-    @Before
-    public final void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        interceptor = new AspectJMethodSecurityInterceptor();
-        AccessDecisionVoter[] voters = new AccessDecisionVoter[]
-                {new RoleVoter(), new PreInvocationAuthorizationAdviceVoter(new ExpressionBasedPreInvocationAdvice())};
-        adm = new AffirmativeBased(Arrays.<AccessDecisionVoter<? extends Object>>asList(voters));
-        interceptor.setAccessDecisionManager(adm);
-        interceptor.setAuthenticationManager(authman);
-        interceptor.setSecurityMetadataSource(new SecuredAnnotationSecurityMetadataSource());
-        AnnotationSecurityAspect secAspect = AnnotationSecurityAspect.aspectOf();
-        secAspect.setSecurityInterceptor(interceptor);
-    }
-
-    @After
-    public void clearContext() {
-        SecurityContextHolder.clearContext();
-    }
-
-    @Test
-    public void securedInterfaceMethodAllowsAllAccess() throws Exception {
-        secured.securedMethod();
-    }
-
-    @Test(expected=AuthenticationCredentialsNotFoundException.class)
-    public void securedClassMethodDeniesUnauthenticatedAccess() throws Exception {
-        secured.securedClassMethod();
-    }
-
-    @Test
-    public void securedClassMethodAllowsAccessToRoleA() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(anne);
-        secured.securedClassMethod();
-    }
-
-    @Test(expected=AccessDeniedException.class)
-    public void internalPrivateCallIsIntercepted() {
-        SecurityContextHolder.getContext().setAuthentication(anne);
-
-        try {
-            secured.publicCallsPrivate();
-            fail("Expected AccessDeniedException");
-        } catch (AccessDeniedException expected) {
-        }
-        securedSub.publicCallsPrivate();
-    }
-
-    @Test(expected=AccessDeniedException.class)
-    public void protectedMethodIsIntercepted() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(anne);
-
-        secured.protectedMethod();
-    }
-
-    @Test
-    public void overriddenProtectedMethodIsNotIntercepted() throws Exception {
-        // AspectJ doesn't inherit annotations
-        securedSub.protectedMethod();
-    }
-
-    // SEC-1262
-    @Test(expected=AccessDeniedException.class)
-    public void denyAllPreAuthorizeDeniesAccess() throws Exception {
-        configureForElAnnotations();
-        SecurityContextHolder.getContext().setAuthentication(anne);
-        prePostSecured.denyAllMethod();
-    }
-
-    @Test
-    public void postFilterIsApplied() throws Exception {
-        configureForElAnnotations();
-        SecurityContextHolder.getContext().setAuthentication(anne);
-        List<String> objects = prePostSecured.postFilterMethod();
-        assertEquals(2, objects.size());
-        assertTrue(objects.contains("apple"));
-        assertTrue(objects.contains("aubergine"));
-    }
-
-    private void configureForElAnnotations() {
-        DefaultMethodSecurityExpressionHandler eh = new DefaultMethodSecurityExpressionHandler();
-        interceptor.setSecurityMetadataSource(new PrePostAnnotationSecurityMetadataSource(
-                new ExpressionBasedAnnotationAttributeFactory(eh)));
-        interceptor.setAccessDecisionManager(adm);
-        AfterInvocationProviderManager aim = new AfterInvocationProviderManager();
-        aim.setProviders(Arrays.asList(new PostInvocationAdviceProvider(new ExpressionBasedPostInvocationAdvice(eh))));
-        interceptor.setAfterInvocationManager(aim);
-    }
+	private AffirmativeBased adm;
+	private @Mock AuthenticationManager authman;
+	private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "",
+			"ROLE_A");
+	// private TestingAuthenticationToken bob = new TestingAuthenticationToken("bob", "",
+	// "ROLE_B");
+	private AspectJMethodSecurityInterceptor interceptor;
+	private SecuredImpl secured = new SecuredImpl();
+	private SecuredImplSubclass securedSub = new SecuredImplSubclass();
+	private PrePostSecured prePostSecured = new PrePostSecured();
+
+	@Before
+	public final void setUp() throws Exception {
+		MockitoAnnotations.initMocks(this);
+		interceptor = new AspectJMethodSecurityInterceptor();
+		AccessDecisionVoter[] voters = new AccessDecisionVoter[] {
+				new RoleVoter(),
+				new PreInvocationAuthorizationAdviceVoter(
+						new ExpressionBasedPreInvocationAdvice()) };
+		adm = new AffirmativeBased(
+				Arrays.<AccessDecisionVoter<? extends Object>> asList(voters));
+		interceptor.setAccessDecisionManager(adm);
+		interceptor.setAuthenticationManager(authman);
+		interceptor
+				.setSecurityMetadataSource(new SecuredAnnotationSecurityMetadataSource());
+		AnnotationSecurityAspect secAspect = AnnotationSecurityAspect.aspectOf();
+		secAspect.setSecurityInterceptor(interceptor);
+	}
+
+	@After
+	public void clearContext() {
+		SecurityContextHolder.clearContext();
+	}
+
+	@Test
+	public void securedInterfaceMethodAllowsAllAccess() throws Exception {
+		secured.securedMethod();
+	}
+
+	@Test(expected = AuthenticationCredentialsNotFoundException.class)
+	public void securedClassMethodDeniesUnauthenticatedAccess() throws Exception {
+		secured.securedClassMethod();
+	}
+
+	@Test
+	public void securedClassMethodAllowsAccessToRoleA() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(anne);
+		secured.securedClassMethod();
+	}
+
+	@Test(expected = AccessDeniedException.class)
+	public void internalPrivateCallIsIntercepted() {
+		SecurityContextHolder.getContext().setAuthentication(anne);
+
+		try {
+			secured.publicCallsPrivate();
+			fail("Expected AccessDeniedException");
+		}
+		catch (AccessDeniedException expected) {
+		}
+		securedSub.publicCallsPrivate();
+	}
+
+	@Test(expected = AccessDeniedException.class)
+	public void protectedMethodIsIntercepted() throws Exception {
+		SecurityContextHolder.getContext().setAuthentication(anne);
+
+		secured.protectedMethod();
+	}
+
+	@Test
+	public void overriddenProtectedMethodIsNotIntercepted() throws Exception {
+		// AspectJ doesn't inherit annotations
+		securedSub.protectedMethod();
+	}
+
+	// SEC-1262
+	@Test(expected = AccessDeniedException.class)
+	public void denyAllPreAuthorizeDeniesAccess() throws Exception {
+		configureForElAnnotations();
+		SecurityContextHolder.getContext().setAuthentication(anne);
+		prePostSecured.denyAllMethod();
+	}
+
+	@Test
+	public void postFilterIsApplied() throws Exception {
+		configureForElAnnotations();
+		SecurityContextHolder.getContext().setAuthentication(anne);
+		List<String> objects = prePostSecured.postFilterMethod();
+		assertEquals(2, objects.size());
+		assertTrue(objects.contains("apple"));
+		assertTrue(objects.contains("aubergine"));
+	}
+
+	private void configureForElAnnotations() {
+		DefaultMethodSecurityExpressionHandler eh = new DefaultMethodSecurityExpressionHandler();
+		interceptor
+				.setSecurityMetadataSource(new PrePostAnnotationSecurityMetadataSource(
+						new ExpressionBasedAnnotationAttributeFactory(eh)));
+		interceptor.setAccessDecisionManager(adm);
+		AfterInvocationProviderManager aim = new AfterInvocationProviderManager();
+		aim.setProviders(Arrays.asList(new PostInvocationAdviceProvider(
+				new ExpressionBasedPostInvocationAdvice(eh))));
+		interceptor.setAfterInvocationManager(aim);
+	}
 }
 
 interface SecuredInterface {
-    @Secured("ROLE_X")
-    void securedMethod();
+	@Secured("ROLE_X")
+	void securedMethod();
 }
 
 class SecuredImpl implements SecuredInterface {
-    // Not really secured because AspectJ doesn't inherit annotations from interfaces
-    public void securedMethod() {
-    }
-
-    @Secured("ROLE_A")
-    public void securedClassMethod() {
-    }
-
-    @Secured("ROLE_X")
-    private void privateMethod() {
-    }
-
-    @Secured("ROLE_X")
-    protected void protectedMethod() {
-    }
-
-    @Secured("ROLE_X")
-    public void publicCallsPrivate() {
-        privateMethod();
-    }
+	// Not really secured because AspectJ doesn't inherit annotations from interfaces
+	public void securedMethod() {
+	}
+
+	@Secured("ROLE_A")
+	public void securedClassMethod() {
+	}
+
+	@Secured("ROLE_X")
+	private void privateMethod() {
+	}
+
+	@Secured("ROLE_X")
+	protected void protectedMethod() {
+	}
+
+	@Secured("ROLE_X")
+	public void publicCallsPrivate() {
+		privateMethod();
+	}
 }
 
 class SecuredImplSubclass extends SecuredImpl {
-    protected void protectedMethod() {
-    }
+	protected void protectedMethod() {
+	}
 
-    public void publicCallsPrivate() {
-        super.publicCallsPrivate();
-    }
+	public void publicCallsPrivate() {
+		super.publicCallsPrivate();
+	}
 }
 
 class PrePostSecured {
-    @PreAuthorize("denyAll")
-    public void denyAllMethod() {
-    }
-
-    @PostFilter("filterObject.startsWith('a')")
-    public List<String> postFilterMethod() {
-        ArrayList<String> objects = new ArrayList<String>();
-        objects.addAll(Arrays.asList(new String[] {"apple", "banana", "aubergine", "orange"}));
-        return objects;
-    }
+	@PreAuthorize("denyAll")
+	public void denyAllMethod() {
+	}
+
+	@PostFilter("filterObject.startsWith('a')")
+	public List<String> postFilterMethod() {
+		ArrayList<String> objects = new ArrayList<String>();
+		objects.addAll(Arrays.asList(new String[] { "apple", "banana", "aubergine",
+				"orange" }));
+		return objects;
+	}
 }

+ 8 - 7
cas/src/main/java/org/springframework/security/cas/SamlServiceProperties.java

@@ -15,19 +15,20 @@
 package org.springframework.security.cas;
 
 /**
- * Sets the appropriate parameters for CAS's implementation of SAML (which is not guaranteed to be actually SAML compliant).
+ * Sets the appropriate parameters for CAS's implementation of SAML (which is not
+ * guaranteed to be actually SAML compliant).
  *
  * @author Scott Battaglia
  * @since 3.0
  */
 public final class SamlServiceProperties extends ServiceProperties {
 
-    public static final String DEFAULT_SAML_ARTIFACT_PARAMETER = "SAMLart";
+	public static final String DEFAULT_SAML_ARTIFACT_PARAMETER = "SAMLart";
 
-    public static final String DEFAULT_SAML_SERVICE_PARAMETER = "TARGET";
+	public static final String DEFAULT_SAML_SERVICE_PARAMETER = "TARGET";
 
-    public SamlServiceProperties() {
-        super.setArtifactParameter(DEFAULT_SAML_ARTIFACT_PARAMETER);
-        super.setServiceParameter(DEFAULT_SAML_SERVICE_PARAMETER);
-    }
+	public SamlServiceProperties() {
+		super.setArtifactParameter(DEFAULT_SAML_ARTIFACT_PARAMETER);
+		super.setServiceParameter(DEFAULT_SAML_SERVICE_PARAMETER);
+	}
 }

+ 111 - 106
cas/src/main/java/org/springframework/security/cas/ServiceProperties.java

@@ -18,118 +18,123 @@ package org.springframework.security.cas;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.util.Assert;
 
-
 /**
  * Stores properties related to this CAS service.
  * <p>
- * Each web application capable of processing CAS tickets is known as a service.
- * This class stores the properties that are relevant to the local CAS service, being the application
- * that is being secured by Spring Security.
+ * Each web application capable of processing CAS tickets is known as a service. This
+ * class stores the properties that are relevant to the local CAS service, being the
+ * application that is being secured by Spring Security.
  *
  * @author Ben Alex
  */
 public class ServiceProperties implements InitializingBean {
 
-    public static final String DEFAULT_CAS_ARTIFACT_PARAMETER = "ticket";
-
-    public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service";
-
-    //~ Instance fields ================================================================================================
-
-    private String service;
-
-    private boolean authenticateAllArtifacts;
-
-    private boolean sendRenew = false;
-
-    private String artifactParameter = DEFAULT_CAS_ARTIFACT_PARAMETER;
-
-    private String serviceParameter = DEFAULT_CAS_SERVICE_PARAMETER;
-
-    //~ Methods ========================================================================================================
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.hasLength(this.service, "service cannot be empty.");
-        Assert.hasLength(this.artifactParameter, "artifactParameter cannot be empty.");
-        Assert.hasLength(this.serviceParameter, "serviceParameter cannot be empty.");
-    }
-
-    /**
-     * Represents the service the user is authenticating to.
-     * <p>
-     * This service is the callback URL belonging to the local Spring Security System for Spring secured application.
-     * For example,
-     * <pre>
-     * https://www.mycompany.com/application/login/cas
-     * </pre>
-     *
-     * @return the URL of the service the user is authenticating to
-     */
-    public final String getService() {
-        return this.service;
-    }
-
-    /**
-     * Indicates whether the <code>renew</code> parameter should be sent to the CAS login URL and CAS
-     * validation URL.
-     * <p>
-     * If <code>true</code>, it will force CAS to authenticate the user again (even if the
-     * user has previously authenticated). During ticket validation it will require the ticket was generated as a
-     * consequence of an explicit login. High security applications would probably set this to <code>true</code>.
-     * Defaults to <code>false</code>, providing automated single sign on.
-     *
-     * @return whether to send the <code>renew</code> parameter to CAS
-     */
-    public final boolean isSendRenew() {
-        return this.sendRenew;
-    }
-
-    public final void setSendRenew(final boolean sendRenew) {
-        this.sendRenew = sendRenew;
-    }
-
-    public final void setService(final String service) {
-        this.service = service;
-    }
-
-    public final String getArtifactParameter() {
-        return this.artifactParameter;
-    }
-
-    /**
-     * Configures the Request Parameter to look for when attempting to see if a CAS ticket was sent from the server.
-     *
-     * @param artifactParameter the id to use.  Default is "ticket".
-     */
-    public final void setArtifactParameter(final String artifactParameter) {
-        this.artifactParameter = artifactParameter;
-    }
-
-    /**
-     * Configures the Request parameter to look for when attempting to send a request to CAS.
-     *
-     * @return the service parameter to use.  Default is "service".
-     */
-    public final String getServiceParameter() {
-        return serviceParameter;
-    }
-
-    public final void setServiceParameter(final String serviceParameter) {
-        this.serviceParameter = serviceParameter;
-    }
-
-    public final boolean isAuthenticateAllArtifacts() {
-        return authenticateAllArtifacts;
-    }
-
-    /**
-     * If true, then any non-null artifact (ticket) should be authenticated.
-     * Additionally, the service will be determined dynamically in order to
-     * ensure the service matches the expected value for this artifact.
-     *
-     * @param authenticateAllArtifacts
-     */
-    public final void setAuthenticateAllArtifacts(final boolean authenticateAllArtifacts) {
-        this.authenticateAllArtifacts = authenticateAllArtifacts;
-    }
+	public static final String DEFAULT_CAS_ARTIFACT_PARAMETER = "ticket";
+
+	public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service";
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private String service;
+
+	private boolean authenticateAllArtifacts;
+
+	private boolean sendRenew = false;
+
+	private String artifactParameter = DEFAULT_CAS_ARTIFACT_PARAMETER;
+
+	private String serviceParameter = DEFAULT_CAS_SERVICE_PARAMETER;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void afterPropertiesSet() throws Exception {
+		Assert.hasLength(this.service, "service cannot be empty.");
+		Assert.hasLength(this.artifactParameter, "artifactParameter cannot be empty.");
+		Assert.hasLength(this.serviceParameter, "serviceParameter cannot be empty.");
+	}
+
+	/**
+	 * Represents the service the user is authenticating to.
+	 * <p>
+	 * This service is the callback URL belonging to the local Spring Security System for
+	 * Spring secured application. For example,
+	 * 
+	 * <pre>
+	 * https://www.mycompany.com/application/login/cas
+	 * </pre>
+	 *
+	 * @return the URL of the service the user is authenticating to
+	 */
+	public final String getService() {
+		return this.service;
+	}
+
+	/**
+	 * Indicates whether the <code>renew</code> parameter should be sent to the CAS login
+	 * URL and CAS validation URL.
+	 * <p>
+	 * If <code>true</code>, it will force CAS to authenticate the user again (even if the
+	 * user has previously authenticated). During ticket validation it will require the
+	 * ticket was generated as a consequence of an explicit login. High security
+	 * applications would probably set this to <code>true</code>. Defaults to
+	 * <code>false</code>, providing automated single sign on.
+	 *
+	 * @return whether to send the <code>renew</code> parameter to CAS
+	 */
+	public final boolean isSendRenew() {
+		return this.sendRenew;
+	}
+
+	public final void setSendRenew(final boolean sendRenew) {
+		this.sendRenew = sendRenew;
+	}
+
+	public final void setService(final String service) {
+		this.service = service;
+	}
+
+	public final String getArtifactParameter() {
+		return this.artifactParameter;
+	}
+
+	/**
+	 * Configures the Request Parameter to look for when attempting to see if a CAS ticket
+	 * was sent from the server.
+	 *
+	 * @param artifactParameter the id to use. Default is "ticket".
+	 */
+	public final void setArtifactParameter(final String artifactParameter) {
+		this.artifactParameter = artifactParameter;
+	}
+
+	/**
+	 * Configures the Request parameter to look for when attempting to send a request to
+	 * CAS.
+	 *
+	 * @return the service parameter to use. Default is "service".
+	 */
+	public final String getServiceParameter() {
+		return serviceParameter;
+	}
+
+	public final void setServiceParameter(final String serviceParameter) {
+		this.serviceParameter = serviceParameter;
+	}
+
+	public final boolean isAuthenticateAllArtifacts() {
+		return authenticateAllArtifacts;
+	}
+
+	/**
+	 * If true, then any non-null artifact (ticket) should be authenticated. Additionally,
+	 * the service will be determined dynamically in order to ensure the service matches
+	 * the expected value for this artifact.
+	 *
+	 * @param authenticateAllArtifacts
+	 */
+	public final void setAuthenticateAllArtifacts(final boolean authenticateAllArtifacts) {
+		this.authenticateAllArtifacts = authenticateAllArtifacts;
+	}
 }

+ 17 - 17
cas/src/main/java/org/springframework/security/cas/authentication/CasAssertionAuthenticationToken.java

@@ -29,28 +29,28 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
  */
 public final class CasAssertionAuthenticationToken extends AbstractAuthenticationToken {
 
-    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
+	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 
-    private final Assertion assertion;
+	private final Assertion assertion;
 
-    private final String ticket;
+	private final String ticket;
 
-    public CasAssertionAuthenticationToken(final Assertion assertion, final String ticket) {
-        super(new ArrayList<GrantedAuthority>());
+	public CasAssertionAuthenticationToken(final Assertion assertion, final String ticket) {
+		super(new ArrayList<GrantedAuthority>());
 
-        this.assertion = assertion;
-        this.ticket = ticket;
-    }
+		this.assertion = assertion;
+		this.ticket = ticket;
+	}
 
-    public Object getPrincipal() {
-        return this.assertion.getPrincipal().getName();
-    }
+	public Object getPrincipal() {
+		return this.assertion.getPrincipal().getName();
+	}
 
-    public Object getCredentials() {
-        return this.ticket;
-    }
+	public Object getCredentials() {
+		return this.ticket;
+	}
 
-    public Assertion getAssertion() {
-        return this.assertion;
-    }
+	public Assertion getAssertion() {
+		return this.assertion;
+	}
 }

+ 226 - 194
cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java

@@ -39,204 +39,236 @@ import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper
 import org.springframework.security.core.userdetails.*;
 import org.springframework.util.Assert;
 
-
 /**
- * An {@link AuthenticationProvider} implementation that integrates with JA-SIG Central Authentication Service
- * (CAS).
+ * An {@link AuthenticationProvider} implementation that integrates with JA-SIG Central
+ * Authentication Service (CAS).
  * <p>
- * This <code>AuthenticationProvider</code> is capable of validating  {@link UsernamePasswordAuthenticationToken}
- * requests which contain a <code>principal</code> name equal to either
- * {@link CasAuthenticationFilter#CAS_STATEFUL_IDENTIFIER} or {@link CasAuthenticationFilter#CAS_STATELESS_IDENTIFIER}.
- * It can also validate a previously created {@link CasAuthenticationToken}.
+ * This <code>AuthenticationProvider</code> is capable of validating
+ * {@link UsernamePasswordAuthenticationToken} requests which contain a
+ * <code>principal</code> name equal to either
+ * {@link CasAuthenticationFilter#CAS_STATEFUL_IDENTIFIER} or
+ * {@link CasAuthenticationFilter#CAS_STATELESS_IDENTIFIER}. It can also validate a
+ * previously created {@link CasAuthenticationToken}.
  *
  * @author Ben Alex
  * @author Scott Battaglia
  */
-public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
-    //~ Static fields/initializers =====================================================================================
-
-    private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
-
-    //~ Instance fields ================================================================================================
-
-    private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
-
-    private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
-    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
-    private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
-    private String key;
-    private TicketValidator ticketValidator;
-    private ServiceProperties serviceProperties;
-    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
-
-
-    //~ Methods ========================================================================================================
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.notNull(this.authenticationUserDetailsService, "An authenticationUserDetailsService must be set");
-        Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
-        Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
-        Assert.hasText(this.key, "A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
-        Assert.notNull(this.messages, "A message source must be set");
-    }
-
-    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-        if (!supports(authentication.getClass())) {
-            return null;
-        }
-
-        if (authentication instanceof UsernamePasswordAuthenticationToken
-            && (!CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER.equals(authentication.getPrincipal().toString())
-            && !CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal().toString()))) {
-            // UsernamePasswordAuthenticationToken not CAS related
-            return null;
-        }
-
-        // If an existing CasAuthenticationToken, just check we created it
-        if (authentication instanceof CasAuthenticationToken) {
-            if (this.key.hashCode() == ((CasAuthenticationToken) authentication).getKeyHash()) {
-                return authentication;
-            } else {
-                throw new BadCredentialsException(messages.getMessage("CasAuthenticationProvider.incorrectKey",
-                        "The presented CasAuthenticationToken does not contain the expected key"));
-            }
-        }
-
-        // Ensure credentials are presented
-        if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
-            throw new BadCredentialsException(messages.getMessage("CasAuthenticationProvider.noServiceTicket",
-                    "Failed to provide a CAS service ticket to validate"));
-        }
-
-        boolean stateless = false;
-
-        if (authentication instanceof UsernamePasswordAuthenticationToken
-            && CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal())) {
-            stateless = true;
-        }
-
-        CasAuthenticationToken result = null;
-
-        if (stateless) {
-            // Try to obtain from cache
-            result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
-        }
-
-        if (result == null) {
-            result = this.authenticateNow(authentication);
-            result.setDetails(authentication.getDetails());
-        }
-
-        if (stateless) {
-            // Add to cache
-            statelessTicketCache.putTicketInCache(result);
-        }
-
-        return result;
-    }
-
-    private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
-        try {
-            final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), getServiceUrl(authentication));
-            final UserDetails userDetails = loadUserByAssertion(assertion);
-            userDetailsChecker.check(userDetails);
-            return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(),
-                    authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion);
-        } catch (final TicketValidationException e) {
-            throw new BadCredentialsException(e.getMessage(), e);
-        }
-    }
-
-    /**
-     * Gets the serviceUrl. If the {@link Authentication#getDetails()} is an
-     * instance of {@link ServiceAuthenticationDetails}, then
-     * {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise,
-     * the {@link ServiceProperties#getService()} is used.
-     *
-     * @param authentication
-     * @return
-     */
-    private String getServiceUrl(Authentication authentication) {
-        String serviceUrl;
-        if(authentication.getDetails() instanceof ServiceAuthenticationDetails) {
-            serviceUrl = ((ServiceAuthenticationDetails)authentication.getDetails()).getServiceUrl();
-        }else if(serviceProperties == null){
-            throw new IllegalStateException("serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
-        }else if(serviceProperties.getService() == null){
-            throw new IllegalStateException("serviceProperties.getService() cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
-        }else {
-            serviceUrl = serviceProperties.getService();
-        }
-        if(logger.isDebugEnabled()) {
-            logger.debug("serviceUrl = "+serviceUrl);
-        }
-        return serviceUrl;
-    }
-
-    /**
-     * Template method for retrieving the UserDetails based on the assertion.  Default is to call configured userDetailsService and pass the username.  Deployers
-     * can override this method and retrieve the user based on any criteria they desire.
-     *
-     * @param assertion The CAS Assertion.
-     * @return the UserDetails.
-     */
-    protected UserDetails loadUserByAssertion(final Assertion assertion) {
-        final CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, "");
-        return this.authenticationUserDetailsService.loadUserDetails(token);
-    }
-
-    @SuppressWarnings("unchecked")
-    /**
-     * Sets the UserDetailsService to use. This is a convenience method to invoke
-     */
-    public void setUserDetailsService(final UserDetailsService userDetailsService) {
-        this.authenticationUserDetailsService = new UserDetailsByNameServiceWrapper(userDetailsService);
-    }
-
-
-    public void setAuthenticationUserDetailsService(final AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService) {
-        this.authenticationUserDetailsService = authenticationUserDetailsService;
-    }
-
-    public void setServiceProperties(final ServiceProperties serviceProperties) {
-        this.serviceProperties = serviceProperties;
-    }
-
-    protected String getKey() {
-        return key;
-    }
-
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public StatelessTicketCache getStatelessTicketCache() {
-        return statelessTicketCache;
-    }
-
-    protected TicketValidator getTicketValidator() {
-        return ticketValidator;
-    }
-
-    public void setMessageSource(final MessageSource messageSource) {
-        this.messages = new MessageSourceAccessor(messageSource);
-    }
-
-    public void setStatelessTicketCache(final StatelessTicketCache statelessTicketCache) {
-        this.statelessTicketCache = statelessTicketCache;
-    }
-
-    public void setTicketValidator(final TicketValidator ticketValidator) {
-        this.ticketValidator = ticketValidator;
-    }
-
-    public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
-        this.authoritiesMapper = authoritiesMapper;
-    }
-
-    public boolean supports(final Class<?> authentication) {
-        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) ||
-           (CasAuthenticationToken.class.isAssignableFrom(authentication)) ||
-           (CasAssertionAuthenticationToken.class.isAssignableFrom(authentication));
-    }
+public class CasAuthenticationProvider implements AuthenticationProvider,
+		InitializingBean, MessageSourceAware {
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
+
+	private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
+	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
+	private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
+	private String key;
+	private TicketValidator ticketValidator;
+	private ServiceProperties serviceProperties;
+	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void afterPropertiesSet() throws Exception {
+		Assert.notNull(this.authenticationUserDetailsService,
+				"An authenticationUserDetailsService must be set");
+		Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
+		Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
+		Assert.hasText(
+				this.key,
+				"A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
+		Assert.notNull(this.messages, "A message source must be set");
+	}
+
+	public Authentication authenticate(Authentication authentication)
+			throws AuthenticationException {
+		if (!supports(authentication.getClass())) {
+			return null;
+		}
+
+		if (authentication instanceof UsernamePasswordAuthenticationToken
+				&& (!CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER
+						.equals(authentication.getPrincipal().toString()) && !CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER
+						.equals(authentication.getPrincipal().toString()))) {
+			// UsernamePasswordAuthenticationToken not CAS related
+			return null;
+		}
+
+		// If an existing CasAuthenticationToken, just check we created it
+		if (authentication instanceof CasAuthenticationToken) {
+			if (this.key.hashCode() == ((CasAuthenticationToken) authentication)
+					.getKeyHash()) {
+				return authentication;
+			}
+			else {
+				throw new BadCredentialsException(
+						messages.getMessage("CasAuthenticationProvider.incorrectKey",
+								"The presented CasAuthenticationToken does not contain the expected key"));
+			}
+		}
+
+		// Ensure credentials are presented
+		if ((authentication.getCredentials() == null)
+				|| "".equals(authentication.getCredentials())) {
+			throw new BadCredentialsException(messages.getMessage(
+					"CasAuthenticationProvider.noServiceTicket",
+					"Failed to provide a CAS service ticket to validate"));
+		}
+
+		boolean stateless = false;
+
+		if (authentication instanceof UsernamePasswordAuthenticationToken
+				&& CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication
+						.getPrincipal())) {
+			stateless = true;
+		}
+
+		CasAuthenticationToken result = null;
+
+		if (stateless) {
+			// Try to obtain from cache
+			result = statelessTicketCache.getByTicketId(authentication.getCredentials()
+					.toString());
+		}
+
+		if (result == null) {
+			result = this.authenticateNow(authentication);
+			result.setDetails(authentication.getDetails());
+		}
+
+		if (stateless) {
+			// Add to cache
+			statelessTicketCache.putTicketInCache(result);
+		}
+
+		return result;
+	}
+
+	private CasAuthenticationToken authenticateNow(final Authentication authentication)
+			throws AuthenticationException {
+		try {
+			final Assertion assertion = this.ticketValidator.validate(authentication
+					.getCredentials().toString(), getServiceUrl(authentication));
+			final UserDetails userDetails = loadUserByAssertion(assertion);
+			userDetailsChecker.check(userDetails);
+			return new CasAuthenticationToken(this.key, userDetails,
+					authentication.getCredentials(),
+					authoritiesMapper.mapAuthorities(userDetails.getAuthorities()),
+					userDetails, assertion);
+		}
+		catch (final TicketValidationException e) {
+			throw new BadCredentialsException(e.getMessage(), e);
+		}
+	}
+
+	/**
+	 * Gets the serviceUrl. If the {@link Authentication#getDetails()} is an instance of
+	 * {@link ServiceAuthenticationDetails}, then
+	 * {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise, the
+	 * {@link ServiceProperties#getService()} is used.
+	 *
+	 * @param authentication
+	 * @return
+	 */
+	private String getServiceUrl(Authentication authentication) {
+		String serviceUrl;
+		if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
+			serviceUrl = ((ServiceAuthenticationDetails) authentication.getDetails())
+					.getServiceUrl();
+		}
+		else if (serviceProperties == null) {
+			throw new IllegalStateException(
+					"serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
+		}
+		else if (serviceProperties.getService() == null) {
+			throw new IllegalStateException(
+					"serviceProperties.getService() cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
+		}
+		else {
+			serviceUrl = serviceProperties.getService();
+		}
+		if (logger.isDebugEnabled()) {
+			logger.debug("serviceUrl = " + serviceUrl);
+		}
+		return serviceUrl;
+	}
+
+	/**
+	 * Template method for retrieving the UserDetails based on the assertion. Default is
+	 * to call configured userDetailsService and pass the username. Deployers can override
+	 * this method and retrieve the user based on any criteria they desire.
+	 *
+	 * @param assertion The CAS Assertion.
+	 * @return the UserDetails.
+	 */
+	protected UserDetails loadUserByAssertion(final Assertion assertion) {
+		final CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(
+				assertion, "");
+		return this.authenticationUserDetailsService.loadUserDetails(token);
+	}
+
+	@SuppressWarnings("unchecked")
+	/**
+	 * Sets the UserDetailsService to use. This is a convenience method to invoke
+	 */
+	public void setUserDetailsService(final UserDetailsService userDetailsService) {
+		this.authenticationUserDetailsService = new UserDetailsByNameServiceWrapper(
+				userDetailsService);
+	}
+
+	public void setAuthenticationUserDetailsService(
+			final AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService) {
+		this.authenticationUserDetailsService = authenticationUserDetailsService;
+	}
+
+	public void setServiceProperties(final ServiceProperties serviceProperties) {
+		this.serviceProperties = serviceProperties;
+	}
+
+	protected String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public StatelessTicketCache getStatelessTicketCache() {
+		return statelessTicketCache;
+	}
+
+	protected TicketValidator getTicketValidator() {
+		return ticketValidator;
+	}
+
+	public void setMessageSource(final MessageSource messageSource) {
+		this.messages = new MessageSourceAccessor(messageSource);
+	}
+
+	public void setStatelessTicketCache(final StatelessTicketCache statelessTicketCache) {
+		this.statelessTicketCache = statelessTicketCache;
+	}
+
+	public void setTicketValidator(final TicketValidator ticketValidator) {
+		this.ticketValidator = ticketValidator;
+	}
+
+	public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
+		this.authoritiesMapper = authoritiesMapper;
+	}
+
+	public boolean supports(final Class<?> authentication) {
+		return (UsernamePasswordAuthenticationToken.class
+				.isAssignableFrom(authentication))
+				|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
+				|| (CasAssertionAuthenticationToken.class
+						.isAssignableFrom(authentication));
+	}
 }

+ 110 - 99
cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java

@@ -30,103 +30,114 @@ import org.springframework.security.core.userdetails.UserDetails;
  * @author Ben Alex
  * @author Scott Battaglia
  */
-public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
-
-    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
-
-    //~ Instance fields ================================================================================================
-    private final Object credentials;
-    private final Object principal;
-    private final UserDetails userDetails;
-    private final int keyHash;
-    private final Assertion assertion;
-
-    //~ Constructors ===================================================================================================
-
-    /**
-     * Constructor.
-     *
-     * @param key to identify if this object made by a given {@link
-     *        CasAuthenticationProvider}
-     * @param principal typically the UserDetails object (cannot  be <code>null</code>)
-     * @param credentials the service/proxy ticket ID from CAS (cannot be
-     *        <code>null</code>)
-     * @param authorities the authorities granted to the user (from the {@link
-     *        org.springframework.security.core.userdetails.UserDetailsService}) (cannot be <code>null</code>)
-     * @param userDetails the user details (from the {@link
-     *        org.springframework.security.core.userdetails.UserDetailsService}) (cannot be <code>null</code>)
-     * @param assertion the assertion returned from the CAS servers.  It contains the principal and how to obtain a
-     *        proxy ticket for the user.
-     *
-     * @throws IllegalArgumentException if a <code>null</code> was passed
-     */
-    public CasAuthenticationToken(final String key, final Object principal, final Object credentials,
-        final Collection<? extends GrantedAuthority> authorities, final UserDetails userDetails, final Assertion assertion) {
-        super(authorities);
-
-        if ((key == null) || ("".equals(key)) || (principal == null) || "".equals(principal) || (credentials == null)
-            || "".equals(credentials) || (authorities == null) || (userDetails == null) || (assertion == null)) {
-            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
-        }
-
-        this.keyHash = key.hashCode();
-        this.principal = principal;
-        this.credentials = credentials;
-        this.userDetails = userDetails;
-        this.assertion = assertion;
-        setAuthenticated(true);
-    }
-
-    //~ Methods ========================================================================================================
-
-    public boolean equals(final Object obj) {
-        if (!super.equals(obj)) {
-            return false;
-        }
-
-        if (obj instanceof CasAuthenticationToken) {
-            CasAuthenticationToken test = (CasAuthenticationToken) obj;
-
-            if (!this.assertion.equals(test.getAssertion())) {
-                return false;
-            }
-
-            if (this.getKeyHash() != test.getKeyHash()) {
-                return false;
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public Object getCredentials() {
-        return this.credentials;
-    }
-
-    public int getKeyHash() {
-        return this.keyHash;
-    }
-
-    public Object getPrincipal() {
-        return this.principal;
-    }
-
-    public Assertion getAssertion() {
-        return this.assertion;
-    }
-
-    public UserDetails getUserDetails() {
-        return userDetails;
-    }
-
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(super.toString());
-        sb.append(" Assertion: ").append(this.assertion);
-        sb.append(" Credentials (Service/Proxy Ticket): ").append(this.credentials);
-
-        return (sb.toString());
-    }
+public class CasAuthenticationToken extends AbstractAuthenticationToken implements
+		Serializable {
+
+	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
+
+	// ~ Instance fields
+	// ================================================================================================
+	private final Object credentials;
+	private final Object principal;
+	private final UserDetails userDetails;
+	private final int keyHash;
+	private final Assertion assertion;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	/**
+	 * Constructor.
+	 *
+	 * @param key to identify if this object made by a given
+	 * {@link CasAuthenticationProvider}
+	 * @param principal typically the UserDetails object (cannot be <code>null</code>)
+	 * @param credentials the service/proxy ticket ID from CAS (cannot be
+	 * <code>null</code>)
+	 * @param authorities the authorities granted to the user (from the
+	 * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+	 * be <code>null</code>)
+	 * @param userDetails the user details (from the
+	 * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+	 * be <code>null</code>)
+	 * @param assertion the assertion returned from the CAS servers. It contains the
+	 * principal and how to obtain a proxy ticket for the user.
+	 *
+	 * @throws IllegalArgumentException if a <code>null</code> was passed
+	 */
+	public CasAuthenticationToken(final String key, final Object principal,
+			final Object credentials,
+			final Collection<? extends GrantedAuthority> authorities,
+			final UserDetails userDetails, final Assertion assertion) {
+		super(authorities);
+
+		if ((key == null) || ("".equals(key)) || (principal == null)
+				|| "".equals(principal) || (credentials == null)
+				|| "".equals(credentials) || (authorities == null)
+				|| (userDetails == null) || (assertion == null)) {
+			throw new IllegalArgumentException(
+					"Cannot pass null or empty values to constructor");
+		}
+
+		this.keyHash = key.hashCode();
+		this.principal = principal;
+		this.credentials = credentials;
+		this.userDetails = userDetails;
+		this.assertion = assertion;
+		setAuthenticated(true);
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public boolean equals(final Object obj) {
+		if (!super.equals(obj)) {
+			return false;
+		}
+
+		if (obj instanceof CasAuthenticationToken) {
+			CasAuthenticationToken test = (CasAuthenticationToken) obj;
+
+			if (!this.assertion.equals(test.getAssertion())) {
+				return false;
+			}
+
+			if (this.getKeyHash() != test.getKeyHash()) {
+				return false;
+			}
+
+			return true;
+		}
+
+		return false;
+	}
+
+	public Object getCredentials() {
+		return this.credentials;
+	}
+
+	public int getKeyHash() {
+		return this.keyHash;
+	}
+
+	public Object getPrincipal() {
+		return this.principal;
+	}
+
+	public Assertion getAssertion() {
+		return this.assertion;
+	}
+
+	public UserDetails getUserDetails() {
+		return userDetails;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(super.toString());
+		sb.append(" Assertion: ").append(this.assertion);
+		sb.append(" Credentials (Service/Proxy Ticket): ").append(this.credentials);
+
+		return (sb.toString());
+	}
 }

+ 43 - 39
cas/src/main/java/org/springframework/security/cas/authentication/EhCacheBasedTicketCache.java

@@ -23,64 +23,68 @@ import org.apache.commons.logging.LogFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.util.Assert;
 
-
 /**
- * Caches tickets using a Spring IoC defined <a href="http://ehcache.sourceforge.net">EHCACHE</a>.
+ * Caches tickets using a Spring IoC defined <a
+ * href="http://ehcache.sourceforge.net">EHCACHE</a>.
  *
  * @author Ben Alex
  */
 public class EhCacheBasedTicketCache implements StatelessTicketCache, InitializingBean {
-    //~ Static fields/initializers =====================================================================================
+	// ~ Static fields/initializers
+	// =====================================================================================
 
-    private static final Log logger = LogFactory.getLog(EhCacheBasedTicketCache.class);
+	private static final Log logger = LogFactory.getLog(EhCacheBasedTicketCache.class);
 
-    //~ Instance fields ================================================================================================
+	// ~ Instance fields
+	// ================================================================================================
 
-    private Ehcache cache;
+	private Ehcache cache;
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public void afterPropertiesSet() throws Exception {
-        Assert.notNull(cache, "cache mandatory");
-    }
+	public void afterPropertiesSet() throws Exception {
+		Assert.notNull(cache, "cache mandatory");
+	}
 
-    public CasAuthenticationToken getByTicketId(final String serviceTicket) {
-        final Element element = cache.get(serviceTicket);
+	public CasAuthenticationToken getByTicketId(final String serviceTicket) {
+		final Element element = cache.get(serviceTicket);
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Cache hit: " + (element != null) + "; service ticket: " + serviceTicket);
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("Cache hit: " + (element != null) + "; service ticket: "
+					+ serviceTicket);
+		}
 
-        return element == null ? null : (CasAuthenticationToken) element.getValue();
-    }
+		return element == null ? null : (CasAuthenticationToken) element.getValue();
+	}
 
-    public Ehcache getCache() {
-        return cache;
-    }
+	public Ehcache getCache() {
+		return cache;
+	}
 
-    public void putTicketInCache(final CasAuthenticationToken token) {
-        final Element element = new Element(token.getCredentials().toString(), token);
+	public void putTicketInCache(final CasAuthenticationToken token) {
+		final Element element = new Element(token.getCredentials().toString(), token);
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Cache put: " + element.getKey());
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("Cache put: " + element.getKey());
+		}
 
-        cache.put(element);
-    }
+		cache.put(element);
+	}
 
-    public void removeTicketFromCache(final CasAuthenticationToken token) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Cache remove: " + token.getCredentials().toString());
-        }
+	public void removeTicketFromCache(final CasAuthenticationToken token) {
+		if (logger.isDebugEnabled()) {
+			logger.debug("Cache remove: " + token.getCredentials().toString());
+		}
 
-        this.removeTicketFromCache(token.getCredentials().toString());
-    }
+		this.removeTicketFromCache(token.getCredentials().toString());
+	}
 
-    public void removeTicketFromCache(final String serviceTicket) {
-        cache.remove(serviceTicket);
-    }
+	public void removeTicketFromCache(final String serviceTicket) {
+		cache.remove(serviceTicket);
+	}
 
-    public void setCache(final Ehcache cache) {
-        this.cache = cache;
-    }
+	public void setCache(final Ehcache cache) {
+		this.cache = cache;
+	}
 }

+ 29 - 30
cas/src/main/java/org/springframework/security/cas/authentication/NullStatelessTicketCache.java

@@ -14,46 +14,45 @@
  */
 package org.springframework.security.cas.authentication;
 
-
 /**
- * Implementation of @link {@link StatelessTicketCache} that has no backing cache.  Useful
+ * Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful
  * in instances where storing of tickets for stateless session management is not required.
  * <p>
- * This is the default StatelessTicketCache of the @link {@link CasAuthenticationProvider} to
- * eliminate the unnecessary dependency on EhCache that applications have even if they are not using
- * the stateless session management.
+ * This is the default StatelessTicketCache of the @link {@link CasAuthenticationProvider}
+ * to eliminate the unnecessary dependency on EhCache that applications have even if they
+ * are not using the stateless session management.
  *
  * @author Scott Battaglia
  *
- *@see CasAuthenticationProvider
+ * @see CasAuthenticationProvider
  */
 public final class NullStatelessTicketCache implements StatelessTicketCache {
 
-    /**
-     * @return null since we are not storing any tickets.
-     */
-    public CasAuthenticationToken getByTicketId(final String serviceTicket) {
-        return null;
-    }
+	/**
+	 * @return null since we are not storing any tickets.
+	 */
+	public CasAuthenticationToken getByTicketId(final String serviceTicket) {
+		return null;
+	}
 
-    /**
-     * This is a no-op since we are not storing tickets.
-     */
-    public void putTicketInCache(final CasAuthenticationToken token) {
-        // nothing to do
-    }
+	/**
+	 * This is a no-op since we are not storing tickets.
+	 */
+	public void putTicketInCache(final CasAuthenticationToken token) {
+		// nothing to do
+	}
 
-    /**
-     * This is a no-op since we are not storing tickets.
-     */
-    public void removeTicketFromCache(final CasAuthenticationToken token) {
-        // nothing to do
-    }
+	/**
+	 * This is a no-op since we are not storing tickets.
+	 */
+	public void removeTicketFromCache(final CasAuthenticationToken token) {
+		// nothing to do
+	}
 
-    /**
-     * This is a no-op since we are not storing tickets.
-     */
-    public void removeTicketFromCache(final String serviceTicket) {
-        // nothing to do
-    }
+	/**
+	 * This is a no-op since we are not storing tickets.
+	 */
+	public void removeTicketFromCache(final String serviceTicket) {
+		// nothing to do
+	}
 }

+ 40 - 34
cas/src/main/java/org/springframework/security/cas/authentication/SpringCacheBasedTicketCache.java

@@ -20,7 +20,6 @@ import org.apache.commons.logging.LogFactory;
 import org.springframework.cache.Cache;
 import org.springframework.util.Assert;
 
-
 /**
  * Caches tickets using a Spring IoC defined {@link Cache}.
  *
@@ -29,52 +28,59 @@ import org.springframework.util.Assert;
  *
  */
 public class SpringCacheBasedTicketCache implements StatelessTicketCache {
-    //~ Static fields/initializers =====================================================================================
+	// ~ Static fields/initializers
+	// =====================================================================================
 
-    private static final Log logger = LogFactory.getLog(SpringCacheBasedTicketCache.class);
+	private static final Log logger = LogFactory
+			.getLog(SpringCacheBasedTicketCache.class);
 
-    //~ Instance fields ================================================================================================
+	// ~ Instance fields
+	// ================================================================================================
 
-    private final Cache cache;
+	private final Cache cache;
 
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    public SpringCacheBasedTicketCache(Cache cache) throws Exception {
-        Assert.notNull(cache, "cache mandatory");
-        this.cache = cache;
-    }
+	public SpringCacheBasedTicketCache(Cache cache) throws Exception {
+		Assert.notNull(cache, "cache mandatory");
+		this.cache = cache;
+	}
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    public CasAuthenticationToken getByTicketId(final String serviceTicket) {
-        final Cache.ValueWrapper element = serviceTicket != null ? cache.get(serviceTicket) : null;
+	public CasAuthenticationToken getByTicketId(final String serviceTicket) {
+		final Cache.ValueWrapper element = serviceTicket != null ? cache
+				.get(serviceTicket) : null;
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Cache hit: " + (element != null) + "; service ticket: " + serviceTicket);
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("Cache hit: " + (element != null) + "; service ticket: "
+					+ serviceTicket);
+		}
 
-        return element == null ? null : (CasAuthenticationToken) element.get();
-    }
+		return element == null ? null : (CasAuthenticationToken) element.get();
+	}
 
-    public void putTicketInCache(final CasAuthenticationToken token) {
-        String key = token.getCredentials().toString();
+	public void putTicketInCache(final CasAuthenticationToken token) {
+		String key = token.getCredentials().toString();
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Cache put: " + key);
-        }
+		if (logger.isDebugEnabled()) {
+			logger.debug("Cache put: " + key);
+		}
 
-        cache.put(key, token);
-    }
+		cache.put(key, token);
+	}
 
-    public void removeTicketFromCache(final CasAuthenticationToken token) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Cache remove: " + token.getCredentials().toString());
-        }
+	public void removeTicketFromCache(final CasAuthenticationToken token) {
+		if (logger.isDebugEnabled()) {
+			logger.debug("Cache remove: " + token.getCredentials().toString());
+		}
 
-        this.removeTicketFromCache(token.getCredentials().toString());
-    }
+		this.removeTicketFromCache(token.getCredentials().toString());
+	}
 
-    public void removeTicketFromCache(final String serviceTicket) {
-        cache.evict(serviceTicket);
-    }
+	public void removeTicketFromCache(final String serviceTicket) {
+		cache.evict(serviceTicket);
+	}
 }

+ 68 - 70
cas/src/main/java/org/springframework/security/cas/authentication/StatelessTicketCache.java

@@ -19,97 +19,95 @@ package org.springframework.security.cas.authentication;
  * Caches CAS service tickets and CAS proxy tickets for stateless connections.
  *
  * <p>
- * When a service ticket or proxy ticket is validated against the CAS server,
- * it is unable to be used again. Most types of callers are stateful and are
- * associated with a given <code>HttpSession</code>. This allows the
- * affirmative CAS validation outcome to be stored in the
- * <code>HttpSession</code>, meaning the removal of the ticket from the CAS
+ * When a service ticket or proxy ticket is validated against the CAS server, it is unable
+ * to be used again. Most types of callers are stateful and are associated with a given
+ * <code>HttpSession</code>. This allows the affirmative CAS validation outcome to be
+ * stored in the <code>HttpSession</code>, meaning the removal of the ticket from the CAS
  * server is not an issue.
  * </p>
  *
  * <P>
  * Stateless callers, such as remoting protocols, cannot take advantage of
- * <code>HttpSession</code>. If the stateless caller is located a significant
- * network distance from the CAS server, acquiring a fresh service ticket or
- * proxy ticket for each invocation would be expensive.
+ * <code>HttpSession</code>. If the stateless caller is located a significant network
+ * distance from the CAS server, acquiring a fresh service ticket or proxy ticket for each
+ * invocation would be expensive.
  * </p>
  *
  * <P>
- * To avoid this issue with stateless callers, it is expected stateless callers
- * will obtain a single service ticket or proxy ticket, and then present this
- * same ticket to the Spring Security secured application on each
- * occasion. As no <code>HttpSession</code> is available for such callers, the
- * affirmative CAS validation outcome cannot be stored in this location.
+ * To avoid this issue with stateless callers, it is expected stateless callers will
+ * obtain a single service ticket or proxy ticket, and then present this same ticket to
+ * the Spring Security secured application on each occasion. As no
+ * <code>HttpSession</code> is available for such callers, the affirmative CAS validation
+ * outcome cannot be stored in this location.
  * </p>
  *
  * <P>
- * The <code>StatelessTicketCache</code> enables the service tickets and proxy
- * tickets belonging to stateless callers to be placed in a cache. This
- * in-memory cache stores the <code>CasAuthenticationToken</code>, effectively
- * providing the same capability as a <code>HttpSession</code> with the ticket
- * identifier being the key rather than a session identifier.
+ * The <code>StatelessTicketCache</code> enables the service tickets and proxy tickets
+ * belonging to stateless callers to be placed in a cache. This in-memory cache stores the
+ * <code>CasAuthenticationToken</code>, effectively providing the same capability as a
+ * <code>HttpSession</code> with the ticket identifier being the key rather than a session
+ * identifier.
  * </p>
  *
  * <P>
- * Implementations should provide a reasonable timeout on stored entries, such
- * that the stateless caller are not required to unnecessarily acquire fresh
- * CAS service tickets or proxy tickets.
+ * Implementations should provide a reasonable timeout on stored entries, such that the
+ * stateless caller are not required to unnecessarily acquire fresh CAS service tickets or
+ * proxy tickets.
  * </p>
  *
  * @author Ben Alex
  */
 public interface StatelessTicketCache {
-    //~ Methods ================================================================
+	// ~ Methods ================================================================
 
-    /**
-     * Retrieves the <code>CasAuthenticationToken</code> associated with the
-     * specified ticket.
-     *
-     * <P>
-     * If not found, returns a
-     * <code>null</code><code>CasAuthenticationToken</code>.
-     * </p>
-     *
-     * @return the fully populated authentication token
-     */
-    CasAuthenticationToken getByTicketId(String serviceTicket);
+	/**
+	 * Retrieves the <code>CasAuthenticationToken</code> associated with the specified
+	 * ticket.
+	 *
+	 * <P>
+	 * If not found, returns a <code>null</code><code>CasAuthenticationToken</code>.
+	 * </p>
+	 *
+	 * @return the fully populated authentication token
+	 */
+	CasAuthenticationToken getByTicketId(String serviceTicket);
 
-    /**
-     * Adds the specified <code>CasAuthenticationToken</code> to the cache.
-     *
-     * <P>
-     * The {@link CasAuthenticationToken#getCredentials()} method is used to
-     * retrieve the service ticket number.
-     * </p>
-     *
-     * @param token to be added to the cache
-     */
-    void putTicketInCache(CasAuthenticationToken token);
+	/**
+	 * Adds the specified <code>CasAuthenticationToken</code> to the cache.
+	 *
+	 * <P>
+	 * The {@link CasAuthenticationToken#getCredentials()} method is used to retrieve the
+	 * service ticket number.
+	 * </p>
+	 *
+	 * @param token to be added to the cache
+	 */
+	void putTicketInCache(CasAuthenticationToken token);
 
-    /**
-     * Removes the specified ticket from the cache, as per  {@link
-     * #removeTicketFromCache(String)}.
-     *
-     * <P>
-     * Implementations should use {@link
-     * CasAuthenticationToken#getCredentials()} to obtain the ticket and then
-     * delegate to to the  {@link #removeTicketFromCache(String)} method.
-     * </p>
-     *
-     * @param token to be removed
-     */
-    void removeTicketFromCache(CasAuthenticationToken token);
+	/**
+	 * Removes the specified ticket from the cache, as per
+	 * {@link #removeTicketFromCache(String)}.
+	 *
+	 * <P>
+	 * Implementations should use {@link CasAuthenticationToken#getCredentials()} to
+	 * obtain the ticket and then delegate to to the
+	 * {@link #removeTicketFromCache(String)} method.
+	 * </p>
+	 *
+	 * @param token to be removed
+	 */
+	void removeTicketFromCache(CasAuthenticationToken token);
 
-    /**
-     * Removes the specified ticket from the cache, meaning that future calls
-     * will require a new service ticket.
-     *
-     * <P>
-     * This is in case applications wish to provide a session termination
-     * capability for their stateless clients.
-     * </p>
-     *
-     * @param serviceTicket to be removed
-     */
-    void removeTicketFromCache(String serviceTicket);
+	/**
+	 * Removes the specified ticket from the cache, meaning that future calls will require
+	 * a new service ticket.
+	 *
+	 * <P>
+	 * This is in case applications wish to provide a session termination capability for
+	 * their stateless clients.
+	 * </p>
+	 *
+	 * @param serviceTicket to be removed
+	 */
+	void removeTicketFromCache(String serviceTicket);
 }

+ 18 - 15
cas/src/main/java/org/springframework/security/cas/userdetails/AbstractCasAssertionUserDetailsService.java

@@ -20,25 +20,28 @@ import org.springframework.security.core.userdetails.AuthenticationUserDetailsSe
 import org.springframework.security.core.userdetails.UserDetails;
 
 /**
- * Abstract class for using the provided CAS assertion to construct a new User object.  This generally is most
- * useful when combined with a SAML-based response from the CAS Server/client.
+ * Abstract class for using the provided CAS assertion to construct a new User object.
+ * This generally is most useful when combined with a SAML-based response from the CAS
+ * Server/client.
  *
  * @author Scott Battaglia
  * @since 3.0
  */
-public abstract class AbstractCasAssertionUserDetailsService
-        implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
+public abstract class AbstractCasAssertionUserDetailsService implements
+		AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
 
-    public final UserDetails loadUserDetails(final CasAssertionAuthenticationToken token) {
-        return loadUserDetails(token.getAssertion());
-    }
+	public final UserDetails loadUserDetails(final CasAssertionAuthenticationToken token) {
+		return loadUserDetails(token.getAssertion());
+	}
 
-    /**
-     * Protected template method for construct a {@link org.springframework.security.core.userdetails.UserDetails} via the supplied CAS
-     * assertion.
-     *
-     * @param assertion the assertion to use to construct the new UserDetails.  CANNOT be NULL.
-     * @return the newly constructed UserDetails.
-     */
-    protected abstract UserDetails loadUserDetails(Assertion assertion);
+	/**
+	 * Protected template method for construct a
+	 * {@link org.springframework.security.core.userdetails.UserDetails} via the supplied
+	 * CAS assertion.
+	 *
+	 * @param assertion the assertion to use to construct the new UserDetails. CANNOT be
+	 * NULL.
+	 * @return the newly constructed UserDetails.
+	 */
+	protected abstract UserDetails loadUserDetails(Assertion assertion);
 }

+ 63 - 53
cas/src/main/java/org/springframework/security/cas/userdetails/GrantedAuthorityFromAssertionAttributesUserDetailsService.java

@@ -25,61 +25,71 @@ import java.util.List;
 import java.util.ArrayList;
 
 /**
- * Populates the {@link org.springframework.security.core.GrantedAuthority}s for a user by reading a list of attributes that were returned as
- * part of the CAS response.  Each attribute is read and each value of the attribute is turned into a GrantedAuthority.  If the attribute has no
- * value then its not added.
+ * Populates the {@link org.springframework.security.core.GrantedAuthority}s for a user by
+ * reading a list of attributes that were returned as part of the CAS response. Each
+ * attribute is read and each value of the attribute is turned into a GrantedAuthority. If
+ * the attribute has no value then its not added.
  *
  * @author Scott Battaglia
  * @since 3.0
  */
-public final class GrantedAuthorityFromAssertionAttributesUserDetailsService extends AbstractCasAssertionUserDetailsService {
-
-    private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";
-
-    private final String[] attributes;
-
-    private boolean convertToUpperCase = true;
-
-    public GrantedAuthorityFromAssertionAttributesUserDetailsService(final String[] attributes) {
-        Assert.notNull(attributes, "attributes cannot be null.");
-        Assert.isTrue(attributes.length > 0, "At least one attribute is required to retrieve roles from.");
-        this.attributes = attributes;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    protected UserDetails loadUserDetails(final Assertion assertion) {
-        final List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
-
-        for (final String attribute : this.attributes) {
-            final Object value = assertion.getPrincipal().getAttributes().get(attribute);
-
-            if (value == null) {
-                continue;
-            }
-
-            if (value instanceof List) {
-                final List list = (List) value;
-
-                for (final Object o : list) {
-                    grantedAuthorities.add(new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString()));
-                }
-
-            } else {
-                grantedAuthorities.add(new SimpleGrantedAuthority(this.convertToUpperCase ? value.toString().toUpperCase() : value.toString()));
-            }
-
-        }
-
-        return new User(assertion.getPrincipal().getName(), NON_EXISTENT_PASSWORD_VALUE, true, true, true, true, grantedAuthorities);
-    }
-
-    /**
-     * Converts the returned attribute values to uppercase values.
-     *
-     * @param convertToUpperCase true if it should convert, false otherwise.
-     */
-    public void setConvertToUpperCase(final boolean convertToUpperCase) {
-        this.convertToUpperCase = convertToUpperCase;
-    }
+public final class GrantedAuthorityFromAssertionAttributesUserDetailsService extends
+		AbstractCasAssertionUserDetailsService {
+
+	private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";
+
+	private final String[] attributes;
+
+	private boolean convertToUpperCase = true;
+
+	public GrantedAuthorityFromAssertionAttributesUserDetailsService(
+			final String[] attributes) {
+		Assert.notNull(attributes, "attributes cannot be null.");
+		Assert.isTrue(attributes.length > 0,
+				"At least one attribute is required to retrieve roles from.");
+		this.attributes = attributes;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected UserDetails loadUserDetails(final Assertion assertion) {
+		final List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
+
+		for (final String attribute : this.attributes) {
+			final Object value = assertion.getPrincipal().getAttributes().get(attribute);
+
+			if (value == null) {
+				continue;
+			}
+
+			if (value instanceof List) {
+				final List list = (List) value;
+
+				for (final Object o : list) {
+					grantedAuthorities.add(new SimpleGrantedAuthority(
+							this.convertToUpperCase ? o.toString().toUpperCase() : o
+									.toString()));
+				}
+
+			}
+			else {
+				grantedAuthorities.add(new SimpleGrantedAuthority(
+						this.convertToUpperCase ? value.toString().toUpperCase() : value
+								.toString()));
+			}
+
+		}
+
+		return new User(assertion.getPrincipal().getName(), NON_EXISTENT_PASSWORD_VALUE,
+				true, true, true, true, grantedAuthorities);
+	}
+
+	/**
+	 * Converts the returned attribute values to uppercase values.
+	 *
+	 * @param convertToUpperCase true if it should convert, false otherwise.
+	 */
+	public void setConvertToUpperCase(final boolean convertToUpperCase) {
+		this.convertToUpperCase = convertToUpperCase;
+	}
 }

+ 129 - 111
cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java

@@ -28,122 +28,140 @@ import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.util.Assert;
 
-
 /**
- * Used by the <code>ExceptionTranslationFilter</code> to commence authentication via the JA-SIG Central
- * Authentication Service (CAS).
+ * Used by the <code>ExceptionTranslationFilter</code> to commence authentication via the
+ * JA-SIG Central Authentication Service (CAS).
  * <p>
  * The user's browser will be redirected to the JA-SIG CAS enterprise-wide login page.
- * This page is specified by the <code>loginUrl</code> property. Once login is complete, the CAS login page will
- * redirect to the page indicated by the <code>service</code> property. The <code>service</code> is a HTTP URL
- * belonging to the current application. The <code>service</code> URL is monitored by the {@link CasAuthenticationFilter},
- * which will validate the CAS login was successful.
+ * This page is specified by the <code>loginUrl</code> property. Once login is complete,
+ * the CAS login page will redirect to the page indicated by the <code>service</code>
+ * property. The <code>service</code> is a HTTP URL belonging to the current application.
+ * The <code>service</code> URL is monitored by the {@link CasAuthenticationFilter}, which
+ * will validate the CAS login was successful.
  *
  * @author Ben Alex
  * @author Scott Battaglia
  */
-public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
-    //~ Instance fields ================================================================================================
-    private ServiceProperties serviceProperties;
-
-    private String loginUrl;
-
-    /**
-     * Determines whether the Service URL should include the session id for the specific user.  As of CAS 3.0.5, the
-     * session id will automatically be stripped.  However, older versions of CAS (i.e. CAS 2), do not automatically
-     * strip the session identifier (this is a bug on the part of the older server implementations), so an option to
-     * disable the session encoding is provided for backwards compatibility.
-     *
-     * By default, encoding is enabled.
-     */
-    private boolean encodeServiceUrlWithSessionId = true;
-
-    //~ Methods ========================================================================================================
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.hasLength(this.loginUrl, "loginUrl must be specified");
-        Assert.notNull(this.serviceProperties, "serviceProperties must be specified");
-        Assert.notNull(this.serviceProperties.getService(),"serviceProperties.getService() cannot be null.");
-    }
-
-    public final void commence(final HttpServletRequest servletRequest, final HttpServletResponse response,
-            final AuthenticationException authenticationException) throws IOException, ServletException {
-
-        final String urlEncodedService = createServiceUrl(servletRequest, response);
-        final String redirectUrl = createRedirectUrl(urlEncodedService);
-
-        preCommence(servletRequest, response);
-
-        response.sendRedirect(redirectUrl);
-    }
-
-    /**
-     * Constructs a new Service Url.  The default implementation relies on the CAS client to do the bulk of the work.
-     * @param request the HttpServletRequest
-     * @param response the HttpServlet Response
-     * @return the constructed service url.  CANNOT be NULL.
-     */
-    protected String createServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
-        return CommonUtils.constructServiceUrl(null, response, this.serviceProperties.getService(), null, this.serviceProperties.getArtifactParameter(), this.encodeServiceUrlWithSessionId);
-    }
-
-    /**
-     * Constructs the Url for Redirection to the CAS server.  Default implementation relies on the CAS client to do the bulk of the work.
-     *
-     * @param serviceUrl the service url that should be included.
-     * @return the redirect url.  CANNOT be NULL.
-     */
-    protected String createRedirectUrl(final String serviceUrl) {
-        return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, this.serviceProperties.isSendRenew(), false);
-    }
-
-    /**
-     * Template method for you to do your own pre-processing before the redirect occurs.
-     *
-     * @param request the HttpServletRequest
-     * @param response the HttpServletResponse
-     */
-    protected void preCommence(final HttpServletRequest request, final HttpServletResponse response) {
-
-    }
-
-    /**
-     * The enterprise-wide CAS login URL. Usually something like
-     * <code>https://www.mycompany.com/cas/login</code>.
-     *
-     * @return the enterprise-wide CAS login URL
-     */
-    public final String getLoginUrl() {
-        return this.loginUrl;
-    }
-
-    public final ServiceProperties getServiceProperties() {
-        return this.serviceProperties;
-    }
-
-    public final void setLoginUrl(final String loginUrl) {
-        this.loginUrl = loginUrl;
-    }
-
-    public final void setServiceProperties(final ServiceProperties serviceProperties) {
-        this.serviceProperties = serviceProperties;
-    }
-
-    /**
-     * Sets whether to encode the service url with the session id or not.
-     *
-     * @param encodeServiceUrlWithSessionId whether to encode the service url with the session id or not.
-     */
-    public final void setEncodeServiceUrlWithSessionId(final boolean encodeServiceUrlWithSessionId) {
-        this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId;
-    }
-
-    /**
-     * Sets whether to encode the service url with the session id or not.
-     * @return whether to encode the service url with the session id or not.
-     *
-     */
-    protected boolean getEncodeServiceUrlWithSessionId() {
-        return this.encodeServiceUrlWithSessionId;
-    }
+public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint,
+		InitializingBean {
+	// ~ Instance fields
+	// ================================================================================================
+	private ServiceProperties serviceProperties;
+
+	private String loginUrl;
+
+	/**
+	 * Determines whether the Service URL should include the session id for the specific
+	 * user. As of CAS 3.0.5, the session id will automatically be stripped. However,
+	 * older versions of CAS (i.e. CAS 2), do not automatically strip the session
+	 * identifier (this is a bug on the part of the older server implementations), so an
+	 * option to disable the session encoding is provided for backwards compatibility.
+	 *
+	 * By default, encoding is enabled.
+	 */
+	private boolean encodeServiceUrlWithSessionId = true;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void afterPropertiesSet() throws Exception {
+		Assert.hasLength(this.loginUrl, "loginUrl must be specified");
+		Assert.notNull(this.serviceProperties, "serviceProperties must be specified");
+		Assert.notNull(this.serviceProperties.getService(),
+				"serviceProperties.getService() cannot be null.");
+	}
+
+	public final void commence(final HttpServletRequest servletRequest,
+			final HttpServletResponse response,
+			final AuthenticationException authenticationException) throws IOException,
+			ServletException {
+
+		final String urlEncodedService = createServiceUrl(servletRequest, response);
+		final String redirectUrl = createRedirectUrl(urlEncodedService);
+
+		preCommence(servletRequest, response);
+
+		response.sendRedirect(redirectUrl);
+	}
+
+	/**
+	 * Constructs a new Service Url. The default implementation relies on the CAS client
+	 * to do the bulk of the work.
+	 * @param request the HttpServletRequest
+	 * @param response the HttpServlet Response
+	 * @return the constructed service url. CANNOT be NULL.
+	 */
+	protected String createServiceUrl(final HttpServletRequest request,
+			final HttpServletResponse response) {
+		return CommonUtils.constructServiceUrl(null, response,
+				this.serviceProperties.getService(), null,
+				this.serviceProperties.getArtifactParameter(),
+				this.encodeServiceUrlWithSessionId);
+	}
+
+	/**
+	 * Constructs the Url for Redirection to the CAS server. Default implementation relies
+	 * on the CAS client to do the bulk of the work.
+	 *
+	 * @param serviceUrl the service url that should be included.
+	 * @return the redirect url. CANNOT be NULL.
+	 */
+	protected String createRedirectUrl(final String serviceUrl) {
+		return CommonUtils.constructRedirectUrl(this.loginUrl,
+				this.serviceProperties.getServiceParameter(), serviceUrl,
+				this.serviceProperties.isSendRenew(), false);
+	}
+
+	/**
+	 * Template method for you to do your own pre-processing before the redirect occurs.
+	 *
+	 * @param request the HttpServletRequest
+	 * @param response the HttpServletResponse
+	 */
+	protected void preCommence(final HttpServletRequest request,
+			final HttpServletResponse response) {
+
+	}
+
+	/**
+	 * The enterprise-wide CAS login URL. Usually something like
+	 * <code>https://www.mycompany.com/cas/login</code>.
+	 *
+	 * @return the enterprise-wide CAS login URL
+	 */
+	public final String getLoginUrl() {
+		return this.loginUrl;
+	}
+
+	public final ServiceProperties getServiceProperties() {
+		return this.serviceProperties;
+	}
+
+	public final void setLoginUrl(final String loginUrl) {
+		this.loginUrl = loginUrl;
+	}
+
+	public final void setServiceProperties(final ServiceProperties serviceProperties) {
+		this.serviceProperties = serviceProperties;
+	}
+
+	/**
+	 * Sets whether to encode the service url with the session id or not.
+	 *
+	 * @param encodeServiceUrlWithSessionId whether to encode the service url with the
+	 * session id or not.
+	 */
+	public final void setEncodeServiceUrlWithSessionId(
+			final boolean encodeServiceUrlWithSessionId) {
+		this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId;
+	}
+
+	/**
+	 * Sets whether to encode the service url with the session id or not.
+	 * @return whether to encode the service url with the session id or not.
+	 *
+	 */
+	protected boolean getEncodeServiceUrlWithSessionId() {
+		return this.encodeServiceUrlWithSessionId;
+	}
 }

+ 306 - 264
cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java

@@ -43,51 +43,64 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 
 /**
- * Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy tickets.
- * <h2>Service Tickets</h2>
+ * Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy
+ * tickets. <h2>Service Tickets</h2>
  * <p>
- * A service ticket consists of an opaque ticket string. It arrives at this filter by the user's browser successfully
- * authenticating using CAS, and then receiving a HTTP redirect to a <code>service</code>. The opaque ticket string is
- * presented in the <code>ticket</code> request parameter.
+ * A service ticket consists of an opaque ticket string. It arrives at this filter by the
+ * user's browser successfully authenticating using CAS, and then receiving a HTTP
+ * redirect to a <code>service</code>. The opaque ticket string is presented in the
+ * <code>ticket</code> request parameter.
  * <p>
- * This filter monitors the <code>service</code> URL so it can
- * receive the service ticket and process it. By default this filter processes the URL <tt>/login/cas</tt>.
- * When processing this URL, the value of {@link ServiceProperties#getService()} is used as the <tt>service</tt> when validating
- * the <code>ticket</code>. This means that it is important that {@link ServiceProperties#getService()} specifies the same value
- * as the <tt>filterProcessesUrl</tt>.
+ * This filter monitors the <code>service</code> URL so it can receive the service ticket
+ * and process it. By default this filter processes the URL <tt>/login/cas</tt>. When
+ * processing this URL, the value of {@link ServiceProperties#getService()} is used as the
+ * <tt>service</tt> when validating the <code>ticket</code>. This means that it is
+ * important that {@link ServiceProperties#getService()} specifies the same value as the
+ * <tt>filterProcessesUrl</tt>.
  * <p>
- * Processing the service ticket involves creating a <code>UsernamePasswordAuthenticationToken</code> which
- * uses {@link #CAS_STATEFUL_IDENTIFIER} for the <code>principal</code> and the opaque ticket string as the
- * <code>credentials</code>.
+ * Processing the service ticket involves creating a
+ * <code>UsernamePasswordAuthenticationToken</code> which uses
+ * {@link #CAS_STATEFUL_IDENTIFIER} for the <code>principal</code> and the opaque ticket
+ * string as the <code>credentials</code>.
  * <h2>Obtaining Proxy Granting Tickets</h2>
  * <p>
- * If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter will respond to requests matching
- * this url so that the CAS Server can provide a PGT to the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
- * <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to respond to proxy receptor requests. By configuring
- * a shared {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the CasAuthenticationFilter one can have the
- * CasAuthenticationFilter handle the proxying requirements for CAS.
+ * If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter
+ * will respond to requests matching this url so that the CAS Server can provide a PGT to
+ * the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
+ * <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to
+ * respond to proxy receptor requests. By configuring a shared
+ * {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the
+ * CasAuthenticationFilter one can have the CasAuthenticationFilter handle the proxying
+ * requirements for CAS.
  * <h2>Proxy Tickets</h2>
  * <p>
- * The filter can process tickets present on any url. This is useful when wanting to process proxy tickets. In order for proxy
- * tickets to get processed {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>. Additionally,
- * if the request is already authenticated, authentication will <b>not</b> occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)}
- * must return a {@link ServiceAuthenticationDetails}. This can be accomplished using the {@link ServiceAuthenticationDetailsSource}.
- * In this case {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
+ * The filter can process tickets present on any url. This is useful when wanting to
+ * process proxy tickets. In order for proxy tickets to get processed
+ * {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>.
+ * Additionally, if the request is already authenticated, authentication will <b>not</b>
+ * occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)} must return a
+ * {@link ServiceAuthenticationDetails}. This can be accomplished using the
+ * {@link ServiceAuthenticationDetailsSource}. In this case
+ * {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
  * <p>
- * Processing the proxy ticket involves creating a <code>UsernamePasswordAuthenticationToken</code> which
- * uses {@link #CAS_STATELESS_IDENTIFIER} for the <code>principal</code> and the opaque ticket string as the
- * <code>credentials</code>. When a proxy ticket is successfully authenticated, the FilterChain continues and the
+ * Processing the proxy ticket involves creating a
+ * <code>UsernamePasswordAuthenticationToken</code> which uses
+ * {@link #CAS_STATELESS_IDENTIFIER} for the <code>principal</code> and the opaque ticket
+ * string as the <code>credentials</code>. When a proxy ticket is successfully
+ * authenticated, the FilterChain continues and the
  * <code>authenticationSuccessHandler</code> is not used.
  * <h2>Notes about the <code>AuthenticationManager</code></h2>
  * <p>
- * The configured <code>AuthenticationManager</code> is expected to provide a provider that can recognise
- * <code>UsernamePasswordAuthenticationToken</code>s containing this special <code>principal</code> name, and process
- * them accordingly by validation with the CAS server. Additionally, it should be capable of using the result of
- * {@link ServiceAuthenticationDetails#getServiceUrl()} as the service when validating the ticket.
+ * The configured <code>AuthenticationManager</code> is expected to provide a provider
+ * that can recognise <code>UsernamePasswordAuthenticationToken</code>s containing this
+ * special <code>principal</code> name, and process them accordingly by validation with
+ * the CAS server. Additionally, it should be capable of using the result of
+ * {@link ServiceAuthenticationDetails#getServiceUrl()} as the service when validating the
+ * ticket.
  * <h2>Example Configuration</h2>
  * <p>
- * An example configuration that supports service tickets, obtaining proxy granting tickets, and proxy tickets is
- * illustrated below:
+ * An example configuration that supports service tickets, obtaining proxy granting
+ * tickets, and proxy tickets is illustrated below:
  *
  * <pre>
  * &lt;b:bean id=&quot;serviceProperties&quot;
@@ -157,236 +170,265 @@ import org.springframework.util.Assert;
  * @author Rob Winch
  */
 public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
-    //~ Static fields/initializers =====================================================================================
-
-    /** Used to identify a CAS request for a stateful user agent, such as a web browser. */
-    public static final String CAS_STATEFUL_IDENTIFIER = "_cas_stateful_";
-
-    /**
-     * Used to identify a CAS request for a stateless user agent, such as a remoting protocol client (e.g.
-     * Hessian, Burlap, SOAP etc). Results in a more aggressive caching strategy being used, as the absence of a
-     * <code>HttpSession</code> will result in a new authentication attempt on every request.
-     */
-    public static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
-
-    /**
-     * The last portion of the receptor url, i.e. /proxy/receptor
-     */
-    private RequestMatcher proxyReceptorMatcher;
-
-    /**
-     * The backing storage to store ProxyGrantingTicket requests.
-     */
-    private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
-
-    private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER;
-
-    private boolean authenticateAllArtifacts;
-
-    private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler();
-
-    //~ Constructors ===================================================================================================
-
-    public CasAuthenticationFilter() {
-        super("/login/cas");
-        setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
-    }
-
-    //~ Methods ========================================================================================================
-
-    @Override
-    protected final void successfulAuthentication(HttpServletRequest request,
-            HttpServletResponse response, FilterChain chain, Authentication authResult)
-            throws IOException, ServletException {
-        boolean continueFilterChain = proxyTicketRequest(serviceTicketRequest(request, response),request);
-        if(!continueFilterChain) {
-            super.successfulAuthentication(request, response, chain, authResult);
-            return;
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
-        }
-
-        SecurityContextHolder.getContext().setAuthentication(authResult);
-
-        // Fire event
-        if (this.eventPublisher != null) {
-            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
-        }
-
-        chain.doFilter(request, response);
-    }
-
-    @Override
-    public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
-            throws AuthenticationException, IOException {
-        // if the request is a proxy request process it and return null to indicate the request has been processed
-        if(proxyReceptorRequest(request)) {
-            logger.debug("Responding to proxy receptor request");
-            CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
-            return null;
-        }
-
-        final boolean serviceTicketRequest = serviceTicketRequest(request, response);
-        final String username = serviceTicketRequest ? CAS_STATEFUL_IDENTIFIER : CAS_STATELESS_IDENTIFIER;
-        String password = obtainArtifact(request);
-
-        if (password == null) {
-            logger.debug("Failed to obtain an artifact (cas ticket)");
-            password = "";
-        }
-
-        final UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
-
-        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
-
-        return this.getAuthenticationManager().authenticate(authRequest);
-    }
-
-    /**
-     * If present, gets the artifact (CAS ticket) from the {@link HttpServletRequest}.
-     * @param request
-     * @return if present the artifact from the {@link HttpServletRequest}, else null
-     */
-    protected String obtainArtifact(HttpServletRequest request) {
-        return request.getParameter(artifactParameter);
-    }
-
-    /**
-     * Overridden to provide proxying capabilities.
-     */
-    protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
-        final boolean serviceTicketRequest = serviceTicketRequest(request, response);
-        final boolean result = serviceTicketRequest || proxyReceptorRequest(request) || (proxyTicketRequest(serviceTicketRequest, request));
-        if(logger.isDebugEnabled()) {
-            logger.debug("requiresAuthentication = "+result);
-        }
-        return result;
-    }
-
-    /**
-     * Sets the {@link AuthenticationFailureHandler} for proxy requests.
-     * @param proxyFailureHandler
-     */
-    public final void setProxyAuthenticationFailureHandler(
-            AuthenticationFailureHandler proxyFailureHandler) {
-        Assert.notNull(proxyFailureHandler,"proxyFailureHandler cannot be null");
-        this.proxyFailureHandler = proxyFailureHandler;
-    }
-
-    /**
-     * Wraps the {@link AuthenticationFailureHandler} to distinguish between
-     * handling proxy ticket authentication failures and service ticket
-     * failures.
-     */
-    @Override
-    public final void setAuthenticationFailureHandler(
-            AuthenticationFailureHandler failureHandler) {
-        super.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler(failureHandler));
-    }
-
-    public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
-        this.proxyReceptorMatcher = new AntPathRequestMatcher("/**" + proxyReceptorUrl);
-    }
-
-    public final void setProxyGrantingTicketStorage(
-            final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
-        this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
-    }
-
-    public final void setServiceProperties(final ServiceProperties serviceProperties) {
-        this.artifactParameter = serviceProperties.getArtifactParameter();
-        this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts();
-    }
-
-    /**
-     * Indicates if the request is elgible to process a service ticket. This method exists for readability.
-     * @param request
-     * @param response
-     * @return
-     */
-    private boolean serviceTicketRequest(final HttpServletRequest request, final HttpServletResponse response) {
-        boolean result = super.requiresAuthentication(request, response);
-        if(logger.isDebugEnabled()) {
-            logger.debug("serviceTicketRequest = "+result);
-        }
-        return result;
-    }
-
-    /**
-     * Indicates if the request is elgible to process a proxy ticket.
-     * @param request
-     * @return
-     */
-    private boolean proxyTicketRequest(final boolean serviceTicketRequest, final HttpServletRequest request) {
-        if(serviceTicketRequest) {
-            return false;
-        }
-        final boolean result = authenticateAllArtifacts && obtainArtifact(request) != null && !authenticated();
-        if(logger.isDebugEnabled()) {
-            logger.debug("proxyTicketRequest = "+result);
-        }
-        return result;
-    }
-
-    /**
-     * Determines if a user is already authenticated.
-     * @return
-     */
-    private boolean authenticated() {
-        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-        return authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken);
-    }
-    /**
-     * Indicates if the request is elgible to be processed as the proxy receptor.
-     * @param request
-     * @return
-     */
-    private boolean proxyReceptorRequest(final HttpServletRequest request) {
-        final boolean result = proxyReceptorConfigured() && proxyReceptorMatcher.matches(request);
-        if(logger.isDebugEnabled()) {
-            logger.debug("proxyReceptorRequest = "+result);
-        }
-        return result;
-    }
-
-    /**
-     * Determines if the {@link CasAuthenticationFilter} is configured to handle the proxy receptor requests.
-     *
-     * @return
-     */
-    private boolean proxyReceptorConfigured() {
-        final boolean result = this.proxyGrantingTicketStorage != null && proxyReceptorMatcher != null;
-        if(logger.isDebugEnabled()) {
-            logger.debug("proxyReceptorConfigured = "+result);
-        }
-        return result;
-    }
-
-    /**
-     * A wrapper for the AuthenticationFailureHandler that will flex the {@link AuthenticationFailureHandler} that is used. The value
-     * {@link CasAuthenticationFilter#setProxyAuthenticationFailureHandler(AuthenticationFailureHandler) will be used for proxy requests
-     * that fail. The value {@link CasAuthenticationFilter#setAuthenticationFailureHandler(AuthenticationFailureHandler)} will be used for
-     * service tickets that fail.
-     *
-     * @author Rob Winch
-     */
-    private class CasAuthenticationFailureHandler implements AuthenticationFailureHandler {
-        private final AuthenticationFailureHandler serviceTicketFailureHandler;
-        public CasAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
-            Assert.notNull(failureHandler,"failureHandler");
-            this.serviceTicketFailureHandler = failureHandler;
-        }
-        public void onAuthenticationFailure(HttpServletRequest request,
-                HttpServletResponse response,
-                AuthenticationException exception) throws IOException,
-                ServletException {
-            if(serviceTicketRequest(request, response)) {
-                serviceTicketFailureHandler.onAuthenticationFailure(request, response, exception);
-            }else {
-                proxyFailureHandler.onAuthenticationFailure(request, response, exception);
-            }
-        }
-    }
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	/** Used to identify a CAS request for a stateful user agent, such as a web browser. */
+	public static final String CAS_STATEFUL_IDENTIFIER = "_cas_stateful_";
+
+	/**
+	 * Used to identify a CAS request for a stateless user agent, such as a remoting
+	 * protocol client (e.g. Hessian, Burlap, SOAP etc). Results in a more aggressive
+	 * caching strategy being used, as the absence of a <code>HttpSession</code> will
+	 * result in a new authentication attempt on every request.
+	 */
+	public static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
+
+	/**
+	 * The last portion of the receptor url, i.e. /proxy/receptor
+	 */
+	private RequestMatcher proxyReceptorMatcher;
+
+	/**
+	 * The backing storage to store ProxyGrantingTicket requests.
+	 */
+	private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
+
+	private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER;
+
+	private boolean authenticateAllArtifacts;
+
+	private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler();
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	public CasAuthenticationFilter() {
+		super("/login/cas");
+		setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	@Override
+	protected final void successfulAuthentication(HttpServletRequest request,
+			HttpServletResponse response, FilterChain chain, Authentication authResult)
+			throws IOException, ServletException {
+		boolean continueFilterChain = proxyTicketRequest(
+				serviceTicketRequest(request, response), request);
+		if (!continueFilterChain) {
+			super.successfulAuthentication(request, response, chain, authResult);
+			return;
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+					+ authResult);
+		}
+
+		SecurityContextHolder.getContext().setAuthentication(authResult);
+
+		// Fire event
+		if (this.eventPublisher != null) {
+			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
+					authResult, this.getClass()));
+		}
+
+		chain.doFilter(request, response);
+	}
+
+	@Override
+	public Authentication attemptAuthentication(final HttpServletRequest request,
+			final HttpServletResponse response) throws AuthenticationException,
+			IOException {
+		// if the request is a proxy request process it and return null to indicate the
+		// request has been processed
+		if (proxyReceptorRequest(request)) {
+			logger.debug("Responding to proxy receptor request");
+			CommonUtils.readAndRespondToProxyReceptorRequest(request, response,
+					this.proxyGrantingTicketStorage);
+			return null;
+		}
+
+		final boolean serviceTicketRequest = serviceTicketRequest(request, response);
+		final String username = serviceTicketRequest ? CAS_STATEFUL_IDENTIFIER
+				: CAS_STATELESS_IDENTIFIER;
+		String password = obtainArtifact(request);
+
+		if (password == null) {
+			logger.debug("Failed to obtain an artifact (cas ticket)");
+			password = "";
+		}
+
+		final UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
+				username, password);
+
+		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
+
+		return this.getAuthenticationManager().authenticate(authRequest);
+	}
+
+	/**
+	 * If present, gets the artifact (CAS ticket) from the {@link HttpServletRequest}.
+	 * @param request
+	 * @return if present the artifact from the {@link HttpServletRequest}, else null
+	 */
+	protected String obtainArtifact(HttpServletRequest request) {
+		return request.getParameter(artifactParameter);
+	}
+
+	/**
+	 * Overridden to provide proxying capabilities.
+	 */
+	protected boolean requiresAuthentication(final HttpServletRequest request,
+			final HttpServletResponse response) {
+		final boolean serviceTicketRequest = serviceTicketRequest(request, response);
+		final boolean result = serviceTicketRequest || proxyReceptorRequest(request)
+				|| (proxyTicketRequest(serviceTicketRequest, request));
+		if (logger.isDebugEnabled()) {
+			logger.debug("requiresAuthentication = " + result);
+		}
+		return result;
+	}
+
+	/**
+	 * Sets the {@link AuthenticationFailureHandler} for proxy requests.
+	 * @param proxyFailureHandler
+	 */
+	public final void setProxyAuthenticationFailureHandler(
+			AuthenticationFailureHandler proxyFailureHandler) {
+		Assert.notNull(proxyFailureHandler, "proxyFailureHandler cannot be null");
+		this.proxyFailureHandler = proxyFailureHandler;
+	}
+
+	/**
+	 * Wraps the {@link AuthenticationFailureHandler} to distinguish between handling
+	 * proxy ticket authentication failures and service ticket failures.
+	 */
+	@Override
+	public final void setAuthenticationFailureHandler(
+			AuthenticationFailureHandler failureHandler) {
+		super.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler(
+				failureHandler));
+	}
+
+	public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
+		this.proxyReceptorMatcher = new AntPathRequestMatcher("/**" + proxyReceptorUrl);
+	}
+
+	public final void setProxyGrantingTicketStorage(
+			final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
+		this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
+	}
+
+	public final void setServiceProperties(final ServiceProperties serviceProperties) {
+		this.artifactParameter = serviceProperties.getArtifactParameter();
+		this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts();
+	}
+
+	/**
+	 * Indicates if the request is elgible to process a service ticket. This method exists
+	 * for readability.
+	 * @param request
+	 * @param response
+	 * @return
+	 */
+	private boolean serviceTicketRequest(final HttpServletRequest request,
+			final HttpServletResponse response) {
+		boolean result = super.requiresAuthentication(request, response);
+		if (logger.isDebugEnabled()) {
+			logger.debug("serviceTicketRequest = " + result);
+		}
+		return result;
+	}
+
+	/**
+	 * Indicates if the request is elgible to process a proxy ticket.
+	 * @param request
+	 * @return
+	 */
+	private boolean proxyTicketRequest(final boolean serviceTicketRequest,
+			final HttpServletRequest request) {
+		if (serviceTicketRequest) {
+			return false;
+		}
+		final boolean result = authenticateAllArtifacts
+				&& obtainArtifact(request) != null && !authenticated();
+		if (logger.isDebugEnabled()) {
+			logger.debug("proxyTicketRequest = " + result);
+		}
+		return result;
+	}
+
+	/**
+	 * Determines if a user is already authenticated.
+	 * @return
+	 */
+	private boolean authenticated() {
+		Authentication authentication = SecurityContextHolder.getContext()
+				.getAuthentication();
+		return authentication != null && authentication.isAuthenticated()
+				&& !(authentication instanceof AnonymousAuthenticationToken);
+	}
+
+	/**
+	 * Indicates if the request is elgible to be processed as the proxy receptor.
+	 * @param request
+	 * @return
+	 */
+	private boolean proxyReceptorRequest(final HttpServletRequest request) {
+		final boolean result = proxyReceptorConfigured()
+				&& proxyReceptorMatcher.matches(request);
+		if (logger.isDebugEnabled()) {
+			logger.debug("proxyReceptorRequest = " + result);
+		}
+		return result;
+	}
+
+	/**
+	 * Determines if the {@link CasAuthenticationFilter} is configured to handle the proxy
+	 * receptor requests.
+	 *
+	 * @return
+	 */
+	private boolean proxyReceptorConfigured() {
+		final boolean result = this.proxyGrantingTicketStorage != null
+				&& proxyReceptorMatcher != null;
+		if (logger.isDebugEnabled()) {
+			logger.debug("proxyReceptorConfigured = " + result);
+		}
+		return result;
+	}
+
+	/**
+	 * A wrapper for the AuthenticationFailureHandler that will flex the
+	 * {@link AuthenticationFailureHandler} that is used. The value
+	 * {@link CasAuthenticationFilter#setProxyAuthenticationFailureHandler(AuthenticationFailureHandler)
+	 * will be used for proxy requests that fail. The value
+	 * {@link CasAuthenticationFilter#setAuthenticationFailureHandler(AuthenticationFailureHandler)}
+	 * will be used for service tickets that fail.
+	 *
+	 * @author Rob Winch
+	 */
+	private class CasAuthenticationFailureHandler implements AuthenticationFailureHandler {
+		private final AuthenticationFailureHandler serviceTicketFailureHandler;
+
+		public CasAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
+			Assert.notNull(failureHandler, "failureHandler");
+			this.serviceTicketFailureHandler = failureHandler;
+		}
+
+		public void onAuthenticationFailure(HttpServletRequest request,
+				HttpServletResponse response, AuthenticationException exception)
+				throws IOException, ServletException {
+			if (serviceTicketRequest(request, response)) {
+				serviceTicketFailureHandler.onAuthenticationFailure(request, response,
+						exception);
+			}
+			else {
+				proxyFailureHandler.onAuthenticationFailure(request, response, exception);
+			}
+		}
+	}
 }

+ 121 - 116
cas/src/main/java/org/springframework/security/cas/web/authentication/DefaultServiceAuthenticationDetails.java

@@ -26,123 +26,128 @@ import org.springframework.security.web.util.UrlUtils;
 import org.springframework.util.Assert;
 
 /**
- * A default implementation of {@link ServiceAuthenticationDetails} that figures
- * out the value for {@link #getServiceUrl()} by inspecting the current
- * {@link HttpServletRequest} and using the current URL minus the artifact and
- * the corresponding value.
+ * A default implementation of {@link ServiceAuthenticationDetails} that figures out the
+ * value for {@link #getServiceUrl()} by inspecting the current {@link HttpServletRequest}
+ * and using the current URL minus the artifact and the corresponding value.
  *
  * @author Rob Winch
  */
-final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails implements ServiceAuthenticationDetails {
-    private static final long serialVersionUID = 6192409090610517700L;
-
-    //~ Instance fields ================================================================================================
-
-    private final String serviceUrl;
-
-    //~ Constructors ===================================================================================================
-
-    /**
-     * Creates a new instance
-     * @param request
-     *            the current {@link HttpServletRequest} to obtain the
-     *            {@link #getServiceUrl()} from.
-     * @param artifactPattern
-     *            the {@link Pattern} that will be used to clean up the query
-     *            string from containing the artifact name and value. This can
-     *            be created using {@link #createArtifactPattern(String)}.
-     */
-    DefaultServiceAuthenticationDetails(String casService, HttpServletRequest request, Pattern artifactPattern) throws MalformedURLException {
-        super(request);
-        URL casServiceUrl = new URL(casService);
-        int port = getServicePort(casServiceUrl);
-        final String query = getQueryString(request,artifactPattern);
-        this.serviceUrl = UrlUtils.buildFullRequestUrl(casServiceUrl.getProtocol(),
-                casServiceUrl.getHost(), port,
-                request.getRequestURI(), query);
-    }
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Returns the current URL minus the artifact parameter and its value, if present.
-     * @see org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails#getServiceUrl()
-     */
-    public String getServiceUrl() {
-        return serviceUrl;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = super.hashCode();
-        result = prime * result
-                + serviceUrl.hashCode();
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
-            return false;
-        }
-        ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
-        return serviceUrl.equals(that.getServiceUrl());
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder result = new StringBuilder();
-        result.append(super.toString());
-        result.append("ServiceUrl: ");
-        result.append(serviceUrl);
-        return result.toString();
-    }
-
-    /**
-     * If present, removes the artifactParameterName and the corresponding value from the query String.
-     * @param request
-     * @return the query String minus the artifactParameterName and the corresponding value.
-     */
-    private String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) {
-        final String query = request.getQueryString();
-        if(query == null) {
-            return null;
-        }
-        final String result = artifactPattern.matcher(query).replaceFirst("");
-        if(result.length() == 0) {
-            return null;
-        }
-        // strip off the trailing & only if the artifact was the first query param
-        return result.startsWith("&") ? result.substring(1) : result;
-    }
-
-    /**
-     * Creates a {@link Pattern} that can be passed into the constructor. This
-     * allows the {@link Pattern} to be reused for every instance of
-     * {@link DefaultServiceAuthenticationDetails}.
-     *
-     * @param artifactParameterName
-     * @return
-     */
-    static Pattern createArtifactPattern(String artifactParameterName) {
-        Assert.hasLength(artifactParameterName);
-        return Pattern.compile("&?"+Pattern.quote(artifactParameterName)+"=[^&]*");
-    }
-
-    /**
-     * Gets the port from the casServiceURL ensuring to return the proper value if the default port is being used.
-     * @param casServiceUrl the casServerUrl to be used (i.e. "https://example.com/context/login/cas")
-     * @return the port that is configured for the casServerUrl
-     */
-    private static int getServicePort(URL casServiceUrl) {
-        int port = casServiceUrl.getPort();
-        if(port == -1) {
-            port = casServiceUrl.getDefaultPort();
-        }
-        return port;
-    }
+final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
+		implements ServiceAuthenticationDetails {
+	private static final long serialVersionUID = 6192409090610517700L;
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private final String serviceUrl;
+
+	// ~ Constructors
+	// ===================================================================================================
+
+	/**
+	 * Creates a new instance
+	 * @param request the current {@link HttpServletRequest} to obtain the
+	 * {@link #getServiceUrl()} from.
+	 * @param artifactPattern the {@link Pattern} that will be used to clean up the query
+	 * string from containing the artifact name and value. This can be created using
+	 * {@link #createArtifactPattern(String)}.
+	 */
+	DefaultServiceAuthenticationDetails(String casService, HttpServletRequest request,
+			Pattern artifactPattern) throws MalformedURLException {
+		super(request);
+		URL casServiceUrl = new URL(casService);
+		int port = getServicePort(casServiceUrl);
+		final String query = getQueryString(request, artifactPattern);
+		this.serviceUrl = UrlUtils.buildFullRequestUrl(casServiceUrl.getProtocol(),
+				casServiceUrl.getHost(), port, request.getRequestURI(), query);
+	}
+
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 * Returns the current URL minus the artifact parameter and its value, if present.
+	 * @see org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails#getServiceUrl()
+	 */
+	public String getServiceUrl() {
+		return serviceUrl;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = super.hashCode();
+		result = prime * result + serviceUrl.hashCode();
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
+			return false;
+		}
+		ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
+		return serviceUrl.equals(that.getServiceUrl());
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder result = new StringBuilder();
+		result.append(super.toString());
+		result.append("ServiceUrl: ");
+		result.append(serviceUrl);
+		return result.toString();
+	}
+
+	/**
+	 * If present, removes the artifactParameterName and the corresponding value from the
+	 * query String.
+	 * @param request
+	 * @return the query String minus the artifactParameterName and the corresponding
+	 * value.
+	 */
+	private String getQueryString(final HttpServletRequest request,
+			final Pattern artifactPattern) {
+		final String query = request.getQueryString();
+		if (query == null) {
+			return null;
+		}
+		final String result = artifactPattern.matcher(query).replaceFirst("");
+		if (result.length() == 0) {
+			return null;
+		}
+		// strip off the trailing & only if the artifact was the first query param
+		return result.startsWith("&") ? result.substring(1) : result;
+	}
+
+	/**
+	 * Creates a {@link Pattern} that can be passed into the constructor. This allows the
+	 * {@link Pattern} to be reused for every instance of
+	 * {@link DefaultServiceAuthenticationDetails}.
+	 *
+	 * @param artifactParameterName
+	 * @return
+	 */
+	static Pattern createArtifactPattern(String artifactParameterName) {
+		Assert.hasLength(artifactParameterName);
+		return Pattern.compile("&?" + Pattern.quote(artifactParameterName) + "=[^&]*");
+	}
+
+	/**
+	 * Gets the port from the casServiceURL ensuring to return the proper value if the
+	 * default port is being used.
+	 * @param casServiceUrl the casServerUrl to be used (i.e.
+	 * "https://example.com/context/login/cas")
+	 * @return the port that is configured for the casServerUrl
+	 */
+	private static int getServicePort(URL casServiceUrl) {
+		int port = casServiceUrl.getPort();
+		if (port == -1) {
+			port = casServiceUrl.getDefaultPort();
+		}
+		return port;
+	}
 }

+ 9 - 10
cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetails.java

@@ -22,10 +22,9 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider
 import org.springframework.security.core.Authentication;
 
 /**
- * In order for the {@link CasAuthenticationProvider} to provide the correct
- * service url to authenticate the ticket, the returned value of
- * {@link Authentication#getDetails()} should implement this interface when
- * tickets can be sent to any URL rather than only
+ * In order for the {@link CasAuthenticationProvider} to provide the correct service url
+ * to authenticate the ticket, the returned value of {@link Authentication#getDetails()}
+ * should implement this interface when tickets can be sent to any URL rather than only
  * {@link ServiceProperties#getService()}.
  *
  * @author Rob Winch
@@ -34,10 +33,10 @@ import org.springframework.security.core.Authentication;
  */
 public interface ServiceAuthenticationDetails extends Serializable {
 
-    /**
-     * Gets the absolute service url (i.e. https://example.com/service/).
-     *
-     * @return the service url. Cannot be <code>null</code>.
-     */
-    String getServiceUrl();
+	/**
+	 * Gets the absolute service url (i.e. https://example.com/service/).
+	 *
+	 * @return the service url. Cannot be <code>null</code>.
+	 */
+	String getServiceUrl();
 }

+ 52 - 44
cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetailsSource.java

@@ -27,58 +27,66 @@ import org.springframework.util.Assert;
 /**
  * The {@code AuthenticationDetailsSource} that is set on the
  * {@code CasAuthenticationFilter} should return a value that implements
- * {@code ServiceAuthenticationDetails} if the application needs to authenticate
- * dynamic service urls. The
- * {@code ServiceAuthenticationDetailsSource#buildDetails(HttpServletRequest)}
- * creates a default {@code ServiceAuthenticationDetails}.
+ * {@code ServiceAuthenticationDetails} if the application needs to authenticate dynamic
+ * service urls. The
+ * {@code ServiceAuthenticationDetailsSource#buildDetails(HttpServletRequest)} creates a
+ * default {@code ServiceAuthenticationDetails}.
  *
  * @author Rob Winch
  */
-public class ServiceAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest,
-        ServiceAuthenticationDetails> {
-    //~ Instance fields ================================================================================================
+public class ServiceAuthenticationDetailsSource implements
+		AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails> {
+	// ~ Instance fields
+	// ================================================================================================
 
-    private final Pattern artifactPattern;
+	private final Pattern artifactPattern;
 
-    private ServiceProperties serviceProperties;
+	private ServiceProperties serviceProperties;
 
-    //~ Constructors ===================================================================================================
+	// ~ Constructors
+	// ===================================================================================================
 
-    /**
-     * Creates an implementation that uses the specified ServiceProperites and the default CAS artifactParameterName.
-     *
-     * @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
-     */
-    public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties) {
-        this(serviceProperties,ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
-    }
+	/**
+	 * Creates an implementation that uses the specified ServiceProperites and the default
+	 * CAS artifactParameterName.
+	 *
+	 * @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
+	 */
+	public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties) {
+		this(serviceProperties, ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
+	}
 
-    /**
-     * Creates an implementation that uses the specified artifactParameterName
-     *
-     * @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
-     * @param artifactParameterName
-     *            the artifactParameterName that is removed from the current
-     *            URL. The result becomes the service url. Cannot be null and
-     *            cannot be an empty String.
-     */
-    public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties, String artifactParameterName) {
-        Assert.notNull(serviceProperties, "serviceProperties cannot be null");
-        this.serviceProperties = serviceProperties;
-        this.artifactPattern = DefaultServiceAuthenticationDetails.createArtifactPattern(artifactParameterName);
-    }
+	/**
+	 * Creates an implementation that uses the specified artifactParameterName
+	 *
+	 * @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
+	 * @param artifactParameterName the artifactParameterName that is removed from the
+	 * current URL. The result becomes the service url. Cannot be null and cannot be an
+	 * empty String.
+	 */
+	public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties,
+			String artifactParameterName) {
+		Assert.notNull(serviceProperties, "serviceProperties cannot be null");
+		this.serviceProperties = serviceProperties;
+		this.artifactPattern = DefaultServiceAuthenticationDetails
+				.createArtifactPattern(artifactParameterName);
+	}
 
-    //~ Methods ========================================================================================================
+	// ~ Methods
+	// ========================================================================================================
 
-    /**
-     * @param context the {@code HttpServletRequest} object.
-     * @return the {@code ServiceAuthenticationDetails} containing information about the current request
-     */
-    public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
-        try {
-            return new DefaultServiceAuthenticationDetails(serviceProperties.getService(),context,artifactPattern);
-        } catch (MalformedURLException e) {
-            throw new RuntimeException(e);
-        }
-    }
+	/**
+	 * @param context the {@code HttpServletRequest} object.
+	 * @return the {@code ServiceAuthenticationDetails} containing information about the
+	 * current request
+	 */
+	public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
+		try {
+			return new DefaultServiceAuthenticationDetails(
+					serviceProperties.getService(), context, artifactPattern);
+		}
+		catch (MalformedURLException e) {
+			throw new RuntimeException(e);
+		}
+	}
 }

+ 1 - 0
cas/src/main/java/org/springframework/security/cas/web/authentication/package-info.java

@@ -3,3 +3,4 @@
  * credentials using CAS.
  */
 package org.springframework.security.cas.web.authentication;
+

+ 10 - 8
cas/src/test/java/org/springframework/security/cas/authentication/AbstractStatelessTicketCacheTests.java

@@ -17,15 +17,17 @@ import org.springframework.security.core.userdetails.User;
  */
 public abstract class AbstractStatelessTicketCacheTests {
 
-    protected CasAuthenticationToken getToken() {
-        List<String> proxyList = new ArrayList<String>();
-        proxyList.add("https://localhost/newPortal/login/cas");
+	protected CasAuthenticationToken getToken() {
+		List<String> proxyList = new ArrayList<String>();
+		proxyList.add("https://localhost/newPortal/login/cas");
 
-        User user = new User("rod", "password", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
-        final Assertion assertion = new AssertionImpl("rod");
+		User user = new User("rod", "password", true, true, true, true,
+				AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
+		final Assertion assertion = new AssertionImpl("rod");
 
-        return new CasAuthenticationToken("key", user, "ST-0-ER94xMJmn6pha35CQRoZ",
-                AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), user, assertion);
-    }
+		return new CasAuthenticationToken("key", user, "ST-0-ER94xMJmn6pha35CQRoZ",
+				AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), user,
+				assertion);
+	}
 
 }

+ 376 - 362
cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java

@@ -41,7 +41,6 @@ import org.springframework.security.web.authentication.WebAuthenticationDetails;
 
 import java.util.*;
 
-
 /**
  * Tests {@link CasAuthenticationProvider}.
  *
@@ -50,365 +49,380 @@ import java.util.*;
  */
 @SuppressWarnings("unchecked")
 public class CasAuthenticationProviderTests {
-    //~ Methods ========================================================================================================
-
-    private UserDetails makeUserDetails() {
-        return new User("user", "password", true, true, true, true,
-                AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
-    }
-
-    private UserDetails makeUserDetailsFromAuthoritiesPopulator() {
-        return new User("user", "password", true, true, true, true,
-                AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B"));
-    }
-
-    private ServiceProperties makeServiceProperties() {
-        final ServiceProperties serviceProperties = new ServiceProperties();
-        serviceProperties.setSendRenew(false);
-        serviceProperties.setService("http://test.com");
-
-        return serviceProperties;
-    }
-
-    @Test
-    public void statefulAuthenticationIsSuccessful() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-
-        StatelessTicketCache cache = new MockStatelessTicketCache();
-        cap.setStatelessTicketCache(cache);
-        cap.setServiceProperties(makeServiceProperties());
-
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.afterPropertiesSet();
-
-        UsernamePasswordAuthenticationToken token =
-            new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-123");
-        token.setDetails("details");
-
-        Authentication result = cap.authenticate(token);
-
-        // Confirm ST-123 was NOT added to the cache
-        assertTrue(cache.getByTicketId("ST-456") == null);
-
-        if (!(result instanceof CasAuthenticationToken)) {
-            fail("Should have returned a CasAuthenticationToken");
-        }
-
-        CasAuthenticationToken casResult = (CasAuthenticationToken) result;
-        assertEquals(makeUserDetailsFromAuthoritiesPopulator(), casResult.getPrincipal());
-        assertEquals("ST-123", casResult.getCredentials());
-        assertTrue(casResult.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_A")));
-        assertTrue(casResult.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_B")));
-        assertEquals(cap.getKey().hashCode(), casResult.getKeyHash());
-        assertEquals("details", casResult.getDetails());
-
-        // Now confirm the CasAuthenticationToken is automatically re-accepted.
-        // To ensure TicketValidator not called again, set it to deliver an exception...
-        cap.setTicketValidator(new MockTicketValidator(false));
-
-        Authentication laterResult = cap.authenticate(result);
-        assertEquals(result, laterResult);
-    }
-
-    @Test
-    public void statelessAuthenticationIsSuccessful() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-
-        StatelessTicketCache cache = new MockStatelessTicketCache();
-        cap.setStatelessTicketCache(cache);
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-
-        UsernamePasswordAuthenticationToken token =
-            new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, "ST-456");
-        token.setDetails("details");
-
-        Authentication result = cap.authenticate(token);
-
-        // Confirm ST-456 was added to the cache
-        assertTrue(cache.getByTicketId("ST-456") != null);
-
-        if (!(result instanceof CasAuthenticationToken)) {
-            fail("Should have returned a CasAuthenticationToken");
-        }
-
-        assertEquals(makeUserDetailsFromAuthoritiesPopulator(), result.getPrincipal());
-        assertEquals("ST-456", result.getCredentials());
-        assertEquals("details", result.getDetails());
-
-        // Now try to authenticate again. To ensure TicketValidator not
-        // called again, set it to deliver an exception...
-        cap.setTicketValidator(new MockTicketValidator(false));
-
-        // Previously created UsernamePasswordAuthenticationToken is OK
-        Authentication newResult = cap.authenticate(token);
-        assertEquals(makeUserDetailsFromAuthoritiesPopulator(), newResult.getPrincipal());
-        assertEquals("ST-456", newResult.getCredentials());
-    }
-
-    @Test
-    public void authenticateAllNullService() throws Exception {
-        String serviceUrl = "https://service/context";
-        ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
-        when(details.getServiceUrl()).thenReturn(serviceUrl);
-        TicketValidator validator = mock(TicketValidator.class);
-        when(validator.validate(any(String.class),any(String.class))).thenReturn(new AssertionImpl("rod"));
-
-        ServiceProperties serviceProperties = makeServiceProperties();
-        serviceProperties.setAuthenticateAllArtifacts(true);
-
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-
-        cap.setTicketValidator(validator);
-        cap.setServiceProperties(serviceProperties);
-        cap.afterPropertiesSet();
-
-        String ticket = "ST-456";
-        UsernamePasswordAuthenticationToken token =
-            new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
-
-        Authentication result = cap.authenticate(token);
-    }
-
-    @Test
-    public void authenticateAllAuthenticationIsSuccessful() throws Exception {
-        String serviceUrl = "https://service/context";
-        ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
-        when(details.getServiceUrl()).thenReturn(serviceUrl);
-        TicketValidator validator = mock(TicketValidator.class);
-        when(validator.validate(any(String.class),any(String.class))).thenReturn(new AssertionImpl("rod"));
-
-        ServiceProperties serviceProperties = makeServiceProperties();
-        serviceProperties.setAuthenticateAllArtifacts(true);
-
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-
-        cap.setTicketValidator(validator);
-        cap.setServiceProperties(serviceProperties);
-        cap.afterPropertiesSet();
-
-        String ticket = "ST-456";
-        UsernamePasswordAuthenticationToken token =
-            new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
-
-        Authentication result = cap.authenticate(token);
-        verify(validator).validate(ticket, serviceProperties.getService());
-
-        serviceProperties.setAuthenticateAllArtifacts(true);
-        result = cap.authenticate(token);
-        verify(validator,times(2)).validate(ticket, serviceProperties.getService());
-
-        token.setDetails(details);
-        result = cap.authenticate(token);
-        verify(validator).validate(ticket, serviceUrl);
-
-        serviceProperties.setAuthenticateAllArtifacts(false);
-        serviceProperties.setService(null);
-        cap.setServiceProperties(serviceProperties);
-        cap.afterPropertiesSet();
-        result = cap.authenticate(token);
-        verify(validator,times(2)).validate(ticket, serviceUrl);
-
-        token.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest()));
-        try {
-            cap.authenticate(token);
-            fail("Expected Exception");
-        }catch(IllegalStateException success) {}
-
-        cap.setServiceProperties(null);
-        cap.afterPropertiesSet();
-        try {
-            cap.authenticate(token);
-            fail("Expected Exception");
-        }catch(IllegalStateException success) {}
-    }
-
-    @Test(expected = BadCredentialsException.class)
-    public void missingTicketIdIsDetected() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-
-        StatelessTicketCache cache = new MockStatelessTicketCache();
-        cap.setStatelessTicketCache(cache);
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-
-        UsernamePasswordAuthenticationToken token =
-                new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "");
-
-        cap.authenticate(token);
-    }
-
-    @Test(expected = BadCredentialsException.class)
-    public void invalidKeyIsDetected() throws Exception {
-        final Assertion assertion = new AssertionImpl("test");
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-
-        StatelessTicketCache cache = new MockStatelessTicketCache();
-        cap.setStatelessTicketCache(cache);
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-
-        CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY", makeUserDetails(), "credentials",
-                AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion);
-
-        cap.authenticate(token);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void detectsMissingAuthoritiesPopulator() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setKey("qwerty");
-        cap.setStatelessTicketCache(new MockStatelessTicketCache());
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void detectsMissingKey() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setStatelessTicketCache(new MockStatelessTicketCache());
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void detectsMissingStatelessTicketCache() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        // set this explicitly to null to test failure
-        cap.setStatelessTicketCache(null);
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void detectsMissingTicketValidator() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-        cap.setStatelessTicketCache(new MockStatelessTicketCache());
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-    }
-
-    @Test
-    public void gettersAndSettersMatch() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-        cap.setStatelessTicketCache(new MockStatelessTicketCache());
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-
-// TODO disabled because why do we need to expose this?
-//        assertTrue(cap.getUserDetailsService() != null);
-        assertEquals("qwerty", cap.getKey());
-        assertTrue(cap.getStatelessTicketCache() != null);
-        assertTrue(cap.getTicketValidator() != null);
-    }
-
-    @Test
-    public void ignoresClassesItDoesNotSupport() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-        cap.setStatelessTicketCache(new MockStatelessTicketCache());
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-
-        TestingAuthenticationToken token = new TestingAuthenticationToken("user", "password", "ROLE_A");
-        assertFalse(cap.supports(TestingAuthenticationToken.class));
-
-        // Try it anyway
-        assertEquals(null, cap.authenticate(token));
-    }
-
-    @Test
-    public void ignoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal() throws Exception {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
-        cap.setKey("qwerty");
-        cap.setStatelessTicketCache(new MockStatelessTicketCache());
-        cap.setTicketValidator(new MockTicketValidator(true));
-        cap.setServiceProperties(makeServiceProperties());
-        cap.afterPropertiesSet();
-
-        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("some_normal_user",
-                "password", AuthorityUtils.createAuthorityList("ROLE_A"));
-        assertEquals(null, cap.authenticate(token));
-    }
-
-    @Test
-    public void supportsRequiredTokens() {
-        CasAuthenticationProvider cap = new CasAuthenticationProvider();
-        assertTrue(cap.supports(UsernamePasswordAuthenticationToken.class));
-        assertTrue(cap.supports(CasAuthenticationToken.class));
-    }
-
-    //~ Inner Classes ==================================================================================================
-
-    private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService {
-
-        public UserDetails loadUserDetails(final Authentication token) throws UsernameNotFoundException {
-            return makeUserDetailsFromAuthoritiesPopulator();
-        }
-    }
-
-    private class MockStatelessTicketCache implements StatelessTicketCache {
-        private Map<String, CasAuthenticationToken> cache = new HashMap<String, CasAuthenticationToken>();
-
-        public CasAuthenticationToken getByTicketId(String serviceTicket) {
-            return cache.get(serviceTicket);
-        }
-
-        public void putTicketInCache(CasAuthenticationToken token) {
-            cache.put(token.getCredentials().toString(), token);
-        }
-
-        public void removeTicketFromCache(CasAuthenticationToken token) {
-            throw new UnsupportedOperationException("mock method not implemented");
-        }
-
-        public void removeTicketFromCache(String serviceTicket) {
-            throw new UnsupportedOperationException("mock method not implemented");
-        }
-    }
-
-    private class MockTicketValidator implements TicketValidator {
-        private boolean returnTicket;
-
-        public MockTicketValidator(boolean returnTicket) {
-            this.returnTicket = returnTicket;
-        }
-
-        public Assertion validate(final String ticket, final String service)
-                throws TicketValidationException {
-            if (returnTicket) {
-                return new AssertionImpl("rod");
-            }
-            throw new BadCredentialsException("As requested from mock");
-        }
-    }
+	// ~ Methods
+	// ========================================================================================================
+
+	private UserDetails makeUserDetails() {
+		return new User("user", "password", true, true, true, true,
+				AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
+	}
+
+	private UserDetails makeUserDetailsFromAuthoritiesPopulator() {
+		return new User("user", "password", true, true, true, true,
+				AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B"));
+	}
+
+	private ServiceProperties makeServiceProperties() {
+		final ServiceProperties serviceProperties = new ServiceProperties();
+		serviceProperties.setSendRenew(false);
+		serviceProperties.setService("http://test.com");
+
+		return serviceProperties;
+	}
+
+	@Test
+	public void statefulAuthenticationIsSuccessful() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+
+		StatelessTicketCache cache = new MockStatelessTicketCache();
+		cap.setStatelessTicketCache(cache);
+		cap.setServiceProperties(makeServiceProperties());
+
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.afterPropertiesSet();
+
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-123");
+		token.setDetails("details");
+
+		Authentication result = cap.authenticate(token);
+
+		// Confirm ST-123 was NOT added to the cache
+		assertTrue(cache.getByTicketId("ST-456") == null);
+
+		if (!(result instanceof CasAuthenticationToken)) {
+			fail("Should have returned a CasAuthenticationToken");
+		}
+
+		CasAuthenticationToken casResult = (CasAuthenticationToken) result;
+		assertEquals(makeUserDetailsFromAuthoritiesPopulator(), casResult.getPrincipal());
+		assertEquals("ST-123", casResult.getCredentials());
+		assertTrue(casResult.getAuthorities().contains(
+				new SimpleGrantedAuthority("ROLE_A")));
+		assertTrue(casResult.getAuthorities().contains(
+				new SimpleGrantedAuthority("ROLE_B")));
+		assertEquals(cap.getKey().hashCode(), casResult.getKeyHash());
+		assertEquals("details", casResult.getDetails());
+
+		// Now confirm the CasAuthenticationToken is automatically re-accepted.
+		// To ensure TicketValidator not called again, set it to deliver an exception...
+		cap.setTicketValidator(new MockTicketValidator(false));
+
+		Authentication laterResult = cap.authenticate(result);
+		assertEquals(result, laterResult);
+	}
+
+	@Test
+	public void statelessAuthenticationIsSuccessful() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+
+		StatelessTicketCache cache = new MockStatelessTicketCache();
+		cap.setStatelessTicketCache(cache);
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, "ST-456");
+		token.setDetails("details");
+
+		Authentication result = cap.authenticate(token);
+
+		// Confirm ST-456 was added to the cache
+		assertTrue(cache.getByTicketId("ST-456") != null);
+
+		if (!(result instanceof CasAuthenticationToken)) {
+			fail("Should have returned a CasAuthenticationToken");
+		}
+
+		assertEquals(makeUserDetailsFromAuthoritiesPopulator(), result.getPrincipal());
+		assertEquals("ST-456", result.getCredentials());
+		assertEquals("details", result.getDetails());
+
+		// Now try to authenticate again. To ensure TicketValidator not
+		// called again, set it to deliver an exception...
+		cap.setTicketValidator(new MockTicketValidator(false));
+
+		// Previously created UsernamePasswordAuthenticationToken is OK
+		Authentication newResult = cap.authenticate(token);
+		assertEquals(makeUserDetailsFromAuthoritiesPopulator(), newResult.getPrincipal());
+		assertEquals("ST-456", newResult.getCredentials());
+	}
+
+	@Test
+	public void authenticateAllNullService() throws Exception {
+		String serviceUrl = "https://service/context";
+		ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
+		when(details.getServiceUrl()).thenReturn(serviceUrl);
+		TicketValidator validator = mock(TicketValidator.class);
+		when(validator.validate(any(String.class), any(String.class))).thenReturn(
+				new AssertionImpl("rod"));
+
+		ServiceProperties serviceProperties = makeServiceProperties();
+		serviceProperties.setAuthenticateAllArtifacts(true);
+
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+
+		cap.setTicketValidator(validator);
+		cap.setServiceProperties(serviceProperties);
+		cap.afterPropertiesSet();
+
+		String ticket = "ST-456";
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
+
+		Authentication result = cap.authenticate(token);
+	}
+
+	@Test
+	public void authenticateAllAuthenticationIsSuccessful() throws Exception {
+		String serviceUrl = "https://service/context";
+		ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
+		when(details.getServiceUrl()).thenReturn(serviceUrl);
+		TicketValidator validator = mock(TicketValidator.class);
+		when(validator.validate(any(String.class), any(String.class))).thenReturn(
+				new AssertionImpl("rod"));
+
+		ServiceProperties serviceProperties = makeServiceProperties();
+		serviceProperties.setAuthenticateAllArtifacts(true);
+
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+
+		cap.setTicketValidator(validator);
+		cap.setServiceProperties(serviceProperties);
+		cap.afterPropertiesSet();
+
+		String ticket = "ST-456";
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
+
+		Authentication result = cap.authenticate(token);
+		verify(validator).validate(ticket, serviceProperties.getService());
+
+		serviceProperties.setAuthenticateAllArtifacts(true);
+		result = cap.authenticate(token);
+		verify(validator, times(2)).validate(ticket, serviceProperties.getService());
+
+		token.setDetails(details);
+		result = cap.authenticate(token);
+		verify(validator).validate(ticket, serviceUrl);
+
+		serviceProperties.setAuthenticateAllArtifacts(false);
+		serviceProperties.setService(null);
+		cap.setServiceProperties(serviceProperties);
+		cap.afterPropertiesSet();
+		result = cap.authenticate(token);
+		verify(validator, times(2)).validate(ticket, serviceUrl);
+
+		token.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest()));
+		try {
+			cap.authenticate(token);
+			fail("Expected Exception");
+		}
+		catch (IllegalStateException success) {
+		}
+
+		cap.setServiceProperties(null);
+		cap.afterPropertiesSet();
+		try {
+			cap.authenticate(token);
+			fail("Expected Exception");
+		}
+		catch (IllegalStateException success) {
+		}
+	}
+
+	@Test(expected = BadCredentialsException.class)
+	public void missingTicketIdIsDetected() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+
+		StatelessTicketCache cache = new MockStatelessTicketCache();
+		cap.setStatelessTicketCache(cache);
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "");
+
+		cap.authenticate(token);
+	}
+
+	@Test(expected = BadCredentialsException.class)
+	public void invalidKeyIsDetected() throws Exception {
+		final Assertion assertion = new AssertionImpl("test");
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+
+		StatelessTicketCache cache = new MockStatelessTicketCache();
+		cap.setStatelessTicketCache(cache);
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+
+		CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY",
+				makeUserDetails(), "credentials",
+				AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion);
+
+		cap.authenticate(token);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void detectsMissingAuthoritiesPopulator() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setKey("qwerty");
+		cap.setStatelessTicketCache(new MockStatelessTicketCache());
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void detectsMissingKey() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setStatelessTicketCache(new MockStatelessTicketCache());
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void detectsMissingStatelessTicketCache() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		// set this explicitly to null to test failure
+		cap.setStatelessTicketCache(null);
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void detectsMissingTicketValidator() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+		cap.setStatelessTicketCache(new MockStatelessTicketCache());
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+	}
+
+	@Test
+	public void gettersAndSettersMatch() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+		cap.setStatelessTicketCache(new MockStatelessTicketCache());
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+
+		// TODO disabled because why do we need to expose this?
+		// assertTrue(cap.getUserDetailsService() != null);
+		assertEquals("qwerty", cap.getKey());
+		assertTrue(cap.getStatelessTicketCache() != null);
+		assertTrue(cap.getTicketValidator() != null);
+	}
+
+	@Test
+	public void ignoresClassesItDoesNotSupport() throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+		cap.setStatelessTicketCache(new MockStatelessTicketCache());
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+
+		TestingAuthenticationToken token = new TestingAuthenticationToken("user",
+				"password", "ROLE_A");
+		assertFalse(cap.supports(TestingAuthenticationToken.class));
+
+		// Try it anyway
+		assertEquals(null, cap.authenticate(token));
+	}
+
+	@Test
+	public void ignoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal()
+			throws Exception {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
+		cap.setKey("qwerty");
+		cap.setStatelessTicketCache(new MockStatelessTicketCache());
+		cap.setTicketValidator(new MockTicketValidator(true));
+		cap.setServiceProperties(makeServiceProperties());
+		cap.afterPropertiesSet();
+
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+				"some_normal_user", "password",
+				AuthorityUtils.createAuthorityList("ROLE_A"));
+		assertEquals(null, cap.authenticate(token));
+	}
+
+	@Test
+	public void supportsRequiredTokens() {
+		CasAuthenticationProvider cap = new CasAuthenticationProvider();
+		assertTrue(cap.supports(UsernamePasswordAuthenticationToken.class));
+		assertTrue(cap.supports(CasAuthenticationToken.class));
+	}
+
+	// ~ Inner Classes
+	// ==================================================================================================
+
+	private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService {
+
+		public UserDetails loadUserDetails(final Authentication token)
+				throws UsernameNotFoundException {
+			return makeUserDetailsFromAuthoritiesPopulator();
+		}
+	}
+
+	private class MockStatelessTicketCache implements StatelessTicketCache {
+		private Map<String, CasAuthenticationToken> cache = new HashMap<String, CasAuthenticationToken>();
+
+		public CasAuthenticationToken getByTicketId(String serviceTicket) {
+			return cache.get(serviceTicket);
+		}
+
+		public void putTicketInCache(CasAuthenticationToken token) {
+			cache.put(token.getCredentials().toString(), token);
+		}
+
+		public void removeTicketFromCache(CasAuthenticationToken token) {
+			throw new UnsupportedOperationException("mock method not implemented");
+		}
+
+		public void removeTicketFromCache(String serviceTicket) {
+			throw new UnsupportedOperationException("mock method not implemented");
+		}
+	}
+
+	private class MockTicketValidator implements TicketValidator {
+		private boolean returnTicket;
+
+		public MockTicketValidator(boolean returnTicket) {
+			this.returnTicket = returnTicket;
+		}
+
+		public Assertion validate(final String ticket, final String service)
+				throws TicketValidationException {
+			if (returnTicket) {
+				return new AssertionImpl("rod");
+			}
+			throw new BadCredentialsException("As requested from mock");
+		}
+	}
 }

+ 172 - 152
cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java

@@ -33,156 +33,176 @@ import java.util.*;
  * @author Ben Alex
  */
 public class CasAuthenticationTokenTests extends TestCase {
-    private final List<GrantedAuthority> ROLES = AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO");
-
-    private UserDetails makeUserDetails() {
-        return makeUserDetails("user");
-    }
-
-    private UserDetails makeUserDetails(final String name) {
-        return new User(name, "password", true, true, true, true, ROLES);
-    }
-
-    public final void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public void testConstructorRejectsNulls() {
-        final Assertion assertion = new AssertionImpl("test");
-        try {
-            new CasAuthenticationToken(null, makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new CasAuthenticationToken("key", null, "Password", ROLES, makeUserDetails(), assertion);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new CasAuthenticationToken("key", makeUserDetails(), null, ROLES, makeUserDetails(), assertion);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, makeUserDetails(), null);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, null, assertion);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            new CasAuthenticationToken("key", makeUserDetails(), "Password", AuthorityUtils.createAuthorityList("ROLE_1", null), makeUserDetails(), assertion);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-    }
-
-    public void testEqualsWhenEqual() {
-        final Assertion assertion = new AssertionImpl("test");
-
-        CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-
-        CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-
-        assertEquals(token1, token2);
-    }
-
-    public void testGetters() {
-        // Build the proxy list returned in the ticket from CAS
-        final Assertion assertion = new AssertionImpl("test");
-        CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-        assertEquals("key".hashCode(), token.getKeyHash());
-        assertEquals(makeUserDetails(), token.getPrincipal());
-        assertEquals("Password", token.getCredentials());
-        assertTrue(token.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ONE")));
-        assertTrue(token.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_TWO")));
-        assertEquals(assertion, token.getAssertion());
-        assertEquals(makeUserDetails().getUsername(), token.getUserDetails().getUsername());
-    }
-
-    public void testNoArgConstructorDoesntExist() {
-        try {
-            CasAuthenticationToken.class.getDeclaredConstructor((Class[]) null);
-            fail("Should have thrown NoSuchMethodException");
-        } catch (NoSuchMethodException expected) {
-            assertTrue(true);
-        }
-    }
-
-    public void testNotEqualsDueToAbstractParentEqualsCheck() {
-        final Assertion assertion = new AssertionImpl("test");
-
-        CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-
-        CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails("OTHER_NAME"), "Password",
-                ROLES, makeUserDetails(), assertion);
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testNotEqualsDueToDifferentAuthenticationClass() {
-        final Assertion assertion = new AssertionImpl("test");
-
-        CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-
-        UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken("Test", "Password", ROLES);
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testNotEqualsDueToKey() {
-        final Assertion assertion = new AssertionImpl("test");
-
-        CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-
-        CasAuthenticationToken token2 = new CasAuthenticationToken("DIFFERENT_KEY", makeUserDetails(), "Password",
-                ROLES, makeUserDetails(), assertion);
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testNotEqualsDueToAssertion() {
-        final Assertion assertion = new AssertionImpl("test");
-        final Assertion assertion2 = new AssertionImpl("test");
-
-        CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-
-        CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion2);
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testSetAuthenticated() {
-        final Assertion assertion = new AssertionImpl("test");
-        CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
-                makeUserDetails(), assertion);
-        assertTrue(token.isAuthenticated());
-        token.setAuthenticated(false);
-        assertTrue(!token.isAuthenticated());
-    }
-
-    public void testToString() {
-        final Assertion assertion = new AssertionImpl("test");
-        CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password",ROLES,
-                makeUserDetails(), assertion);
-        String result = token.toString();
-        assertTrue(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1);
-    }
+	private final List<GrantedAuthority> ROLES = AuthorityUtils.createAuthorityList(
+			"ROLE_ONE", "ROLE_TWO");
+
+	private UserDetails makeUserDetails() {
+		return makeUserDetails("user");
+	}
+
+	private UserDetails makeUserDetails(final String name) {
+		return new User(name, "password", true, true, true, true, ROLES);
+	}
+
+	public final void setUp() throws Exception {
+		super.setUp();
+	}
+
+	public void testConstructorRejectsNulls() {
+		final Assertion assertion = new AssertionImpl("test");
+		try {
+			new CasAuthenticationToken(null, makeUserDetails(), "Password", ROLES,
+					makeUserDetails(), assertion);
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new CasAuthenticationToken("key", null, "Password", ROLES, makeUserDetails(),
+					assertion);
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new CasAuthenticationToken("key", makeUserDetails(), null, ROLES,
+					makeUserDetails(), assertion);
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES,
+					makeUserDetails(), null);
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new CasAuthenticationToken("key", makeUserDetails(), "Password", ROLES, null,
+					assertion);
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+
+		try {
+			new CasAuthenticationToken("key", makeUserDetails(), "Password",
+					AuthorityUtils.createAuthorityList("ROLE_1", null),
+					makeUserDetails(), assertion);
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+	}
+
+	public void testEqualsWhenEqual() {
+		final Assertion assertion = new AssertionImpl("test");
+
+		CasAuthenticationToken token1 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		CasAuthenticationToken token2 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		assertEquals(token1, token2);
+	}
+
+	public void testGetters() {
+		// Build the proxy list returned in the ticket from CAS
+		final Assertion assertion = new AssertionImpl("test");
+		CasAuthenticationToken token = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+		assertEquals("key".hashCode(), token.getKeyHash());
+		assertEquals(makeUserDetails(), token.getPrincipal());
+		assertEquals("Password", token.getCredentials());
+		assertTrue(token.getAuthorities()
+				.contains(new SimpleGrantedAuthority("ROLE_ONE")));
+		assertTrue(token.getAuthorities()
+				.contains(new SimpleGrantedAuthority("ROLE_TWO")));
+		assertEquals(assertion, token.getAssertion());
+		assertEquals(makeUserDetails().getUsername(), token.getUserDetails()
+				.getUsername());
+	}
+
+	public void testNoArgConstructorDoesntExist() {
+		try {
+			CasAuthenticationToken.class.getDeclaredConstructor((Class[]) null);
+			fail("Should have thrown NoSuchMethodException");
+		}
+		catch (NoSuchMethodException expected) {
+			assertTrue(true);
+		}
+	}
+
+	public void testNotEqualsDueToAbstractParentEqualsCheck() {
+		final Assertion assertion = new AssertionImpl("test");
+
+		CasAuthenticationToken token1 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		CasAuthenticationToken token2 = new CasAuthenticationToken("key",
+				makeUserDetails("OTHER_NAME"), "Password", ROLES, makeUserDetails(),
+				assertion);
+
+		assertTrue(!token1.equals(token2));
+	}
+
+	public void testNotEqualsDueToDifferentAuthenticationClass() {
+		final Assertion assertion = new AssertionImpl("test");
+
+		CasAuthenticationToken token1 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken(
+				"Test", "Password", ROLES);
+		assertTrue(!token1.equals(token2));
+	}
+
+	public void testNotEqualsDueToKey() {
+		final Assertion assertion = new AssertionImpl("test");
+
+		CasAuthenticationToken token1 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		CasAuthenticationToken token2 = new CasAuthenticationToken("DIFFERENT_KEY",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		assertTrue(!token1.equals(token2));
+	}
+
+	public void testNotEqualsDueToAssertion() {
+		final Assertion assertion = new AssertionImpl("test");
+		final Assertion assertion2 = new AssertionImpl("test");
+
+		CasAuthenticationToken token1 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+
+		CasAuthenticationToken token2 = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion2);
+
+		assertTrue(!token1.equals(token2));
+	}
+
+	public void testSetAuthenticated() {
+		final Assertion assertion = new AssertionImpl("test");
+		CasAuthenticationToken token = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+		assertTrue(token.isAuthenticated());
+		token.setAuthenticated(false);
+		assertTrue(!token.isAuthenticated());
+	}
+
+	public void testToString() {
+		final Assertion assertion = new AssertionImpl("test");
+		CasAuthenticationToken token = new CasAuthenticationToken("key",
+				makeUserDetails(), "Password", ROLES, makeUserDetails(), assertion);
+		String result = token.toString();
+		assertTrue(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1);
+	}
 }

+ 53 - 52
cas/src/test/java/org/springframework/security/cas/authentication/EhCacheBasedTicketCacheTests.java

@@ -27,62 +27,63 @@ import org.springframework.security.cas.authentication.EhCacheBasedTicketCache;
 
 import static org.junit.Assert.*;
 
-
 /**
  * Tests {@link EhCacheBasedTicketCache}.
  *
  * @author Ben Alex
  */
 public class EhCacheBasedTicketCacheTests extends AbstractStatelessTicketCacheTests {
-    private static CacheManager cacheManager;
-
-    //~ Methods ========================================================================================================
-    @BeforeClass
-    public static void initCacheManaer() {
-        cacheManager = CacheManager.create();
-        cacheManager.addCache(new Cache("castickets", 500, false, false, 30, 30));
-    }
-
-    @AfterClass
-    public static void shutdownCacheManager() {
-        cacheManager.removalAll();
-        cacheManager.shutdown();
-    }
-
-    @Test
-    public void testCacheOperation() throws Exception {
-        EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
-        cache.setCache(cacheManager.getCache("castickets"));
-        cache.afterPropertiesSet();
-
-        final CasAuthenticationToken token = getToken();
-
-        // Check it gets stored in the cache
-        cache.putTicketInCache(token);
-        assertEquals(token, cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ"));
-
-        // Check it gets removed from the cache
-        cache.removeTicketFromCache(getToken());
-        assertNull(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ"));
-
-        // Check it doesn't return values for null or unknown service tickets
-        assertNull(cache.getByTicketId(null));
-        assertNull(cache.getByTicketId("UNKNOWN_SERVICE_TICKET"));
-    }
-
-    @Test
-    public void testStartupDetectsMissingCache() throws Exception {
-        EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
-
-        try {
-            cache.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        Ehcache myCache = cacheManager.getCache("castickets");
-        cache.setCache(myCache);
-        assertEquals(myCache, cache.getCache());
-    }
+	private static CacheManager cacheManager;
+
+	// ~ Methods
+	// ========================================================================================================
+	@BeforeClass
+	public static void initCacheManaer() {
+		cacheManager = CacheManager.create();
+		cacheManager.addCache(new Cache("castickets", 500, false, false, 30, 30));
+	}
+
+	@AfterClass
+	public static void shutdownCacheManager() {
+		cacheManager.removalAll();
+		cacheManager.shutdown();
+	}
+
+	@Test
+	public void testCacheOperation() throws Exception {
+		EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
+		cache.setCache(cacheManager.getCache("castickets"));
+		cache.afterPropertiesSet();
+
+		final CasAuthenticationToken token = getToken();
+
+		// Check it gets stored in the cache
+		cache.putTicketInCache(token);
+		assertEquals(token, cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ"));
+
+		// Check it gets removed from the cache
+		cache.removeTicketFromCache(getToken());
+		assertNull(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ"));
+
+		// Check it doesn't return values for null or unknown service tickets
+		assertNull(cache.getByTicketId(null));
+		assertNull(cache.getByTicketId("UNKNOWN_SERVICE_TICKET"));
+	}
+
+	@Test
+	public void testStartupDetectsMissingCache() throws Exception {
+		EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
+
+		try {
+			cache.afterPropertiesSet();
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException expected) {
+			assertTrue(true);
+		}
+
+		Ehcache myCache = cacheManager.getCache("castickets");
+		cache.setCache(myCache);
+		assertEquals(myCache, cache.getCache());
+	}
 }

+ 12 - 13
cas/src/test/java/org/springframework/security/cas/authentication/NullStatelessTicketCacheTests.java

@@ -14,7 +14,6 @@
  */
 package org.springframework.security.cas.authentication;
 
-
 import org.junit.Test;
 import org.springframework.security.cas.authentication.CasAuthenticationToken;
 import org.springframework.security.cas.authentication.NullStatelessTicketCache;
@@ -30,18 +29,18 @@ import static org.junit.Assert.*;
  */
 public class NullStatelessTicketCacheTests extends AbstractStatelessTicketCacheTests {
 
-    private StatelessTicketCache cache = new NullStatelessTicketCache();
+	private StatelessTicketCache cache = new NullStatelessTicketCache();
 
-    @Test
-    public void testGetter() {
-        assertNull(cache.getByTicketId(null));
-        assertNull(cache.getByTicketId("test"));
-    }
+	@Test
+	public void testGetter() {
+		assertNull(cache.getByTicketId(null));
+		assertNull(cache.getByTicketId("test"));
+	}
 
-    @Test
-    public void testInsertAndGet() {
-        final CasAuthenticationToken token = getToken();
-        cache.putTicketInCache(token);
-        assertNull(cache.getByTicketId((String) token.getCredentials()));
-    }
+	@Test
+	public void testInsertAndGet() {
+		final CasAuthenticationToken token = getToken();
+		cache.putTicketInCache(token);
+		assertNull(cache.getByTicketId((String) token.getCredentials()));
+	}
 }

部分文件因为文件数量过多而无法显示