This page describes how to create modules to extend the core XNAT service, including adding such features as:
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.
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.
There are only two steps to creating a module, at least if you look at it from a high enough level:
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.
|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/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.|
This should contain the actual jar dependencies defined in your dependency descriptor files. These must follow the Maven 1.0 repository structure, that is:
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
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.
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:
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:
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.
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:
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:
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.
There are a number of issues with the current state of the modules functionality.
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.
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.