0

I am currently migrating an app from Java 8 to Java 11 via Java 9. The app uses a custom executable war framework and makes use of nested class resolution code from here: http://pavel.savara.sweb.cz/files/JarJarURLConnection.java. This code allows the bootstrap code in the executable war to load classes from jars in its own archive using urls of the form:

jar:jarjar:file:<path_to_war>^/WEB-INF/<path_to_jar>!/<path_to_class>

The intent is that the initial jar protocol causes the JRE to treat jarjar:file:<path_to_war>^/WEB-INF/<path_to_jar> as a resource to load and then anything after !/ is the path of the class to be loaded.

The custom jarjar protocol handler parses the requested url jarjar:file:<path_to_war>^/WEB-INF/<path_to_jar> and replaces it with another jar protocol url: jar:file:<path_to_war>!/WEB-INF/<path_to_jar> (note the switch from ^ to !).

This works fine up to Java 8 but fails from Java 9 onwards and I'm not sure why. The JRE seems to ignore the transformed url created by parseURL and attempts to use the plain war location without the nested jar from WEB-INF.

Any help much appreciated. This code is part of some other more complex code so we'd prefer not have to rework the entire thing.

EDIT: Github repository with basic code and a readme describing difference in behaviour between Java 8 and Java 11...

https://github.com/jugglingcats/execwar-test

EDIT2: Running both in debugger I can see that the path is quite different, and on JDK11 a url with double ! is produced which breaks everything.

In JDK8 when parseUrl is called the stack looks like this:

parseURL:111, JarJarURLConnection$JarJarURLStreamHandler (com.acme)
<init>:639, URL (java.net)
<init>:507, URL (java.net)
<init>:456, URL (java.net)
parseSpecs:175, JarURLConnection (java.net)
<init>:158, JarURLConnection (java.net)
<init>:81, JarURLConnection (sun.net.www.protocol.jar)
openConnection:41, Handler (sun.net.www.protocol.jar)
openConnection:1001, URL (java.net)
findResource:715, URLClassPath$Loader (sun.misc)
findResource:225, URLClassPath (sun.misc)
run:572, URLClassLoader$2 (java.net)
run:570, URLClassLoader$2 (java.net)
doPrivileged:-1, AccessController (java.security)
findResource:569, URLClassLoader (java.net)
getResource:1089, ClassLoader (java.lang)
getResourceAsStream:233, URLClassLoader (java.net)
main:23, Main (com.acme)

You can see we are already in openConnection.

In JDK11 it looks like:

parseURL:111, JarJarURLConnection$JarJarURLStreamHandler (com.acme)
<init>:674, URL (java.net)
<init>:541, URL (java.net)
<init>:488, URL (java.net)
run:487, URLClassPath$3 (jdk.internal.loader)
run:476, URLClassPath$3 (jdk.internal.loader)
doPrivileged:-1, AccessController (java.security)
getLoader:475, URLClassPath (jdk.internal.loader)
getLoader:444, URLClassPath (jdk.internal.loader)
findResource:290, URLClassPath (jdk.internal.loader)
run:655, URLClassLoader$2 (java.net)
run:653, URLClassLoader$2 (java.net)
doPrivileged:-1, AccessController (java.security)
findResource:652, URLClassLoader (java.net)
getResource:1400, ClassLoader (java.lang)
getResourceAsStream:322, URLClassLoader (java.net)
main:23, Main (com.acme)

Issue seems to be in these two lines (at URLClassPath:487):

    URL nestedUrl = new URL(file.substring(0, file.length() - 2));
    return new JarLoader(nestedUrl, jarHandler, lmap, acc);

The JarLoader constructor creates a new jar url like so:

        JarLoader(URL url, URLStreamHandler jarHandler,
                  HashMap<String, Loader> loaderMap,
                  AccessControlContext acc)
            throws IOException
        {
            super(new URL("jar", "", -1, url + "!/", jarHandler));
            csu = url;
            handler = jarHandler;
            lmap = loaderMap;
            this.acc = acc;

            ensureOpen();
        }

The JarLoader.base property now has value jar:jarjar:jar:file:webapp.war!/lib.jar!/ which is going to break things.

jugglingcats
  • 698
  • 9
  • 29
  • Make a simple self standing example. – Thorbjørn Ravn Andersen Feb 23 '21 at 18:46
  • Thanks, the code of JarJarURLConnection is a bit long to paste here, but have created a minimal repro in github and edited description with the link – jugglingcats Feb 23 '21 at 20:30
  • Well done. It appears that JarJarURLConnection is 1) very, very old and 2) is not easy to see where the difference happens. My guess is that some toString() have changed slightly. You are very close - have you considered running the example in two debug sessions side by side yourself to see when the flow diverges? – Thorbjørn Ravn Andersen Feb 23 '21 at 23:36
  • Thanks for the encouragement ;). I stepped through the code for both and in JDK11 it ends up building a JARUrlConnection for jar:jarjar:jar:file:webapp.war!/lib.jar!/ (note the double '!') which is not going to work. Not sure if there is a workaround. – jugglingcats Feb 24 '21 at 07:42
  • 1
    I also noted that the handling of URLs with multiple `!/` is completely broken (but consistently broken at all places), as described in [this answer](https://stackoverflow.com/a/58980655/2711488) but I wasn’t aware that anything changed from JDK 8 to newer JDKs. Does the code end up with a different URL in JDK 8? – Holger Feb 24 '21 at 07:48
  • Interesting. You didn’t notice where exactly the string diverged? – Thorbjørn Ravn Andersen Feb 24 '21 at 07:58
  • The code diverges quite significantly, have added more info to the original post. Thanks for staying with me! – jugglingcats Feb 24 '21 at 08:33
  • Note that everything has moved from com.sun.* to jdk.internal.* which most likely was part of the modularization effort in JigSaw. You still need to pinpoint the exact location where the two JDK's begin to behave differently. – Thorbjørn Ravn Andersen Feb 24 '21 at 12:41
  • hi @jugglingcats, please consider contributing the fixes back. The file is also part of Robocode project and it's indeed very old. I don't even remember why I have it directly on my personal pages. See https://github.com/robo-code/robocode/blob/master/robocode.host/src/main/java/net/sf/robocode/host/jarjar/JarJarURLConnection.java – Pavel Savara Mar 14 '21 at 12:51
  • Hi @PavelSavara, the creator! Unfortunately I couldn't make it work and in the end decided to stay on Java 8 - at least for the time being. I actually don't think it's possible to have it work in the elegant (... or devious?!) way you did it using the later JDKs, but I might be wrong. – jugglingcats Mar 16 '21 at 12:28
  • @PavelSavara I found another issue that only affects Java 8 and which is much easier to fix. See https://stackoverflow.com/questions/66640928/custom-undertow-executable-war-webapp-taking-a-long-time-to-start-on-java-8-qui – jugglingcats Mar 16 '21 at 18:25

0 Answers0