Improving Web Development Productivity

If you’re doing Web development you know how painful it is to have to package your code in a WAR and redeploy it every time you want to try out a change you made to your HTML, JSP or servlet code. Fortunately, Maven can help. There are two plugins that can alleviate this problem: the Cargo plugin and the Jetty plugin. You’ll discover how to use the Jetty plugin in this section as you’ve already seen how to use the Cargo plugin in a previous section.

The Jetty plugin creates a custom Jetty configuration that is wired to your source tree. The plugin is configured by default to look for resource files in src/main/webapp, and it adds the compiled classes in target/classes to its execution classpath. The plugin monitors the source tree for changes, including the pom.xml file, the web.xml file, the src/main/webapp tree, the project dependencies and the compiled classes and classpath resources in target/classes. If any change is detected, the plugin reloads the Web application in Jetty.

A typical usage for this plugin is to develop the source code in your IDE and have the IDE configured to compile classes in target/classes (this is the default when the Maven IDE plugins are used to set up your IDE project). Thus any recompilation in your IDE will trigger a redeploy of your Web application in Jetty, providing an extremely fast turnaround time for development.

Let’s try the Jetty plugin on the DayTrader web module. The following has been added to the web/pom.xml file:

[...]
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-j2ee_1.4_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</plugin>
[...]/

The scanIntervalSeconds configuration property tells the plugin to monitor for changes every 10 seconds. The reason for the dependency on the J2EE specification JAR is because Jetty is a servlet engine and doesn’t provide the EJB specification JAR. Since the Web application earlier declared that the specification must be provided through the provided scope, adding this dependency to the plugin adds it to the classpath for Jetty.

You execute the Jetty plugin by typing mvn jetty:run:

C:\dev\m2book\code\j2ee\daytrader\web>mvn jetty:run
[…]
[INFO] [jetty:run]
[INFO] Configuring Jetty for project:
Apache Geronimo DayTrader Web Module
[INFO] Webapp source directory is: C:\dev\m2book\code\j2ee\daytrader\web\src\main\webapp
[INFO] web.xml file located at: C:\dev\m2book\code\j2ee\daytrader\
web\src\main\webapp\WEB-INF\web.xml
[INFO] Classes located at: C:\dev\m2book\code\j2ee\daytrader\
web\target\classes
[INFO] tmp dir for webapp will be C:\dev\m2book\code\j2ee\daytrader\web\target\jetty-tmp
[INFO] Starting Jetty Server ...
[INFO] No connectors configured, using defaults: org.mortbay.jetty.nio.SelectChannelConnector listening on 8080
with maxIdleTime 30000
0 [main] INFO org.mortbay.log - Logging to
org.slf4j.impl.SimpleLogger@1242b11 via org.mortbay.log.Slf4jLog
[INFO] Context path = /daytrader-web
[INFO] Webapp directory =
C:\dev\m2book\code\j2ee\daytrader\web\src\main\webapp
[INFO] Setting up classpath ...
[INFO] Finished setting up classpath
[INFO] Started configuring web.xml, resource base=
C:\dev\m2book\code\j2ee\daytrader\web\src\main\webapp
[INFO] Finished configuring web.xml
681 [main] INFO org.mortbay.log - Started SelectChannelConnector @ 0.0.0.0:8080
[INFO] Starting scanner at interval of 10 seconds.

As you can see, Maven pauses as Jetty is now started and may be stopped at anytime by simply typing Ctrl-C, but then the fun examples won’t work!. Your Web application has been deployed and the plugin is waiting, listening for changes. Open a browser with the http://localhost:8080/daytrader-web/register.jsp URL as shown in Figure 4-9 to see the Web application running.

Figure 4-9: DayTrader JSP registration page served by the Jetty plugin

Note that the application will fail if you open a page that calls EJBs. The reason is that we have only deployed the Web application here, but the EJBs and all the back end code has not been deployed. In order to make it work you’d need to have your EJB container started with the DayTrader code deployed in it. In practice it’s easier to deploy a full EAR as you’ll see below.

Now let’s try to modify the content of this JSP by changing the opening account balance. Edit web/src/main/webapp/register.jsp, search for “10000” and replace it with “90000” (a much better starting amount!):

<TD colspan="2" align="right">$<B> </B><INPUT size="20" type="text" 
name="money" value='<%= money==null ? "90000" : money %>'></TD>

