About this book

Managing dependencies in a multi-module project is difficult. In a multi-module project, libraries need to share transitive relations with each other. Maven eliminates this need by reading the project files of dependencies to figure out their inter-relations and other related information. Gaining an understanding of project dependencies will allow you to fully utilize Maven and use it to your advantage.

Aiming to give you a clear understanding of Maven’s functionality, this book focuses on specific case studies that shed light on highly useful Maven features which are often disregarded. The content of this book will help you to replace homebrew processes with more automated solutions.

This practical guide focuses on the variety of problems and issues which occur during the conception and development phase, with the aim of making dependency management as effortless and painless as possible. Throughout the course of this book, you will learn how to migrate from non-Maven projects to Maven, learn Maven best practices, and how to simplify the management of multiple projects. The book emphasizes the importance of projects as well as identifying and fixing potential conflicts before they become issues. The later sections of the book introduce you to the methods that you can use to increase your team’s productivity. This book is the perfect guide to help make you into a proud software craftsman.

Publication date:
October 2013
Publisher
Packt
Pages
158
ISBN
9781783283019

 

Chapter 1. Basic Dependency Management

In this chapter, we will recollect and/or explain the basics behind dependency management.

Throughout this work, we will assume that you know the basic notions about Maven, that is, repository, POM, plugin, goal, build, lifecycles, and so on.

 

Nomenclature


Let's review some basic notions related to Maven and dependencies.

Reminders on Maven origins

In late 1970s, Dr Stuart Feldman's make and makefiles allowed developers to order a build process, and then to build automatically a project.

In 2000, Sun released an equivalent of makefiles for Java platform, which is now known as Apache Ant. Ant used XML files to order and script the build.

Apache Maven was released in 2005 based on different concepts, it is aimed at automating the build. Ant was build oriented, Maven is project oriented; all Java projects may be considered as nodes and leaves of a huge dependency graph.

Dependency

What is a dependency? A dependency is another archive—JAR, ZIP, and so on—which your current project needs in order to compile, build, test, and/or to run. In other terms, a dependency is a file or a group of files that contain(s) the classes pointed at in your import clauses, and even more (think of an object that would be dynamically created, thanks to reflexivity).

From Maven's viewpoint, a project depends on other projects, and is depended on by other projects. Anyway, a project only need know which projects it does depend on; there is no burden to spread or inform the projects that depend on it.

The dependencies are gathered in the pom.xml file, inside of a <dependencies> tag.

On launching Maven on a project, either for a build or another goal, the dependencies are resolved, and are then loaded from the local repository; if they are not present there, then Maven will download them from a remote repository and store them in the local repository. Anyway, a manual installation of an archive remains possible.

Usually, the local repository is located at the home directory, for example, C:\Documents and Settings\myLogin\.m2\repository under Windows XP, C:\Users\myLogin\.m2\repository under Windows Vista/7/8, or ~/.m2/repository under Linux and Unix.

Tip

You can override the local repository folder. To perform that, you can add a tag <localRepository> within your settings.xml, for example:

<localRepository>/path/to/any/folder</localRepository>

Alternatively, you may dynamically specify a different folder, adding explicitly the destination folder as a runtime property on executing Maven, for example:

mvn clean \
-Dm2.localRepository=/path/to/another/folder/somewhere/else
 

Long and short designations


Maven standardizes the way to identify a dependency. So, each dependency is described by the following data:

  • groupId: A macro group or family of projects or archives to which a project belongs. Usually a same groupId gathers projects released by a same editor or that share a same functional domain. For example, junit, org.hibernate, and org.richfaces.ui.

  • artifactId: The unique identifier of the project among the projects sharing the same groupId. For example, junit, hibernate-annotations, and richfaces-components-ui.

  • version: This tag can have various values:

    • If no version is hinted, then Maven will use the last available artifact in the local repository.

    • The version of the project may be one of the following:

      1. A release version: A release version is "tagged", and can be considered as stable ; it shall not change anymore once published. For example,: 4.9, 3.3.1.GA, and 4.1.0.Final.

      2. A Snapshot version. Snapshots are artifacts still in development. They are expected to change and to be updated often. For example, SNAPSHOT, 1.0-SNAPSHOT (assumed to be released later as 1.0), and 1.2.3-SNAPSHOT (assumed to be released later as 1.2.3).

  • classifier: We will study this dependency in detail in Chapter 3, Dependency Designation (advanced).

  • type: We will study this dependency in detail in Chapter 3, Dependency Designation (advanced).

  • scope: We will study this dependency in detail in Chapter 3, Dependency Designation (advanced).

  • Other tags are available, we will review them later: exclusion, optional, and systemPath.

