Creating a new project

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:

  1. Standard directory structure
  2. pom.xml - the Project Object Model skeleton. This the file that defines many things about our project including documentation, dependancies, build plugins, profiles (good for building for different environments) and repositories.

    I like to break a large project up into smaller, more manageable and reusable chunks. So for this project I defined the following:

    • Data
    • Web
    • Java code for the web
    • Documentation

      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.

Personalized settings

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>

Build lifecycles

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:

  1. Write a test case
  2. Write some code based on the test case (or the other way around)
  3. Check that the code compiles:
      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.

    • If the project artifact is not in your local repository, maven will look down your list of repositories for the artifact. By default this will be http://www.ibiblio.org/maven/. Have a look there and you'll see how a maven repository is structured.
    • A new folder structure is created under your project called 'target' which might look like:
        +---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
      

Deploying to an application server

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>

Project build sequence

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.

Maven Goodies

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   

Creating source distributions

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!

Updating the Repository

// TODO