We are creating a Spring Boot executable WAR since it needs to work deployed in existing Servlet Containers and it needs to work standalone. This executable WAR will be downloadable by a third party company/user and used with a database that we don't know before hand. Even if we want to pre-packaged all common JDBC drivers some of them have licensing restrictions.

What we need is a way to add additional jars that can be picked up by the WarLauncher in the same way that Spring Boot automatically picks up application.properties or ./config/application.properties.

Why not have the WarLauncher automatically scan the file system for file:WEB-INF/lib and file:WEB-INF/lib-provided? My need for this is for the WarLauncher, but my guess is that the same could happen for the JarLauncher.

The other packaging layout won't work for us, e.g. ZIP/PropertiesLauncher, since we need to be able to deploy the package in an existing ServletContainer.

Asking the user to add the missing drivers to the WAR package after the fact is error prone (could overwrite the MANIFEST.MF, replace all content, etc), doesn't work if the executable WAR has the initial embedded shell script (at least it didn't seem to work with the jar tool). And in general it is a good practice to make sure the packaging we ship is immutable.

Comment From: wilkinsona

Why not have the WarLauncher automatically scan the file system for file:WEB-INF/lib and file:WEB-INF/lib-provided?

Because that's not the contract of a war file which is intended to be a self-contained artifact.

My need for this is for the WarLauncher, but my guess is that the same could happen for the JarLauncher

We already have properties launcher that lets you do this with a jar.

And in general it is a good practice to make sure the packaging we ship is immutable.

I agree with this and it is an excellent argument against what you have proposed for the war launcher. If you really want to do this, I would recommend using a custom layout, support for which was introduced in 1.5

Comment From: ydewit

Because that's not the contract of a war file which is intended to be a self-contained artifact.

I am not suggesting to break the WAR file contract (what contract specifically you are referring to?) more than it is already broken with the provided executable WAR (Isn't the WEB-INF/lib-provided already breaking this?)

In the same way that I can add a mysql driver to an installed tomcat/lib folder for a WAR deployment, why not allow the embedded tomcat in the WAR file to augment the Tomcat server classpath with jars found in an external folder? It basically makes sure I can use the executable WAR in these two contexts with one artifact.

We already have properties launcher that lets you do this with a jar.

Which doesn't address the functionality I am looking for. As far as I understand, a ZIP layout / PropertiesLauncher means the artifact is no longer a WAR file. And that means I have to provide two separate artifacts for download.

I agree with this and it is an excellent argument against what you have proposed for the war launcher. If you really want to do this, I would recommend using a custom layout, support for which was introduced in 1.5

I fail to see how the immutability of the executable-WAR file is an argument against this proposal in the same way that externalizing application.properties isn't.

Just look at it from a different perspective. An executable WAR is a WAR file + an embedded Tomcat. I am just proposing to make the embedded Tomcat classpath extensible out of the box. So leave the WAR content/contract/etc intact.

Comment From: wilkinsona

what contract specifically you are referring to

The contract that it is a self-contained artefact

Which doesn't address the functionality I am looking for

I understand that. I was responding to your suggestion that your proposal could be used for JarLauncher as well by pointing out that PropertiesLauncher already provides that functionality.

I fail to see how the immutability of the executable-WAR file is an argument against this proposal in the same way that externalizing application.properties isn't.

I consider externalised configuration to be separate to externalised control of the class path.

I am just proposing to make the embedded Tomcat classpath extensible out of the box

We consider this sort of thing to be an edge case. You can either use a jar file and PropertiesLauncher or you can implement your own layout with a customised WarLauncher to meet your specific needs. Alternatively, you could launch your application using something like java -cp your-app.war:jdbc-driver.jar org.springframework.boot.loader.WarLauncher

Comment From: ydewit

Could you point me to documentation/examples on how to create a custom layout?

Comment From: ydewit

Found this and this. Are these the best resources available?

However, It just seems crazy that I have to go this (roundabout) route so that I simply can augment the embedded Tomcat classpath with an external ./lib/mysql-jdbc.jar.

Comment From: wilkinsona

There's a sample too: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-custom-layout

Comment From: ydewit

@wilkinsona thanks, this seems a bit more bite-sized.

Comment From: ydewit

I created a simple project based on the above sample and I noticed that the WAR contents are properly written by default (I used Layouts.War), but the boot loader is not. I basically don't want to change anything in the current executable WAR layout. It is perfect as it is.

I want to extend the current launcher with additional file:./lib/*.jar to the list of URLs returned by Launcher.getClassPathArchives(). Does that mean I have to also extend the Spring Boot loader and write it's classes to my executable WAR using the custom layout project? I seems so.

Comment From: wilkinsona

Does that mean I have to also extend the Spring Boot loader and write it's classes to my executable WAR using the custom layout project? I seems so.

Yes

Comment From: ydewit

I had a pleasant surprise in that I didn't need to re-implement anything, but reuse existing functionality as all of it was factored out in the -tools project.

However, at the end of the day, this is what is what the project boils down to:

@Override
protected List<Archive> getClassPathArchives() throws Exception {
    File externalLib = getExternalLibFolder();
    List<Archive> archives = super.getClassPathArchives();
    if (externalLib.exists()) {
        File[] jarFiles = externalLib.listFiles((FileFilter) file -> file.getName().toLowerCase().endsWith(".jar"));
        if (jarFiles != null) {
            for (File jarFile : jarFiles) {
                archives.add(new JarFileArchive(jarFile));
            }
        }
    }
    return archives;
}

/**
 * @return
 */
private File getExternalLibFolder() {
    String externalLibFolder = System.getenv("EXTERNAL_LIB_FOLDER");
    if( externalLibFolder == null || externalLibFolder.length() == 0 )
        externalLibFolder = EXTERNAL_LIB_FOLDER;
    return new File(externalLibFolder);
}   

So to spare others from having to figure this out on their own again, here is the project I put together. I will see if I can publish it into Maven central.

Comment From: rrnavinprasad

@ydewit the sample project link is missing. It would be very great if you can post the entire example. I mean the way in which the war is built using the pom (maven) or gradle build.. I am missing something which doesn't allow me to use the custom launcher. I am building war and deploying it in tomcat.

Comment From: ydewit

@rrnavinprasad The project has moved to BitBucket. You can find it here.

However, this project will only work if you run Tomcat embedded with an executable WAR.

Comment From: knusprigerschweinsbraten

@ydewit: Unfortunately the BitBucket repository seems to be private. Would it be possible for you to move it into the public space or at least share the entire example?

Comment From: wuwen5

you can

java -Dloader.path=${APP_HOME}/ext_lib -cp your-app.jar org.springframework.boot.loader.PropertiesLauncher