Long designation

So, basically, the preceding dependencies are equivalent to the following block in pom.xml:

     <dependencies>
         <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.9</version>
          </dependency>
          <dependency>
              <groupId>org.hibernate</groupId>
              <artifactId>hibernate-annotations</artifactId>
             <version>3.3.1.GA</version>
             <!-- ''GA'' stands for ''General Availability''-->
         </dependency>
         <dependency>
             <groupId>org.richfaces.ui</groupId>
             <artifactId>richfaces-components-ui</artifactId>
             <version>4.1.0.Final</version>
         </dependency>
     </dependencies>

Tip

You can specify a range of values instead of a unique one. This will detailed later.

If your pom.xml points to many artifacts of the same groupId (which is common with frameworks such as Spring, and Hibernate), you would rather use properties in order to factorize the code and be sure to upgrade consistently the group when needed:

In other terms, you should add a properties block, and then reference them. Taking the same example, you should get something similar to the following:

     <properties>
          <junit.version>4.9</junit.version>
          <hibernate.version>3.3.1.GA</hibernate.version>
          <richfaces.version>4.1.0.Final</richfaces.version>
      </properties>
  
     <dependencies>
          <dependency>
              <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>${junit.version}</version>
       </dependency>
         <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-annotations</artifactId>
             <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.richfaces.ui</groupId>
             <artifactId>richfaces-components-ui</artifactId>
            <version>${richfaces.version}</version>
        </dependency>
    </dependencies>

The classic XML block notation is called long designation.

Short designation

Long designation is verbose and fits only XML usage. This is why in other contexts, such as in logs, the short designation is preferred. It consists in collapsing the XML tree tags.

The preceding three dependencies are equivalent to the following:

  • junit:junit:jar:4.9:compile

  • org.hibernate:hibernate-annotations:jar:3.3.1.GA:compile

  • org.richfaces.ui:richfaces-components-ui:jar:4.1.0.Final:compile

You may have noticed that jar and compile were introduced; in a few words, they correspond to the default values for the tags type and scope.

 

Visualizing dependencies


Modern IDEs include many features to help visualize dependencies.

With the dependency plugin, Maven includes a goal to print the tree of dependencies. The dependency:tree, for example, will be given as follows:

/workarea/development/cartography/$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Cartography Application 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ cartography ---
[INFO] com.stepinfo.poc.cartography:cartography:jar:1.0-SNAPSHOT
[INFO] +- mockobjects:mockobjects-core:jar:0.09:test
[INFO] +- junit:junit:jar:4.9:test
[INFO] |  \- org.hamcrest:hamcrest-core:jar:1.1:test
[INFO] +- org.hibernate:hibernate-annotations:jar:3.3.1.GA:compile
[INFO] |  +- org.hibernate:hibernate:jar:3.2.6.ga:compile
[INFO] |  |  +- net.sf.ehcache:ehcache:jar:1.2.3:compile
[INFO] |  |  +- javax.transaction:jta:jar:1.0.1B:compile
[INFO] |  |  +- asm:asm-attrs:jar:1.5.3:compile
[INFO] |  |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.6:compile
[INFO] |  |  +- cglib:cglib:jar:2.1_3:compile
[INFO] |  |  +- asm:asm:jar:1.5.3:compile
[INFO] |  |  \- commons-collections:commons-collections:jar:2.1.1:compile
[INFO] |  +- org.hibernate:hibernate-commons-annotations:jar:3.0.0.ga:compile
[INFO] |  +- org.hibernate:ejb3-persistence:jar:1.0.1.GA:compile
[INFO] |  \- commons-logging:commons-logging:jar:1.0.4:compile
[INFO] +- org.springframework:spring-core:jar:3.1.0.RELEASE:compile
[INFO] |  \- org.springframework:spring-asm:jar:3.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-orm:jar:3.1.0.RELEASE:compile
[INFO] |  +- org.springframework:spring-beans:jar:3.1.0.RELEASE:compile
[INFO] |  +- org.springframework:spring-jdbc:jar:3.1.0.RELEASE:compile
[INFO] |  \- org.springframework:spring-tx:jar:3.1.0.RELEASE:compile
[INFO] |     +- aopalliance:aopalliance:jar:1.0:compile
[INFO] |     +- org.springframework:spring-aop:jar:3.1.0.RELEASE:compile
[INFO] |     \- org.springframework:spring-context:jar:3.1.0.RELEASE:compile
[INFO] |        \- org.springframework:spring-expression:jar:3.1.0.RELEASE:compile
[INFO] +- mysql:mysql-connector-java:jar:5.1.6:compile
[INFO] +- commons-lang:commons-lang:jar:2.3:compile
[INFO] \- log4j:log4j:jar:1.2.16:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.485s
[INFO] Finished at: Mon Jun 24 19:08:20 CEST 2013
[INFO] Final Memory: 5M/15M
[INFO] ------------------------------------------------------------------------

This tool will be very useful when we deal with conflicts of JARs.

 

Transitive dependencies


One of Maven's major contributions is the way it deals and manages not only direct dependencies, but also transitive ones.

The concept of transitivity

Dependencies are transitive. This means that if A depends on B and B depends on C, then A depends on both B and C. Theoretically, there is no limit to the depth of dependency. So, if you observe the following diagram of the tree of dependencies, you will notice that by transitivity, A depends on B, C, D, … until Z:

Even worse, we could have added a bit of complexity in mixing different versions of the same artifacts. In this very example with A as root project, B and C are level 1 or direct dependencies, D, E, J, and F are level 2 dependencies, C, G, H, and K are level 3, and so on.

You can imagine that the greater the level of dependencies, the more complex the situation is. The underlying issue of transitivity, you may guess, is when dependencies bear on the same groupId/artifactId but with different versions.

Resolution

Maven carries out the following algorithm to choose between two different versions:

  • Nearest first: A dependency of lower level has priority over another of the higher depth. Hence, a direct dependency has priority over a transitive dependency.

  • First found: At the same level, the first dependency that is found is taken.

This algorithm is known as dependency mediation.

Let's consider an example. The following diagram shows a dependency tree:

Here is the corresponding dependencies block in POM:

    <dependencies>
         <dependency>
              <groupId>directory</groupId>
              <artifactId>apacheds-core</artifactId>
              <version>0.9.3</version>
              <!--implicit dependency to commons-io:commons-io:1.0-->
          </dependency>
          <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-exec</artifactId>
             <version>2.9.7</version>
             <!--implicit dependency to commons-io:commons-io:1.4-->
         </dependency>
         <dependency>
             <groupId>org.apache.tapestry</groupId>
             <artifactId>tapestry-upload</artifactId>
             <version>5.3.7</version>
             <!--implicit dependency to commons-io:commons-io:2.0.1-->
         </dependency>
         <dependency>
             <groupId>com.googlecode.grep4j</groupId>
             <artifactId>grep4j</artifactId>
             <version>1.7.5</version>
             <!--implicit dependency to commons-io:commons-io:2.4-->
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
             <version>2.3</version>
         </dependency>

The commons-io-2.3 dependency is a dependency of level 1. So, even though it is declared after other artifacts and their transitive dependencies, then the dependency mediation will resolve commons-io to version 2.3. This case illustrates the concept of nearest first.

Now let's compare to a POM for which commons-io-2.3 has been deleted from level 1. The dependency tree shown in the following diagram:

