Created attachment 33326 [details] Test class for StandardJarScanner An application was created with Tomcat 8 and Apache Felix both embedded on the same JVM. Apache felix happens to add a URL to the application classloader's classpath via it's extension manager, which is used to support bundle fragment/extension features of the OSGi spec: https://github.com/apache/felix/blob/ac5ec40ee40ec92dc8124e22afa855ab2c4a850f/framework/src/main/java/org/apache/felix/framework/ExtensionManager.java#L106 When Tomcat is started on the same JVM and a context is created, Tomcat will attempt to perform a jar scan on the classloader's classpath. It will call getURLs to enumerate all classpath URLs and peek inside for pluggability purposes: https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java#L221 For each URL enumerated from the classloader Tomcat attempts to transform it into a ClassPathEntry. For the special URL that Apache Felix adds, ClassPathEntry's getName method will return the empty string, as there is no file part in the URL itself: https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java#L353 There is no way to prevent this issue with a JarScanFilter, since the check method called here will use the empty string file name, which can't be matched using the glob matching algorithm: https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java#L237 Eventually this falls through to the process() method through this call: https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java#L244 Since the URL "http://felix.extensions:9/" has a compatible scheme (http) but no file part (it doesn't match the jar extension) we fall through to the else condition in the process method, which will attempt to call new File(new URL("http://felix.extensions:9")) and fail, because the File(URL) constructor requires that the URL start with "file:/": https://github.com/apache/tomcat/blob/trunk/java/org/apache/tomcat/util/scan/StandardJarScanner.java#L317 This has the effect of bailing out the entire webapp loading. Steps to reproduce: I've created a test case which demonstrates the bug. I added this test case to TestStandardJarScanner to validate against 9.0 TRUNK, though the original bug was found on tomcat 8x. For convenience i've attached a copy of the test class with this test case embedded in it. /** * Tomcat should ignore URLs which do not have a file part and do not use the file scheme. */ @Test public void skipsInvalidClasspathURLNoFilePartNoFileScheme() { StandardJarScanner scanner = new StandardJarScanner(); LoggingCallback callback = new LoggingCallback(); TesterServletContext context = new TesterServletContext() { @Override public ClassLoader getClassLoader() { URLClassLoader urlClassLoader; try { urlClassLoader = new URLClassLoader(new URL[] { new URL("http://felix.extensions:9/") }); } catch (MalformedURLException e) { throw new RuntimeException(e); } return urlClassLoader; } }; scanner.scan(JarScanType.PLUGGABILITY, context, callback); } WORKAROUNDS: 1. Disable classpath scanning in context.xml. This is a bit cumbersome for war files which contain their own context.xml as it requires a rebuild of all those downstream modules. 2. Add a LifecycleListener to hook into context creation events before they are initialized and set the jar filter to ignore empty string file names (due to the missing file name part of the class path entry).
Here's the stack trace from the attached test case / unit test: java.lang.IllegalArgumentException: URI scheme is not "file" at java.io.File.<init>(File.java:421) at org.apache.tomcat.util.scan.StandardJarScanner.process(StandardJarScanner.java:317) at org.apache.tomcat.util.scan.StandardJarScanner.scan(StandardJarScanner.java:244) at org.apache.tomcat.util.scan.TestStandardJarScanner.test(TestStandardJarScanner.java:83) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Just a slight correction: I mentioned in the description "There is no way to prevent this issue with a JarScanFilter" Just to clarify, there is no way to prevent this with the built in StandardJarScanFilter. As the workaround states, you can subclass and create your own to do this.
Thanks for the report and the test case. It made investigating this really simple. The fix and test case have been applied to 9.0.x and 8.0.x. Just the fix was applied to 7.0.x due to the refactoring of StandardJarScanner that took place between 7.0.x and 8.0.x.