2013/08/12

Creating maven archetypes (tutorial)

 
 

Introduction

 
Let's say that after a lot of research, trial and errors, you set up a base maven project with a combination of frameworks and a bunch of tricky configurations. Your project works now, it prints "Hello World!" in the browser window, text file or terminal, and you feel proud of yourself for being able to set up the whole thing. Now that you have this small code base, you can make a copy of the project folder and rename it as template-xyzw-project so if you (or any other programmer) wish to start a new project, you copy this template-xyzw-project folder, rename it and apply the appropiated tweaks. Lot of work, right?
 
Wouldn't be easier if you could incorporate this customized project to a maven catalog so you can instantiate it from the terminal with a mvn archetype:generate command instead of copy/paste/adjust everything?
 
Maven can take a project folder and generate the maven archetype file (a jar) that can be later installed on a maven proxy/repository to be available for all the people that have access to it. This is what this tutorial is about.
 

Example Project

 
As example, lets create an archetype for a kind of complex project, a liferay 6 portlet with Vaadin 7, Spring and the Spring-Vaadin-portlet integration add-on, the working template project can be found at GitHub. Although the project target looks kind of complex, the project layout is pretty simple, it only consist of one pom.xml that lists all the dependencies, you can also use a project of your own if you prefer.
 

Generate the base-archetype

 
cd into the project directory (or, in the case of a multi-module project, cd into the parent/aggregator project) and run the following command:
    
      $ mvn archetype:create-from-project
This will generate a bulk of files and folders under the target/generated-sources/archetype directory:
        .
        ├── src
        │   ├── main
        │   │   └── resources
        │   │       ├── archetype-resources
        │   │       │   └── src
        │   │       │       └── main
        │   │       │           ├── java
        │   │       │           │   ├── portlets
        │   │       │           │   └── services
        │   │       │           │       └── impl
        │   │       │           └── webapp
        │   │       │               ├── VAADIN
        │   │       │               │   ├── themes
        │   │       │               │   │   └── test-portlet
        │   │       │               │   └── widgetsets
        │   │       │               └── WEB-INF
        │   │       │                   └── spring-conf
        │   │       └── META-INF
        │   │           └── maven
        │   └── test
        │       └── resources
        │           └── projects
        │               └── basic 
        └── target
            ├── classes
            │   ├── archetype-resources
            │   │   └── src
            │   │       └── main
            │   │           ├── java
            │   │           │   ├── portlets
            │   │           │   └── services
            │   │           │       └── impl
            │   │           └── webapp
            │   │               ├── VAADIN
            │   │               │   ├── themes
            │   │               │   │   └── test-portlet
            │   │               │   └── widgetsets
            │   │               └── WEB-INF
            │   │                   └── spring-conf
            │   └── META-INF
            │       └── maven
            └── test-classes
                └── projects
                    └── basic

From this folder structure you can discard the target directory sub-tree (the red one), just mvn clean it.
 
This is just an initial archetype, there are some details left, for example, if you want, you can let the user change the base package from com.test.portlets to com.organization.application.portlets and adjust all the package references (updating all the xml, properties, etc files). That's the next step.
 

Adjusting the archetype

 
Here you have two options, you can work the archetype inplace (cd target/resources/archetype) or you can make a copy in another directory of the archetype folder and cd into it, the second option is preferred as if you screw some configuration file, you can start over making another copy from the original archetype folder, it's up to you.
 
There are two files that orchestate the archetype building process, the first one is the pom.xml in the archetype directory, it describes the project as a maven archetype (yes, the archetype folder is indeed a maven project), you can change the groupId, artifactId and version properties used to address the project in the GAV way, modify these properties to change the archetype name.
 
The other file, and the most important one in this process, is archetype-metadata.xml located under the src/main/resources/META-INF/maven directory. This file groups the resources as file sets (fileSets) that are going to be filtered by the archetype during the generation process, every file set is of the form:
  • filtered="true" indicates that the resource files of the file set should be parsed. Maven will use Apache Velocity templating framework to substitute/replace/conditional replace values in your files.
  • packaged="true" From the documentation: "flags maven that the resource files have to be packaged, which means the selected files will be generated/copied in a directory structure that is prepended by the package property. They can be non-packaged, which means that the selected files will be generated/copied without that prepend."
  • encoding="UTF-8" indicates that your files are in the specified encoding.
Your archetype-metadata.xml should look something like this: You have to manually add the file sets that aren't listed in the above xml, normally you wouldn't have to do that, but this project employs some files that maven thinks shouldn't be fitlered and it groups them in a non-filtered file set. Sometimes you have to delete some of this maven generated file sets, like those that group IDE files (e.g,. .settings, .project, etc) that you forgot to manually delete from the project folder prior to running mvn archetype:create-from-project.
 
