-
Enhancement
-
Resolution: Unresolved
-
P4
-
None
-
8u66
-
generic
-
generic
A DESCRIPTION OF THE REQUEST :
The field "elements" of the class "javax.security.auth.Subject.SecureSet" is of type "java.util.LinkedList" instead of something more appropriate like "java.util.HashSet".
JUSTIFICATION :
When adding huge amounts of "java.security.Principal" objects to a "javax.security.auth.Subject" principal set, the method "java.security.Principal.equals(Object)" is being invoked unnecessarily often.
For n principals being added to an empty principal set the invocation count is (using http://asciimath.org/):
sum_(k=1)^n k-1
In an enterprise application (we are using Java EE 5 and JBoss EAP 4.3) with large call graphs the security context is being propagated very often. And each time the "javax.security.auth.Subject" instance is being copied - unfortunately without the usage of the "copy" constructor.
We have profiled our application and counted ~1 mio "java.security.Principal.equals(Object)" calls for fairly small call graphs.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When adding to a set of any kind, the method "java.lang.Object.equals(Object)" should not be invoked n times in a worst case scenario (that is the object to be added is not part of the set yet) where n is the number of elements in the set.
The set should rather make use of the hashCode-equals-contract.
ACTUAL -
"javax.security.auth.Subject.SecureSet.add(E)" checks the backing "java.util.LinkedList" for duplicates of objects to be added.
---------- BEGIN SOURCE ----------
package com.acme;
import java.security.Principal;
import java.util.concurrent.atomic.AtomicLong;
import javax.security.auth.Subject;
public class MyPrincipal implements Principal {
private static AtomicLong
hashCodeInvocationCount = new AtomicLong(),
equalsInvocationCount = new AtomicLong()
;
private final String name;
public MyPrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int hashCode() {
hashCodeInvocationCount.incrementAndGet();
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
equalsInvocationCount.incrementAndGet();
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyPrincipal other = (MyPrincipal) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public static void main(String[] args) {
// This is pretty much a login.
Subject s = new Subject();
final int theAmountOfPrincipalsTheUserHas = 100;
for (int i = 0; i < theAmountOfPrincipalsTheUserHas; ++i) s.getPrincipals().add(new MyPrincipal("Principal #" + i));
// This is after a cache hit, e.g. in an application server. We're copying the principals.
final int theAmountOfInvocationsWithCacheHits = 5;
for (int i = 0; i < theAmountOfInvocationsWithCacheHits; ++i) {
Subject copy = new Subject();
for (Principal e : s.getPrincipals()) copy.getPrincipals().add(e);
for (Object e : s.getPublicCredentials()) copy.getPublicCredentials().add(e);
for (Object e : s.getPrivateCredentials()) copy.getPrivateCredentials().add(e);
}
System.out.println(hashCodeInvocationCount.get() + " hashCode invocations");
System.out.println(equalsInvocationCount.get() + " equals invocations");
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
One can replace the "javax.security.auth.Subject" implementation via the Java Endorsed Standards Override Mechanism https://docs.oracle.com/javase/7/docs/technotes/guides/standards/
The field "elements" of the class "javax.security.auth.Subject.SecureSet" is of type "java.util.LinkedList" instead of something more appropriate like "java.util.HashSet".
JUSTIFICATION :
When adding huge amounts of "java.security.Principal" objects to a "javax.security.auth.Subject" principal set, the method "java.security.Principal.equals(Object)" is being invoked unnecessarily often.
For n principals being added to an empty principal set the invocation count is (using http://asciimath.org/):
sum_(k=1)^n k-1
In an enterprise application (we are using Java EE 5 and JBoss EAP 4.3) with large call graphs the security context is being propagated very often. And each time the "javax.security.auth.Subject" instance is being copied - unfortunately without the usage of the "copy" constructor.
We have profiled our application and counted ~1 mio "java.security.Principal.equals(Object)" calls for fairly small call graphs.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When adding to a set of any kind, the method "java.lang.Object.equals(Object)" should not be invoked n times in a worst case scenario (that is the object to be added is not part of the set yet) where n is the number of elements in the set.
The set should rather make use of the hashCode-equals-contract.
ACTUAL -
"javax.security.auth.Subject.SecureSet.add(E)" checks the backing "java.util.LinkedList" for duplicates of objects to be added.
---------- BEGIN SOURCE ----------
package com.acme;
import java.security.Principal;
import java.util.concurrent.atomic.AtomicLong;
import javax.security.auth.Subject;
public class MyPrincipal implements Principal {
private static AtomicLong
hashCodeInvocationCount = new AtomicLong(),
equalsInvocationCount = new AtomicLong()
;
private final String name;
public MyPrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int hashCode() {
hashCodeInvocationCount.incrementAndGet();
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
equalsInvocationCount.incrementAndGet();
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyPrincipal other = (MyPrincipal) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public static void main(String[] args) {
// This is pretty much a login.
Subject s = new Subject();
final int theAmountOfPrincipalsTheUserHas = 100;
for (int i = 0; i < theAmountOfPrincipalsTheUserHas; ++i) s.getPrincipals().add(new MyPrincipal("Principal #" + i));
// This is after a cache hit, e.g. in an application server. We're copying the principals.
final int theAmountOfInvocationsWithCacheHits = 5;
for (int i = 0; i < theAmountOfInvocationsWithCacheHits; ++i) {
Subject copy = new Subject();
for (Principal e : s.getPrincipals()) copy.getPrincipals().add(e);
for (Object e : s.getPublicCredentials()) copy.getPublicCredentials().add(e);
for (Object e : s.getPrivateCredentials()) copy.getPrivateCredentials().add(e);
}
System.out.println(hashCodeInvocationCount.get() + " hashCode invocations");
System.out.println(equalsInvocationCount.get() + " equals invocations");
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
One can replace the "javax.security.auth.Subject" implementation via the Java Endorsed Standards Override Mechanism https://docs.oracle.com/javase/7/docs/technotes/guides/standards/