User Tools

Site Tools


maven

Introduction

Maven is yet another build automation tool, comparable to e.g. ant; check the project homepage and the wikipedia page.

Characteristics

  • convention over configuration
  • tries to enforce best practices and standards
  • tries to enforce clean dependency management.

Directory layout of a maven project

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.company.project.*
│   │   └── resources
│   └── test
│       ├── java
│       │   └── com.company.project.*
│       └── resources
└── target

Project Object Model (POM)

The POM is the heart of all Maven projects. It is an XML file containing all the information and configuration details required for a project.

Example for a minimal pom.xml

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0.0</version>
</project>

The default packaing type is jar. To build a war, just add:

  <packaging>war</packaging>

For more details go over there or there.

Basics

Creating a project

mvn archetype:generate # pick type and answer basic questions

This creates the pom.xml and a basic directory structure.

If you want to create it by hand instead:

mkdir -p src/main/{java,resources}
mkdir -p src/test/{java,resources}
touch pom.xml

Building

mvn verify # build, test, package, integration-test and verify 

see http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

Local Install

Install your project to the local repository

mvn install

Deploy to Maven repository

Configure the target repository in pom.xml:

  <distributionManagement>
 
    <repository>
      <id>internal.repo</id>
      <name>Internal Releases</name>
      <url>http://192.168.1.10:8081/nexus/content/repositories/releases/</url>
    </repository>
 
    <snapshotRepository>
        <id>internal.repo</id>
        <name>Internal Snapshots</name>
        <url>http://192.168.1.10:8081/nexus/content/repositories/snapshots/</url>
    </snapshotRepository>
 
  </distributionManagement>

Dont forget to set your credentials in ~/.m2/settings.xml :

<settings>
<servers>
  <server>
    <id>internal.repo</id>
    <username>deployment</username>
    <password>deployment123</password>
  </server>
</servers>
</settings>

Deploy to the repository:

mvn deploy

Deploy to JBoss

Note: replace /opt/jboss with /your/path/to/jboss and also make sure to use the correct server IP.

JBoss / WildFly

This works about the same since JBoss 7 up to (currently) WildFly 10

Server Preparations

If you only want to deploy to localhost no preparation is required. Just start the server.

To connect to a remove server the management interface must be bound to an interface. This can be achieved with the startup parameter -bmanagement. In the example webservices are bound 0.0.0.0, which means everywhere (the opposite of the default, which is localhost only).

/opt/jboss/bin/standalone.sh -b 0.0.0.0 -bmanagement your.server.ip.address

Additionally you should create a management user by using this script (changes /opt/jboss/standalone|domain/configuration/mgmt-users.properties):

/opt/jboss/bin/add-user.sh

JBoss 7: jboss-as-maven-plugin

Add the jboss-as-maven-plugin to your pom.xml and also configure the management user you just created.

<plugin>
	<groupId>org.jboss.as.plugins</groupId>
	<artifactId>jboss-as-maven-plugin</artifactId>
	<version>7.3.Final</version>
	<configuration>
		<username>user</username>
		<password>password</password>
	</configuration>
</plugin>

Then you are ready to rumble:

  mvn clean package
  # local deployment
  mvn jboss-as:deploy
  # remote deployment
  # this seems to be buggy: if a hostname is specified in pom.xml it will be ignored at the command line!
  mvn -Djboss-as.hostname=your.server.ip.address jboss-as:deploy
 
  # undeploy
  mvn jboss-as:undeploy

