Jetty deployment with Maven

Keine Kommentare

In a project I’m involved in, we are developing a web application that has to be deployed on WebSphere. Although the integration of RSA (Rational Software Architect) with WebSphere is pretty much okay, we found that local deployments for development testing are slow.
As we have already organised our project with Maven, we came up with the idea to use Jetty instead of WebSphere for our development environment (we will still be deploying at least once a day to a WebSphere environment for full testing and verification).

Seems pretty straight-forward right? Especially with all the information found on the internet. However we stumbled upon a, not too wellknown problem which makes this interesting to write about.

First I’ll give some background and describe the problem. Then I’ll give an overview of our total solution together with excerpts from our Maven configuration and source code.

Background and problem description

The main technologies used for our web application are Spring (MVC) and JSP’s with custom tags. When the end-user requests a web-page, it will be dynamically build-up from various (generic and re-usable) ‚widgets‘, for which we have created our own tag-libs. As the MVC controller receives the user’s request, internal requests are necessary to retrieve all page components and build the entire page. This is where the problem with Jetty kicks in.

For our site we are using servlet mappings that end with „/*“, and thus all external requests are routed to a specific servlet. On the WebSphere server, this servlet mapping is not applied to the internal requests we do to construct our page. On Jetty however, this servlet mapping is applied to every request, including our internal requests.
The result is that as soon as we had our Jetty deployment setup, we would get strange errors as the Spring controller didn’t know what to do with our internal requests.

Solution

As we didn’t (and couldn’t) want to change our solution of the „/*“ servlet mapping, we were looking for a temporary workaround that was only applicable during development with Jetty, but wouldn’t have any impact in testing and production environments.
The eventual workaround we found was two-fold; use a custom web.xml configuration for Jetty deployment and, a small web-application used to re-route requests. The Maven module structure became as follows;

web-app-root (pom-module)
     |-- web-app-war (war-module)
               |-- src/main/...
               |-- src/deploy/assembly/...
               |-- src/deploy/resources/...
     |-- web-app-ear (ear-module)
     |-- jettydeployment (war-module)

The „deploy“ directory is created purely to store necessary configuration for Jetty deployment, and is omitted during a normal (release) build of the project. A Maven profile is used to include the JettyDeployment only when requested.
No changes were necessary to the web-app root module or the ear module.

The profile-configuration part of the pom.xml of the web-app-war looks as follows;

<profiles>
  <profile>
    <id>jetty</id>
    <build>
      <plugins>
        <plugin>
          <artifactId>maven-assembly-plugin</artifactId>
          <executions>
            <execution>
              <id>Downloads</id>
              <phase>generate-sources</phase>
              <goals>
                <goal>directory-single</goal>
              </goals>
              <configuration>
                <descriptors>
                  <descriptor>src/deploy/assembly/artifacts.xml</descriptor>
                </descriptors>
              </configuration>
            </execution>
          </executions>
        </plugin>
 
        <plugin>
          <groupId>org.mortbay.jetty</groupId>
          <artifactId>jetty-maven-plugin</artifactId>
          <version>7.3.0.v20110203</version>
          <configuration>
            <webAppConfig>
              <contextPath>/jetty/</contextPath>
              <descriptor>${basedir}/src/deploy/resources/jetty-web.xml</descriptor>
              <jettyEnvXml>${basedir}/src/deploy/resources/jetty-env.xml</jettyEnvXml>
              <extraClasspath>${basedir}/src/deploy/resources/</extraClasspath>
            </webAppConfig>
            <contextHandlers>
              <contextHandler implementation="org.eclipse.jetty.webapp.WebAppContext">
                <war>${project.build.directory}/${artifactId}-artifacts.dir/deploy/JettyDeployment.war</war>
                <contextPath>/</contextPath>
                <extraClasspath>${basedir}/src/deploy/resources/</extraClasspath>
              </contextHandler>
            </contextHandlers>
          </configuration>
          <executions>
            <execution>
              <id>start-jetty</id>
              <phase>install</phase>
              <goals>
                <goal>run</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
    <dependencies>
      <dependency>
        <groupId>com.ibm.j2ee</groupId>
        <artifactId>j2ee</artifactId>
        <scope>compile</scope>
      </dependency>
      <dependency>
        <groupId>nl.codecentric.web</groupId>
        <artifactId>JettyDeployment</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <type>war</type>
      </dependency>
    </dependencies>
  </profile>
</profiles>

As you see, the original web-app is deployed on the „/jetty/“ context path, while the JettyDeployment module is deployed under „/“ context path.

Also the deployment configuration for the web-app points to a custom „jetty-web.xml“. Here the servlet mapping is changed from „/*“ to „*.do“.
The assembly plugin is used here to get the JettyDeployment module war included, so it can also be deployed on the Jetty server. The configuration is pretty straight-forward:

<assembly>
  <id>artifacts</id>
  <formats>
    <format>dir</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <outputDirectory>deploy</outputDirectory>
      <outputFileNameMapping>${artifact.artifactId}.${artifact.extension}</outputFileNameMapping>
      <includes>
        <include>*:JettyDeployment</include>
      </includes>
    </dependencySet>
  </dependencySets>
</assembly>
[/code]
 
Then to the JettyDeployment module. The "web.xml" contains the following configuration to make sure the requests end-up in the correct servlet:
 
[code lang="xml"]
<servlet>
  <servlet-name>application</servlet-name>
  <servlet-class>nl.codecentric.jetty.ApplicationProxyServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
 
<servlet-mapping>
  <servlet-name>application</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

The code of the „ApplicationProxyServlet“ to route the requests correctly looks as follows;

public class ApplicationProxyServlet extends HttpServlet {
 
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doForward(req, resp);
	}
 
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doForward(req, resp);
	}
 
	private void doForward(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		System.err.println("Received path: " + req.getContextPath() + req.getPathInfo());
		final String dispatchPath = req.getPathInfo() + ".do";
		System.err.println("Dispatch path: " + dispatchPath);
		ServletContext context = getServletContext().getContext("/jetty" + req.getContextPath());
		System.err.println("Real path form context: " + context.getRealPath(dispatchPath));
		System.err.println("Default Servlet context name: " + getServletContext().getServletContextName());
		System.err.println("New Servlet context name: " +context.getServletContextName());
		RequestDispatcher dispatcher = context.getRequestDispatcher(dispatchPath);
		System.err.println(dispatcher.toString());
		dispatcher.forward(req, resp);
	}
 
}

As you see, it simply takes the request, adds „/jetty“ to the context path and appends „.do“ to the end of the request.

And finally the bat-file we created to quickly run the Maven build with Jetty (this shows debug-mode enabled):

@ECHO off
set MAVEN_OPTS_BACKUP=%MAVEN_OPTS%
set MAVEN_OPTS=
 
REM Build JettyDeployment module
cd JettyDeployment
call mvn -Dmaven.test.skip=true clean install
cd ..
 
REM Build and run web-app on Jetty
cd web-app-war
set MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9009,server=y,suspend=n"
call mvn -Dmaven.test.skip=true clean install -P jetty
 
cd ..
set MAVEN_OPTS=%MAVEN_OPTS_BACKUP%

Hope you enjoyed this posting. Let me know if you have any questions or remarks!

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.