Maven is yet another build automation tool, comparable to e.g. ant; check the project homepage and the wikipedia page.
Directory layout of a maven project
├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com.company.project.* │ │ └── resources │ └── test │ ├── java │ │ └── com.company.project.* │ └── resources └── target
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.
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
mvn verify # build, test, package, integration-test and verify
see http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
Install your project to the local repository
mvn install
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
Note: replace /opt/jboss with /your/path/to/jboss and also make sure to use the correct server IP.
This works about the same since JBoss 7 up to (currently) WildFly 10
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
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
Basically this works the same as the old jboss plugin.
Documentation: https://docs.jboss.org/wildfly/plugins/maven/latest/
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
svn propedit svn:ignore .
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
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
Remove Eclipse artefacts
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 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.
Often you have (hopefully) fast running unit tests and slower running integration tests. Maven provides two different goals to cleanly seperate these two:
<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>
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.
In your pom.xml
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies>
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>
When declaring a “normal” version such as 4.10 for JUnit, internally this is represented as “allow anything, but prefer 4.10”
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.
Valid scopes are
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.
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.
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
<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>
<dependency> <groupId>com.example</groupId> <artifactId>mylib</artifactId> <version>1.0</version> </dependency>
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.
Make sure your pom.xml contains a valid SCM section (more info http://maven.apache.org/pom.html#SCM
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>
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>
<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>
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
Commit changes & check Javadoc
Verify the build
mvn verify
Prepare the release information
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.
tbd
To make production deployments really easy, here is a script that does
#!/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
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
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
there are some useful Maven plugins for J2EE (and JBoss). Have a look at Maven J2EE