Developing Modules

This page describes how to create modules to extend the core XNAT service, including adding such features as:

  • New data types
  • New screens and pages in the XNAT interface
  • Overriding existing screens and pages in the XNAT interface
  • Adding custom RESTful services
  • Adding custom tabs to various property pages, including the system configuration and project properties settings

XNAT 1.6 modules differ from a true plug-in architecture in that the module code is integrated at build time into the XNAT code base and deployed along with the web application. Module resources can not be cleanly deactivated or removed from a deployed XNAT installation. In fact, the module functionality works almost exactly the same as customizing XNAT in earlier versions. For more information, see the Customizing XNAT page in the documentation. It's also extremely helpful to watch the video from the XNAT workshop.

Module Projects in Maven

Maven provides a fairly robust if byzantine way of compiling arbitrary resources into a jar archive (arbitrary contrasts with compiled Java code and pre-defined build targets, which are quite straightforward to compile into an archive). To make it easier to deal with Maven's idiosyncrasy, we've created a couple of Maven archetypes to make it easier to create a new module project in Maven.

Selecting an Archetype from a Catalog

 To create a new Maven module project, run the following command:

mvn archetype:generate -DarchetypeCatalog=http://maven.xnat.org/libs-release

This should present you with a choice of at least one archetype option:

Choose archetype:
1: http://maven.xnat.org/libs-release -> org.nrg.xnat:xnat-module-archetype (Creates a single-module project template. This is useful for a
                targetted project containing one type of functionality.)
2: http://maven.xnat.org/libs-release -> org.nrg.xnat:xnat-multimodule-archetype (Creates a multi-module project template. This is useful for a
                large project containing multiple data types.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): :

Enter the number corresponding to the org.nrg.xnat:xnat-module-archetype archetype (in this case 1) and press Enter. This will lead you through a series of questions about your project attributes. Once you've gone through all these, Maven will generate your project template:

Choose archetype:
1: http://maven.xnat.org/libs-release -> org.nrg.xnat:xnat-module-archetype (Creates a single-module project template. This is useful for a
                targetted project containing one type of functionality.)
2: http://maven.xnat.org/libs-release -> org.nrg.xnat:xnat-multimodule-archetype (Creates a multi-module project template. This is useful for a
                large project containing multiple data types.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Define value for property 'groupId': : org.nrg.xnat
Define value for property 'artifactId': : foo
Define value for property 'version':  1.0-SNAPSHOT: : 1.0.0-SNAPSHOT
Define value for property 'package':  org.nrg.xnat: :
Confirm properties configuration:
groupId: org.nrg.xnat
artifactId: foo
version: 1.0.0-SNAPSHOT
package: org.nrg.xnat
 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: xnat-module-archetype:1.6
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.nrg.xnat
[INFO] Parameter: artifactId, Value: foo
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: org.nrg.xnat
[INFO] Parameter: packageInPathFormat, Value: org/nrg/xnat
[INFO] Parameter: package, Value: org.nrg.xnat
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: groupId, Value: org.nrg.xnat
[INFO] Parameter: artifactId, Value: foo
[INFO] project created from Archetype in dir: D:\Code\Projects\XDC\foo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1:27.396s
[INFO] Finished at: Mon Oct 29 13:03:57 CDT 2012
[INFO] Final Memory: 9M/153M
[INFO] ------------------------------------------------------------------------

Specifying a Particular Archetype on the Command Line

Instead of selecting an archetype from a list pulled from a catalog, you can also just specify all of the options for the archetype and the attributes for the newly created project directly on the Maven command line. The command below shows how to create an XNAT multi-module project from the archetype:

mvn archetype:generate \
    -DarchetypeCatalog=http://maven.xnat.org/libs-release/archetype-catalog.xml \
    -DarchetypeGroupId=org.nrg.xnat \
    -DarchetypeArtifactId=xnat-multimodule-archetype \
    -DarchetypeVersion=1.6 \
    -DgroupId=org.nrg.xnatx \
    -DartifactId=bar \
    -Dversion=1.0.0-SNAPSHOT

Using IDEs to Create a New Module Project

Most modern development environments have tools to use Maven archetypes to create new projects. This section describes how to do this with two of the most widely-used IDEs.

IntelliJ IDEA

JetBrains' IntelliJ IDEA doesn't require any previous configuration to use an archetype from a new catalog. To create a new module in IDEA:

  1. Select File->Add Module ... or right-click in your Project window and select New->Module.
  2. In the Ad Module dialog, select Create module from scratch.
  3. Click Next.
  4. Enter the name for your new module in the Module name box, as well as the appropriate location for the module.
  5. Under Select type, click on Maven Module.
  6. Click Next.
  7. On the next page, enter your desired GroupIdArtifactId, and Version.
  8. If you haven't yet created a module from an XNAT archetype on your current IntelliJ IDEA installation, click on the Add archetype... button.
  9. In the Add Archetype dialog, set the following attributes:
  10. Click OK.
  11. Repeat the previous steps to add another archetype, this time with the ArtifactId set to xnat-multimodule-archetype.
  12. Select the archetype you want to use to create your module.
  13. Click Next.
  14. Review the data for your module. If everything's OK, click Finish.

And that's it. Once the Maven project generation step has completed, you should have the frame of your new XNAT module available in IntelliJ IDEA.

