Spring-boot: ServletContext getResourceAsStream for file in META-INF/resources does not work in an IDE, spring-boot:run, or bootRun

Created on 7 Mar 2017  路  12Comments  路  Source: spring-projects/spring-boot

Hi,

I've just upgraded from Boot 1.5.1 to 1.5.2 and am now getting null back from getServletContext().getResourceAsStream. This happens in STS (3.8.3) only and not when running from the command line.

I've distilled the problem down into a simple project on Bit Bucket

My simple project consists of a WAR which contains a Servlet 'SomeServlet' which looks for a resource /scripts/lib/dave.js inside a dependent JAR /resources/META-INF/resources. If the resource is not found I throw an exception.
. Boot 1.5.1 works fine on both command line and in STS. Boot 1.5.2 works fine on the command line but breaks in Eclipse returning null.

Any ideas?

Thanks

bug

Most helpful comment

@davidmelia Thanks for the sample project and the details on how you're running the app. As I suspected, the problem was introduced by the changes for #8299.

Here's a work around:

@Bean
public TomcatEmbeddedServletContainerFactory tomcat() {
    TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = new TomcatEmbeddedServletContainerFactory();
    tomcatEmbeddedServletContainerFactory.addContextCustomizers((context) -> {
        context.addLifecycleListener((event) -> {
            if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                ClassLoader classLoader = getClass().getClassLoader();
                if (classLoader instanceof URLClassLoader) {
                    for (URL url: ((URLClassLoader)classLoader).getURLs()) {
                        if ("file".equals(url.getProtocol())) {
                            File file = new File(url.getFile());
                            if (file.isFile() || new File(file, "META-INF/resources").isDirectory()) {
                                context.getResources().createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/", url, "/META-INF/resources");
                            }
                        }
                    }
                }
            }
        });
    });
    return tomcatEmbeddedServletContainerFactory;
}

All 12 comments

It may be this change that was made in 1.4.5 and 1.5.2.

When you say this it works fine when running from the command line, how are you running your application? Are you using mvn spring-boot:run or java -jar?

java -jar

And interestingly mvn spring-boot:run causes the error as well.

Hi,

attached another sample project that seems too reproduce this same issue, this time when trying to set up SiteMesh 2.

With 1.5.1, the file from $PROJECT/src/main/resources/META-INF/resources/WEB-INF/decorators.xml gets readed. With 1.5.2, we end up with the following stacktrace:

com.opensymphony.module.sitemesh.factory.FactoryException: Cannot construct Factory : com.opensymphony.module.sitemesh.factory.DefaultFactory: java.lang.IllegalStateException: Cannot load excludes configuration file "/WEB-INF/decorators.xml" as specified in "sitemesh.xml" or "sitemesh-default.xml"
at com.opensymphony.module.sitemesh.Factory.getInstance(Factory.java:50) ~[sitemesh-2.4.2.jar:na]
at com.opensymphony.sitemesh.webapp.SiteMeshFilter.initContentProcessor(SiteMeshFilter.java:107) ~[sitemesh-2.4.2.jar:na]
at com.opensymphony.sitemesh.webapp.SiteMeshFilter.doFilter(SiteMeshFilter.java:54) ~[sitemesh-2.4.2.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.11.jar:8.5.11]

The line causing the issue involves one config.getServletContext().getResourceAsStream(.. returning null with 1.5.2.

(this happens when running app from console, no STS involved)

oh, and notice that we need org.apache.tomcat.embed:tomcat-embed-jasper in order to get the application running with 1.5.1. If that dependency is omitted, we get the same stacktrace as with 1.5.2.

@davidmelia Thanks for the sample project and the details on how you're running the app. As I suspected, the problem was introduced by the changes for #8299.

Here's a work around:

@Bean
public TomcatEmbeddedServletContainerFactory tomcat() {
    TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = new TomcatEmbeddedServletContainerFactory();
    tomcatEmbeddedServletContainerFactory.addContextCustomizers((context) -> {
        context.addLifecycleListener((event) -> {
            if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                ClassLoader classLoader = getClass().getClassLoader();
                if (classLoader instanceof URLClassLoader) {
                    for (URL url: ((URLClassLoader)classLoader).getURLs()) {
                        if ("file".equals(url.getProtocol())) {
                            File file = new File(url.getFile());
                            if (file.isFile() || new File(file, "META-INF/resources").isDirectory()) {
                                context.getResources().createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/", url, "/META-INF/resources");
                            }
                        }
                    }
                }
            }
        });
    });
    return tomcatEmbeddedServletContainerFactory;
}

@juanpablo-santos Your sample project fails for me with both 1.5.1 and 1.5.2. That's actually what I would expect as, per the servlet spec, loading content from META-INF/resources is only supported for nested jars. #8324 is tracking the possibility of changing that.

Hi @wilkinsona,

thanks for looking into the sample. In it, with 1.5.2, http://localhost:8080/index.html gets you the "Whitelabel Error Page", with the underlying exception being:

There was an unexpected error (type=Internal Server Error, status=500).
Cannot construct Factory : com.opensymphony.module.sitemesh.factory.DefaultFactory: java.lang.IllegalStateException: Cannot load excludes configuration file "/WEB-INF/decorators.xml" as specified in "sitemesh.xml" or "sitemesh-default.xml"

This is caused because config.getServletContext().getResourceAsStream(.. is returning null inside Sitemesh.

With 1.5.1, the same URL gets another "Whitelabel Error Page", but this time the underlying exception is:

There was an unexpected error (type=Not Found, status=404).
No message available

which is expected as Sitemesh decorator itself is a blank page, there aren't any controllers, etc. But the former line is able to locate the decorators file inside [src/main/resources/]META-INF/resources as config.getServletContext().getResourceAsStream(.. doesn't return null anymore. (Sitemesh templates don't need to be static resources, they could also be jsps, so maybe that's why they get readed from there - tomcat-embed-jasper is also lurking in there..).

In any case, applying the provided workaround with 1.5.2 also fixes the issue, so thanks for your prompt response! :-)

Hi,

found one corner case with the above solution, spring-boot maven plugin won't start if the project has a dependency of type pom, yielding the following exception:

java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[]]
        at java.util.concurrent.FutureTask.report(FutureTask.java:122) [na:1.8.0_112]
        at java.util.concurrent.FutureTask.get(FutureTask.java:192) [na:1.8.0_112]
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:939) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:872) [tomcat-embed-core-8.5.11.jar:8.5.11]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [tomcat-embed-core-8.5.11.jar:8.5.11]
[...]
Caused by: java.lang.IllegalArgumentException: The file specified by base and internal path [c:\java\.m2\repo\com\twelvemonkeys\bom\bom\3.3.2\bom-3.3.2.pom]\[/META-INF/resources] does not exist.
        at org.apache.catalina.webresources.FileResourceSet.checkType(FileResourceSet.java:172) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
        at org.apache.catalina.webresources.AbstractFileResourceSet.initInternal(AbstractFileResourceSet.java:145) ~[tomcat-embed-core-8.5.11.jar:8.5.11]
        at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107) [tomcat-embed-core-8.5.11.jar:8.5.11]

one possible quick fix is to substitue the innermost if above with:

if( ( file.isFile() && !file.getName().endsWith( ".pom" ) ) || new File( file, "META-INF/resources" ).isDirectory() ) {

@juanpablo-santos Thank you for trying the snapshot

I've just realised the failure above wasn't with the fix in the snapshots, it's with the workaround I suggested above. The fix in the snapshots is more defensive.

Was this page helpful?
0 / 5 - 0 ratings