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
[...] <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> [...]/
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
C:devm2bookcodej2eedaytraderweb>mvn jetty:run […] [INFO] [jetty:run] [INFO] Configuring Jetty for project: Apache Geronimo DayTrader Web Module [INFO] Webapp source directory is: C:devm2bookcodej2eedaytraderwebsrcmainwebapp [INFO] web.xml file located at: C:devm2bookcodej2eedaytrader websrcmainwebappWEB-INFweb.xml [INFO] Classes located at: C:devm2bookcodej2eedaytrader webtargetclasses [INFO] tmp dir for webapp will be C:devm2bookcodej2eedaytraderwebtargetjetty-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:devm2bookcodej2eedaytraderwebsrcmainwebapp [INFO] Setting up classpath ... [INFO] Finished setting up classpath [INFO] Started configuring web.xml, resource base= C:devm2bookcodej2eedaytraderwebsrcmainwebapp [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 packagefrom another window, for example) or when the
pom.xmlfile is modified.
jetty:run-exploded: The plugin runs the package phase as with the
jetty:run-war goalThen it deploys the unpacked Web application located in
jetty:run-wargoal deploys the WAR file). The plugin then watches the following files:
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:devm2bookcodej2eedaytraderweb>mvn jetty:run-exploded [...] [INFO] [war:war] [INFO] Exploding webapp... [INFO] Copy webapp resources to C:devm2bookcodej2eedaytraderwebtargetdaytrader-web-1.0 [INFO] Assembling webapp daytrader-web in C:devm2bookcodej2eedaytraderwebtargetdaytrader-web-1.0 [INFO] Generating war C:devm2bookcodej2eedaytraderwebtargetdaytrader-web-1.0.war [INFO] Building war: C:devm2bookcodej2eedaytraderwebtargetdaytrader-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!