All dependencies to commons-io are of level 2, and differ on the versions: 0.9.3 (via apacheds-core), 1.4 (via camel-exec), 2.0.1 (via tapestry-upload), and 2.4 (via grep4j). Unlike a popular belief, the resolution will not lead to take the greatest version number (that is, 2.4), but the first transitive version that appears in the dependency tree, in other terms 0.9.3.

Had another dependency been declared before apacheds-core, its embed version of commons-io would have been resolved instead of version 0.9.3. This case illustrates the concept of first found.

Exclusions

Let's consider the following example:

Our project needs JUnit-4.11, as well as DBUnit-2.4.9 and commons-collections-2.1. But the two latter depend on other versions of JUnit, respectively 2.3.0 and 3.2. Moreover, commons-collections depends on JUnit-3.8.1. Therefore, on building the project with goal test, we may encounter strange behaviors.

In this situation, you have to use an <exclusion> tag, in order to break the transitive dependency.

The POM will look similar to the following:

     <dependencies>
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>${junit.version}</version>
              <scope>test</scope>
          </dependency>
          <dependency>
             <groupId>org.dbunit</groupId>
             <artifactId>dbunit</artifactId>
             <version>${dbunit.version}</version>
             <scope>test</scope>
             <exclusions>
                 <!--Exclude transitive dependency to JUnit-3.8.2 -->
                 <exclusion>
                     <artifactId>junit</artifactId>
                     <groupId>junit</groupId>
                 </exclusion>
                <!--Exclude transitive dependency to Commons-Collections-3.1-->
                 <exclusion>
                     <artifactId>commons-collections
                     </artifactId>
                     <groupId>commons-collections</groupId>
                 </exclusion>
             </exclusions>
         </dependency>
         <dependency>
             <groupId>commons-collections</groupId>
             <artifactId>commons-collections</artifactId>
             <version>${commons-collections.version}
             </version>
             <exclusions>
                 <!--Exclude transitive dependency to JUnit-3.8.1 -->
                 <exclusion>
                     <artifactId>junit</artifactId>
                     <groupId>junit</groupId>
                 </exclusion>
             </exclusions>
         </dependency>
     </dependencies>

Most of the time, you will choose to exclude a transitive dependency for one of the following reasons:

  • Conflicts between versions of the same artifact of your project, such as preceding version.

  • Conflicts between artifacts of your project and artifacts from the platform of deployment, such as Tomcat or another server. For instance, if your project depends on wsdl4j-1.5.3 and is deployed on JBoss AS 7.1.1, then a conflict may appear with JBoss's dependency to wsdl4j-1.6.2.

  • In some cases, you do not want some of your dependencies to be exported within the archive you build (even though in this case, using a play on the dependency scope should be more elegant). The opposite case (when you need use your own dependencies and ignore the similar artifacts bundled with the server) will be exposed in Chapter 6, Release and Distribute.

Optional dependencies

The previous mechanism, based on exclusion tag, is in charge of the depending project to exclude unwanted dependencies.

Another mean exists to exclude transitive dependencies. This time, the charge lies on the project on which it is depended on. Maven provides the optional tag that takes a boolean value (true/false).

Let's consider the following example of dependencies:

Here are the corresponding POMs:

  • For project back, the POM is as follows:

     <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>
              com.packt.maven.dependency.optional
          </groupId>
          <artifactId>back</artifactId>
         <version>1.0-SNAPSHOT</version>
         <name>Example of POM which is depended onwith 'optional' at true
         </name>
         <packaging>jar</packaging>
     
         <!-- no dependency at all -->
     
     </project>
  • For project middle, the POM is as follows:

     <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>
              com.packt.maven.dependency.optional
          </groupId>
          <artifactId>middle</artifactId>
         <version>1.0-SNAPSHOT</version>
         <name>Example of POM with an optional dependency
         </name>
         <packaging>jar</packaging>
     
         <dependencies>
             <dependency>
                 <groupId>
                     com.packt.maven.dependency.optional
                 </groupId>
                 <artifactId>back</artifactId>
                 <version>1.0-SNAPSHOT</version>
                 <!-- The dependency to artifact 'back' is set at optional-->
                 <optional>true</optional>
             </dependency>
         </dependencies>
    
     </project>
  • For project front, the POM is as follows:

     <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>
              com.packt.maven.dependency.optional
          </groupId>
          <artifactId>front</artifactId>
         <version>1.0-SNAPSHOT</version>
         <name>Example of POM with scope import
             dependencyManagement of two artifactswith a version
             conflict because of transitive dependencies
         </name>
         <packaging>jar</packaging>
         <dependencies>
             <!-- Regular dependency ; 'front' dependson 'middle'-->
             <dependency>
                 <groupId>
                     com.packt.maven.dependency.optional
                 </groupId>
                 <artifactId>middle</artifactId>
                 <version>1.0-SNAPSHOT</version>
             </dependency>
         </dependencies>
     </project>

Now, we will see how to display the dependency trees. For middle, the tree is not different from what it would be, had the optional tag been set at false:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ middle ---
[INFO] com.packt.maven.dependency.optional:middle:jar:1.0-SNAPSHOT
[INFO] \- com.packt.maven.dependency.optional:back:jar:1.0-SNAPSHOT:compile

But for front, we get the following output:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ front ---
[INFO] com.packt.maven.dependency.optional:front:jar:1.0-SNAPSHOT
[INFO] \- com.packt.maven.dependency.optional:middle:jar:1.0-SNAPSHOT:compile

In other terms, middle has prevented its dependency to back to propagate transitively to other projects that depend on middle (among which front; but middle has no idea of front).

Had we removed the optional tag, we would have got that other trace:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ front ---
[INFO] com.packt.maven.dependency.optional:front:jar:1.0-SNAPSHOT
[INFO] \- com.packt.maven.dependency.optional:middle:jar:1.0-SNAPSHOT:compile
[INFO]    \- com.packt.maven.dependency.optional:back:jar:1.0-SNAPSHOT:compile

As a conclusion, exclusions and optional allow to break the chain of transitivity. This may be driven either by the depending project or by the one on which it is depended on.

 

Parents/modules


When the number of your projects rises, the need of factorization and rationalization arises. Two tools exist for this purpose: parent POMs and modules. Although both kinds are often merged, they belong to different registers.

Parent POM

Parent POMs, aka super POMs, offer a mechanism of inheritance. They allow you to factorize some data and constants, among which are the following few:

  • Common dependencies: In other terms, the artifacts that part or all of children POMs will depend on. Inscribing them in a parent POM has the same effect as writing them several times (and possibly having to update them manually on upgrading).

  • Properties, such as:

    • Plugin

    • Declarations

    • Executions and IDs

  • Configurations

  • Common data: Developers' names, SCM address, distribution management, and so on.

  • Resources

A parent POM, as a non archive target, will be declared with packaging pom; it is neither an archive nor expected to be distributed. It only references other projects.

