<< Back to previous view

[CLJ-971] Jar within a jar throws a runtime error Created: 10/Apr/12  Updated: 18/Apr/14

Status: Open
Project: Clojure
Component/s: None
Affects Version/s: Release 1.2, Release 1.3
Fix Version/s: Release 1.7

Type: Defect Priority: Major
Reporter: Ron Romero Assignee: Unassigned
Resolution: Unresolved Votes: 1
Labels: None
Environment:

Maven using the one-jar plugin


Attachments: Text File clj-971-1.patch     Text File clj-971-2.patch    
Patch: Code
Approval: Vetted

 Description   

I've created two jar files in my multi-project Maven setup. The first jar is the "engine", and it includes the clojure jar in it. The other jar is the "application". It includes the engine and then packages itself into a one-jar jar file. This means we have a jar within a jar: The "onejar" contains the engine jar, which in turn contains that clojure jar.

I then get an error in the runtime:

Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:616)
at com.simontuffs.onejar.Boot.run(Boot.java:340)
at com.simontuffs.onejar.Boot.main(Boot.java:166)
Caused by: java.lang.ExceptionInInitializerError
at com.ziroby.clojure.App.main(App.java:14)
... 6 more
Caused by: java.lang.NullPointerException
at clojure.lang.RT.lastModified(RT.java:374)
at clojure.lang.RT.load(RT.java:408)
at clojure.lang.RT.load(RT.java:398)
at clojure.lang.RT.doInit(RT.java:434)
at clojure.lang.RT.<clinit>(RT.java:316)
... 7 more

See also my Stack Overflow question on this at http://stackoverflow.com/questions/7763480/making-an-executable-jar-that-evals-clojure-strings

In researching it, I've found the problem lies in RT.lastModified, where it tries to determine last modified time by looking at the modified time on the jar file for Clojure. But there's not actually a jar file, since it's embedded in another.

I've found that adding a null check solves the problem. My lastModified looks like this now:

static public long lastModified(URL url, String libfile) throws Exception{
if(url.getProtocol().equals("jar")) { ZipEntry entry = ((JarURLConnection) url.openConnection()).getJarFile().getEntry(libfile); if (entry != null) return entry.getTime(); }

return url.openConnection().getLastModified();
}

This runs successfully.

If you'd prefer, I can submit a patch, or commit directly.



 Comments   
Comment by Anders Sveen [ 11/Nov/13 3:29 AM ]

Would be awesome if you could get this working. Wanting to use som Clojure libs in Java so Leiningen uberjar is not an option right now.

Comment by Paavo Parkkinen [ 24/Nov/13 7:11 AM ]

Took the code change in the description and rolled it into a patch to hopefully push this forward a little bit.

Comment by Alex Miller [ 24/Nov/13 10:06 AM ]

Thanks Paavo - on a quick scan, it looks like in the case you're interested in the updated code would now call url.openConnection() twice - perhaps that could be factored out?

Comment by Paavo Parkkinen [ 25/Nov/13 6:19 AM ]

New patch with duplicate calls to url.openConnection() factored out.

Comment by Gleb Kanterov [ 13/Dec/13 4:30 AM ]

I had the same issue, adding following line to manifest file worked for me

One-Jar-URL-Factory: com.simontuffs.onejar.JarClassLoader$OneJarURLFactory
Comment by Stuart Halloway [ 31/Jan/14 1:04 PM ]

Can we get a test showing the normal path and the can't-read-inside path?

Comment by Sean Shubin [ 17/Apr/14 9:34 PM ]

I notice this solution falls back on the last modified date for the entire jar, while it is still possible to get the date of the individual file, albeit less efficiently.
I posted a way to get the individual file date in CLJ-1405.
Perhaps you don't need the date of the individual file, but I am having a hard time groking the calling code so I am not sure what its intent is.
I did notice in the calling code that the if statement
(classURL != null && (cljURL == null || lastModified(classURL, classfile) > lastModified(cljURL, cljfile))) || classURL == null
Is logically equivalent to the much simpler
classURL == null || cljURL == null || lastModified(classURL, classfile) > lastModified(cljURL, cljfile)

Generated at Wed Apr 23 05:18:58 CDT 2014 using JIRA 4.4#649-r158309.