Delivering JSP from OSGI bundles with Apache Tomcat
It seems obvious to me that web developers want to develop modular web applications and that we expect these modules to deliver content. Java 9 has official requests to provide OSGI support which is already the status-quo when it comes to modular applications in Java. It seems to me that Java has been vastly abandoned by the web development industry because it doesn’t meet the needs of most web developers. Developers don’t expect to spend days digging through log files and server source code to achieve such basic features as theme defined HTML and direct content delivery from modules. Sadly, this is the zeitgeist for Java web development today.
It certainly is possible to deliver JSP content from an OSGI bundle, but how to do it seems to be a closely guarded secret. There isn’t much information available on the web on how it can be done. The actual solutions tend to be offensive delivery systems which need to extract the content into the web application path, or resorting to the use of specific application server platforms. These solutions are simply not acceptable to me so I took a few days to dig through Tomcat to find the best solution. What I discovered is that Tomcat doesn’t actually provide a solution, but it could with the addition of a few lines of code and the injection of a usable classloader.
Injecting a classloader into Tomcat is not very difficult if your using tomcat-embed. You simply pass your classloader to the WebappLoader class constructor and then pass it to the setLoader method of your application context. In my case, I wanted to use tag libraries so I wrapped my OSGI bundle class loader with with a URLClassloader and added the JSTL tag library to it and passed that to my Application Context. This sounds like a good solution for delivering JSP files from OSGI bundles in tomcat, but it isn’t. (see next page)
The Tomcat ApplicationContext does NOT look into the classloader for jsp files. Because of this you have two options available. The first option is really the best solution, and that is to modify the tomcat source code. To enable this capability you simply need to edit the getResource and getResourceAsStream methods to look into the classloader for resources. Simply use the classloader which is registered with the application context as that is where it intuitively should be. I suppose it may be possible to use the Thread’s contextClassLoader for this purpose but that would require a lot of classloader juggling. Setting the appropriate classloader in the application context once, and forgetting about it is a reasonable solution. Your second option is to extend tomcat itself. This is the route I took and named my extension Bestat, the Cat Goddess. It seems fitting since it requires torturing Tomcat source code into submission, without actually changing it. I ended up extending the Tomcat startup class, StandardHost, StandardContext, and the ApplicationContext, which seems to be the minimal set of extensions needed to get Tomcat to use a custom ApplicationContext by default.
Extending Tomcat’s Catalina system is fairly painless, but if your a real masochist, you can also extend Jasper. While attempting to get the JSP’s to load I was getting errors because I initially didn’t override getResourceAsStream. Debugging this was nearly impossible because even with debug level logging enabled Jasper is very quiet about the true cause of the problem. I “extended” the JspServlet and JspServletWrapper to add logging and this is how I was able to solve the problem. I use the term extended lightly since Jasper source code is not extensible by any stretch of the imagination. In addition to extending the classes, I also needed to copy/paste all of the original source code into the extensions, changing all private members to protected to make them useful.
This is the basis for my Bestat server. I have no plans on releasing the source code because I don’t want to make it easier for Tomcat developers to block this capability. While reviewing the source code it seems that their developers have gone to great lengths to avoid allowing content delivery from the classloader, which is truly the only sensible place to deliver it from. It seems probable that the Apache Tomcat project will need to be forked to support modern web application development, but as of now it is still possible to torture the code into submission.