-
Notifications
You must be signed in to change notification settings - Fork 487
Rework resolving the default for Context.INITIAL_CONTEXT_FACTORY #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework resolving the default for Context.INITIAL_CONTEXT_FACTORY #579
Conversation
Hello, Will this PR fix Java 16 error ? (I'm using gradle 7) SbtemplateApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
Caused by: java.lang.IllegalAccessError at AbstractContextSource.java:77 I tried few versions and this error appear on every of them :
Thank you. |
@Yberion yes, it will. You can try out a snapshot build from jitpack. |
? |
@bryant1410 sorry, I tagged you accidentally. |
Thank you for the answer.
Fixed it, waiting for the official release ! :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR, @iherasymenko! Please see my comments inline.
core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/springframework/ldap/core/support/AbstractContextSource.java
Show resolved
Hide resolved
try { | ||
this.contextFactory = Class.forName("com.sun.jndi.ldap.LdapCtxFactory"); | ||
} catch (ClassNotFoundException e) { | ||
LOG.error("Cannot resolve com.sun.jndi.ldap.LdapCtxFactory. contextFactory must be explicitly specified", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't necessarily an error since the application may call setContextFactory
later. Would this work be better in the afterPropertiesSet
method? Then, you would be able to state definitively that the class is correctly or incorrectly configured and throw an exception accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the logging level to TRACE
and added the validation to the afterPropertiesSet
method. I did not completely move the resolution to the afterPropertiesSet
method since having it there would result in the same behavior change described here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on how the same behavior would result? It would be an error to use this class before calling afterPropertiesSet
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
elaborated below.
…hat the method `getContextFactory()` returns the assigned default With this approach in place, one will be able to specify a different implementation using the setContextFactory method should `com.sun.jndi.ldap.LdapCtxFactory` gets removed one day (since this class is not exported by the `java.naming` modules in JDK 9+ and is an implementation detail).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick turn around, @iherasymenko! Please see an additional comment in my review.
private DirContextAuthenticationStrategy authenticationStrategy = new SimpleDirContextAuthenticationStrategy(); | ||
|
||
public AbstractContextSource() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be moved into afterPropertiesSet
, too, so that it's all in one place.
For example, in afterPropertiesSet
you would do:
if (this.contextFactory == null) {
try {
this.contextFactory = Class.forName(DEFAULT_CONTEXT_FACTORY);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Could not find " + DEFAULT_CONTEXT_FACTORY + " and no context factory specified");
}
}
One thing that's nice about this is that the ClassNotFoundException
is not generated unless no context factory is specified. Since exceptions are expensive to generate, it's nice when they can be avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my original idea, yes. However, there will be a slight behavioral change compared to 2.3.3.
LdapContextSource ctx = new LdapContextSource();
// afterPropertiesSet is not required.
Class<?> contextFactory = ctx.getContextFactory(); // Version 2.3.3 would return com.sun.jndi.ldap.LdapCtxFactory
whereas with the change applied
LdapContextSource ctx = new LdapContextSource();
ctx.afterPropertiesSet(); // required otherwise the below will return a null value
Class<?> contextFactory = ctx.getContextFactory();
Please let me know if that is acceptable and I will update the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it alleviate your concern if the PR also changed to run this logic in a private getter?
For example, you could add:
private Class<?> ensureContextFactoryName() {
if (this.contextFactory != null) {
return this.contextFactory.getName();
}
try {
this.contextFactory = Class.forName(DEFAULT_CONTEXT_FACTORY);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Could not find " + DEFAULT_CONTEXT_FACTORY + " and no context factory specified");
}
return this.contextFactory.getName();
}
Then, in afterPropertiesSet
, you'd do:
ensureContextFactoryName();
And in setupAnonymousEnv
:
env.put(Context.INITIAL_CONTEXT_FACTORY, ensureContextFactoryName());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just initialize the DEFAULT_CONTEXT_FACTORY
in the static code block and do not make any other changes?
private static final Class<?> DEFAULT_CONTEXT_FACTORY;
static {
try {
DEFAULT_CONTEXT_FACTORY = Class.forName("com.sun.jndi.ldap.LdapCtxFactory");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's problematic since it errors on something that may not be a problem. The class not being present is only a problem if the application does not set a context factory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jzheaux The com.sun.jndi.ldap.LdapCtxFactory
class is always present but not always could be accessed. The ClassNotFoundException
will never be thrown and catching it is just because it's a checked exception. In other words the DEFAULT_CONTEXT_FACTORY
will always be present and so will be set the contextFactory
field by default as well.
Following code example works on Java 16 properly:
public class MainClass {
private static final Class<?> DEFAULT_CONTEXT_FACTORY;
static {
try {
DEFAULT_CONTEXT_FACTORY = Class.forName("com.sun.jndi.ldap.LdapCtxFactory");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
System.out.println(DEFAULT_CONTEXT_FACTORY.getName());
}
}
Value of the DEFAULT_CONTEXT_FACTORY
is used in the AbstractContextSource
only for two things:
- return value of
getContextFactory()
env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory.getName());
With my version of change all of the above will continue to work properly and no behavior change will be introduced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. It's interesting that Class.forName
works when the direct reference does not. Given that, the PR's constructor won't throw the exception in normal JDK 16 circumstances.
Thanks, @iherasymenko for the PR! Based on this comment, it appears that the extra work to delay the thrown exception is needless. I squashed your two commits adjusted the copyright header to be |
A follow up for #568, #570, #543.
With this approach in place, one will be able to specify a different implementation using the setContextFactory method in case
com.sun.jndi.ldap.LdapCtxFactory
gets removed one day (since this class is not exported by thejava.naming
modules in JDK 9+ and is an implementation detail).Having the
contextFactory
field initialized as follows would lead to theExceptionInInitializerError
which users would not be able to overcome.private static final Class<?> DEFAULT_CONTEXT_FACTORY = org.springframework.util.ClassUtils.resolveClassName("com.sun.jndi.ldap.LdapCtxFactory", null)