Note, that you will not find the deployment as expected in /opt/jboss/standalone/deployments, but it is hidden away in a zip archive named with a sha1-hash in /opt/jboss/standalone/data/content - to avoid manual tampering. You can see and manage (disable / undeploy) all deployments in the management console on port 9990 though (e.g. http://localhost:9990).

Actually an entry is added to the end of /opt/jboss/standalone/configuration/standalone.xml for each deployment:

<deployments>
  <deployment name="my.war" runtime-name="my.war">
    <content sha1="30e075d8bf475021ecf82a2979a89c202dd31044"/>
  </deployment>
</deployments>

Full documentation: https://docs.jboss.org/jbossas/7/plugins/maven/latest/index.html

WildFly 8 and higher: wildfly-maven-plugin

Basically this works the same as the old jboss plugin.

Documentation: https://docs.jboss.org/wildfly/plugins/maven/latest/

Older JBoss Versions

To deploy via JMX over HTTP

  mvn jboss:deploy 

To copy the file directly to the deploy directory of a local server:

  export JBOSS_HOME=/opt/jboss
  mvn jboss:hard-deploy

Maven + Eclipse

  • Don't check in the Eclipse-specific config files. Maven can generate them for you!
  • svn propedit svn:ignore .
  • add one line for .project .classpath and .settings each

Eclipse plugins

Checking out a Maven project from SCM

Using m2e Import

With m2e installed

File –> Import.. –> Maven –> Project from SCM

should work. But the Plugin does not work very well with the Eclipse Team provider (in Eclipse Juno): http://stackoverflow.com/questions/6729511/checkout-maven-project-from-scm-no-connectors

Workaround

With the m2e Plugin installed.

1. Check out the project using normal Eclipse Team provider. The project will not function as java project just yet.

2. Close the project. Go to console and enter

mvn eclipse:clean
mvn eclipse:eclipse

3. Re-open the project. Right click the project:

Configure.. –> Convert to Maven project (ignore the errors).

4. Close the project. Go to console and

rm .classpath

5. Re-open the project. Right click the project:

Maven –> Update Project

Convert Eclipse project to Maven

Remove Eclipse artefacts

  • .classpath
  • .settings
  • .project

Close the project. Go to console and enter

mvn eclipse:clean
mvn eclipse:eclipse

Re-open the project. Right click the project:

Configure.. –> Convert to Maven project (ignore the errors).

Close the project. Go to console and

rm .classpath

Re-open the project. Right click the project:

Maven –> Update Project

Be sure to set all Maven dependencies for your project.

Create new JBoss Service

Create a pom.xml by adapting an already existing one (by hand)

Create the src folder hierarchy by hand:

mkdir -p src/main/java
mkdir -p src/main/resources
mkdir -p src/test/java
mkdir -p src/test/resources

Then import the pom.xml into Eclipse as described above. If you Eclipse has a full installation of JBoss Tools (tested with Eclipse Indigo) the created project will be a web project and can be dragged and dropped to servers within Eclipse.

Testing

Integration Tests

Often you have (hopefully) fast running unit tests and slower running integration tests. Maven provides two different goals to cleanly seperate these two:

  • Unit Tests
    • are fast
    • are run using the maven-surefire-plugin during the goal “test”
    • Test classes need to be located in src/test/java/
    • Class names need to end in “Test” (eg. RouterTest.java )
    • run with mvn package
  • Integration Tests
    • may be slow
    • are run by the maven-failsafe-plugin during the goal “integration-test”
    • Test classes also need to be located in src/test/java/
    • Class names need to end in “IT” (eg. RouterIT.java)
    • run with mvn verify

Setting up Integration Tests

  • Write a JUnit Integration Test (class name must end in “IT”)
  • Configure the maven-failsafe-plugin in pom.xml
<build>
 
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.12.3</version>
     <executions>
       <execution>
         <goals>
           <goal>integration-test</goal>
           <goal>verify</goal>
         </goals>
       </execution>
     </executions>
  </plugin>
 
</build>
  • Run integration tests with mvn verify

Building an executable jar

Use the maven-shade-plugin to build an uber-jar with mvn package.

Note, that you have to care about shadowing problems yourself and handle them with transformers, e.g. use an AppendingTransformer to prevent SQLite and PostgreSQL overwriting META-INF/services/java.sql.Driver.

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>2.4.1</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>shade</goal>
          </goals>
          <configuration>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>my.package.Main</mainClass>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/services/java.sql.Driver</resource>
              </transformer>
            </transformers>
            <!-- in case a signed jar is contained in the dependences -->
            <filters>
              <filter>
                <artifact>*:*</artifact>
                <excludes>
                  <exclude>META-INF/*.SF</exclude>
                  <exclude>META-INF/*.DSA</exclude>
                  <exclude>META-INF/*.RSA</exclude>
                </excludes>
              </filter>
            </filters>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

The hint on dealing with signed jars is from http://zhentao-li.blogspot.co.at/2012/06/maven-shade-plugin-invalid-signature.html.

The deprecated way is to use the maven-assembly-plugin:

<build>
  <plugins>
 
  <!-- Package as Executable jar -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>2.2.1</version>
 
      <configuration>
        <appendAssemblyId>true</appendAssemblyId>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
          <manifest>
            <mainClass>com.company.my.MainClass</mainClass>
          </manifest>
        </archive>
      </configuration>
 
      <!-- To automatically build the jar-with-dependencies 
      during the package phase, add the next section -->
      <executions>
        <execution>
          <id>package-jar-with-dependencies</id>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
        </execution>
      </executions>
 
    </plugin>                
 
  </plugins>
</build>

If the execution goal is not enabled, the executable jar can be built manually using

mvn clean verify assembly:single # must be all in one command. otherwise it does not work. no idea why.

Dependency Management

Declaring Dependencies

In your pom.xml

  <dependencies>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.10</version>
                <scope>test</scope>
  	</dependency>
  </dependencies>

Dependencies in an internal repository

Assuming your company builds a library which is available from an internal maven repository you can specify that dependency same as above, but you also have to tell maven about your local repo. There are two ways to do this

* Enter the repository in your pom.xml

<repositories>
	<repository>
		<id>local-maven-repo</id>
		<url>http://192.168.1.10:8081/nexus/content/groups/public/</url>
		<snapshots>
			<updatePolicy>always</updatePolicy>
		</snapshots>
		<releases>
			<updatePolicy>always</updatePolicy>
		</releases>
	</repository>
</repositories>

* enter the repository in your ~/.m2/settings.xml

<settings>
 
  <profiles>
    <!-- Define a profile which knows the local repo -->
    <profile>
      <id>default</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation> 
      <repositories>
        <repository>
          <id>local-maven-repo</id>
          <name>Local Repo</name>
          <releases>
            <updatePolicy>always</updatePolicy>
          </releases>
          <snapshots>
            <updatePolicy>always</updatePolicy>
          </snapshots>
          <url>http://192.168.1.10:8081/nexus/content/groups/public/</url>;
        </repository>
      </repositories>
    </profile>
  </profiles>
 
</settings>

Version

When declaring a “normal” version such as 4.10 for JUnit, internally this is represented as “allow anything, but prefer 4.10”

Version Ranges

You can specify a range of versions that would satisfy a given dependency:

Range Meaning
(,1.0] x < = 1.0
1.0 “Soft” requirement on 1.0 (just a recommendation - helps select the correct version if it matches all ranges)
[1.0] Hard requirement on 1.0
[1.2,1.3] 1.2 < = x < = 1.3
[1.0,2.0) 1.0 < = x < 2.0
[1.5,) x > = 1.5
(,1.0],[1.2,) x < = 1.0 or x > = 1.2. Multiple sets are comma-separated
(,1.1),(1.1,) This excludes 1.1 if it is known not to work in combination with this library

Default strategy: Of the overlapping ranges, the highest soft requirement is the version to be used. If there are no soft requirements inside the prescribed ranges, the most recent version is used. If that does not fit the described ranges, then the most recent version number in the prescribed ranges is used. If the ranges exclude all versions, an error occurs.

Scope

Valid scopes are

  • compile - the default. Makes the dependency available in all classpaths of a project. Propagated to dependent projects.
  • provided - indicates you expect the JDK or a container (eg. JBoss) to provide the dependency at runtime. –> Dependency is only available on the compilation and test classpath, and is not transitive.
  • runtime - dependency is not required for compilation, but is for execution. –> Dependency is in the runtime and test classpaths, but not the compile classpath.
  • test - dependency is not required for normal use of the application, and is only available for the test compilation and execution phases.
  • system - This scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository.
  • import - used on a dependency of type pom in the <dependencyManagement> section. It indicates that the specified POM should be replaced with the dependencies in that POM's <dependencyManagement> section.

Transitive Dependencies

Transitive dependencies are a new feature in Maven 2.0. This allows you to avoid needing to discover and specify the libraries that your own dependencies require, and including them automatically.

This feature is facilitated by reading the project files of your dependencies from the remote repositories specified. In general, all dependencies of those projects are used in your project, as are any that the project inherits from its parents, or from its dependencies.

Unmanaged Dependencies

A clean way to handle unamanaged dependencies is described here: https://devcenter.heroku.com/articles/local-maven-dependencies

This is also useful in the case not all people working with a maven project can resolve all dependences (because some dependencies are only available on internal repositories). The internal-only dependencies can be published with this method.

  • Use mvn to deploy the library to a local repository. This will create the correct directory structure plus meta files.
mvn deploy:deploy-file \
    -Durl=file:my_repository \
    -Dfile=mylib-1.0.jar \
    -DgroupId=com.example \
    -DartifactId=mylib \
    -Dpackaging=jar \
    -Dversion=1.0
  • Add the local repository to you pom.xml
<repositories>
    <!--other repositories if any-->
    <repository>
        <id>project.local</id>
        <name>project</name>
        <url>file:${project.basedir}/my_repository</url>
        <snapshots>
           <updatePolicy>always</updatePolicy>
        </snapshots>
        <releases>
           <updatePolicy>always</updatePolicy>
        </releases>
    </repository>
</repositories>
  • Add the dependency like you normally would:
<dependency>
    <groupId>com.example</groupId>
    <artifactId>mylib</artifactId>
    <version>1.0</version>
</dependency>

Release Management

Use Case: you are developing on a version like “2.1.5-SNAPSHOT”. Once the version is stable you want to make a 2.1.5 release, tag it in SVN and start working on the next version (2.1.6-SNAPSHOT or something like that). The Maven Release Plugin supports this.

Set-Up

Configure SCM

Make sure your pom.xml contains a valid SCM section (more info http://maven.apache.org/pom.html#SCM

SVN

The typical directory hierarchy with trunk, tags and branches must be used and created manually. If e.g. the directory tags is missing release:perform will fail.

<scm>
    <!-- Base URL of repository (trunk/tags/branches independent)-->
    <url>scm:svn:http://svn.my.company.com/repository</url>
    <!-- Current working url (NOT TAG) - read-only, used for e.g. update -->
    <connection>scm:svn:http://svn.my.company.com/repository/trunk/my-project</connection>
    <!-- Current working url (with write privileges) -->
    <developerConnection>scm:svn:http://svn.my.company.com/repository/trunk/my-project</developerConnection>
</scm>

Mercurial

The whole project just resides in one directory. Separate directories for trunk/tags/branches are not required, this is done automatically via the tagging and branching functionality of mercurial.

The urls are therefore always the same (and must not be a subdirectory of the project)

<scm>
    <url>scm:hg:https://hg.my.company.com/repository</url>
    <connection>scm:hg:https://hg.my.company.com/repository</connection>
    <developerConnection>scm:hg:https://hg.my.company.com/repository</developerConnection>
</scm>

Also the <build>-part of your pom.xml should add a dependency to the maven-scm-provider-hg. (Probably that's optional .. test this in the future)

<pluginManagement>
	<plugins>
		<plugin>
			<artifactId>maven-release-plugin</artifactId>
			<version>2.4.1</version>
			<dependencies>
				<dependency>
					<groupId>org.apache.maven.scm</groupId>
					<artifactId>maven-scm-provider-hg</artifactId>
					<version>1.8.1</version>
				</dependency>
			</dependencies>
		</plugin>
	</plugins>
</pluginManagement>

Optional: Specify a custom location for the tags

<build>
 <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-release-plugin</artifactId>
      <configuration>
            <tagBase>http://svn.my.company.com/repository/tags</tagBase>
      </configuration>
 </plugin>
</build>

Optional: Specify deployment location

Handling SCM credentials

This should work both for SVN and mercurial:

Either handle them externally: ensure you DON't have to enter your SCM login each time (i.e. your login information is stored somewhere by SVN)

OR edit ~/.m2/settings.xml and add a section

  <server>                                                                      
    <id>server-hostname-or-IP</id>                                                      
    <username>username</username>                                           
    <password>mellon</password>                                               
  </server>                                                                     

OR add the credentials explicitly

mvn release:perform  -Dusername=user -Dpassword=password

Release Workflow

Commit changes & check Javadoc

  • Make sure everything is committed to your VCS
  • Check for javadoc errors with mvn site (mvn javadoc:javadoc does not seem to build everything) - this avoids annoying problems at the very end (mvn release:perform))

Verify the build

  • Verify all files in your project are committed
  • Verify your project has no SNAPSHOT dependencies
  • Verify <version> of your project's pom.xml is a SNAPSHOT version
  • Verify the project
mvn verify

Prepare the release information

  • Confirm what the project's next RELEASE version will be
  • Confirm what the project's SVN tag for the next release version will be
  • Confirm what the project's next SNAPSHOT version will be

Dry run Since the Release Plugin performs a number of operations that change the project, it may be wise to do a dry run before a big release or on a new project. To do this, commit all of your files as if you were about to run a full release and run:

mvn release:prepare -DdryRun=true # then check the generated files 
mvn release:clean # Removes all of the files created above, and the project will be ready to execute the proper release

Prepare the release

mvn release:prepare  # answer the questions maven asks

Check the generated release.properties.

If something is not right the release can be rolled back with: “mvn release:rollback”

CAUTION: rollback currently does NOT remove already created tags in your SCM. You have to delete the tag yourself.

Perform the release if everything looks alright:

mvn release:perform

Note: When deploying a new release to a Nexus repository fails, make sure that there's not already an artifact of the same name & version there. If you are deploying to a release Nexus repository, you cannot overwrite an existing file.

Staging a release

tbd

Deploy release to production JBoss

To make production deployments really easy, here is a script that does

  • get the war file that shall be deployed
  • starting the jboss-cli
  • deploy my.war
#!/usr/bin/env python
 
#
# Usage:  ./deploy.py <artifact_version>
# Example: ./deploy.py 0.0.1
#
 
import os
import sys
import logging
logging.basicConfig(filename="deploy.log", level=20, format="%(asctime)-15s %(levelname)s\t(%(filename)s:%(lineno)d) -  %(message)s")
 
artifact_id = "fancy-services"
warname = "fancy.war"
nexus_repo="http://nexus:port/nexus/content/repositories/releases/path/to/artifact/" +artifact_id+ "/"
jboss_base_cmd = """/opt/jboss/bin/jboss-cli.sh --connect --controller=127.0.0.1:9999 --user=elder --password=secret """
 
 
def jboss(command_text, raise_on_error=True):
	cmd = jboss_base_cmd + """ --command="%s" """ % command_text
	returncode=os.system(cmd)
	if raise_on_error and returncode != 0:
		raise Exception("command %s failed" % command_text)
 
def downloadArtifact():
	url = nexus_repo + release_version + "/" + versioned_artifact
	os.system("rm {0}".format(warname))
	logging.debug("Getting artifact from " + url)
	returncode = os.system("wget {url} -O {war}".format(url=url, war=warname))
	if returncode!=0:
		raise Exception("Download Failed: " + url)
 
def showDeployments():
	print "Currently deployed on host:\n"
	jboss("deploy -l", raise_on_error=False) # list deployments
 
 
release_version = sys.argv[1]
versioned_artifact = artifact_id+"-"+release_version + ".war"
 
logging.info("Attempting to deploy " + versioned_artifact)
try:
	downloadArtifact()
	jboss("undeploy {0}".format(warname), raise_on_error=False ) # undeploy old version
	jboss("deploy {0}".format(warname), raise_on_error=True) # deploy new version
 
	logging.info("SUCCESS: " + versioned_artifact + " deployed.")
	print "\n\nSUCCESS\n"
	showDeployments()
except Exception, e: 
	logging.exception("ERROR: Deployment of %s failed " % versioned_artifact)
	print "\n\nERROR: Deployment failed\n\n"
	print e

Misc

Create archetypes from existing projects

Using an existing project (preferrably a stable release) you can simply create and locally install an archetype:

# cd <wherever the pom.xml of the project of your choice resides>
mvn archetype:create-from-project
cd target/generated-sources/archetype/
mvn install

Afterwards the newly created archetype will be listed here and can be used to create a new project

mvn archetype:generate

Or only list locally installed archetypes:

mvn archetype:generate -DarchetypeCatalog=local

See also: official documentation

Export source code of dependencies

To get a directory with xxx-src.jar dependencies of your project do

mvn dependency:copy-dependencies -Dclassifier=sources -DincludeArtifactIds=jmapmatcher-core,ariadne-core
 
# ls target/dependency

see http://maven.apache.org/plugins/maven-dependency-plugin/copy-dependencies-mojo.html

Maven and J2EE

there are some useful Maven plugins for J2EE (and JBoss). Have a look at Maven J2EE

Useful Links

maven.txt · Last modified: 2016/12/15 16:01 by mstraub