For this example, I need to tell maven to filter the files README and README.md to change the project name that appears in the header and reflect the project name given by the user during project instantiation. Also want to filter the files with extension .scss located inside the folder VAADIN/themes/test-portlet.
  1. For README and README.md, you only have to change the lines 27-34 in the above archetype-metadata.xml to: Open those README files and add the following lines at the beginning of the file (if they are not already there):
    #set( $symbol_pound = '#' )
    #set( $symbol_dollar = '$' )
    #set( $symbol_escape = '\' )
    
    Every file that you want to be filtered by maven has to start with those lines, even the .java files, but don't worry, maven archetype plugin have done this for you with all the file-sets that are initially listed in the archetype-metadata.xml file, so you only add those that doesn't appear in a filtered file set (or in any file set at all). Then add the value ${rootArtifactId} whenever you want the supplied user project name to appear.
    #set( $symbol_pound = '#' )
    #set( $symbol_dollar = '$' )
    #set( $symbol_escape = '\' )
    
    README for module ${artifactId}
    ===============================
    
    This module belongs to the project ${rootArtifactId}
    
    
  2. For the .scss files, first remove the .scss include from the unfiltered file set: Now include the .scss files in a new file set: As done above, open the .scss files and add the #set lines, then replace those values that contains the project name (test-portlet) with ${rootArtifactId}, for example, open the file styles.scss: Then replace test-portlet with ${rootArtifactId}: Remember to check the java source files, and adjust those variables, methods, commets, etc that depends on the supplied project name, substitute them with ${rootArtifactId}. Note that you can even tweak the code comments to reflect the user application name. Repeat the process with all the files you want to include and filter.
     
    You can also replace package strings like com.test.portlets with the user supplied package name using the ${package} variable. There are another variables you can use:
     
    Variable Meaning
    ${rootArtifactId} Already explained above, it holds the value entered by the user as the project name (the value that maven ask as the artifactId: in the prompt when the user runs the archetype)
    ${artifactId} If your project is composed by one module, this variable will have the same value as ${rootArtifactId}, but if the project contains several modules, this variable will be replaced by the module name inside every module folder, for example: given a module named portlet-domain inside a project named portlet, all the files inside this module folder that are to be filtered will have the value of the variable ${artifactId} replaced by portlet-domain whereas the ${rootArtifactId} variable will be replaced by portlet
    ${package} The user provided package for the project, also prompted by maven when the user runs the archetype
    ${packageInPathFormat} The same value as ${package} variable but replacing '.' with the character '/', e.g:, for the package com.foo.bar this variable is com/foo/bar
    ${groupId} The user supplied groupId for the project, prompted by maven when the user runs the archetype
    ${version} The user supplied version for the project, prompted by maven when the user runs the archetype
 
Now, for the last part of this section, if you want to rename some of the project files or folders to the user supplied project name, you have to change those files/folder names to __rootArtifactId__. For example, rename the VAADIN/themes/test-portlet/test-portlet.scss to VAADIN/themes/test-portlet/__rootArtifactId__.scss, so it will be renamed as the user supplied project name with the extension .scss. Again, repeat this process with all those files and/or folders that you want to rename to reflect the user's project name.
 

Further tweaking

 
Now, one question left, wouldn't be nice if the source folder estructure is of the form com/your-company/your-application-name? Wouldn't be even nicer if some of the web.xml values reference your application name instead of the test-portlet inherated from the template?
 
Well, result that this has been taked care of by maven with the attribute package in the file set, it will copy the src folder content and paste it under a folder that represents the user provided project package name.
 
So, there is nothing left to adjust, let's create the archetype.
 

Creating the archetype (again)

 
You only have to cd in the parent archetype and run:
    
      $ mvn install
This will re-generate the target folder and the test-vaadin7-portlet-archetype-1.0.0-SNAPSHOT jar file, if you want to change the archetype project (thus, change the name of the jar name), remember to edit the archetype pom.xml file groupId and artifactId properties.
 

Test your archetype locally

 
The mvn install line executed in the previous section will generate a file archetype-catalog.xml in your ~/.m2 directory with the following content: Then, type:
    
    $ mvn archetype:generate
Your newly deployed archetype will be listed as the last one of a list that will appear in your terminal, choose the archetype, fill in the parameters and hit enter, open up your file browser and check that all the generated files accomodates to the supplied parameters.
 

