e42.uk Circle Device

 

Quick Reference

Jetty 9.2+ Embedded JSP Error

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();
	}
}

Quick Links: Techie Stuff | General | Personal | Quick Reference