Discussion:
RFR JDK-8044627: Update JNDI to work with modules
Pavel Rappo
2014-09-16 11:12:14 UTC
Permalink
Hi everyone,

Could you please review my change for JDK-8044627?

http://cr.openjdk.java.net/~prappo/8044627/webrev.00/

-Pavel
Daniel Fuchs
2014-09-16 13:11:18 UTC
Permalink
Post by Pavel Rappo
Hi everyone,
Could you please review my change for JDK-8044627?
http://cr.openjdk.java.net/~prappo/8044627/webrev.00/
-Pavel
Hi Pavel,

Given that helper.loadClass uses the context class loader,
Should you also simply use
ServiceLoader<InitialContextFactory> loader =
ServiceLoader.load(InitialContextFactory.class);
at lines 680-681 ?


Also it might be good to log an RFE against the ServiceLoader, so
that you could look for a service implementation of a specific
concrete class without having to instantiate all the other
service implementations encountered along the way.
Streams should provide a nice infrastructure for such an API.

It would certainly be more robust than looping over
ServiceLoader.iterator().next() - which is unfortunately the only
option available to you at the moment.
This seems a bit fragile to me - unless it's guaranteed that
the various InitialContextFactory have no static initializer
that might throw exceptions (such as SecurityException) - and
that their default constructor does nothing (so that instantiating
e.g. com.sun.jndi.cosnaming.CNCtxFactory when you're actually
looking for com.sun.jndi.ldap.LdapCtxFactory has no side effect
- which fortunately seems to be the case).

Also - it would be good to clarify the specification of
public static Context getInitialContext(Hashtable<?,?> env)

It was not clear to me that you would loop over all the
services found by the ServiceLoader until you'd find one
whose concrete class matched the name pointed to by
Context.INITIAL_CONTEXT_FACTORY.

best regards,

-- daniel
Pavel Rappo
2014-09-16 14:14:49 UTC
Permalink
Daniel,
Post by Daniel Fuchs
Given that helper.loadClass uses the context class loader,
Should you also simply use
ServiceLoader<InitialContextFactory> loader =
ServiceLoader.load(InitialContextFactory.class);
at lines 680-681 ?
It needs to be the system class loader to allow for JNDI providers that might be on the class path or module path.
Post by Daniel Fuchs
Also it might be good to log an RFE against the ServiceLoader, so
that you could look for a service implementation of a specific
concrete class without having to instantiate all the other
service implementations encountered along the way.
Streams should provide a nice infrastructure for such an API.
It would certainly be more robust than looping over
ServiceLoader.iterator().next() - which is unfortunately the only
option available to you at the moment.
IMO, it's not just an inconvenience, but rather a part of ServiceLoader's design. I mean, it's definitely designed to provide, so to say, a "one-to-many" mapping for the classes (providers) that implements some interface (a service) to a client. It merely delivers you implementations. You should than iterate though them and decide which one satisfies your needs. I'm not sure it's a good idea to get services based on their implementation classnames. It's more likely to be a little bit a flaw in design from the JNDI part -- when you have to specify the implementation class's FQN. But given the history of the JNDI (it's started as a project outside the JDK) -- it's totally understandable.
(Just as a side-note, have a look at these examples of usage of ServiceLoader:

java.net.InetAddress:862
java.time.chrono.AbstractChronology:274
java.time.chrono.AbstractChronology:307
javax.xml.validation.SchemaFactoryFinder:405
javax.xml.xpath.XPathFactoryFinder:403
java.time.zone.ZoneRulesProvider:177)
Post by Daniel Fuchs
Also - it would be good to clarify the specification of
public static Context getInitialContext(Hashtable<?,?> env)
It was not clear to me that you would loop over all the
services found by the ServiceLoader until you'd find one
whose concrete class matched the name pointed to by
Context.INITIAL_CONTEXT_FACTORY.
Don't you think it becomes than 'overspecified'? Why should we want to tie ourselves?
Post by Daniel Fuchs
This seems a bit fragile to me - unless it's guaranteed that
the various InitialContextFactory have no static initializer
that might throw exceptions (such as SecurityException) - and
that their default constructor does nothing (so that instantiating
e.g. com.sun.jndi.cosnaming.CNCtxFactory when you're actually
looking for com.sun.jndi.ldap.LdapCtxFactory has no side effect
- which fortunately seems to be the case).
Well, no one can guarantee us this. Even a constructor could do all the things you've mentioned :) It's just the nature of a factory. It should better be stateless and without any side effects.

