How do you get embedded Jetty 9 to successfully resolve the JSTL URI?
Solution 1
Sadly, none of the answers so far have worked for me. But I finally found a solution to my issue. This is going to sound like a hack, and it definitely feels like one.
But if I get everything set up just as I described in my question, up to the point where the JSTL won't resolve, I can then take one step that makes everything work, almost like magic.
That cringe-inducing step is changing the extension on the .war
file to .jar
. Once I do that, the JSTL resolves just fine, and everything works.
So right now I produce a .war
that you can stick into a servlet container or you can rename to .jar
and run standalone. And it works on both Unix and Windows, whereas the way I was doing this before wouldn't work on Unix due to a bug present in the jsp-2.1-glassfish
library mentioned by James Cook.
The relevant bits from my pom:
<properties>
<jetty.version>9.0.5.v20130815</jetty.version>
<war.class>com.domain.package.DeployWebapp</war.class>
<war.class.path>com/domain/package</war.class.path>
</properties>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>main-class-placement</id>
<phase>prepare-package</phase>
<configuration>
<tasks>
<move todir="${project.build.directory}/${project.artifactId}-${project.version}/${war.class.path}">
<fileset dir="${project.build.directory}/classes/${war.class.path}">
<include name="DeployWebapp.class" />
</fileset>
</move>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<id>jetty-classpath</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty,javax.servlet,javax.el,org.glassfish.web,org.eclipse.jetty.orbit,org.ow2.asm,javax.annotation</includeGroupIds>
<outputDirectory>
${project.build.directory}/${project.artifactId}-${project.version}
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<attachClasses>true</attachClasses>
<archiveClasses>true</archiveClasses>
<archive>
<manifest>
<mainClass>${war.class}</mainClass>
</manifest>
</archive>
<packagingExcludes>META-INF/*.SF</packagingExcludes>
<packagingExcludes>META-INF/*.DSA</packagingExcludes>
<packagingExcludes>META-INF/*.RSA</packagingExcludes>
</configuration>
</plugin>
Solution 2
So here is yet another solution. I was struggling with very similar problem only I have a separate war file and a simple embedder class which creates a Jetty server for me and starts it up pointing to possibly any war file. Here is how this all work.
The war file doesn't have any tld library in
WEB-INF/lib
and is completely separated from the loader miniapplication.-
The Loader application Main class which starts the server and points it to any war file has following dependencies (maven):
<!-- Jetty webapp --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-webapp</artifactId> <version>${jettyVersion}</version> </dependency> <!-- With JSP support --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-jsp</artifactId> <version>${jettyVersion}</version> </dependency>
-
The loading class itself looks as:
Server server = new Server(cncPort); WebAppContext webApp = new WebAppContext(); webApp.setContextPath(applicationContext); webApp.setWar(jettyHome + "/" + warFile); server.setHandler(webApp); try { server.start(); server.join(); } catch (Exception ex) { System.out.println("Failed to start server."); ex.printStackTrace(); }
-
The resulting package in my case looks as follows:
+ EmbedderApp | +-- lib - EmbeddedApp.jar <-- JAR with the embedder class - com.sun.el-2.2.0.v201303151357.jar - javax.el-2.2.0.v201303151357.jar - javax.servlet-3.0.0.v201112011016.jar - javax.servlet.jsp-2.2.0.v201112011158.jar - javax.servlet.jsp.jstl-1.2.0.v201105211821.jar - jetty-http-9.0.6.v20130930.jar - jetty-io-9.0.6.v20130930.jar - jetty-jsp-9.0.6.v20130930.jar - jetty-security-9.0.6.v20130930.jar - jetty-server-9.0.6.v20130930.jar - jetty-servlet-9.0.6.v20130930.jar - jetty-util-9.0.6.v20130930.jar - jetty-webapp-9.0.6.v20130930.jar - jetty-xml-9.0.6.v20130930.jar - org.apache.jasper.glassfish-2.2.2.v201112011158.jar - org.apache.taglibs.standard.glassfish-1.2.0.v201112081803.jar - org.eclipse.jdt.core-3.8.2.v20130121.jar
-
My dependecies were simply added via assembly plugin as
<dependencySet> <outputDirectory>lib</outputDirectory> <scope>runtime</scope> </dependencySet>
-
I have a shell start script launching the embedding class and here comes what took me ages to figure out.
I used a manifest with classpath embedded within the jar and setting my
CLASSPATH=<PATH_TO_APP>\lib\EmbeddedApp.jar
assuming the reset of the dependencies are part of my classpath through the manifest. And I was gettign the same unresolvable URI error.Once I added changed the
CLASSPATH
variable within my script to contain all the jars explicitely it started to work.for jar in ${APP_ROOT}/lib/*.jar; do CLASSPATH=$jar:${CLASSPATH}; done
Hope this can save someones time :-)
Solution 3
After fiddling around with a1kmm's solution and ending up with NullPointers I noticed I did not set a Classloader on the WebAppContext. Using the following line I no longer need the custom classloading/manifest scanning setup.
webAppContext.setClassLoader(new WebAppClassLoader(getClass().getClassLoader(), webAppContext));
Solution 4
I had the same problem starting Jetty from a Surefire test; the problem was that Jetty 9 doesn't look at the manifests of any jar files except in WEB-INF, which is incompatible with the way I was writing my tests.
To work around the problem, I wrote a little bit of code to find the jar files from the manifest, and put them into a new intermediate URLClassLoader.
This is what my functional test setup function ended up looking like to get it to work with Jetty 9:
@Before @SuppressWarnings("unchecked")
public void setUp() throws Exception {
WebAppContext ctx = new WebAppContext("src/main/webapp", "/");
Server server = new Server(9000);
ctx.setServer(server);
server.setHandler(ctx);
ctx.preConfigure();
ctx.addOverrideDescriptor("src/main/webapp/WEB-INF/tests-web.xml");
// Replace classloader with a new classloader with all URLs in manifests
// from the parent loader bubbled up so Jasper looks at them.
ClassLoader contextClassLoader = ctx.getClassLoader();
ClassLoader parentLoader = contextClassLoader.getParent();
if (contextClassLoader instanceof WebAppClassLoader &&
parentLoader instanceof URLClassLoader) {
LinkedList<URL> allURLs =
new LinkedList<URL>(Arrays.asList(((URLClassLoader)parentLoader).getURLs()));
for (URL url : ((LinkedList<URL>)allURLs.clone())) {
try {
URLConnection conn = new URL("jar:" + url.toString() + "!/").openConnection();
if (!(conn instanceof JarURLConnection))
continue;
JarURLConnection jconn = (JarURLConnection)conn;
Manifest jarManifest = jconn.getManifest();
String[] classPath = ((String)jarManifest.getMainAttributes().getValue("Class-Path")).split(" ");
for (String cpurl : classPath)
allURLs.add(new URL(url, cpurl));
} catch (IOException e) {} catch (NullPointerException e) {}
}
ctx.setClassLoader(
new WebAppClassLoader(
new URLClassLoader(allURLs.toArray(new URL[]{}), parentLoader),
((WebAppClassLoader)contextClassLoader).getContext()));
}
server.start();
}
My code sample is placed in the public domain - you may use it in your own code (attribution appreciated but not required).
Solution 5
I was having this exact problem. And I solved it in the most unusual way.
I am using Maven as the build tool, but here is how I was building my self-exec WAR.
<profile>
<id>Jetty_9</id>
<properties>
<jetty9.version>9.0.4.v20130625</jetty9.version>
</properties>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
<version>3.0.0.v201112011016</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${jetty9.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${compileSource}</source>
<target>${compileSource}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>main-class-placement</id>
<phase>prepare-package</phase>
<configuration>
<target>
<move todir="${project.build.directory}/${project.build.finalName}/">
<fileset dir="${project.build.directory}/classes/">
<include name="Main.class"/>
</fileset>
</move>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>jetty-classpath</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>
org.eclipse.jetty,org.slf4j,ch.qos
</includeGroupIds>
<includeScope>provided</includeScope>
<excludes>META-INF/*.SF,META-INF/*.RSA,about.html, about_files/**, readme.txt,
plugin.properties, jetty-dir.css
</excludes>
<outputDirectory>
${project.build.directory}/${project.build.finalName}
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<archive>
<manifest>
<mainClass>Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>default-war</id>
<phase>package</phase>
<goals>
<goal>war</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
But like yourself I was getting that error. After Googling the death out of it after 2 days I found this - http://internna.blogspot.co.uk/2011/08/step-by-step-executable-war-files.html
By switching out the jetty-jsp dependency for this one:
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-2.1-glassfish</artifactId>
<version>2.1.v20100127</version>
</dependency>
It all started magically working!
As of this moment I can't explain why it does work. But I am keen to find out
Related videos on Youtube
Comments
-
William Proffitt about 1 year
I'm packaging a Web Application Archive (.war) so that it can be started via
java -jar webapp.war
in a shell by launching an embedded copy of Jetty 9 using this code in a main class:int port = Integer.parseInt(System.getProperty("port", "80")); // I know this has implications :) String contextPath = System.getProperty("contextPath", ""); Server server = new Server(port); ProtectionDomain domain = Deployer.class.getProtectionDomain(); URL location = domain.getCodeSource().getLocation(); WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/" + contextPath); webapp.setWar(location.toExternalForm()); server.setHandler(webapp); server.start(); server.join();
However, I'm running into this error when the first JSP containing a JSTL taglib declaration gets compiled:
org.apache.jasper.JasperException: /WEB-INF/html/user/login.jsp(2,62) PWC6188: 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:92) at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:378) at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:172) at org.apache.jasper.compiler.TagLibraryInfoImpl.generateTLDLocation(TagLibraryInfoImpl.java:431) at org.apache.jasper.compiler.TagLibraryInfoImpl.<init>(TagLibraryInfoImpl.java:240) at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:502) at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:582) at org.apache.jasper.compiler.Parser.parseElements(Parser.java:1652) at org.apache.jasper.compiler.Parser.parse(Parser.java:185) at org.apache.jasper.compiler.ParserController.doParse(ParserController.java:244) at org.apache.jasper.compiler.ParserController.parse(ParserController.java:145) at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:212) at org.apache.jasper.compiler.Compiler.compile(Compiler.java:451) at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:625) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:492) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:378) at javax.servlet.http.HttpServlet.service(HttpServlet.java:848) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:698) etc...
The first couple lines of that JSP are as follows:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
I've looked around quite a bit (this doesn't seem to be a new issue) and have tried the following solutions:
- Slimming down my dependencies and looking for conflicts (currently I'm only depending on
jetty-server
,jetty-webapp
, andjetty-jsp
, all version9.0.4.v20130625
) - Specifying an explicit
<taglib>
mapping in the webapp's web.xml file that points to JSTL directly (got this idea from reading the JSP spec) - Modifying the server classpath as per this answer
- Taking advantage of methods of WebAppContext such as
addServerClass
andsetParentLoaderPriority
According to Jetty's documentation, using JSTL should just work, but I think the embedded context may be changing the way JSTL gets loaded and causing it to fail.
Would appreciate any ideas or suggestions. This setup would be replacing an older setup that did the same thing successfully on Windows but was not functioning on Linux due to the inclusion of an old dependency that brought in this bug. Unfortunately, I haven't been able to find a quick replacement for that dependency (groupId
org.mortbay.jetty
artifactIdjsp-2.1-glassfish
version2.1.v20100127
) that doesn't introduce the JSTL URI stack trace mentioned above.UPDATE: I've found a suboptimal solution. A downgrade to Jetty 7 inspired by this thread now has me up and running. This is great news, but it's discouraging that if I later required any functionality exclusive to Jetty 8 or Jetty 9 that I'd have to scrap this deployment infrastructure. Any insight on JSTL taglib issue in Jetty 9 would still be appreciated.
- Slimming down my dependencies and looking for conflicts (currently I'm only depending on
-
Travis Schneeberger almost 10 yearsAlso, does not cause problems for Jetty 8.1
-
Ben Challenor about 9 yearsInflating the classpath worked for me too - thanks so much! I was about to give up on this.
-
Jan Zyka about 9 yearsHehe, I feel your pain, took me ages to figure out
-
Alexey over 8 yearsI just added jsp-2.1-glassfish to my depencencies and it works?
-
James 'Cookie' Cook over 8 yearsHey! I would love to help you, but this answer being over 2 years old now, the memory is a little fuzzy. I think it has all moved on from then.
-
TedTrippin almost 4 yearshi @JanZyka how do you get your javax.el dependency, is it direct or child dependency?
-
Jan Zyka almost 4 yearsI think it was transiitive dependency, but it is 9 years back ... :) no guarantees