Eclipse

  1. Verify that you have the M2E plugin installed
  2. Select File->New-->Project... 
  3. Choose project type Maven–>Maven Project, click Next.
  4. Ensure that "Create a simple project (skip archetype skeletion)" is unchecked.  You can take the default workspace location, or choose your own.  Click Next.
  5. Now we'll tell Eclipse where to find the archetype catalog.  Configure-->Add Remote Catalog...
    1. Catalog file: http://maven.xnat.org/libs-release/archetype-catalog.xml
    2. Description: your choosing.  Something like "XNAT Archetype Catalog" is fine.
    3. Click OK twice.
  6. Choose the newly-created catalog from the drop-down list.  
  7. Choose the type of module you want (single or multi).  Click Next.
  8. Enter appropriate configuration info for the module (the following is just an example, adapt to your environment):
    1. GroupId: org.nrg.xnatx
    2. ArtifactId: myModule
    3. Version: 1.0.0-SNAPSHOT
    4. Package (default package for java classes): org.nrg.xnatx.myModule
    5. Click Finish.
That should give you the frame of your new XNAT module.

Exploring the New Module Project

Once this is completed, you'll have a folder named the same as your artifactId property, in this case foo. This will contain a pom.xml, as well as a number of subfolders and file resources:

foo/pom.xml
foo/project-dependencies/foo-dependencies.xml
foo/repository/org.foo/jars/bar.jar
foo/src/main/java/org/my/project/ModuleClass1.java
foo/src/main/resources/module-resources/conf/foo-config.xml
foo/src/main/resources/module-resources/foo.properties
foo/src/main/resources/module-resources/images/logo.gif
foo/src/main/resources/module-resources/pages/favicon.ico
foo/src/main/resources/module-resources/schemas/foo/foo.xsd
foo/src/main/resources/module-resources/schemas/xnat/xnat.xsd
foo/src/main/resources/module-resources/scripts/foo.js
foo/src/main/resources/module-resources/style/foo.css
foo/src/main/resources/module-resources/templates/screens/site_description.vm
foo/src/main/resources/module-resources/web-conf/web-projectMerge.xml
foo/src/test/java/org/my/project/ModuleClass1Test.java
foo/src/test/resources/ModuleTestResource.txt

The top-level pom.xml is your primary project definition, which configures:

  • Property values that are shared throughout any subprojects (xnat-module-archetype doesn't have subprojects by default, while xnat-multimodule-archetype does), including group ID and project versions
  • The preferred versions of external dependencies
  • The preferred version and configuration for plugins
  • Options for distribution management, i.e. how modules can be deployed to Maven repositories

As an aggregation project, the top-level project from the xnat-multimodule-archetype dictates what subprojects are included in a build. The project generated from the archetype references the newly created subproject project. When you rename the subproject project to something more useful, you'll also need to change the <module> element in the top-level project so that the name there reflects the change. Likewise, as you add more subprojects to a project, you'll need to add <module> elements to the top-level project in order for all of your projects to be picked up in a single build step.

Once you've created your project and one or more modules under the project, you can run your build. This is as simple as running the following command in the folder where your top-level project lives:

mvn clean package 

This will run Maven with the top-level project, which should in turn build all of the subprojects specified in the <modules> element in the top-level project definition.

This next section should hopefully be deprecated soon by a project-based ability to automatically stage the generated archives into your modules repository. It's just not there yet.

Once that build has completed, all of your projects will be compiled into the target folder underneath each of the subproject folders. The resulting archives will be named something like:

projectArtifactId -moduleArtifactId-version.jar

To stage your module, simple copy each of these archives into the folder specified by the xdat.modules.location property in your build.properties file. The next time you run setup or update, the new modules will get picked up and deployed as part of the build. This part of the process is described in the next section.

Staging Modules for Deployment

As noted earlier, modules are integrated into XNAT during the build and deploy phase of the process. The maven.xml that is invoked by the setup and update scripts manages this integration. To use this for deployment, you really only need to do two things:

  • Put your modules in a folder
  • Set the value of the xdat.modules.location property in the build.properties file to the location of the folder containing your modules

After setting this up, any time you run setup or update, your modules will be expanded and pushed into your project at build time.

For a bit more technical information, it's worth noting that xnat_builder actually looks in two places for modules:

  • The folder indicated by the xdat.modules.location property, AKA the custom modules repository
  • The modules folder located within your xnat_builder folder, AKA the default modules repository

If no value is specified for the xdat.modules.location property, module integration is still performed with any modules that may be stored in the default modules repository.

The idea is that, in many cases, on-going XNAT development can actually be done via module development and promoted from optional to standard features in XNAT simply by moving the modules into the default modules repository. SInce just about any type of core XNAT functionality can be packaged in a module, this provides a flexible way to provide optional or augmented service functionality that may eventually become a standard feature.

Known Issues

There are a number of issues with the current state of the modules functionality.

Java Development Is Difficult In Modules

Java code in modules essentially rides atop the full XNAT server code stack. The problem is that reliance on XNAT code is made difficult by the non-standard packaging (or lack of any packaging) of the XNAT code base. Most of the Java code in XNAT itself is built and pushed into the WEB-INF/classes folder of the web application and is never available as a jar for reference on the classpath. In addition, the folder structure of the builder differs from the standard Maven Java structure (src/java versus src/main/java), which makes integration into Maven-aware IDEs such as Eclipse with m2eclipse or IntelliJ IDEA difficult. There are changes under way to maven.xml to try to deal with these issues, including converting from Maven source paths and integrating XNAT classes into the IDE classpath.  In the meantime, there is a workaround for referencing XNAT code from your module.

Final Staging of Modules to Repository Doesn't Exist

As of now, you have to manually copy each generated jar archive from the target folder of each module into the modules repository. The goal is to have this as an automated part of the aggregated module build.

$label.name