-Pavel
Alan Bateman
2014-09-16 14:56:50 UTC
Permalink
Post by Pavel Rappo
Daniel,
Post by Daniel Fuchs
Given that helper.loadClass uses the context class loader,
Should you also simply use
ServiceLoader<InitialContextFactory> loader =
ServiceLoader.load(InitialContextFactory.class);
at lines 680-681 ?
It needs to be the system class loader to allow for JNDI providers that might be on the class path or module path.
The TCCL would be more appropriate here as that would allow for JNDI
providers that are bundled with the application (the TCCL should
eventually delegate to the system class loader so it should find the
JNDI provider on the class path or linked into the runtime image too).
Post by Pavel Rappo
IMO, it's not just an inconvenience, but rather a part of ServiceLoader's design. I mean, it's definitely designed to provide, so to say, a "one-to-many" mapping for the classes (providers) that implements some interface (a service) to a client. It merely delivers you implementations. You should than iterate though them and decide which one satisfies your needs. I'm not sure it's a good idea to get services based on their implementation classnames.
Right, it's the user of ServiceLoader that does the selection. The real
issue here of course is the JNDI API where the user specifies the class
name of the implementation factory when creating the initial context.
I've no doubt that it would be done differently if re-designed now.

-Alan
Daniel Fuchs
2014-09-16 15:37:44 UTC
Permalink
Post by Pavel Rappo
Daniel,
Post by Daniel Fuchs
Given that helper.loadClass uses the context class loader,
Should you also simply use
ServiceLoader<InitialContextFactory> loader =
ServiceLoader.load(InitialContextFactory.class);
at lines 680-681 ?
It needs to be the system class loader to allow for JNDI providers that might be on the class path or module path.
So is it expected that modules (e.g. java.corba) will register
their own service provider for the InitialContextFactory
(I mean - using META-INF/services/)?
Post by Pavel Rappo
Post by Daniel Fuchs
Also it might be good to log an RFE against the ServiceLoader, so
that you could look for a service implementation of a specific
concrete class without having to instantiate all the other
service implementations encountered along the way.
Streams should provide a nice infrastructure for such an API.
It would certainly be more robust than looping over
ServiceLoader.iterator().next() - which is unfortunately the only
option available to you at the moment.
IMO, it's not just an inconvenience, but rather a part of ServiceLoader's design. I mean, it's definitely designed to provide, so to say, a "one-to-many" mapping for the classes (providers) that implements some interface (a service) to a client. It merely delivers you implementations. You should than iterate though them and decide which one satisfies your needs. I'm not sure it's a good idea to get services based on their implementation classnames. It's more likely to be a little bit a flaw in design from the JNDI part -- when you have to specify the implementation class's FQN. But given the history of the JNDI (it's started as a project outside the JDK) -- it's totally understandable.
Right. It's not usual to use the service loader to look for a specific
concrete implementation class name. So on the one side what you're doing
may be better than modifying ServiceLoader to support an unusual usage.
I can buy this.
[snip]
Post by Pavel Rappo
javax.xml.validation.SchemaFactoryFinder:405
javax.xml.xpath.XPathFactoryFinder:403
[snip]

I know these two ;-) I updated JAXP to use ServiceLoader :-)

The difference however is that in this two case we're looking for
a service implementing a specific feature, we're not looking for a
service whose concrete class matches a specific class name.

In the case of InitialContextFactory - as a client of the API, I
would be a bit surprised if - asking for a LDAP InitialContext,
I received a CORBA exception. This would look strange to me.
But maybe there's nothing you can do given how JNDI is
currently working.

A possibilty might be to use InitialContextFactoryBuilder as the
service interface instead, and loop over the implementations until
you find one that does not throw NamingException - but then it
would be difficult to make the distinction between
'This builder does not support the Context.INITIAL_CONTEXT_FACTORY
that you ask for' and 'The InitialContextFactory could not be
instantiated for various reasons'...