So, a parent POM may look similar to the following:

  <?xml version="1.0" encoding="UTF-8"?>
  <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <!-- Either in a parent POM or in a independent POM,the triplet groupId/artifactId/version remain the uniqueway to identify a project-->
     <groupId>com.packt.dependencyManagement.chapter1
     </groupId>
     <artifactId>superPom</artifactId>
     <version>1.0</version>

     <!-- The full name of the project-->
     <name>Example of super POM: rationalizes properties,
         versions, plugins, etc.
     </name>
     <!-- The version of the parent POM ; notice it can orcannot be the same as the sons' versions -->
     <packaging>pom</packaging>

     <!-- These properties are common to all son POMs-->
     <properties>
         <project.build.sourceEncoding>UTF-8
         </project.build.sourceEncoding>
         <maven.compiler.source>1.6</maven.compiler.source>
         <maven.compiler.target>1.6</maven.compiler.target>
         <gwt.version>2.0.3</gwt.version>
         <!-- etc. -->
         <dbunit.version>2.4.8</dbunit.version>
         <!-- sonar config -->
         <sonar.jdbc.url>
             jdbc:mysql://localhost:3306/sonar?useUnicode=true
         </sonar.jdbc.url>
         <sonar.jdbc.driver>com.mysql.jdbc.Driver
         </sonar.jdbc.driver>
         <!-- etc. -->
     </properties>

     <!-- The Source Code Management -->
     <scm>
         <connection>
             scm:svn:http://localhost:8066/packt/branches/1.0
         </connection>
         <!-- etc. -->
     </scm>

     <!-- The distribution management -->
     <distributionManagement>
         <repository>
             <id>repoDistrib</id>
             <name>repoDistrib</name>
             <!-- Here we use a local repository -->
             <url>file:///C:/artifacts</url>
         </repository>
     </distributionManagement>
     <build>
         <plugins>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>sonar-maven-plugin</artifactId>
                 <version>2.0</version>
             </plugin>
             <!-- etc. -->
         </plugins>
     </build>
  </project>

As you can see, the data of the parent POM are not specific to one project; unlike, the data are shared by many other projects. Moreover, the scalability is swift: you can add any child project; this will not disturb the behavior of the parent POM.

The son project will refer to its parent with this header, that is, as usual by the triplet groupId/artifactId/version version.

  <project xmlns="http://maven.apache.org/POM/4.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <!--The identifier of the parent POM-->
      <parent>
          <groupId>com.packt.dependencyManagement.chapter1</groupId>
          <artifactId>superPom</artifactId>
          <version>2.6.0-SNAPSHOT</version>
      </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>sonPom</artifactId>
     <name>The first son project of the parent POM</name>
     <packaging>jar</packaging>
  </project>

Modules

Whereas parent POM provided a mechanism of inheritance, Maven modules provide a mechanism of aggregation, this means you can define groups of projects on which to run the same goal.

Let's consider the following pom.xml, and let's assume it is located in /anywhere/multimodule/ folder:

  <?xml version="1.0" encoding="UTF-8"?>
  <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.packt.dependencyManagement.chapter1</groupId>
     <artifactId>multimodule-simple</artifactId>
     <name>An example of multimodule POM</name>
     <packaging>pom</packaging>
     <version>1.0-SNAPSHOT</version>

     <modules>
         <module>multimodule-ear</module>
         <module>multimodule-war</module>
     </modules>
 </project>

Two modules are declared as many n-tiers architectures: the first for an EAR module, and a second one for a WAR module. On each and every goal that is executed on com.packt.maven.dependency:multimodule-simple:pom:1.0-SNAPSHOT, the same goal will be executed on the /anywhere/multimodule/multimodule-ear/pom.xml and /anywhere/multimodule/multimodule-war/pom.xml files.

For instance, the call (-f pom.xml is redundant, we write it only to be explicit) /anywhere/multimodule/ $ mvn clean install –f pom.xml is equivalent to the following:

/anywhere/multimodule/ $ cd multimodule-ear
/anywhere/multimodule/multimodule-ear/ $ mvn clean install –f pom.xml
/anywhere/multimodule/multimodule-ear/ $ cd ../multimodule-war
/anywhere/multimodule/multimodule-war/ $ mvn clean install –f pom.xml

The interest of modules appears when you combine them with profiles. Then, only the modules declared in the linked profile will be run.

Let's consider the following dependency tree:

There are three archives (EAR, WebApp, and Intranet), which depend on JARs (technical, services interface, web resources, and so on).

