[JAVA-4956] MongoClientFactory creates MongoClientImpl, not MongoClient (using JNDI) Created: 02/May/23  Updated: 06/Jul/23  Resolved: 06/Jul/23

Status: Closed
Project: Java Driver
Component/s: Configuration
Affects Version/s: None
Fix Version/s: None

Type: Question Priority: Major - P3
Reporter: Jason McLaren Assignee: Jeffrey Yemin
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

Summary

I'm attempting to create com.mongodb.client.MongoClient instances using JNDI. The com.mongodb.client.MongoClientFactory is creating com.mongodb.client.internal.MongoClientImpl instances instead.

This used to work with the legacy driver (using com.mongodb.MongoClient and com.mongodb.client.jndi.MongoClientFactory).

Please provide the version of the driver. If applicable, please provide the MongoDB server version and topology (standalone, replica set, or sharded cluster).

MongoDB Java driver 4.9.1 (also tried 4.3.4 and 4.6.1)

MongoDB 4.4 (Atlas hosted by mongodb.com), replica set

Tomcat 9.0.73

Jersey 1.19.3

Java 8

How to Reproduce

My server.xml contains the following:

<GlobalNamingResources>
<Resource name="mongodb/GlobalMongoClient"
auth="Container"
type="com.mongodb.client.MongoClient"
closeMethod="close"
factory="com.mongodb.client.MongoClientFactory"
singleton="true"
connectionString="{{ mongo_db | escape }}"
maxTotal="100"
maxIdle="10"
minIdle="2"
/>

</GlobalNamingResources>

My context.xml contains the following:

<ResourceLink name="mongodb/MyMongoClient"
global="mongodb/GlobalMongoClient"
type="com.mongodb.client.MongoClient"/>

I copy the following files to the Tomcat global lib directory before loading the application:

  • mongodb-driver-sync-4.9.1.jar
  • mongodb-driver-core-4.9.1.jar
  • slf4j-api-1.7.25.jar
  • slf4j-log4j12-1.7.25.jar
  • log4j-1.2.17.jar
  • bson-4.9.1.jar
  • bson-record-codec-4.9.1.jar

In my Java application, I use the following code to obtain a MongoClient:

final MongoClient mongoClient =
InitialContext.doLookup("java:comp/env/mongodb/MyMongoClient");

When I load the application in Tomcat, everything seems fine, but when I send an http request to the server, I get the following error in the Tomcat logs:

02-May-2023 22:27:15.746 WARNING [http-nio-127.0.0.1-8080-exec-2] org.apache.naming.NamingContext.lookup Unexpected exception resolving reference        java.lang.IllegalArgumentException: The local resource link [MyMongoClient] that refers to global resource [mongodb/GlobalMongoClient] was expected to return an instance of [com.mongodb.client.MongoClient] but returned an instance of [com.mongodb.client.internal.MongoClientImpl]                   at org.apache.naming.factory.ResourceLinkFactory.getObjectInstance(ResourceLinkFactory.java:158)                                                 at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:332)
                at org.apache.naming.NamingContext.lookup(NamingContext.java:864)                                                                                at org.apache.naming.NamingContext.lookup(NamingContext.java:158)
                at org.apache.naming.NamingContext.lookup(NamingContext.java:850)                                                                                at org.apache.naming.NamingContext.lookup(NamingContext.java:158)
                at org.apache.naming.NamingContext.lookup(NamingContext.java:850)                                                                                at org.apache.naming.NamingContext.lookup(NamingContext.java:158)                                                                                at org.apache.naming.NamingContext.lookup(NamingContext.java:850)
                at org.apache.naming.NamingContext.lookup(NamingContext.java:172)                                                                                at org.apache.naming.SelectorContext.lookup(SelectorContext.java:161)
                at javax.naming.InitialContext.lookup(InitialContext.java:417)
                at javax.naming.InitialContext.doLookup(InitialContext.java:290)
                at com.legendpower.DBInput.<init>(DBInput.java:35)
                at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
                at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
                at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
                at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
                at com.sun.jersey.server.spi.component.ResourceComponentConstructor._construct(ResourceComponentConstructor.java:245)
                at com.sun.jersey.server.spi.component.ResourceComponentConstructor.construct(ResourceComponentConstructor.java:233)
                at com.sun.jersey.server.impl.resource.PerRequestFactory$PerRequest._getInstance(PerRequestFactory.java:182)
                at com.sun.jersey.server.impl.resource.PerRequestFactory$AbstractPerRequest.getInstance(PerRequestFactory.java:144)
                at com.sun.jersey.server.impl.application.WebApplicationContext.getResource(WebApplicationContext.java:239)
                at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:83)
                at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147)
                at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84)
                at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1542)
                at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1473)
                at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1419)
                at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1409)
                at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:409)
                at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:558)
                at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:733)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at com.legendpower.GuiloadFilter.doFilter(GuiloadFilter.java:36)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
                at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
                at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
                at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
                at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
                at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
                at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
                at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
                at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
                at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
                at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
                at java.lang.Thread.run(Thread.java:750)

