0

I have created the following Groovy Script to transform a JSON Document with a Java Library. But somehow I am not able to load the class from a jar that I need. I always get java.lang.ClassNotFoundException: de.is24.gis.geotools.CoordinateTransformer

The Jar file is in the same directory the groovy script is. I can not edit the way I call the groovy script. It is called automatically by a river.

import groovy.json.JsonSlurper

geo = new GeoTransformer()
geo.transform(ctx.document)

class GeoTransformer {
    void transform(doc) {
        this.getClass().classLoader.addURL(new File("gis-geotools-1.9.0.jar").toURL())
        def CoordinateTransformer = Class.forName("de.is24.gis.geotools.CoordinateTransformer").newInstance();

        def x = doc.realEstateCommonData.locationDto.geoCoordinateDto.xCoordinate;
        def y = doc.realEstateCommonData.locationDto.geoCoordinateDto.yCoordinate;

        def coords = CoordinateTransformer.transformFromLambertEuToWgs84(x,z)

        println coords.getLatitude()
        println coords.getLongitude()
        def jsonObj = new JsonSlurper().parseText( '{"type" : "Point", "coordinates" : [' + coords.getLatitude() + ',' + coords.getLongitude() + ']}' )

        doc.location = jsonObj       
    }
}
Alfergon
  • 5,463
  • 6
  • 36
  • 56
MeiSign
  • 1,487
  • 1
  • 15
  • 39
  • 1
    How are you running that? What version of Groovy? What does `println this.getClass().classLoader` show you? What does _"It is called automatically by a river."_ mean? – tim_yates Aug 21 '13 at 08:14
  • This script is called by a river. A river is a programm which synchronizes data sources. In this case MongoDb > Elasticsearch. To be honest I have no Idea how the script is called but I will investigate on this. println gives: `groovy.lang.GroovyClassLoader$InnerLoader@52ed3b53` – MeiSign Aug 21 '13 at 08:27
  • There is no `getRootLoader()` in ClassLoader or URLClassLoader. What is the ClassLoader, and what is the `getRootLoader()` method supposed to do (except returning null)? – JB Nizet Aug 21 '13 at 08:27
  • I don't see any: http://groovy.codehaus.org/gapi/groovy/lang/GroovyClassLoader.InnerLoader.html – JB Nizet Aug 21 '13 at 08:31
  • @MeiSign Does: `this.getClass().classLoader.addURL(...` work? – tim_yates Aug 21 '13 at 08:31
  • It might have been the mistake!! Thank you guys. I think I still have some other bugs. I red the rootloader thing here: http://groovy.codehaus.org/Class+Loading Strange, that it doesnt work... If you add it as answer I can flag it. – MeiSign Aug 21 '13 at 08:41
  • I have updated the question... The needed Class is still not accessible :-/ – MeiSign Aug 21 '13 at 08:51
  • @JBNizet the rootLoader is added here: http://groovy.codehaus.org/groovy-jdk/java/lang/ClassLoader.html as a new method in the metaClass of ClassLoader. The problem must be the way the groovy script is being executed... Maybe try the [good old java route](http://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime)? – tim_yates Aug 21 '13 at 09:03
  • @MeiSign Added a possible solution, not sure it will work though – tim_yates Aug 21 '13 at 09:12

2 Answers2

0

Not sure why you can't get to the rooLoader, it must be to do with how this Groovy script is executed.

You could try this (obviously untested)

class GeoTransformer {
    void transform( doc ) {
        def urlLoader = new GroovyClassLoader()
        urlLoader.addURL( new File("gis-geotools-1.9.0.jar").toURL() )

        def coordTransformer = Class.forName( "de.is24.gis.geotools.CoordinateTransformer",
                                              true,
                                              urlLoader ).newInstance()

        def x = doc.realEstateCommonData.locationDto.geoCoordinateDto.xCoordinate;
        def y = doc.realEstateCommonData.locationDto.geoCoordinateDto.yCoordinate;

        def coords = coordTransformer.transformFromLambertEuToWgs84( x, z )

        println coords.latitude
        println coords.longitude

        doc.location = [ type:'Point',
                         coordinates:[ coords.latitude, coords.longitude ] ]
    }
}

I got rid of the JsonSlurper bit at the bottom, and just created the map directly (I assume doc.location needs to be a map)?

Edit:

This works in the Groovy Console:

def jar = new File( '/path/to/commons-collections-3.2.1.jar' )
def loader = new GroovyClassLoader()
loader.addURL( jar.toURL() )

def bag = Class.forName( 'org.apache.commons.collections.bag.HashBag',
                         true,
                         loader ).newInstance()
bag.add( 'tim' )

println bag
println bag.getClass().name

And prints:

[tim]
org.apache.commons.collections.bag.HashBag
tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • In that case I get `groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.net.URLClassLoader(java.net.URL, groovy.lang.GroovyClassLo ader$InnerLoader)` – MeiSign Aug 21 '13 at 09:52
  • @MeiSign odd.. Can you try the above again (edited to use GCL rather than UCL) – tim_yates Aug 21 '13 at 10:26
  • `java.lang.ClassNotFoundException: de.is24.gis.geotools.CoordinateTransformer` I have also tried the absolute Path of the jar file to make sure he searches in the right spot... – MeiSign Aug 21 '13 at 10:35
  • @MeiSign is gis-geotools-1.9.0.jar available anywhere so I can try locally? – tim_yates Aug 21 '13 at 10:38
  • unfortunately not but you could try it with another jar file maybe. I also asked the creator of the river code to have a look into this issue maybe it is a problem how the groovy support is implemented. – MeiSign Aug 21 '13 at 10:41
  • @MeiSign Tried with commoons-collections, and it works for me (see edit)... I think it might be an issue with how the _river_ thing handles classloading? :-/ – tim_yates Aug 21 '13 at 10:59
  • I get the same issue running groovy from java from windows batch file. I get the original issue: null rootclassloader, then with Tim's workaround above, I get subsequent ClassNotFound for dependencies... i.e. if HashBag has dependencies they won't be loaded in the same new GroovyClassLoader – Rhubarb Mar 09 '17 at 18:36
0

I had the same problem when running groovy from java via GroovyShell. This solution works for me and solves the dependency-loading problems that MeiSign mentions in tim_yates solution. Explanation follows:

    def thisLoader = this.class.classLoader
    // try the "proper" way to find the root classloader
    def rootLoader = DefaultGroovyMethods.getRootLoader(thisLoader)
    if (rootLoader == null) {
        // Root classloader is not a groovy RootLoader, but we still need it,
        // so walk up the hierarchy and get the top one (whose parent is null)
        // When running from Java this is sun.misc.Launcher.ExtClassLoader
        rootLoader = thisLoader
        ClassLoader parentLoader = rootLoader.getParent()
        while (parentLoader != null) {
            rootLoader = parentLoader
            parentLoader = parentLoader.getParent()
        }
    }

    rootLoader.addURL(new File("gis-geotools-1.9.0.jar").toURL())
    def CoordinateTransformer = 
        Class.forName("de.is24.gis.geotools.CoordinateTransformer",
                       true,
                       rootLoader).newInstance();

When running groovy from java using groovy.lang.GroovyShell.main, the this.classLoader is GroovyClassLoader.InnerLoader When running groovy from command line groovy.bat, the class loader is org.codehaus.groovy.tools.RootLoader

When you call getRootLoader, it walks up the classloader hiearchy - with getParent() - until it finds an instance of a RootLoader. If it doesn't it finds null. (That's the NPE in the title of this question)

The problem is that when running from Java the heirarchy tops out at sun.misc.Launcher.ExtClassLoader which is clearly not a groovy class at all, let alone the groovy RootLoader. Specifically the hiearchy is:

GroovyClassLoader.InnerLoader
 --> GroovyClassLoader
    ---> sun.misc.Launcher.AppClassLoader 
        ----> sun.misc.Launcher.ExtClassLoader
           ------> null

How it ends up that way is pretty obscure in GroovyMain (but if you really want to set it yourself there's a GroovyShell constructor that takes a ClassLoader).

Anyway, Tim's solution doesn't work in depth, because the new ClassLoader you are creating on the fly is used only to load that class and not subsequent classes. You really do need to add the classpath entries to the root classpath. So I simply used the real root when the groovy root fails.

Here's the original code from org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport

/**
 * Iterates through the classloader parents until it finds a loader with a class
 * named "org.codehaus.groovy.tools.RootLoader". If there is no such class
 * <code>null</code> will be returned. The name is used for comparison because
 * a direct comparison using == may fail as the class may be loaded through
 * different classloaders.
 *
 * @param self a ClassLoader
 * @return the rootLoader for the ClassLoader
 * @see org.codehaus.groovy.tools.RootLoader
 * @since 1.5.0
 */
public static ClassLoader getRootLoader(ClassLoader self) {
    while (true) {
        if (self == null) return null;
        if (isRootLoaderClassOrSubClass(self)) return self;
        self = self.getParent();
    }
}

private static boolean isRootLoaderClassOrSubClass(ClassLoader self) {
    Class current = self.getClass();
    while(!current.getName().equals(Object.class.getName())) {
        if(current.getName().equals(RootLoader.class.getName())) return true;
        current = current.getSuperclass();
    }

    return false;
}
Rhubarb
  • 34,705
  • 2
  • 49
  • 38