The need for a custom Maven build lifecycle

A standard Maven 2.0 project provides 3 fixed build "lifecycles" called clean, default and site.
See also: Introduction to the Build Lifecycle" on the Maven website.

These standard build lifecycles all focus on, and only on, the project artifacts themselves. This means the build lifecycle and all the possible configurations thereof really concerns the final artifacts and their metadata only, NOT the (target) environment where it is intended to be used.

The final phase of the default lifecycle, deploy, concerns the deployment of project artifacts to a remote Maven repository. The intended usage of deployed artifacts comes only after this phase, and is in general out-of-scope from the POV of Maven itself, except when an artifact itself is used as dependency by another Maven project. (which actually is the most common, "expected" use-case).

For the default and most common Maven artifact type, jar, this is perfectly fine, but for application server integration frameworks like Jetspeed-2 Portal, it is NOT.

In contrast to more generic low-level (development) frameworks like Springframework, Jetspeed-2 comes with a very specific set of components, usually assembled and configured only on integration level. While Jetspeed-2 itself is assembled and configured using Maven-2 and Springframework on a very low-level detail, it should require only a few specific configurations (and provide room for extensions) to adapt and integrate for a custom project and its environment.

These custom project and enviroment specific changes/overrides and extensions should be configured separately and independently from the base Jetspeed-2 Portal configuration to support clean and clear maintainenance of the custom project lifecycle itself.

For these requirements, the standard provided Maven build lifecycles and extension points are (currently) falling a bit short.

Ways to customize the Maven build lifecycle