So I'm not sure it would be better.
Post by Pavel Rappo
java.time.zone.ZoneRulesProvider:177)
Post by Daniel Fuchs
Also - it would be good to clarify the specification of
public static Context getInitialContext(Hashtable<?,?> env)
It was not clear to me that you would loop over all the
services found by the ServiceLoader until you'd find one
whose concrete class matched the name pointed to by
Context.INITIAL_CONTEXT_FACTORY.
Don't you think it becomes than 'overspecified'? Why should we want to tie ourselves?
Maybe it's because I haven't used JNDI very often, but when
I read the spec I didn't realize that the value of
Context.INITIAL_CONTEXT_FACTORY would have any effect if
ServiceLoader was used - because it's not usual to use
ServiceLoader in this way (even though the usage of
ServiceLoader is nested below 'the class specified in
the Context.INITIAL_CONTEXT_FACTORY</tt> environment property
is used').
I'm not sure it would be overspecifying to say that the ServiceLoader
is used to locate and load an implementation of the service whose
concrete class matches the named class - since that's what it's
doing anyway - but I understand your concern.
Post by Pavel Rappo
Post by Daniel Fuchs
This seems a bit fragile to me - unless it's guaranteed that
the various InitialContextFactory have no static initializer
that might throw exceptions (such as SecurityException) - and
that their default constructor does nothing (so that instantiating
e.g. com.sun.jndi.cosnaming.CNCtxFactory when you're actually
looking for com.sun.jndi.ldap.LdapCtxFactory has no side effect
- which fortunately seems to be the case).
Well, no one can guarantee us this. Even a constructor could do all the things you've mentioned :) It's just the nature of a factory. It should better be stateless and without any side effects.
So a broken InitialContextFactory for technology XXX can
prevent getting the InitialContext for technology YYY even if both
have nothing in common, and even if XXX is never actually
used in the application.
On the other hand I don't see that you have any other choice
but propagate the exception if one is emitted - as you can't know
whether it has been emitted by the class you're trying to load :-(

-- daniel
Post by Pavel Rappo
-Pavel
Pavel Rappo
2014-09-16 17:07:50 UTC
Permalink
Daniel,
Post by Daniel Fuchs
So is it expected that modules (e.g. java.corba) will register
their own service provider for the InitialContextFactory
(I mean - using META-INF/services/)?
Alan's already answered this point. TCCL is the way to go. You are right.
Post by Daniel Fuchs
The difference however is that in this two case we're looking for
a service implementing a specific feature, we're not looking for a
service whose concrete class matches a specific class name.
Agreed.
Post by Daniel Fuchs
In the case of InitialContextFactory - as a client of the API, I
would be a bit surprised if - asking for a LDAP InitialContext,
I received a CORBA exception. This would look strange to me.
Once again, agreed.
Post by Daniel Fuchs
A possibilty might be to use InitialContextFactoryBuilder as the
service interface instead, and loop over the implementations until
you find one that does not throw NamingException - but then it
would be difficult to make the distinction between
'This builder does not support the Context.INITIAL_CONTEXT_FACTORY
that you ask for' and 'The InitialContextFactory could not be
instantiated for various reasons'...
The closest thing to what you described here would be java.sql.DriverManager:664, I think. I don't think this is an acceptable for JNDI though. This would require to modify all the providers out there to provide a new entry point -- their own implementation of InitialContextFactoryBuilder.
Post by Daniel Fuchs
Maybe it's because I haven't used JNDI very often, but when
I read the spec I didn't realize that the value of Context.INITIAL_CONTEXT_FACTORY would have any effect if
ServiceLoader was used - because it's not usual to use
ServiceLoader in this way (even though the usage of
ServiceLoader is nested below 'the class specified in
the Context.INITIAL_CONTEXT_FACTORY</tt> environment property
is used').
I'm not sure it would be overspecifying to say that the ServiceLoader
is used to locate and load an implementation of the service whose
concrete class matches the named class - since that's what it's
doing anyway - but I understand your concern.
It's definitely worth thinking about. Thanks.
Post by Daniel Fuchs
So a broken InitialContextFactory for technology XXX can
prevent getting the InitialContext for technology YYY even if both
have nothing in common, and even if XXX is never actually
used in the application.
+1 It indeed seems very strange at first sight. But I believe this thing is a subjective tradeoff between the effects caused by unexpected "CORBA exception" and the effects caused by some strange behaviour after an "unrelated" exception was silently swallowed by the runtime.
It's more like a fail-fast behaviour, when even "unrelated" things are reported about:

I dropped by your house to deliver the letter. Btw, it seems you've left you kettle on...

Some time ago I asked myself the same question. And looked through the codebase to see how the ServiceLoader is used. I found some "fault-tolerant" implementations:

com.sun.media.sound.JSSecurityManager:189
java.sql.DriverManager:605
sun.tools.jconsole.JConsole:1004
com.sun.tools.attach.spi.AttachProvider:261
com.sun.tools.jdi.VirtualMachineManagerImpl:95
com.sun.tools.jdi.VirtualMachineManagerImpl:124
javax.script.ScriptEngineManager:124

As you can see there's no right answer here. It certainly depends on the behaviour you want to achieve. Suppose your service provider are plugins (as it is the case for JConsole example above) then it doesn't make any sense to shutdown if any single plugin failed. Leave this decision to a client. Report the warning. On the language level there's no such thing is warning. Only exceptions and return values. We could emulate warning with logging but... I'm sure there are many reasons why that might not fly.

-Pavel
Alan Bateman
2014-09-22 08:55:32 UTC
Permalink
Post by Pavel Rappo
Hi everyone,
Could you please review my change for JDK-8044627?
Pavel - are you planning to send an updated webrev based on the
discussion so far?

The other thing that I meant to ask is whether this change will add
service configuration files for the RMI, DNS and CosNaming
implementations, maybe this is going to be another patch?

-Alan
Pavel Rappo
2014-10-14 11:11:25 UTC
Permalink
Here's the updated webrev: http://cr.openjdk.java.net/~prappo/8044627/webrev.01/

-Pavel
Post by Pavel Rappo
Hi everyone,
Could you please review my change for JDK-8044627?
Pavel - are you planning to send an updated webrev based on the discussion so far?
The other thing that I meant to ask is whether this change will add service configuration files for the RMI, DNS and CosNaming implementations, maybe this is going to be another patch?
-Alan
Pavel Rappo
2014-10-14 14:03:53 UTC
Permalink
OK, so what I will do for now is I exclude these 4 files and push without them. I'll create a new issue to add them later.

-Pavel
Post by Daniel Fuchs
Hi Pavel,
I saw your mail on build-dev.
I guess the issue will resolve itself once we have the
modular image.
I wonder whether the way to go for now would be
to add a single META-INF/services file - as you suggest -
in java.naming, with the 4 lines inside, and log a bug/RFE
to follow up on that once the modular image is there.
best regards,
-- daniel
Once we move to modules then these service configuration files will go away. The resolver will build a service-use graph based on the provides/uses in the module descriptors and that will link the JNDI module to the providers. Pavel just needs a short term solution and having all the providers in one file is okay for that. A better short term solution is to just concatenate them in the build, we are already doing this in the jigsaw/m2 forest for the JDI connectors.
-Alan
Chris Hegarty
2014-10-14 14:09:23 UTC
Permalink
Post by Pavel Rappo
OK, so what I will do for now is I exclude these 4 files and push without them. I'll create a new issue to add them later.
That sounds like a fine plan. This issue has already gone on for long enough, and I don?t think that the crooks of the change should have to wait even longer.

Consider this issue Reviewed, provided that the changes in the webrev minus the 4 services files compile and test ok. Then you can push the services files once build support is added.

-Chris.
Post by Pavel Rappo
-Pavel
Post by Daniel Fuchs
Hi Pavel,
I saw your mail on build-dev.
I guess the issue will resolve itself once we have the
modular image.
I wonder whether the way to go for now would be
to add a single META-INF/services file - as you suggest -
in java.naming, with the 4 lines inside, and log a bug/RFE
to follow up on that once the modular image is there.
best regards,
-- daniel
Once we move to modules then these service configuration files will go away. The resolver will build a service-use graph based on the provides/uses in the module descriptors and that will link the JNDI module to the providers. Pavel just needs a short term solution and having all the providers in one file is okay for that. A better short term solution is to just concatenate them in the build, we are already doing this in the jigsaw/m2 forest for the JDI connectors.
-Alan
Daniel Fuchs
2014-10-14 14:15:49 UTC
Permalink
Post by Chris Hegarty
Post by Pavel Rappo
OK, so what I will do for now is I exclude these 4 files and push without them. I'll create a new issue to add them later.
That sounds like a fine plan. This issue has already gone on for long enough, and I don?t think that the crooks of the change should have to wait even longer.
Right. I see it now. If you have no providers - then the old
'helper.loadClass(className).newInstance();'
code will be executed - which should still work for now.

So not pushing the META-INF/services files sounds fine.

best regards,

-- daniel
Post by Chris Hegarty
Consider this issue Reviewed, provided that the changes in the webrev minus the 4 services files compile and test ok. Then you can push the services files once build support is added.
-Chris.
Post by Pavel Rappo
-Pavel
Post by Daniel Fuchs
Hi Pavel,
I saw your mail on build-dev.
I guess the issue will resolve itself once we have the
modular image.
I wonder whether the way to go for now would be
to add a single META-INF/services file - as you suggest -
in java.naming, with the 4 lines inside, and log a bug/RFE
to follow up on that once the modular image is there.
best regards,
-- daniel
Once we move to modules then these service configuration files will go away. The resolver will build a service-use graph based on the provides/uses in the module descriptors and that will link the JNDI module to the providers. Pavel just needs a short term solution and having all the providers in one file is okay for that. A better short term solution is to just concatenate them in the build, we are already doing this in the jigsaw/m2 forest for the JDI connectors.
-Alan
Chris Hegarty
2014-10-14 14:33:07 UTC
Permalink
Post by Daniel Fuchs
Post by Chris Hegarty
Post by Pavel Rappo
OK, so what I will do for now is I exclude these 4 files and push without them. I'll create a new issue to add them later.
That sounds like a fine plan. This issue has already gone on for long enough, and I don?t think that the crooks of the change should have to wait even longer.
Right. I see it now. If you have no providers - then the old
'helper.loadClass(className).newInstance();'
code will be executed - which should still work for now.
Exactly.
Post by Daniel Fuchs
So not pushing the META-INF/services files sounds fine.
Erik just pinged me in relation to the META-INF files in the webrev, two of which are in the wrong location. They are directly under 'META-INF?, where they should all be under ?META-INF/services?. This is just a note for Pavel, when he follows up later with the addition of these service configuration files, and also to avoid confusion.

-Chris.
Post by Daniel Fuchs
best regards,
-- daniel
Post by Chris Hegarty
Consider this issue Reviewed, provided that the changes in the webrev minus the 4 services files compile and test ok. Then you can push the services files once build support is added.
-Chris.
Post by Pavel Rappo
-Pavel
Post by Daniel Fuchs
Hi Pavel,
I saw your mail on build-dev.
I guess the issue will resolve itself once we have the
modular image.
I wonder whether the way to go for now would be
to add a single META-INF/services file - as you suggest -
in java.naming, with the 4 lines inside, and log a bug/RFE
to follow up on that once the modular image is there.
best regards,
-- daniel
Once we move to modules then these service configuration files will go away. The resolver will build a service-use graph based on the provides/uses in the module descriptors and that will link the JNDI module to the providers. Pavel just needs a short term solution and having all the providers in one file is okay for that. A better short term solution is to just concatenate them in the build, we are already doing this in the jigsaw/m2 forest for the JDI connectors.
-Alan
Pavel Rappo
2014-10-14 14:36:41 UTC
Permalink
Thanks a lot!

-Pavel
Post by Chris Hegarty
META-INF files in the webrev, two of which are in the wrong location. They are directly under 'META-INF?, where they should all be under ?META-INF/services?. This is just a note for Pavel, when he follows up later with the addition of these service configuration files, and also to avoid confusion.
Loading...