Unable to load a groovy classes

Tags: , , ,



I am trying to serialize and deserialize a groovy object with the below class in the jenkins pipeline.

SerializationUtil.groovy

package com.sample;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * A simple class with generic serialize and deserialize method implementations
 */
public class SerializationUtil {

    // deserialize to Object from given file
    public static Object deserialize(String fileName) throws IOException,
            ClassNotFoundException {
        FileInputStream fis = new FileInputStream(fileName);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object obj = ois.readObject();
        ois.close();
        System.out.println(obj);
        return obj;
    }

    // serialize the given object and save it to file
    public static void serialize(Object obj, String fileName)
            throws IOException {
        FileOutputStream fos = new FileOutputStream(fileName);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);

        fos.close();
    }

}

Test.groovy

package com.sample;

public class Test implements Serializable {
    String key;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    @Override
    public String toString() {
        return "com.sample.Test{" +
                "key='" + key + ''' +
                '}';
    }
}

Jenkins pipeline script

Test test = new Test()
test.setKey("sample")
SerializationUtil.serialize(test,"/temp/test.txt")
Test test2 = SerializationUtil.deserialize("/temp/test.txt")

I was able to serialize the object but not deserialize. I am getting the below exception.

java.lang.ClassNotFoundException: com.sample.Test
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:543)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:628)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at sun.reflect.GeneratedMethodAccessor8862.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:47)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:20)
    at com.sample.SerializationUtil.deserialize(SerializationUtil.groovy:20)

From the exception, I could see that GroovyClassLoader is not called and I assume that might be the issue.

Answer

The problem is, as you have identified already, that no GroovyClassLoader gets involved… specifically not the one knowing your current classes.

While an ObjectOutputStream does not really care what classloader a class was defined with the ObjectInputStream has to make assumptions here, since it needs to create an instance. Judging from the trace the “nearest” ClassLoader that woulde be selected for the object instance creation would be the class loader containing the groovy runtime. Sadly that is a common problem with Groovy, as the keep introducing more and more caller sensitive logic in Java.

Anyway, if you also have something like this (ScriptLoaderObjectInputStream.groovy):

class ScriptLoaderObjectInputStream extends ObjectInputStream {
  ScriptLoaderObjectInputStream(InputStream str) { super(str) }

  protected Class resolveClass(ObjectStreamClass desc) {
    return this.class.classLoader.loadClass(desc.getName())
  }
}

and replace your usage of ObjectInputStream with this one, it should work. It should work, because this is a script file as well and the resulting class should have the same class loader your other generated classes get.

For further reading I find this one quite nice: https://rsankarx.wordpress.com/2012/06/08/java-serialization-classloaders/

DISCLAIMER: I wrote this here, no IDE, no spell checking and no testing got involved.



Source: stackoverflow