There are many ways to customize the Maven build lifecycle:

  1. Using (only) profiles
    Profiles within a Maven project (pom.xml) file allows to augment or replace default lifecycle behavior, but maintains the predefined lifecycle phase handling. And, because the default lifecycle already configures standard behavior, trying to achieve something very different from this standard behavior (like deploying and configuring a complete Portal application for a specific application server) quickly becomes very unwielding and difficult to setup and maintain (if properly doable at all).

  2. Using "standalone" plugins
    An alternative is writing and/or leveraging so called "standalone" Maven plugins which run outside a lifecycle. These plugins don't affect or are affected by the default lifecycle. But... with the current Maven 2.0 model they also only can have a single configuration within a project (pom.xml) file. If many different tasks (and possible different environments) are needed, the configuration quickly becomes very large and complex and/or requires splitting it up into multiple profile definitions. But then, the complex configuration is only replaced by more complex and error prone (manual) build instructions for the end user.

    Furthermore, as these plugins run outside any lifecycle, none of the standard Maven plugins can be leveraged (nor directly "invoked" which isn't supported by Maven). Often needed standard functionality like the copying/filtering of resource files will have to be provided by the standalone plugin itself too!

    Finally, while a standalone plugin can "fork" off a default lifecycle (phase), this also means (potentially) all the standard provided lifecycle behavior is executed too again, leading to the same problems as with the previous option (see: above).

  3. Adding additional dedicated sub projects
    This is the "standard" Maven-2 solution if multiple "artifacts" are needed for a certain project: split the project up in additional sub projects, each providing its own "dedicated" result (note: with Maven-1 it was easy and common practice to write custom jelly "goals" in maven.xml for this purpose).

    While this is certainly a good solution, and allows to leverage the full Maven-2 feature set, it has several caveats too.

    First of all, it can clutter the project structure quite a bit when many different "tasks" are required. And, as these "tasks" usually are not meant to be run automatically during the normal build lifecycle, they should not be configured as sub modules of a parent project but then require manual navigation to and invocation of the developer. Using (again) multiple profiles (see above) to "merge" several of these tasks in one common project (pom) file can "automate" this, but then requires the developer to know and specify each needed (combination of) profile(s) to execute.

    Additionally, while maybe "good" practice by itself, it usually requires all artifacts and resources needed for such a dedicated "task" sub project are build and installed in the local or a remote Maven repository first. This potentially adds yet more complexity and redundance to a project configuration when such resources already are available locally within the context of another (sub) project anyway.

  4. Using custom project (pom.xml) files
    Maven-2 allows execution of non-default (pom.xml) project files using a command line option (-f <project file>).

    This solution elevates some of the caveats of the previous solution: there is no need to split up a project into several sub projects as a custom "task oriented" project file can directly access the project local resources.

    On the other hand, this solution requires using additional command line parameters and thus again more effort from the developer.

  5. Using a custom lifecycle extension
    Finally, it is possible to replace the default lifecycle of a project (pom.xml) file alltogether by using a custom <packaging> type.

    Such a custom lifecycle definition must be provided through a custom plugin extension and specifically be configured within the project file itself. And a custom lifecycle extension will have to provide the complete definition of all the intended behavior. If that includes "old" standard behavior (like building and installing a war) such a solution quickly falls down to the first solution discussed above, although it allows more (but still fixed) flexibility and choices for configuration.

    Other caveats of this solution are that probably multiple custom lifecycles will be required (e.g. different ones for pom,war,jar,ear etc.), and that (future) default IDE tooling support will be very unlikely.

Of the above solution, solution 2 and 5 are the most difficult to maintain and use. Solution 3 or 4, possibly in combination with solution 1, are more in line with the intended usage of Maven-2, but do have as major caveat that they require more complex manual (commandline) usage instructions.

What really is missing here is automation: scripting/configuration support for the different tasks and commandline parameters as needed for a specific custom project and its target environment(s).

While writing custom shell scripts, or maybe even using a Apache Ant script, to automate the different Maven-2 commands is very well doable, it will easily result in every project having its own custom (and thus different) way of doing things. And, as it will depend on yet another build tool, environment incompatibility issues (e.g. bash scripts usually don't run on Windows) easily can make this even more complex to maintain.

The jetspeed:mvn Maven Plugin - Automating Maven tasks using Maven

To "solve" these problems, the Jetspeed-2 provided solution is a custom Maven plugin, jetspeed:mvn, to automate Maven-2 execution from within Maven itself, using a configuration within a (parent) project file very similar to Apache Ant target definitions.

The jetspeed:mvn plugin is a standalone plugin (see discussion above), but requires only a very straightforward configuration and only one additional commandline parameter to be executed.

An example commandline usage and a part of the (simplified) configuration as used by the Jetspeed-2 project itself to build, install, configure a database and finally deploy a full Jetspeed Demo Portal is:

$mvn jetspeed:mvn -Dtarget=demo
<plugin>
  <groupId>org.apache.portals.jetspeed-2</groupId>
  <artifactId>jetspeed-mvn-maven-plugin</artifactId>
  <version>${org.apache.portals.jetspeed.version}</version>
  <configuration>
    <targets combine.children="append">
      <target>
        <id>demo-install</id>
        <dir>@rootdir@/applications/jetspeed-demo</dir>
      </target>
      <target>
        <id>proddb</id>
        <name>db-init</name>
        <properties>
          <database.type>production</database.type>
        </properties>
      </target>
      <target>
        <id>demo-seed</id>
        <name>demo</name>
        <dir>@rootdir@/applications/jetspeed-demo</dir>
        <profiles>seed</profiles>
      </target>
      <target>
        <id>demo-db</id>
        <depends>proddb,demo-seed</depends>
      </target>
      <target>
        <id>demo-deploy</id>
        <name>demo</name>
        <dir>@rootdir@/applications/jetspeed-demo</dir>
        <profiles>deploy</profiles>
      </target>
      <target>
        <id>demo</id>
        <depends>demo-install,demo-db,demo-deploy</depends>
      </target>
    </targets>
  </configuration>
</plugin>

The above example configuration shows several target "tasks" which can optionally "depend" on other targets (tasks) which will automatically be executed first. In addition, specific properties, profiles and goals can be also be defined which will be passed on to the Maven execution environment. Furthermore, the location of another (possibly custom) project file to execute, and even a custom maven user settings file, can also be specified and used, allowing full control of the target Maven execution environment.

The jetspeed:mvn plugin is based upon and adapted from the standard Maven Invoker Plugin, which is (only) targetted at running integration test projects as attached to the integration-test lifecycle phase.

The jetspeed:mvn Plugin expands upon the standard Invoker Plugin by allowing to be invoked directly from the commandline and by providing a generic and configurable chaining of execution targets. In a way this is similar to Apache Ant build scripts, but using the standard Maven-2 project build lifecycle handling for the actually execution of the individual target tasks.

Full description and configuration definition for the jetspeed:mvn plugin is provided on the Mvn Plugin page and detailed usages are given on the Building Jetspeed menu pages.