It is extremely useful to be able to query the class path for the existence of files. ClassLoader includes a method, getResources(), for this purpose. It returns an enumeration (it's an old API) of the URLs of all the matching resources. In terms of Sun's ClassLoaders, including URLClassLoader, if you request a path, and not a file, you get the contents of folder if it exists. A typical path might be "org/example/myapp/pages/", in order to search for resources, including class files, within the org.example.myapp.pages package. For a file URL, this is a sequence lines that identify the immediate contents of the folder. For a jar URL, you can access the java.util.jar.JarFile via the URL's connection (it will be a JarURLConnection). Unfortunately, the Catalina ClassLoader does not follow this example, and requesting the contents of a directory resource returns an empty enumeration. In addition, because of how Catalina partially unpacks WARs for deployment, the classes are not visible via ServletContext.getResourcesPaths() either, since the exploded WAR doesn't include a WEB-INF/classes folder. The end result is that there's no way to scan the classpath in the way that certain frameworks (in my case, Tapestry 5, but I've seen indications that Facelets has the same issue) need to. By comparison, Jetty (4, 5, 6) does provide the necessary access.
Created attachment 19616 [details] Sample code that relies on the missing functionality
As a stop-gap, making sure that packaged classes are visible via ServletContext.getResourcePaths() would be sufficient for my needs.
This (the getResources returning a complete enumeration) seems like a reasonable request. I don't have the bandwidth right now to delve into it and create a patch, but if you care to provide one I'll try to evaluate and commit it quickly. You'll probably want to look at org.apache.catalina.loader.WebappClassloader#findResources, http://svn.apache.org/repos/asf/tomcat/container/tc5.5.x/catalina/src/share/org/apache/catalina/loader/WebappClassLoader.java, to start with. The getResources method itself in that class is not overridden, using the java.net.URLClassloader default. One other thing to consider is behavior when running inside a security manager. WebappClassloader already has a privileged findResource section, which is good. findResources should also probably check for the same permissions / run in a similar privileged action. I'm not sure what you mean with respect to "partially" unpacking WAR files. If you specify that the WAR should be unpacked, it's fully unpacked. Finally, if it's really urgent for you and you don't want to wait for one of us to review a patch and/or a Tomcat release that includes the patch, you can easily provide a Tapestry 5-suitable classloader by specifying the Loader element of the Context to be your own class name, as documented at http://tomcat.apache.org/tomcat-5.5-doc/config/loader.html
For the short term, I'll be focussing on a work around, as a build time enhancement (a Maven plugin). That's necessary either way, to address the installed base (including earlier versions of Tomcat, and likely other servlet containers as well). I would still like to see this fixed, as it would then not be necessary to have this extra step. As it is, I'm going to piss off some people who'll want an Ant task as well as a Maven plugin.
Personally, I don't find the use case convincing at this point, and I don't understand the explanation of the problem. The only documentation about the getResources method is the javadocs: Finds all the resources with the given name. A resource is some data (images, audio, text, etc) that can be accessed by class code in a way that is independent of the location of the code. And in URLClassLoader: Returns an Enumeration of URLs representing all of the resources on the URL search path having the specified name. As I stated earlier, I do not understand how it is assumed that this is supposed to do a wildcard match (it would be written cleanly as getResources("org/example/myapp/pages/*")). The wording seems to imply the opposite, actually, and I wonder if the behavior was as intended or not. If you have a reasonably simple patch which implements this, I would consider it, otherwise it is not going to be addressed (and I think the bug should be marked as WONTFIX).
Comments below: (In reply to comment #5) > Personally, I don't find the use case convincing at this point, and I don't > understand the explanation of the problem. > > The only documentation about the getResources method is the javadocs: > Finds all the resources with the given name. A resource is some data > (images, audio, text, etc) that can be accessed by class code in a way > that is independent of the location of the code. A Directory isa File, and you find those don't you? > > As I stated earlier, I do not understand how it is assumed that this is supposed > to do a wildcard match (it would be written cleanly as > getResources("org/example/myapp/pages/*")). The wording seems to imply the > opposite, actually, and I wonder if the behavior was as intended or not. There's no wildcarding going on-- it's just: ClassLoader.findResources("/META-INF"); With the expectation that it will return an Enumeration of URLs to all META-INF directories on the classpath. I really need to take a look and see what Seam did to get around this with their component loading by classpath searching. > > If you have a reasonably simple patch which implements this, I would consider > it, otherwise it is not going to be addressed (and I think the bug should be > marked as WONTFIX). > Even with the realization that there's no wildcarding going on? just the expectation that a directory is a valid Unified Resource *Location*? Oracle's AS has the same issue, but has since made the fix.
(In reply to comment #6) > ClassLoader.findResources("/META-INF"); > > With the expectation that it will return an Enumeration of URLs to all META-INF > directories on the classpath. The current implementation does that, feel free to look at the code, it's very explicit (WebappClassLoader.findResources). This is not what this case requests: "if you request a path, and not a file, you get the contents of folder if it exists" (the contents of a folder, AFAIK, is the enumeration of the files which are in the folder).
talked more with Remy-- things work as expected-- it should just return pointers (URL) to the directory, then it's up to the client code to walk the directories based on if the connections returned are JarUrlConnections or should be treated as physical file locations. Example here: https://facelets.dev.java.net/source/browse/facelets/src/java/com/sun/facelets/util/Classpath.java?rev=1.5&view=markup
Closing based on comment 8.