Uploading your archetype to your local maven proxy

 
Now that you've created and tested your archetype, maybe you want to share it with your teammates, to do that you can deploy your archetype in your organization proxy-repository, but this process depends on your local maven proxy artifact installation instructions, for the case of Nexus, it as easy loging in with an user credentials that let you upload artifacts to your Releases Repository (or Snapshots, depending on what Repository you want to deploy) and upload the artifact passing in the values for Group (groupId), Arfifact (artifactId), Version (version) and choose maven-archetype for Packaging. Upload the archetype jar generated in the target folder and click the button "Upload Artifact(s)", after this, the archetype should be available for all the user that have access the the proxy.
 
 

Multi-module projects

 
Is common to have several modules in the application structure, for example, the domain classes of a project can be grouped in a application-persist subproject, the web application that serves as the application front-end can be in a application-webapp subproject, and so on. All these submodules would be controlled by an Aggregator Project. The project layout should be in hierarchical form, the parent/aggregator project must contain the module subprojects directories inside it. If you start from a flat layout structure and run mvn archetype:create-from-project, maven will generate the module archetype rosource folders in the directory /src/main/resources, then in the archetype creation (mvn install) it will complain that it can't find the archetype resource files inside the folder /src/main/resources/archetype-resources and will exit the execution with an error message. To solve this problem you have to manually move the module archetype resources to the /src/main/resources/archetype-resources directory or rearrange your project structure to the hierarchical form. Is usually the case that the project modules are named <project-name>-module-name and reside inside the parent folder named <project-name>. For example, given a project named nextgeo with the modules persist, web-services and web-app, a common project folder names for this modules would be: nextgeo-persist, nextgeo-web-services and nextgeo-web-app all inside the nextgeo folder. To process a project layout such as this, run mvn archetype:create-from-project inside the project parent folder, then rename the subfolders nextgeo-persist, nextgeo-web-services and nextgeo-web-app (inside the src/main/resources/archetype-resources folder) to: __rootArtifactId__-persist, __rootArtifactId__-web-services and __rootArtifactId__-web-app, also in the archetype-metadata.xml you would see that the file-sets are grouped together by a <modules> tag: Change the base project name (nextgeo) with the variable ${rootArtifactId}, except for the dir attribute that points to a folder name (this must remain equal to the phisycal folder name or maven won't be able to copy/filter the folder and its content), like this: Everything else in the process stays the same, after you finish editing your files, run mvn install and it should work as well as before, now your user can make an instance of the archetype by running mvn archetype:generate. It the user provides, for example, the project name wumpus (rootArtifactId = wumpus), the generated folders should be: wumpus-persist, wumpus-web-services and wumpus-web-app, all inside the wumpus parent folder.
 

Summarized process

  1. cd in the project folder.
  2. Run mvn archetype:create-from-project.
  3. cd in the <project-folder>/target/generated-sources/archetype.
  4. Make a copy of the archetype folder (Optional).
  5. Delete the target subfolder (Optional).
  6. Change the archetype groupId, artifactId, version and description for your archetype in the pom.xml.
  7. Edit the file <archetype-folder>/src/main/resources/META-INF/maven/archetype-metadata.xml, add the file-sets you consider neccesary, edit the existing ones, remember that the attribute filtered="true" will filter the files in the file-set replacing variables like ${rootArtifactId} with the project name given by the user in the form of the artifactId that maven ask him/her during generation (that moment when the users uses your archetype). Also remember that packaged="true" will copy the files in the file-set in a destination folder that matches the package given by the user during generation.
  8. Edit the files you want to be specially processed, remember whenever you see the project name (your project name) replace it with the ${rootArtifactId} variable.
  9. Rename those files/folders that you want reflect the user provided project name during generation with __rootArtifactId__.
  10. If your project is a multi-module project, then rename the modules folders using __rootArtifactId__ inside the name (only if the module folder name contains a reference to the project name), and update the archetype-metadata.xml module tags accordingly. Remember that ${rootArtifactId} will contain the project name whereas ${artifactId} will contain the module name.
  11. Run mvn install from the archetype folder.
  12. Test it using mvn archetype:generate, search for the last entry in the list.
 

