====== Introduction ======
Maven is yet another build automation tool, comparable to e.g. ant; check the [[http://maven.apache.org/|project homepage]] and the [[http://en.wikipedia.org/wiki/Apache_Maven|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
4.0.0
com.mycompany.app
my-app
1.0.0
The default packaing type is jar. To build a war, just add:
war
For more details go [[http://maven.apache.org/pom.html#Quick_Overview|over there]] [[http://maven.apache.org/guides/introduction/introduction-to-the-pom.html|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:
internal.repo
Internal Releases
http://192.168.1.10:8081/nexus/content/repositories/releases/
internal.repo
Internal Snapshots
http://192.168.1.10:8081/nexus/content/repositories/snapshots/
Dont forget to **set your credentials** in ~/.m2/settings.xml :
internal.repo
deployment
deployment123
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 [[jenkinscontinuous_delivery_with_jenkins|jboss-as-maven-plugin]] to your pom.xml and also configure the management user you just created.
org.jboss.as.plugins
jboss-as-maven-plugin
7.3.Final
user
password
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:
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 =====
* [[http://eclipse.org/m2e/ | m2e Plugin]]
===== 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
org.apache.maven.plugins
maven-failsafe-plugin
2.12.3
integration-test
verify
* Run integration tests with //mvn verify//
====== Building an executable jar ======
Use the [[https://maven.apache.org/plugins/maven-shade-plugin/usage.html|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.
org.apache.maven.plugins
maven-shade-plugin
2.4.1
package
shade
my.package.Main
META-INF/services/java.sql.Driver
*:*
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
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:
org.apache.maven.plugins
maven-assembly-plugin
2.2.1
true
jar-with-dependencies
com.company.my.MainClass
package-jar-with-dependencies
package
single
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**
junit
junit
4.10
test
===== 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
local-maven-repo
http://192.168.1.10:8081/nexus/content/groups/public/
always
always
* enter the repository in your ~/.m2/settings.xml
default
true
local-maven-repo
Local Repo
always
always
http://192.168.1.10:8081/nexus/content/groups/public/;
==== 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.
* http://docs.codehaus.org/display/MAVEN/Dependency+Mediation+and+Conflict+Resolution#DependencyMediationandConflictResolution-DependencyVersionRanges
==== 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 section. It indicates that the specified POM should be replaced with the dependencies in that POM's 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
project.local
project
file:${project.basedir}/my_repository
always
always
* Add the dependency like you normally would:
com.example
mylib
1.0
===== Useful Links =====
* http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
====== 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 [[http://maven.apache.org/plugins/maven-release-plugin/ | 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:svn:http://svn.my.company.com/repository
scm:svn:http://svn.my.company.com/repository/trunk/my-project
scm:svn:http://svn.my.company.com/repository/trunk/my-project
=== 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:hg:https://hg.my.company.com/repository
scm:hg:https://hg.my.company.com/repository
scm:hg:https://hg.my.company.com/repository
Also the -part of your pom.xml should add a dependency to the maven-scm-provider-hg.
(Probably that's optional .. test this in the future)
maven-release-plugin
2.4.1
org.apache.maven.scm
maven-scm-provider-hg
1.8.1
==== Optional: Specify a custom location for the tags ====
org.apache.maven.plugins
maven-release-plugin
http://svn.my.company.com/repository/tags
==== Optional: Specify deployment location ====
see [[maven#deploy_to_maven_repository]]
==== 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-hostname-or-IP
username
mellon
**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))
* In case your site build takes ages due to errors like "[ERROR] Unable to determine if resource batik:batik-util:jar:1.6-1:compile exists in https://repository.jboss.org/nexus/content/groups/public/" simply run mvn site -Ddependency.locations.enabled=false ([[https://whatiscomingtomyhead.wordpress.com/2011/04/20/if-your-maven-site-build-is-too-slow/|source]])
**Verify the build**
* Verify all files in your project are committed
* Verify your project has no SNAPSHOT dependencies
* Verify 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 [[http://maven.apache.org/maven-release/maven-release-plugin/examples/rollback-release.html|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
# 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
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: [[http://maven.apache.org/archetype/maven-archetype-plugin/advanced-usage.html|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 ======
* http://www.java-tutorial.ch/maven/maven-release
* http://maven.apache.org/plugins/maven-release-plugin/index.html