2

I'm trying to grab a resource in ClassLoader. A simplified version of the code looks something like this:

String ePath = "rewrite/common/RenameFunctor.groovy"
String fPath = ThClType.class.getClassLoader().getResource(ePath);

the response I get back as fPath is jar:file:/Users/myName/warPath/warName.war!/WEB-INF/classes!/rewrite/common/RenameFunctor.groovy. The actual path to the resource we want it exactly that, except without the second exclamation point. (Unlike warName.war, classes is just a normal directory.)

Does anyone know what might have caused the extra exclamation point and/or what might be done to fix it? This is as part of a process of updating some pretty old code that I didn't write, so if esoteric customization of ClassLoader behavior is possible, it's possible it's been done in this case. if it is possible, then I don't know how to check, and would appreciate any insight.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Ben Barden
  • 2,001
  • 2
  • 20
  • 28
  • Not sure about the implementation details, but the 'path' of the resource within the class path is `/rewrite/common/RenameFunctor.groovy`, the `!`-separated parts seem to identify the path to the entry in the class path: first the WAR `/Users/myName/warPath/warName.war` and then within the WAR `/WEB-INF/classes`. It is the contents of that last entry which is actually added to the class path. – Mark Rotteveel Apr 27 '18 at 18:05
  • @MarkRotteveel Right... but `/WEB-INF/classes` isn't actually a WAR. That's the issue. The path *should* be `jar:file:/Users/myName/warPath/warName.war!/WEB-INF/classes/rewrite/common/RenameFunctor.groovy` – Ben Barden Apr 27 '18 at 18:07
  • 1
    You are missing my point. Neither the WAR itself nor `WEB-INF/classes` are on the class path (instead `WEB-INF/classes` is part of the class path). However the **content** of `WEB-INF/classes` is on the class path. For example using `SomeClass.class.getResourceAsStrean("/rewrite/common/RenameFunctor.groovy")` will be able to load the content of that file, but `SomeClass.class.getResourceAsStrean("/WEB-INF/classes/rewrite/common/RenameFunctor.groovy")` will not, because `/WEB-INF/classes` is not on the class path. It looks like the WAR class loader indicates this by separating the paths by `!` – Mark Rotteveel Apr 27 '18 at 18:13
  • Related: https://stackoverflow.com/questions/9530549/why-contextclassloader-returns-path-with-exclamation-character – Mark Rotteveel Apr 27 '18 at 18:19
  • @MarkRotteveel okay. I think I get what you're saying, though I don't know exactly how to confirm it in this case. Unfortunately, I'm trying to use this to populate a GroovyCodeSource, so the "use the stream" answer is not as helpful as one might like it to be. I could cobble together a Reader for it, but I don't think I could manage the codeBase. Still, it looks like that's at least a partial answer, and it was helpful, and I'd invite you to write it up as one. It does answer one of the two questions in the original question. – Ben Barden Apr 27 '18 at 18:41
  • None of this was an answer it was a comment nor did I suggest to use a stream, I used in an attempt it to illustrate that you are misinterpreting the meaning of the resource. As I said in my first comment, I'm not 100% sure of the exact internals (hence the comments, not an answer). The `!` is in a sense a separator of the coordinates used by the class loader to find a specific resource. The resource itself is `/rewrite/common/RenameFunctor.groovy`, and the parts before the `!` are a chain of coordinates for the specific location of the class path where that resource is located. – Mark Rotteveel Apr 27 '18 at 19:29
  • @MarkRotteveel it looks like a decent answer to "Does anyone know what might have caused the extra exclamation point" to *me*, at least... and the "maybe you should use a stream" was off of the other answer you linked. I've now solved the problem (codebase wasn't as difficult a hurdle as I'd thought), and personally, I found your comments helpful enough that I'd happily upvote them as an answer, even if it's not a complete enough answer to earn a checkmark. Whether you want to write it up as such, however, is up to you. – Ben Barden Apr 27 '18 at 19:33
  • I'll see if I can write up an answer tomorrow. – Mark Rotteveel Apr 27 '18 at 19:35

1 Answers1

1

Warning: this answer is part supposition and guess-work on the mechanics, it may not be 100% correct, but I think it comes close enough. As far as I know actual WAR class loading will vary per servlet container or application server, so this answer may not hold for all of them.

If you look at

jar:file:/Users/myName/warPath/warName.war!/WEB-INF/classes!/rewrite/common/RenameFunctor.groovy

you can split it up in the following parts:

  1. file:/Users/myName/warPath/warName.war
  2. /WEB-INF/classes
  3. /rewrite/common/RenameFunctor.groovy

The actual resource that is on the class path is the last one, /rewrite/common/RenameFunctor.groovy, the other parts are the coordinates used by the war class loader to find the part of the class path that contains that resource: first the location of the war file itself, file:/Users/myName/warPath/warName.war, and then the path within the war, /WEB-INF/classes.

This theory build on the documentation of the JarURLConnection which states:

A URL Connection to a Java ARchive (JAR) file or an entry in a JAR file.

The syntax of a JAR URL is:

jar:<url>!/{entry}  

for example:

jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class

Jar URLs should be used to refer to a JAR file or entries in a JAR file. The example above is a JAR URL which refers to a JAR entry. If the entry name is omitted, the URL refers to the whole JAR file: jar:http://www.foo.com/bar/baz.jar!/

So for a plain jar, the first part of the URL identifies the jar file itself, and the second part identifies the resource within the jar.

Technically war files are jar files, but contrary to jar files, the war file itself is not part of the class path. Instead it contains elements that are added to the class path., for example jar files in WEB-INF/lib and the classes and other files in WEB-INF/classes.

The ! separated parts then define the steps taken by the war class loader to locate a specific resource, in this case /rewrite/common/RenameFunctor.groovy.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197