How to Create an XNAT Module - Developer Guide

Quick Index

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.

Why Modules?

Modules are a bridge between the existing XNAT customization capabilities and a future plugin architecture. Although modules do have the restriction of not being separable from your XNAT application once the application's been deployed, they have the advantage that they can be easily separated into separate functional groups, instead of needing to be pushed together into the previous projects folder format. This allows you to put all of your XNAT customizations into modules, place the module archives into the modules repository specified in your build.properties file, and run setup or update. This is a much cleaner separation of functionality and allows for easier exchange of data types, service enhancements, and so on.

Creating a Module

There are only two steps to creating a module, at least if you look at it from a high enough level:

  • Put some files in a folder structure
  • Create an archive–jar or zip will work–of that folder structure

Structuring a Module

The first part is the tricky one. You need to know where to put resources in order for them to be properly incorporated into the deployed XNAT application. For the most part, these details are beyond the scope of this documentation, since that pertains specifically to the requirements for customizing XNAT as described elsewhere. Generally, you should follow the structure of the existing resources in the projects folder after you've run setup once to initialize your XNAT deployment.

Folder Description
src/schemas Data-type definition schemas should be placed in here. Each schema should have its own subfolder of the same name, so, e.g., src/schemas/iq/iq.xsd. If you create your module project using the XNAT archetype, you can also include the src/schemas/xnat/xnat.xsd so that your XML editor can properly resolve built-in XNAT data types. The resource builder as defined in the archetype will ignore the XNAT schema and not replicate it in your module archive.
src/java Java classes can be placed here. You can override existing XNAT Java classes if necessary, but generally you should try to subclass existing classes or create entirely new classes.
src/templates Velocity templates should go in this folder. The templates folder has the highest precedence in the search order, so any template here that has the same path and name as an existing template will override that template. Accordingly, the templates in this folder will be located using the standard Turbine search rules that apply to the existing Velocity templates in base-templates, xdat-templates, and xnat-templates.
src/images Images can be referenced from any templates.
src/web-conf Any resources in here will be copied into the WEB-INF/conf folder of the deployed application. If you want to include Spring context configuration files that are processed on application start-up, you should name the file project-context.xml.
src/scripts Javascript scripts.
src/style CSS style sheets.
project-dependencies This should contain one or more dependency descriptor files using the Maven 1.0 format (you can find a sample in your XNAT installation in the file plugin-resources/project-skeletons/xnat/project-dependencies/dependencies.xml). The format of the file is as a fragment of XML as opposed to a fully validated XML file. Each element should be a dependency element describing one jar dependency contained in the repository folder. The dependency descriptor files should have names that match the pattern *-dependency.xml or *-dependencies.xml.
repository

This should contain the actual jar dependencies defined in your dependency descriptor files. These must follow the Maven 1.0 repository structure, that is:

repository/groupId/jars/artifactId-version.jar

The groupId, artifactId, and version must be the same as those specified in the dependency descriptor files.

Including modules into XNAT does not require you to have run setup previously. You can clone a clean copy of xnat_builder, create your build.properties file, and run setup and your modules will be integrated properly. Running setup is just a useful way to view the folder structure of the projects folders.

Suppose you have a module with a data type mine:someData, a custom Java class to handle the data type, a RESTful service to manage the data objects, customized screens to edit and display the data objects, and a Spring configuration file for some service that helps with the data. The module structure might look something like this:

Module Folder
  pom.xml
  repository/group/jars/item-1.0.jar
  project-dependencies/mine-dependencies.xml  
  src
    schemas/mine/mine.xsd
    java/org/nrg/xdat/om/MineSomedata.java
    java/org/nrg/xnat/restlets/extensions/SomeDataRestlet.java
    templates/screens/XDATScreen_edit_mine_someData.vm
    templates/screens/XDATScreen_report_mine_someData.vm
    web-conf/mine-context.xml

Adding Spring Configurations

