I'll outline a description of the precise problem here, but if you're looking for a deep-dive on exactly how I arrived here, including sample code, take a look at this Stack Overflow post.
In Java Maven projects, the "default" layout (and what most projects use) is src/main/java, src/main/resources, src/test/java, and src/test/resources. However, those are only defaults, and nothing prevents a team from using a different layout. The following configuration supports the layout source/production/java, source/production/resources, source/test/java, and source/test/resources:
<build>
...
<sourceDirectory>source/production/java</sourceDirectory>
<resources>
<resource>
<directory>source/production/resources</directory>
</resource>
</resources>
<testSourceDirectory>source/test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>source/test/resources</directory>
</testResource>
</testResources>
...
</build>
The maven-war-plugin has a similar default: It looks for web application resources in src/main/webapp. However, once again, this is just a default, and it can be configured using <warSourceDirectory>:
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<warSourceDirectory>source/production/web</warSourceDirectory>
...
</configuration>
</plugin>
...
</plugins>
...
</build>
A problem arises in the fact that Spring Boot hard-codes src/main/webapp in several places throughout the code, which makes it impossible to use a layout which uses a different directory structure for web resources.
I've contributed to Spring before (it's been many years), so I'm willing to work on a pull request to address this problem, but I need some guidance first. I need to know things like: Where would this get configured (spring-boot-maven-plugin? somewhere else?)? How do I get that configuration in those places in code? Perhaps other things that I don't yet know that I need to know.
Comment From: mdeinum
Looking at the hits you found, most of them are in integration/smoke tests for the framework itself. There are 2 that might be troublesome (well actually 1 as the other is configurable).
The one in @WebAppConfiguration defaults to src/main/webapp but can be overridden, the one in the JspTemplateAvailabilityProvider will fallback to src/main/webapp and that might need to be polished/made configurable.
Update (1)
After trying to create a demo to show a possible workaround it appears the main issue is with the detection of a valid document root in the DocumentRoot class. When not a war or exploded war it will only look into 3 default directories (see here).
Not sure if the same logic should be applied in the JspTemplateAvailabilityProvider as that checks the existence of JspConfig to delegate to the container or use the default src/main/webapp directory.
Update (2)
As a workaround for running from the command-line or IDE, a TomcatContextCustomizer can be added to set the proper docBase for this case
@Bean
public TomcatContextCustomizer docBaseCustomizer() {
return new TomcatContextCustomizer() {
public void customize(Context context) {
File root = new File("source/production/webapp");
if (root.exists() && root.isDirectory()) {
context.setDocBase(root.getAbsolutePath());
}
}
};
}
The drawback is that this adds another location to set the proper docBase next to the one in the pom.xml.
Update (3)
After some more digging it appears that there is a ConfigurableServletWebServerFactory.setDocumentRoot. There is also a ServletWebServerFactoryCustomizer which already binds properties from the ServerProperties to the ConfigurableServletWebServerFactory. If the ServerProperties would also expose a document-root property it could be set through the customizer. Then in a maven or gradle build, you could process the application.[properties|yml] to add the proper value to that property.
Setting the value would only need to be done if the passed in path is a directory and exists (basically the same check that is done in the DocumentRoot class when processing the default locations. It might be worthwhile to add a warning when the location is going to be ignored. (Like when deployed to a server or when running the packaged war).
Comment From: philwebb
See https://github.com/spring-projects/spring-boot/pull/12859 for more background
Comment From: larsgrefer
If JspTemplateAvailabilityProvider could be modified to use javax.servlet.ServletContext#getResource in order check if a given jsp file exists, the only other usage would be the configuration of the embedded servlet-container itself.
For configuring said servlet container, org.springframework.boot.web.servlet.server.DocumentRoot#getCommonDocumentRoot is only used, if no war packaging can be detected. So it's used for either jar-packaged applications or applications that aren't packaged at all, like unit-tests run by Maven/Gradle or when the application is started directly from an IDE.
I support the suggestion to make the webroot(s) configurable using a property. The Spring Boot Maven and Gradle plugins could then set this property to the correct value when executing the application or its tests.
For jar-based applications, it might also be a nice feature to be able to configure a webroot.
17233 might add some more background about what is (or should be) considered as web root under different deployment and packaging options.
Comment From: philwebb
I did a bit of digging on this one and I think we'll perhaps need to revisit the fix for #12805 (solved by PR #12859 in Boot 1.5). Perhaps we can make the RunMojo set a system property that the JspTemplateAvailabilityProvider can pick it up.
It's a bit tricky because currently RunMojo doesn't know about the Maven war plugin.