Cargo + Maven + Two Tomcats

Recently I've been struggling to make Maven execute two Tomcat instances (with different applications on different ports). I decided to put a solution here so you don't have to discover it by yourself.

Goal

The goal was to run two web applications on different ports before integration tests, and once the tests are finished, to stop them.

IMPORTANT If you do not need to run your webapps on different ports than this post is not for you. Simply put more then one webapp under <deployables> of a single Tomcat instance.

Tools

Maven, Maven Cargo Plugin and Tomcat.

Solution

This is how the first application is started:

<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<executions>
<execution>
<id>start-first-app</id>
<phase>pre-integration-test</phase>
<configuration>
<container>
<containerId>tomcat7x</containerId>
<zipUrlInstaller>
<url>file:resources/${tomcat.zip.file}</url>
<downloadDir>${project.basedir}/resources</downloadDir>
<extractDir>${project.build.directory}/extracts</extractDir>
</zipUrlInstaller>
<systemProperties>
...
</systemProperties>
<log>${project.build.directory}/first_app.log</log>
</container>
<configuration>
<properties>
<cargo.servlet.port>${first_app.server.port}</cargo.servlet.port>
<cargo.tomcat.ajp.port>${first_app.ajp.port}</cargo.tomcat.ajp.port>
<cargo.rmi.port>${first_app.rmi.port}</cargo.rmi.port>
</properties>
<home>${project.build.directory}/tomcat_first_app</home>
</configuration>
<deployables>
<deployable>
<properties>
<context>ROOT</context>
</properties>
</deployable>
</deployables>
</configuration>
<goals>
<goal>start</goal>
</goals>
</execution>

Things to notice:

  • containerId must be equal to one of the containers supported by Cargo - see here for the full list
  • zipUrlInstaller - I keep zipped Tomcat in SVN
  • systemProperties - yeah, you can pass some to the Tomcat if you wish (your application can make use of them)
  • log - optional. If not set all logs will go to standard output. In case of more than one web app running, it is sometimes more convenient to have separate logs.
  • configuration properties - this is crucial! Every instance of Tomcat you plan to start during your build must have unique cargo.servlet.port and cargo.rmi.port. Not sure about the cargo.tomcat.ajp.port but I added it just in case.
  • home - this is where your Tomcat will be installed.
  • context - this is how you make your application appear under e.g. http://localhost:8080 instead of http://localhost:8080/myapp
  • deployable - no groupId or artifactId mentioned here because we deploy the application from this project (which is of type war)

Now the configuration for stopping of the first application

<execution>
<id>stop-first-app</id>
<phase>verify</phase>
<configuration>
<container>
<containerId>tomcat7x</containerId>
</container>
<configuration>
<properties>
<cargo.servlet.port>${first_app.server.port}</cargo.servlet.port>
</properties>
<home>${project.build.directory}/tomcat_first_app</home>
</configuration>
</configuration>
<goals>
<goal>stop</goal>
</goals>
</execution>

Three things are identical to the start configuration

  • containerId
  • cargo.servlet.port
  • home

Please notice the phases that were used so far: pre-integration-test and verify. For the second application we have to use different phases because Maven for some reasons does not allow the same plugin to be run twice during the same lifecycle phase.

Ok, let us start the second application:

<execution>
<id>start-second-app</id>
<phase>package</phase>
<configuration>
<container>
<containerId>tomcat7x</containerId>
<zipUrlInstaller>
<url>file:resources/${tomcat.zip.file}</url>
<downloadDir>${project.basedir}/resources</downloadDir>
<extractDir>${project.build.directory}/extracts</extractDir>
</zipUrlInstaller>
<systemProperties>
...
</systemProperties>
<log>${project.build.directory}/second_app.log</log>
</container>
<configuration>
<properties>
<cargo.servlet.port>${second_app.server.port}</cargo.servlet.port>
<cargo.tomcat.ajp.port>${second_app.ajp.port}</cargo.tomcat.ajp.port>
<cargo.rmi.port>${second_app.rmi.port}</cargo.rmi.port>
</properties>
<home>${project.build.directory}/tomcat_second_app</home>
</configuration>
<deployer />
<deployables>
<deployable>
<groupId>some.groupId</groupId>
<artifactId>some.artifact</artifactId>
<type>war</type>
<properties>
<context>ROOT</context>
</properties>
</deployable>
</deployables>
</configuration>
<goals>
<goal>start</goal>
</goals>
</execution>

Nothing new here. Some elements are different though:

  • phase - it is pacakge this time - different than previously
  • log - if we specify log files it makes sense to make them different (but remember, this is optional)
  • properties - servlet, rmi and ajp ports - if they do not differ you will encounter errors
  • home - yeah, definitely we need a different directory for this second Tomcat
  • deployer - this one is tricky - if you do not specify it, cargo will deploy the current application! (provided that it is of type war or ear)
  • deployable - this is a different application (I haven't shown it but you also need to add a dependency for the same artifact - can be with the test scope.

And now stopping it. There is nothing new here. Notice the different phase (post-integration-test)!

<execution>
<id>stop-second-app</id>
<phase>post-integration-test</phase>
<configuration>
<container>
<containerId>tomcat7x</containerId>
</container>
<configuration>
<properties>
<cargo.servlet.port>${second_app.server.port}</cargo.servlet.port>
</properties>
<home>${project.build.directory}/tomcat_second_app</home>
</configuration>
</configuration>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>

Well, that is it. Good luck with your Maven/Cargo development.

P.S. If you have questions please ask them on appropriate mailing lists.

Please comment using