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.
Advertisement
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.