Coming soon...

Adding Hibernate Classes

Coming soon...

Archiving a Module

Once you have your resources in your module structure, all you need to do is create an archive from the structure. This archive should include all of the path information of the resources relative to the top-level module folder. That is, when you list the archive contents, there should be a top-level src folder that contains all of the resources that are supposed to be located under src. The module functionality includes the ability to work with jar or zip archives.

Generally jar is the easiest cross-platform means of creating an archive (although zip is so ubiquitous as to make little difference, the syntax of the zip command does vary somewhat across platforms). To create a jar archive of your module directly, use the following command:

jar cf module.jar *

This will create the archive module.jar. Of course, name your jar as appropriate for its function.

You can create a zip by the same means. Using 7-Zip on Windows, the command would look something like this:

7z -r -tzip module.zip *

The last way to create a module archive, and probably the preferred method once it's been fully developed and validated in practice, is to use Maven to create a jar. This requires a Maven project (pom.xml) for your module project. See below for information on creating and managing a Maven module project.

Module Projects in Maven

Under Construction

The module Maven structure is still fairly unstable. The Maven archetype discussed below works to simply archive the module, but provides no support for compilation or unit testing. Future releases should greatly enhance this functionality, but may also change the contents of this documentation.

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 Maven archetype to make it easier to create a new module project in Maven.

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

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

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

Choose archetype:
1: http://maven.xnat.org/libs-snapshot-local -> org.nrg.xnat:modules-archetype (Archetype for an XNAT multi-module project.)

Enter the number corresponding to the org.nrg.xnat:modules-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-snapshot-local -> org.nrg.xnat:modules-archetype (Archetype for an XNAT multi-module project.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Downloading: http://maven.xnat.org/libs-snapshot/org/nrg/xnat/modules-archetype/1.0.0-SNAPSHOT/maven-metadata.xml
Downloaded: http://maven.xnat.org/libs-snapshot/org/nrg/xnat/modules-archetype/1.0.0-SNAPSHOT/maven-metadata.xml (758 B at 26.4 KB/sec)
Downloading: http://maven.xnat.org/libs-snapshot/org/nrg/xnat/modules-archetype/1.0.0-SNAPSHOT/maven-metadata.xml
Downloaded: http://maven.xnat.org/libs-snapshot/org/nrg/xnat/modules-archetype/1.0.0-SNAPSHOT/maven-metadata.xml (758 B at 32.2 KB/sec)
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: : Y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: modules-archetype:1.0.0-SNAPSHOT
[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] Parent element not overwritten in /Users/rherrick/Code/research/foo/subproject/pom.xml
[INFO] project created from Archetype in dir: /Users/rherrick/Code/research/foo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2:43.075s
[INFO] Finished at: Fri Mar 09 10:02:47 CST 2012
[INFO] Final Memory: 7M/81M
[INFO] ------------------------------------------------------------------------

Once this is completed, you'll have a folder named the same as your artifactId property. This will contain a pom.xml, as well as a subfolder named subproject with its own pom.xml and a copy of xnat.xsd:

pom.xml
subproject/pom.xml
subproject/src/schemas/xnat/xnat.xsd

The top-level pom.xml is both the parent project and a module aggregation project (refer to the Maven documentation for Introduction to the POM for more on parent and aggregation projects). As a parent project, the top-level projects configures:

  • Property values that are shared throughout all subprojects, 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 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.

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.

Module Developer Tips

  • Think outside your box. Most module development springs from customization that you have done to your own XNAT environment, which you now want to share to the world. However, not all XNATs are the same. For example, the root URL of some XNATs will be "/" while others will be "/xnat/". If you have code that uses relative paths, use the js variable serverRoot in your code so it will work across any XNAT setup. 
  • Be sure to account for and document all dependencies in your XNAT environment that affect your module. Can this module be useful outside that environment? If so, what customization might need to be done? 

$label.name