34 comments:

  1. After step 11, you should run mvn archetype:update-local-catalog in folder where your archetype resides. Archetype plugin reads archetype catalogs. Google about it.

    ReplyDelete
    Replies
    1. Thanks for the suggestion and sorry for the late reply.

      Delete
  2. Very useful article!!!
    Thank you very much!!!

    ReplyDelete
    Replies
    1. You're welcome, glad it was of help! And sorry for taking so long to answer the comment.

      Delete
  3. Thank you for this simple and straightforward overview of the archetype creation process.

    ReplyDelete
    Replies
    1. You're welcome :)
      Sorry for the late reply.

      Delete
  4. +1 Thank you for this tutorial.

    ReplyDelete
    Replies
    1. You're welcome, glad it helped, sorry for the late reply.

      Delete
  5. I have a xml file it's path is src/main/resources/archetype-resources/src/main/resources/sql/myapp/DemoUser.xml.when execute mvn archetype:generate I specify artifactId is foo.bar and package is com.foo.bar(-DartifactId=foo.bar -Dpackage=com.foo.bar) but I want to DemoUser.xml in folder sql/bar/DemoUser.xml, how to implement this? That is replace myapp to bar.

    ReplyDelete
    Replies
    1. What about renaming your folder to `src/main/resources/archetype-resources/src/main/resources/sql/__artifactId__/DemoUser.xml` and then specify the artifactId as bar and the groupId as com.foo?

      Hope it helps.

      Delete
    2. Thanks! I resolved it by this way: in `archetype-resources/pom.xml` I resolved app name from artifactId. #set ($artifactId = "${artifactId}")
      #set ($index = $artifactId.indexOf('.'))
      #set ($index = $index + 1)
      #set ($appName = $artifactId.substring($index)). Then in archetype-metadata.xml I add appName property and default value:

      ${appName}

      . It seems you must put velocity syntax in pom.xml, if you put in archetype-metadata.xml it cannot be resolved.

      Delete
  6. Thanks!
    Sorry for the late reply.

    ReplyDelete
  7. hi I have a question of how to specify a default package value so user do not need specify a package parameter explicitly? please see http://stackoverflow.com/questions/38419903/when-execute-maven-archetype-generate-how-to-specify-a-default-package-value-cor

    ReplyDelete
  8. Hello , I have a question, what if you want a json file to be filtered? because if you add the lines
    #set( $symbol_pound = '#' )
    #set( $symbol_dollar = '$' )
    #set( $symbol_escape = '\' )
    then it will become an invalid json file. Thank you!

    ReplyDelete
  9. why when put the command mvn archetype:generate and then put the number of an archetype give this error:
    [ERROR] ResourceManager : unable to find resource 'archetype-resources/pom.xml' in any resource loader. ????

    ReplyDelete
  10. Hi

    Am trying to create the new archetype as per your tutorial , but i do not want the .java files to include , in my instance of the module structure created based on the new archetype , can you please help me how to do this

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Hello, I got a question:
    How can I control that generate or not a file by a required property?
    e.g:
    if (prop.use.redis == true) {
    generateFile("src/main/resources/redis.xml");
    }
    else {
    System.out.println("do not need generate redis.xml");
    }

    ReplyDelete
  13. This comment has been removed by the author.

    ReplyDelete
  14. This is my first time visit to your blog and I am very interested in the articles that you serve. Provide enough knowledge for me. Thank you for sharing useful and don't forget, keep sharing useful info: Visit Office Rental Singapore

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. From your discussion I have understood that which will be better for me and which is easy to use. Really, I have liked your brilliant discussion. I will comThis is great helping material for every one visitor. You have done a great responsible person. i want to say thanks owner of this blog.

    Python Online certification training
    python Training institute in Chennai
    Python training institute in Bangalore

    ReplyDelete
  17. I am obliged to you for sharing this piece of information here and updating us with your resourceful guidance. Hope this might benefit many learners. Keep sharing this gainful articles and continue updating for us.
    oneplus mobile service centre in chennai
    oneplus mobile service centre
    oneplus service center near me

    ReplyDelete
  18. Your very own commitment to getting the message throughout came to be rather powerful and have consistently enabled employees just like me to arrive at their desired goals.

    And indeed, I’m just always astounded concerning the remarkable things served by you. Some four facts on this page are undeniably the most effective I’ve had.
    MATLAB TRAINING IN CHENNAI | Best MATLAB TRAINING Institute IN CHENNAI
    EMBEDDED SYSTEMS TRAINING IN CHENNAI |Best EMBEDDED TRAINING Institute IN CHENNAI
    MCSA / MCSE TRAINING IN CHENNAI |Best MCSE TRAINING Institute IN CHENNAI
    CCNA TRAINING IN CHENNAI | Best CCNA TRAINING Institute IN CHENNAI
    ANDROID TRAINING IN CHENNAI |Best ANDROID TRAINING Institute IN CHENNAI

    ReplyDelete
  19. Excellent Blogs,Appreciating for this intense work and keep doing more...Learn Python to get a enormous impact in your life
    python training in chennai | python training in annanagar | python training in omr | python training in porur | python training in tambaram | python training in velachery

    ReplyDelete
  20. Excellent Blogs,Appreciating for this intense work and keep doing more...Learn Python to get a enormous impact in your life

    AWS training in Chennai

    AWS Online Training in Chennai

    AWS training in Bangalore

    AWS training in Hyderabad

    AWS training in Coimbatore

    AWS training

    ReplyDelete
  21. very interesting stuff.thanks for sharing.Angular training in Chennai

    ReplyDelete