Maven projects are built around the idea of archetypes. The built in ones are standard Java (the default one), Documentation and Web. This project uses both.
To create them:
Java:
mvn archetype:create -DgroupId=org.crud -DartifactId=crud-java
Documentation:
mvn archetype:create -DgroupId=org.crud.doco -DartifactId=doco -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-site
Web:
mvn archetype:create -DgroupId=org.crud -DartifactId=crud-web -DarchetypeArtifactId=maven-archetype-webapp
Note: Batch files exist for all these under the "maven-scripts" folder
There are other archetypes available but are not part of standard Maven yet such as a J2EE archetype.
It is also possible to define your own archetypes, for more info: http://maven.apache.org/guides/mini/guide-creating-archetypes.html
Once you execute one of these you'll see a new folder structure that should look like this:
crud-java +---pom.xml +---src +---main | +---java | +---org | +---crud +---test +---java +---org +---crud
The important things to note here are:
I like to break a large project up into smaller, more manageable and reusable chunks. So for this project I defined the following:
There are other options for this, eg each project can/should have it own documentation
To drive all this from the top there is a master pom.xml. This looks like any other POM but has sub modules and shared project information such as dependancies and profiles.
<modules> <module>crud-data</module> <module>crud-java</module> <module>crud-web</module> </modules>
Each sub-project must also point back to this parent by adding a parent such as:
<parent> <groupId>org.crud</groupId> <artifactId>app</artifactId> <version>1.0-SNAPSHOT</version> </parent>
Once all this is setup we can easily add in new modules/sub-projects and have them integrated with the master project. It would also be simple to have a large tree of projects defined in the same way.
Now it is simple to add code and other resources and have them built, tested, deployed and released.
Maven can be configured at different levels. When maven is run the first time it will create a settings.xml in your user home folder. You can then take this and add to it as required such as for:
Adding proxy servers:
<proxies> <proxy> <active>true</active> <protocol>http</protocol> <host>proxy</host> <port>8080</port> <username></username> <password></password> <nonProxyHosts>company_repos_server</nonProxyHosts> </proxy> </proxies>
Changing the location of your local repository:
<localRepository>c:/Maven2Repos</localRepository>
Setting a mirror to the central repository:
<mirrors> <mirror> <id>planetmirror</id> <name>Australian Mirror of http://repo1.maven.org/maven2/</name> <url>http://public.planetmirror.com/maven2/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>
When you are developing an application there will be a series of tasks performed, some more than others. This is the build cycle. Maven helps with this by having built in tasks for assisting you with this lifecycle. Initially there is the setup of your project as described next.
A typical cycle might be:
mvn compile
Note: You didn't have to write anything to get that to work, beyond the initial setup.
This should produce output something like:
[INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------------- [INFO] Building Maven Quick Start Archetype [INFO] task-segment: [compile] [INFO] ---------------------------------------------------------------------------- [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] Compiling 1 source file to C:\Temp\mvn\crud-java\target\classes [INFO] ---------------------------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] ---------------------------------------------------------------------------- [INFO] Total time: 4 seconds [INFO] Finished at: Wed Mar 15 14:23:36 EST 2006 [INFO] Final Memory: 2M/9M [INFO] ----------------------------------------------------------------------------
The first time you issue a 'mvn' command a few things will happen.
+---classes | +---org | +---crud | | +---aso | | +---components | | | +---model | | +---engine | | +---pages | | +---util | +---rz | +---squeezer +---surefire-reports +---test-classes +---org +---crud +---components +---pages
This consists of compiled files, test case results, generated documents and jars/wars.
If there are any compile errors you will be told and the build will fail. E.g:
[INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------------- [INFO] Building Maven Quick Start Archetype [INFO] task-segment: [compile] [INFO] ---------------------------------------------------------------------------- [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [WARNING] Artifact junit:junit:jar:3.8.1 retains local scope 'test' overriding broader scope 'compile' given by a dependency. If this is not intended, modify or remove the local scope. [INFO] [compiler:compile] Compiling 1 source file to C:\projects\Tapestry_Cayenne_Crud\crud-java\target\classes [INFO] ---------------------------------------------------------------------------- [ERROR] BUILD FAILURE [INFO] ---------------------------------------------------------------------------- [INFO] Compilation failure C:\projects\Tapestry_Cayenne_Crud\crud-java\src\main\java\org\crud\pages\CrudAddModify.java:[25,8] org.crud.pa ges.CrudAddModify is not abstract and does not override abstract method setCurrentFieldTracking(java.lang.Obje ct) in org.crud.pages.CrudAddModify [INFO] ---------------------------------------------------------------------------- [INFO] For more information, run Maven with the -e switch [INFO] ---------------------------------------------------------------------------- [INFO] Total time: 2 seconds [INFO] Finished at: Wed Mar 15 14:28:46 EST 2006 [INFO] Final Memory: 2M/10M [INFO] ----------------------------------------------------------------------------
Next we might want to test our code by running unit test cases that you should write...right? Remember our maven generated folder structure from above? It guides us to put our test cases under the folder:
+---test +---java +---<your package structure>
...and our test cases should be in a file called xxxTest.java, eg. CrudListTest.java. That way the Maven test battery will pick up the file and run it as a JUnit test case.
Then we should run the tests. This will also call any dependant Maven build phases such as 'compile'.
mvn test
This will product output like the following:
[INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------------- [INFO] Building Maven Quick Start Archetype [INFO] task-segment: [test] [INFO] ---------------------------------------------------------------------------- [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [WARNING] Artifact junit:junit:jar:3.8.1 retains local scope 'test' overriding broader scope 'compile' given by a dependency. If this is not intended, modify or remove the local scope. [INFO] [compiler:compile] Compiling 1 source file to C:\projects\Tapestry_Cayenne_Crud\crud-java\target\classes [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [surefire:test] [INFO] Setting reports dir: C:\projects\Tapestry_Cayenne_Crud\crud-java\target/surefire-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- [surefire] Running org.crud.AppTest [surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.11 sec <* snip --- Test case output here --- snip *> [surefire] Running org.crud.components.CrudMetaInfoTest [surefire] Tests run: 5, Failures: 0, Errors: 0, Time elapsed: 2.062 sec [surefire] Running org.crud.pages.CrudListTest [surefire] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.531 sec Results : [surefire] Tests run: 7, Failures: 0, Errors: 0 [INFO] ---------------------------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] ---------------------------------------------------------------------------- [INFO] Total time: 6 seconds [INFO] Finished at: Wed Mar 15 14:39:28 EST 2006 [INFO] Final Memory: 4M/8M [INFO] ----------------------------------------------------------------------------
If a test fails the whole build will fail, which is what we want. You then go back and fix your code and run through the cycle again until everything passes.
Create a jar or war of your project. This will be put under the "target" folder.
mvn package
You'll need to install the package into your local repository so it can be used with other projects. By default this will be user home/.m2/repository unless you've setup your settings.xml to be different. Remember that this will now call all phases below this from the bottom up: compile->test->package then install
mvn install
This is a nice way to do the repetitive tasks of code, test, fix, build (or should that be test then code?)
Sometimes though you might be wanting to install your application and not have it tested everytime, such as for small changes, so you can turn off testing for that particular run:
mvn -Dmaven.test.skip=true install
Of course next you'll want to see your web application running on an app server, let's go about deploying it there.
mvn tomcat:deploy -P local
This runs the tomcat plugin to deploy using the "local" profile. There is some setup behind all this and I'll introduce the concept of profiles.
If you look inside the pom.xml for the web application project you'll see a build plugin defined. This tells Maven to download and make available a new plugin from the central repository for the deploying to the Tomcat server. There are others available for other app servers, if you can't find one for yours you might have to get advanced and write your own plugin - its only a bit of Java and XML ;)
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <configuration> <url>${tomcat.manager.url}</url> <update>true</update> <server>tomcatLocal</server> </configuration> </plugin> </plugins> </build>
The value for the manager url is taken from a set of profiles that are defined in the parent project POM, like so:
<profiles> <profile> <id>local</id> <activation> <property> <name>local</name> </property> </activation> <properties> <tomcat.manager.url>http://localhost:8081/manager</tomcat.manager.url> </properties> </profile> <profile> <id>prod</id> <activation> <property> <name>prod</name> </property> </activation> <properties> <tomcat.manager.url>http://prod_server:8081/manager</tomcat.manager.url> </properties> </profile> </profiles>
This has defined 2 profiles; one for local development and one for deploying to a production environment. The property definition inside the properties tag is an arbitrary property I have defined but you'll notice it matches the one inside the web applications POM:
<configuration> <url>${tomcat.manager.url}</url>
There is one final thing here though. How do we tell maven which profile to use? This is done by passing the profile name to trigger to maven. So to deploy to our compile, test, build and deploy our application to the server we want we issue the command:
mvn clean:clean tomcat:deploy -P local or mvn clean:clean tomcat:deploy -P prod
Oh wait! We didn't tell Maven what the Tomcat manager credentials to do this are. One way to do this is set this up in the settings.xml file for the user.
<servers> <server> <username>admin</username> <password>zzz</password> <id>tomcatLocal</id> </server> </servers>
The id tag here links in to the name given for tomcat plugin as defined in the web applications POM...recall this line?
<server>tomcatLocal</server>
When you start to build a number of dependant projects you will need to remember that if you've changed a bottom level project you'll need to run a 'mvn install' so the dependant projects can access the changes. For example if I've made changes to the crud-data project and I want to redeploy the web application I'll need to install the crud-data project first.
I'd like to know how to have dependant projects built and not just used from the repos.
Clean the target folder to make sure everything in your next build will be up to date.
mvn clean
Generate documentation for your project
mvn site
Generate an IntelliJ Idea project
mvn idea:idea
Generate an Eclipse project
mvn eclipse:eclipse
Commands can be chained
mvn clean install
Create an assembly.xml file in the same folder as your pom.xml. This will contain files to include/exclude and the target output type, eg zip. Eg for generated doco:
<assembly> <id>docs</id> <formats> <format>zip</format> </formats> <fileSets> <fileSet> <includes> <include>target/**</include> </includes> <excludes> <exclude>**/*.jar</exclude> <exclude>**/*.zip</exclude> </excludes> </fileSet> </fileSets> </assembly>
OR for source code distribution
<assembly> <id>src</id> <formats> <format>zip</format> <format>jar</format> </formats> <fileSets> <fileSet> <excludes> <exclude>**/target/**</exclude> </excludes> </fileSet> </fileSets> </assembly>
This is similar to ant style constructs.
Then create the assembly:
mvn assembly:assembly -Ddescriptor=assembly.xml
inside the target folder you'll once again see the outputs
There are many more things this neato tool can do, check out the maven site for more!
// TODO