Sometimes you work only on the EAR, sometimes only on the Intranet; and sometimes you work on the three. With modules, you can define profiles of compilation. So, the pom.xml will look similar to the following (the code is self-documented):

  <?xml version="1.0" encoding="UTF-8"?>
  <project xmlns="http://maven.apache.org/POM/4.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.packt.dependencyManagement.chapter1
      </groupId>
      <artifactId>multimodule-withProfiles</artifactId>
      <name>Example multimodule POM using profiles</name>
     <packaging>pom</packaging>
     <version>1.2.3-SNAPSHOT</version>

     <profiles>
         <profile>
             <!-- In this profile, all submodules will be built ;
              please note the order of build: from the module withthe least dependencies, to the module with the most ones -->
             <id>all</id>
             <activation>
                 <!-- This profile is active by default,if no other is specified-->
                 <activeByDefault>true</activeByDefault>
             </activation>
             <modules>
                 <!--Submodules needed to build EAR-->
                 <module>technical</module>
                 <module>entities</module>
                 <module>services-interface</module>
                 <module>Ldap</module>
                 <module>DAO</module>
                 <module>Services-implementation</module>
                 <!--Now EAR can be built-->
                 <module>EAR</module>

                 <!--Submodules needed to build WebApp-->
                 <module>Web-resources</module>
                 <!-- Services-implementation and Entitieshave already been built,
                   we do not mention them here-->
                 <module>JSF-utilities</module>
                 <module>WebApp</module>

                 <!--Submodules needed to build Intranet-->
                 <module>Intranet-resources</module>
                 <!-- Services-implementation and Entities have already been built,
                   we do not mention them here-->
                 <module>GWT-utilities</module>
                 <module>Intranet</module>
             </modules>
         </profile>
         <profile>
             <!--In this profile, we build only artifactswhich EAR depends on, then EAR-->
             <id>EAR</id>
             <modules>
                 <module>technical</module>
                 <module>entities</module>
                 <module>services-interface</module>
                 <module>Ldap</module>
                 <module>DAO</module>
                 <module>Services-implementation</module>
                 <module>EAR</module>
             </modules>
             <activation>
                 <!-- this profile is not active by default : therefore, it will not be calledunless explicitly specified -->
                 <activeByDefault>false</activeByDefault>
             </activation>
         </profile>
         <profile>
             <!--In this profile, we build only artifacts which WebApp depends on, then WebApp -->
             <id>WebApp</id>
             <modules>
                 <!-- We need add Entities and Services-interface,because unlike above they have not been build-->
                 <module>entities</module>
                 <module>services-interface</module>
                 <module>Web-resources</module>
                 <module>JSF-utilities</module>
                 <module>WebApp</module>
             </modules>
         </profile>
         <profile>
             <!--In this profile, we build only artifactswhich Intranet depends on, then Intranet -->
             <id>Intranet</id>
             <modules>
                 <!-- We need add Entities and Services-interface,because unlike above they have not been build-->
                 <module>entities</module>
                 <module>services-interface</module>
                 <module>Intranet-resources</module>
                 <module>GWT-utilities</module>
                 <module>Intranet</module>
             </modules>
         </profile>
     </profiles>

  </project>

A call to /anywhere/multimodule-withProfiles/ $ mvn clean install –f pom.xml is equivalent to the following:

/anywhere/multimodule-withProfiles/ $ mvn clean install –f pom.xml -Pall

But you can decide to build only EAR using the following:

/anywhere/multimodule-withProfiles/ $ mvn clean install –f pom.xml -PEAR

You can build only the Intranet using the following:

/anywhere/multimodule-withProfiles/ $ mvn clean install –f pom.xml –Pintranet

You can also build with two profiles, for instance EAR and WebApp:

/anywhere/multimodule-withProfiles/ $ mvn clean install \
                                     –f pom.xml \
                                     –PEAR,WebApp

In a further chapter, we will dive into module mechanisms.

Tip

Most of the time, module descriptions are gathered within the parent POM.

 

Version ranges


Earlier, we have seen an artifact is described by the groupId/artifactId/version triplet. Actually, you can specify not only a version number, but also a range of versions.

The grammatical meaning of the mathematical signs is as follows:

  • Parenthesis signs ( and ) hint an including range

  • Brackets signs [ and ] hint an excluding range

  • Commas separate subsets

The following table explains the grammatical meaning of a few ranges:

Range

Meaning

1.2

Version equals to 1.2 or is starting with 1.2

[1.2]

Version strictly equal to 1.2

(,1.2]

