Using createObject, I am unable to call a method i...
# lucee
s
Using createObject, I am unable to call a method inside a java inner class. Backstory: I’m attempting to locally use the maxmind database and reader API. This allows me to get location data on an IP address. Along with their database and reader, maxmind also has a simple caching object which can be used to speed up API requests. I am able to get their database reader API up and running, however, I am struggling including the cache object and hoped someone here may have a better idea on what I could try. This is what I’m attempting to replicate with Lucee:
Copy code
new DatabaseReader.Builder(file).withCache(new CHMCache()).build();
Builder
is an inner class of
DatabaseReader
. I’m able to init() Builder (and subsequently the database reader API) using the
$
notation as defined in the Lucee docs. However I am unable to call the Builder
withCache()
method. The API docs for
build()
and
withCache()
are here. This is the code I’m running; 1st what works, and then 2nd my failing attempt at including the cache object. 1st: This works, it creates a database using a maxmind DB file, and the database reader API:
Copy code
<cfscript>

ip = '209.85.231.104';

// build the database file object
dbfile = "/pathto/geoip/lib/GeoLite2-Country.mmdb";
db = createObject("java","java.io.File").init(expandpath(dbfile));

// initiate the database reader object
reader = createObject("java", "com.maxmind.geoip2.DatabaseReader$Builder",expandpath('/pathto/geoip/lib/geoip2-3.0.1.jar')).init(db).build();

// create ipaddr class 
iaddrClass=CreateObject("java", "java.net.InetAddress");

// test
address=iaddrClass.getByName(ip);
writedump(reader.country(address).getCountry().getName());
writedump(reader.country(address).getCountry().getIsoCode());


</cfscript>
2nd: This doesn't work, I create the cache object and attempt to call it on the builder object with
.withCache(maxMind_CHMCaching)
and returns the following error:
No matching Method/Function for com.maxmind.geoip2.DatabaseReader$Builder.withCache(com.maxmind.db.CHMCache) found
Copy code
<cfscript>

ip = '209.85.231.104';

// build the database object
dbfile = "/pathto/geoip/lib/GeoLite2-Country.mmdb";
db = createObject("java","java.io.File").init(expandpath(dbfile));

// initiate a maxmind caching object
maxMind_CHMCaching = createObject("java", "com.maxmind.db.CHMCache",expandpath('/pathto/geoip/lib/maxmind-db-2.0.0.jar')).init();

// initiate the database reader object using cache
reader = createObject("java", "com.maxmind.geoip2.DatabaseReader$Builder",expandpath('/pathto/geoip/lib/geoip2-3.0.1.jar')).init(db).withCache(maxMind_CHMCaching).build();

// create ipaddr class 
iaddrClass=CreateObject("java", "java.net.InetAddress");

// test
address=iaddrClass.getByName(ip);
writedump(reader.country(address).getCountry().getName());
writedump(reader.country(address).getCountry().getIsoCode());


</cfscript>
If anyone has an idea of what I should try next I would appreciate the feedback.
b
@steveduke I would try adding the jars to
this.javaSettings
and see if that works
I don't know if this is realted to your issue, but I've noticed when you provide jars to the createObject function, that applies only to the create object and not any chained methods, which operate in the current threads class loader. Furthermore, your
maxMind_CHMCaching
object is created from another create object call which is going to have use another class loader. I know part of the check Lucee's reflection does when calling a method is that the object must not only be of the correct type, but also from the same classloader which is likely related. See the comment thread on this ticket: https://luceeserver.atlassian.net/browse/LDEV-2296?focusedCommentId=45849
Also, dump out
Copy code
createObject("java", "com.maxmind.geoip2.DatabaseReader$Builder",expandpath('/pathto/geoip/lib/geoip2-3.0.1.jar')).init(db)
and make sure it has a
withCache()
method
s
@bdw429s Thanks for the response. I already had the geoip jar directory path in
this.javaSettings
. I changed it to specify each jar file and still no luck. I also modified the createObject to include an array of all the jar files too, and still this fails. Your thoughts on how createObject loads the class may have some bearing on whats happening. I dumped out the Builder, and it does have the withCache() method:
b
I already had the geoip jar directory path in
this.javaSettings
. I changed it to specify each jar file and still no luck. I also modified the createObject to include an array of all the jar files too, and still this fails.
Hold on-- you have the jar files in the
this.javaSettings
AND in the arguments to createObject? That in itself seems problematic. What happens if you use this.javaSettings and don't pass anything to createObject at all?
s
Kinda, I just (15 minutes ago) cycled through different combinations to test. However, you are correct with the paths in two places being the issue ! I modified this:
Copy code
"com.maxmind.geoip2.DatabaseReader$Builder",expandpath('/pathto/geoip/lib/geoip2-3.0.1.jar')).init(db).withCache(maxMind_CHMCaching).build();
to this:
Copy code
reader = createObject("java", "com.maxmind.geoip2.DatabaseReader$Builder" ).init(db).withCache(maxMind_CHMCaching).build();
And it works!
I had loadPaths set in the application.cfc to include the maxMind jar directory
b
FWIW, I consider this to be a bug in Lucee that it requires the same class loader to have loaded the interface and the implementation.
My questions to Micha on that ticket asking why that requirement exists have gone unanswered however for quite some time 😕
s
I saw
A question if I may, why would having two path locations, if they are to the same destination, be this problematic?
b
Not sure what you mean-
Are you asking about passing an array of jar paths to createObject?
Every time you do that, Lucee creates a new classloader on the fly which is used as the context classloader during the creation of the class you asked for.
But if you do it 2 or more times, Lucee is creating a different context class loader every time
The fact that it's loading classes from the same physical jars is meaningless
Because Lucee's reflection check will still see classes from each as coming from a different CL
And specifying jars to the
this.javaSettings
in your
Application.cfc
will load those jars into yet another class loader in the app
s
so having
this.javaSettings
in my
Application.cfc
but removing the jar list from createObject means Lucee uses the same class loader. Hence, the withCache() method is found.
Thanks again for your help.
b
Basically, yes