Additional Background

Using this code to load the MongoClient via JNDI worked with mongo-java-driver version 3.12.8 and MongoDB 4.2, using the `com.mongodb.client.jndi.MongoClientFactory` resource factory to create the `com.mongodb.client.MongoClient` resource.

I have guessed at some of the configuration above, because the JNDI documentation appears to be out of date as of driver version 4.3 (the oldest version still online). I filed a separate bug about that here:

https://jira.mongodb.org/browse/DOCS-16093

I tried a number of variations but couldn't get it to work. If I'm using it incorrectly, I would be happy to be corrected.



 Comments   
Comment by Tom Selander [ 06/Jul/23 ]

jmclaren@legendpower.com Let's close it out for now, and when this comes back up feel free to file another ticket later in the year. If you make note of this ticket number, you can reference this ticket when it comes time to refile.

Comment by Jason McLaren [ 29/Jun/23 ]

I still need to solve this problem, but it may get pushed out to later this year, as I have some more urgent priorities at work.

Would it be better to close this ticket and re-open a new one later when I have a test case, or to leave it open?

Comment by Tom Selander [ 26/Jun/23 ]

jmclaren@legendpower.com Hey Jason, have you had a chance to look into this at all? Please advise whether this is something that you still plan on looking at right now to get a reproducible example, or if we can close this out?

Comment by Jason McLaren [ 31/May/23 ]

Just responding to the bot to keep this alive. I will look into why there might be two different class loaders. I'll also see if I can remove enough code to produce a minimal reproducible example. 

Comment by PM Bot [ 30/May/23 ]

Hi jmclaren@legendpower.com! JAVA-4956 is awaiting your response.

If this is still an issue for you, please open Jira to review the latest status and provide your feedback. Thanks!

Comment by Jeffrey Yemin [ 22/May/23 ]

jmclaren@legendpower.com I'm moving this back to Waiting for Reporter, as I don't see a path forward for resolving this without a full reproduction scenario.

Comment by Jeffrey Yemin [ 10/May/23 ]

jmclaren@legendpower.com sorry for the SPAM. I'm not sure what we can do about this, as it looks from the source code of ResourceLinkFactory that this should work as designed.

I suspect that the issue is that ResourceLinkFactory uses a different ClassLoader than MongoClientFactory, and that's why the check !expectedClazz.isAssignableFrom(result.getClass()) is returning false (since a class loaded by one class loader is not necessarily assignable from a class loaded by a different class loader).

I'm not, however, sure how you would test this hypothesis in the context of your application.

Comment by Jason McLaren [ 10/May/23 ]

What additional details are needed?

Comment by PM Bot [ 10/May/23 ]

Hey jmclaren@legendpower.com, We need additional details to investigate the problem. If this is still an issue for you, please provide the requested information.

Comment by Jeffrey Yemin [ 02/May/23 ]

I'm not all that familiar with Tomcat but one thing I can clarify is that com.mongodb.client.internal.MongoClientImpl is an internal class that implements the com.mongodb.client.MongoClient interface, so I would expect this to work.

The legacy driver probably works because com.mongodb.MongoClient is a class, not an interface.

I looked at the Tomcat documentation for GlobalNamingResource, and saw that the example uses the type javax.sql.DataSource, which is also an interface, so it doesn't seem likely that interfaces are not supported at all.

And here's a link to the source code: https://github.com/apache/tomcat/blob/9.0.73/java/org/apache/naming/factory/ResourceLinkFactory.java#L155-L160.

It looks like it is doing the right thing, so it's not clear why Class#isAssignableFrom would return false. The only thing I can think of is some sort of ClassLoader issue

Comment by Service Account: DBX TPM [ 02/May/23 ]

Hi jmclaren@legendpower.com, thank you for reporting this issue! The team will look into it and get back to you soon.

Generated at Thu Feb 08 09:03:22 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.