Anything less than 1.2, included

(,1.2)

Anything less than 1.2, excluded

[1.2, )

Anything greater than 1.2, included

(1.2,)

Anything greater than 1.2, excluded

(1.2,3.4)

Anything between 1.2 and 3.4, both excluded

(1.2,3.4]

Anything between 1.2 (excluded) and 3.4 (included)

[1.2,3.4)

Anything between 1.2 (included) and 3.4 (excluded)

[1.2,3.4]

Anything between 1.2 and 3.4, both included

(,1.2],[3.4,)

Anything less that 1.2 (included) or greater than 3.4 (included)

(,1.2),(1.2,)

Anything except 1.2

The following are few meaningful examples, which will help you understand the ranges in a better way:

Range

Versions that will be accepted

Versions that will not be accepted

1.2

1.2, 1.2.0, 1.2.3-GA, 1.2.3-Final

1.1, 3.4, Foo-1.2

[1.2]

1.2

1.2.0, 1.2.3-GA, 1.2.3-Final, 1.1, 3.4, Foo-1.2

(,1.2]

0.1, 1.0, 1.1.1, 1.2

1.3, 3.4

(,1.2)

0.1, 1.0, 1.1.1

1.2, 1.3, 3.4

[1.2, )

1.2, 1.3, 3.4

0.1, 1.0, 1.1.1

(1.2,)

1.3, 3.4

0.1, 1.0, 1.1.1, 1.2

(1.2,3.4)

1.2.1, 1.3, 3.3.9

1.1.8, 1.2, 3.4, 3.4.1

(1.2,3.4]

1.2.1, 1.3, 3.3.9, 3.4

1.1.8, 1.2, 3.4.1

[1.2,3.4)

1.2, 1.2.1, 1.3, 3.3.9

1.1.8, 3.4, 3.4.1

[1.2,3.4]

1.2, 1.2.1, 1.3, 3.3.9, 3.4

1.1.8, 3.4.1

(,1.2],[3.4,)

1.1.9, 1.2, 3.4, 3.5

1.3, 3.0

(,1.2),(1.2,)

1.1, 1.3, 3.4

1.2

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

 

Summary


In this opening chapter, we learned or revised different concepts. We are henceforth able to:

  • Define a dependency and designations, whether short or long

  • Describe the mechanism of dependency mediation

  • Define parent POMs with different submodules

  • Define different version ranges

About the Author

  • Jonathan LALOU

    Jonathan LALOU is an engineer fascinated by new technologies, computer sciences, and the digital world since his childhood. A graduate of the Ecole des Mines—one of the best French polytechnic institutes—Jonathan has more than 14 years of experience in Java and the JEE ecosystem.

    Jonathan has worked for several global companies and financial institutions, such as Syred, Philips, Sungard, Ixis CIB, BNP Paribas, and Amundi AM. He has strong ties, daily contacts, and frequent trips in Western Europe, Northern America, Judea, and emerging Asia. During his career, Jonathan has successfully climbed many levels: developer, architect, Scrum master, team leader, and project manager.

    Now, Jonathan is CTO at SayaSoft (http://www.sayasoft.fr), a digital company focused on very high value added projects he founded with two partners. SayaSoft brings Java environment, migration of small and large organizations to agility, and Android development to a new level. SayaSoft's customers are ensured to get high-quality releases and quick ROI.

    Jonathan's skills include a wide range of technologies and frameworks, such as Spring, JPA/Hibernate, GWT, Mule ESB, JSF/PrimeFaces, Groovy, Android, EJB, JMS, application servers, agile methods, and, of course, Apache Maven.

    Jonathan also authored Apache Maven Dependency Management, published by Packt Publishing in October 2013.

    Jonathan is available on the cloud. You can read his blog at http://jonathan.lalou.free.fr, catch him on Twitter at http://twitter.com/john_the_cowboy, and find him on LinkedIn at http://www.linkedin.com/in/jonathanlalou.

    Browse publications by this author
Book Title
Unlock this full book FREE 10 day trial
Start Free Trial