JSP Error in Jetty 9.2+
IMPORTANT: This is likey still true but I have moved on to a later version of jetty which means that the error is different and this will not solve the problem please see jettyjsp94error.html.
In Jetty 9.2 and above the default JSP engine has changed from Glassfish to Apache Jasper. I have been using a simple ServerRun.java file for development work (effectively an embedded jetty instance) which stopped working when I upgraded to Jetty 9.3.6.
The error I got was related to compiling JSP files:
org.apache.jasper.JasperException: Unable to compile class for JSP at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:600) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:363) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:340) ... java.lang.NullPointerException at org.apache.jasper.JspCompilationContext.getTldResourcePath(JspCompilationContext.java:551) at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:410) at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:469) ...
or
org.apache.jasper.JasperException: java.lang.IllegalStateException: No org.apache.tomcat.InstanceManager set in ServletContext at org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:176) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:375) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396) ... java.lang.IllegalStateException: No org.apache.tomcat.InstanceManager set in ServletContext at org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(InstanceManagerFactory.java:32) at org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:170) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:375) ...
During some futher testing I also found that if I do not include a ContainerIncludeJarPattern (see source) I would get the following error
org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:55) at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:277)
The root cause of this is that the method org.apache.jasper.servlet.JasperInitializer#onStartup
is not called and so does not set up the TldCache
(whatever that is). To fix this you may
write a simple ServletContextInitialiser
but that may not be so good if you are planning
to deploy a WAR into a stand alone servlet container (as I am planning to do). Ideally we
want to add some lines to ServerRun.java
so we are not interfering with the standard startup
of the servlet when deployed in a container.
You can see a comment in ServletContainerInitializersStarter
which also points to the problem
I was facing.
/** * ServletContainerInitializersStarter * * Call the onStartup() method on all ServletContainerInitializers, after having * found all applicable classes (if any) to pass in as args. */
You should be able to find the full code here: https://github.com/jetty-project/embedded-jetty-jsp/
the key lines for me were that I had not configured the scratch / temp area for Jasper to use
for storing the compiled JSP files. Most importantly, I had not configured org.eclipse.jetty.containerInitializers
.
context.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers()); context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager()); context.addBean(new ServletContainerInitializersStarter(context), true);
Apache Jasper will not use the base system class loader either, so we must create a new class loader and make our context use that class loader. If you know something about class loaders you will understand why, if not class loaders are interesting, you should go and find out more about them.
context.setClassLoader(getUrlClassLoader());
You can fill in the missing bits from the github repo, you will also find the updated
ServerRun.java
in my servlet tutorial.
Updated Embedded Servlet Starter
This supports JSP and other stuff like JNDI, mostly hacked together from the earlier repo.
public class ServerRun { private static File getScratchDir() throws IOException { File tempDir = new File(System.getProperty("java.io.tmpdir")); File scratchDir = new File(tempDir.toString(), "embedded-jetty-jsp"); if (!scratchDir.exists()) { if (!scratchDir.mkdirs()) { throw new IOException("Unable to create scratch directory: " + scratchDir); } } return scratchDir; } private static List<ContainerInitializer> jspInitializers() { JettyJasperInitializer sci = new JettyJasperInitializer(); ContainerInitializer initializer = new ContainerInitializer(sci, null); List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>(); initializers.add(initializer); return initializers; } public static void main(String[] args) throws Exception { Server server = new Server(8080); System.setProperty("org.apache.jasper.compiler.disablejsr199", "false"); WebAppContext webapp = new WebAppContext(); webapp.setDescriptor("src/main/webapp/WEB-INF/web.xml"); /* * All these configurations allow us to use things like Annotations * JSP 3.1 (@Servlet not CDI, you need weld for that) and JNDI. */ webapp.setConfigurations(new Configuration[] { new AnnotationConfiguration(), new WebInfConfiguration(), new WebXmlConfiguration(), new MetaInfConfiguration(), new FragmentConfiguration(), new EnvConfiguration(), new PlusConfiguration(), new JettyWebXmlConfiguration() }); webapp.setAttribute("javax.servlet.context.tempdir", getScratchDir()); webapp.setResourceBase("src/main/webapp/"); webapp.setContextPath("/core"); webapp.setAttribute( "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*/[^/]*servlet-api-[^/]*\.jar$|.*/javax.servlet.jsp.jstl-.*\.jar$|.*/[^/]*taglibs.*\.jar$"); /* * Configure the application to support the compilation of JSP files. * We need a new class loader and some stuff so that Jetty can call the * onStartup() methods as required. */ webapp.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers()); webapp.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager()); webapp.addBean(new ServletContainerInitializersStarter(webapp), true); webapp.setClassLoader(new URLClassLoader(new URL[0], ServerRun.class.getClassLoader())); webapp.setParentLoaderPriority(true); server.setHandler(webapp); server.start(); server.join(); } }