Now refresh your browser (usually the F5 key) and the new value will appear as shown below.

Figure 4-10: Modified registration page automatically reflecting our source change

That’s nifty, isn’t it? What happened is that the Jetty plugin realized the page was changed and it redeployed the Web application automatically. The Jetty container automatically recompiled the JSP when the page was refreshed.

There are various configuration parameters available for the Jetty plugin such as the ability to define Connectors and Security realms. For example if you wanted to run Jetty on port 9090 with a user realm defined in etc/realm.properties, you would use:

<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
[...]
<connectors>
<connector implementation=
"org.mortbay.jetty.nio.SelectChannelConnector">
<port>9090</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<userRealms>
<userRealm implementation=
"org.mortbay.jetty.security.HashUserRealm">
<name>Test Realm</name>
<config>etc/realm.properties</config>
</userRealm>
</userRealms>
</configuration>
</plugin>

You can also configure the context under which your Web application is deployed by using the contextPath configuration element. By default, the plugin uses the module’s artifactId from the POM.

It’s also possible to pass in a jetty.xml configuration file using the jettyConfig configuration element. In that case anything in the jetty.xml file will be applied first. For a reference of all configuration options see the Jetty plugin documentation at http://jetty.mortbay.org/maven-plugin/index.html.

Now imagine that you have an awfully complex Web application generation process, that you have custom plugins that do all sorts of transformations to Web application resource files, possibly generating some files, and so on. The strategy above would not work as the Jetty plugin would not know about the custom actions that need to be executed to generate a valid Web application. Fortunately, there’s a solution.

The WAR plugin has an exploded goal which produces an expanded Web application in the target directory. Calling this goal ensures that the generated Web application is the correct one. The Jetty plugin also contains two goals that can be used in this situation:

  • jetty:run-war: The plugin first runs the package phase which generates the WAR file. Then the plugin deploys the WAR file to the Jetty server and it performs hot redeployments whenever the WAR is rebuilt (by calling mvn package from another window, for example) or when the pom.xml file is modified.
  • jetty:run-exploded: The plugin runs the package phase as with the jetty:run-war goal Then it deploys the unpacked Web application located in target/ (whereas the jetty:run-war goal deploys the WAR file). The plugin then watches the following files: WEB-INF/lib, WEB-INF/classes, WEB-INF/web.xml, and pom.xml; any change to those files results in a hot redeployment.

To demonstrate, execute mvn jetty:run-exploded goal on the web module:

C:\dev\m2book\code\j2ee\daytrader\web>mvn jetty:run-exploded
[...]
[INFO] [war:war]
[INFO] Exploding webapp...
[INFO] Copy webapp resources to C:\dev\m2book\code\j2ee\daytrader\web\target\daytrader-web-1.0
[INFO] Assembling webapp daytrader-web in C:\dev\m2book\code\j2ee\daytrader\web\target\daytrader-web-1.0
[INFO] Generating war C:\dev\m2book\code\j2ee\daytrader\web\target\daytrader-web-1.0.war
[INFO] Building war: C:\dev\m2book\code\j2ee\daytrader\web\target\daytrader-web-1.0.war
[INFO] [jetty:run-exploded]
[INFO] Configuring Jetty for project: DayTrader :: Web Application
[INFO] Starting Jetty Server ...
0 [main] INFO org.mortbay.log - Logging to org.slf4j.impl.SimpleLogger@78bc3b via org.mortbay.log.Slf4jLog
[INFO] Context path = /daytrader-web
2214 [main] INFO org.mortbay.log - Started SelectChannelConnector @ 0.0.0.0:8080
[INFO] Scanning ...
[INFO] Scan complete at Wed Feb 15 11:59:00 CET 2006
[INFO] Starting scanner at interval of 10 seconds.

As you can see the WAR is first assembled in the target directory and the Jetty plugin is now waiting for changes to happen. If you open another shell and run mvn package you’ll see the following in the first shell’s console:

[INFO] Scan complete at Wed Feb 15 12:02:31 CET 2006
[INFO] Calling scanner listeners ...
[INFO] Stopping webapp ...
[INFO] Reconfiguring webapp ...
[INFO] Restarting webapp ...
[INFO] Restart completed.
[INFO] Listeners completed.
[INFO] Scanning ...

You’re now ready for productive web development. No more excuses!