XNAT Plugin Configurations
The configuration of XNAT plugins is managed through an XNAT plugin class. An XNAT plugin class is written in Java and distinguished mainly by being annotated with @XnatPlugin. When XNAT starts up, it looks on its classpath, finds all of the installed plugin classes, and processes each one.
A plugin class serves a number of functions:
- It provides a unique identifier for the plugin that can be used to distinguish classes, properties, and other resources associated with the plugin from those provided by other plugins
- It can indicate which packages in the plugin should be scanned for data entities. These are classes that provide a way to persist data in the database without having to write code that performs database operations directly or any SQL. You can read a basic tutorial on creating data entities, as well as how to use the NRG Entity Service Framework on which most of XNAT's data entity services are based.
- It can identify XNAT data models and how they should be configured. XNAT data models differ from standard data entities: data models define the primary domain objects for XNAT, specifically medical imaging sessions and associated data, research projects and the subjects participating in a project, clinical instruments for evaluating subjects, and so on.
- The @XnatPlugin annotation itself includes the Spring Framework's @Configuration annotation. This gives you the ability to configure and initialize services, create event listeners and handlers, retrieve properties from configuration files, launch REST API controllers, and so on. It's really the launch point for a complete web application development framework and allows you to fully integrate your plugin into the XNAT platform and server architecture.
Creating an @XnatPlugin Class
Creating an @XnatPlugin class starts with creating the class itself. The code below shows a basic class declaration:
Basic Java class declaration
package edu.miskatonic.xnat.foo.plugin;
public class MyXnatPlugin {
}
This class declaration is so basic that it doesn't actually do anything. That's OK: an @XnatPlugin class can actually do a lot with very little. The next step is to make this an @XnatPlugin class by adding the annotation:
@XnatPlugin-annotation Java class
package edu.miskatonic.xnat.foo.plugin;
import org.nrg.framework.annotations.XnatPlugin;
@XnatPlugin(value = "myXnatPlugin", name = "My XNAT Plugin")
public class MyXnatPlugin {
}
At this point, you now have an XNAT plugin class. It still doesn't do much yet, but it does a lot more than it did before that annotation was added. By virtue of having that annotation, once you build your plugin XNAT can discover this class and will load it at start-up. The plugin will also appear in the list of loaded plugins in XNAT.
Configuring XNAT Data Models
XNAT data models represent the primary domain objects in XNAT. The core set of XNAT data models includes:
- DICOM imaging sessions with modalities including MR, PET, CT, and many many more
- Image assessors that provide context and analysis of your imaging sessions, such as automated or manual QCs, protocol validation, FreeSurfers, and more
- Subjects and projects that represent particular research efforts and their associated study cohorts
- Subject assessors that evaluate and analyze subject attributes, which can include simple demographics, complex family, economic, and social histories, drug and dietary histories, mental, psychological, and neurological assessment instruments, and many more
These data models represent the instruments and experiments that compose the heart of medical imaging research and are the primary deliverable of the XNAT system.
A data model itself is defined in a type of XML called an XML schema definition or XSD. This is an XML file that defines how other XML files should be composed, which includes defining the elements, attributes on those elements, which elements are valid within parent elements, and so on. In the XNAT context, these XSDs are known as data-type schemas.
Writing an XNAT data-type schema is an entire topic unto itself. See XNAT Data Type Development for documentation on creating one.
Presuming that you have one or more data-type schemas for your plugin, you can add them to the plugin and configure them easily.
First, the schemas must be placed in the appropriate location in your project. With the Gradle build structure, the location is:
src/main/resources/schemas/namespace/schema.xsd
In most cases, namespace and schema are actually the same. For example, the main XNAT data-type schema that includes its data types for MR and PET sessions, subjects, projects, and more, is in src/main/resources/schemas/xnat/xnat.xsd.
Second, you need to declare each of your data models with the @XnatPlugin's dataModels attribute. This takes one or more @XnatDataModel annotations as a value (if more than one @XnatDataModel annotations is specified, you must surround them with the '{' and '}' delimiters and separate each with a comma).
Let's suppose that your plugin has two data models, MyImageAssessor and MySubjectAssessor. Adding these to the @XnatPlugin annotation above would result in the following:
@XnatPlugin-annotation Java class
package edu.miskatonic.xnat.foo.plugin;
import org.nrg.framework.annotations.XnatDataModel;
import org.nrg.framework.annotations.XnatPlugin;
@XnatPlugin(value = "myXnatPlugin", name = "My XNAT Plugin",
dataModels = {@XnatDataModel(value = "myPlugin:myImageAssessor",
singular = "My Image Assessor",
plural = "My Image Assessors",
code = "MIA"),
@XnatDataModel(value = "myPlugin:mySubjectAssessor",
singular = "My Subject Assessor",
plural = "My Subject Assessors",
code = "MSA")})
public class MyXnatPlugin {
}
In combination with the processing XNAT performs on start-up with data-type schemas that it discovers, these data-model configurations will make your new data types available for use on your XNAT system.
Adding Data Entity Packages
Data entity classes provide a convenient way to store data in the database without having to write a bunch of database transactions, SQL, and other verbose and difficult-to-manage code. These are easier to write and manage than XNAT data objects, which makes them great for implementing infrastructure services in XNAT, but don't provide the deep integration into XNAT's core data model and related services, so they're not appropriate for creating data types.
The first thing you'll notice about a data entity class is that it will be annotated with the @Entity annotation:
package edu.miskatonic.xnat.foo.entities;
import javax.persistence.Entity;
@Entity
public class MyEntity {
}
However, this annotation isn't enough on its own for this class to be picked up by XNAT's transaction manager. The transaction manager has to know where to look for entity classes. Our particular implementation looks within a limited list of Java packages. You can add your package that contains data entity packages to that list just by adding the package to the entityPackages attribute on your @XnatPlugin annotation:
@XnatPlugin-annotation Java class
package edu.miskatonic.xnat.foo.plugin;
import org.nrg.framework.annotations.XnatPlugin;
@XnatPlugin(value = "myXnatPlugin", name = "My XNAT Plugin",
entityPackages = "edu.miskatonic.xnat.foo.entities")
public class MyXnatPlugin {
}
Note that this isn't enough to start using your entity classes: you'll also need a corresponding service and repository object for your entity. For more on this, refer to Data Persistence with the NRG Entity Service Framework. You'll also need to tell XNAT where to find your service and repository objects, as described in the next section.
Configuring and Initializing System Services
As noted earlier, @XnatPlugin includes the Spring @Configuration annotation, which means that you can easily create and inject components into XNAT's main application context. There are two ways to do this:
- The easiest way is by specifying packages that contain components in the @ComponentScan annotation. Any component classes in that package or any of its subpackages will be created automatically. Component classes are defined by being annotated by a Spring @Component-based annotation, which includes but isn't limited to:
- @Component
- @Repository
- @Service
- @Controller
- @RestController
- @XapiRestController
- @XnatPlugin
- You can also create your component or service explicitly with a method within your plugin class annotated with the @Bean annotation. This gives you a greater level of control over how your object is created, since you're writing the code for it directly.
The code below demonstrates both methods of creating an object.
@XnatPlugin-annotation Java class
package edu.miskatonic.xnat.foo.plugin;
import edu.miskatonic.xnat.foo.repositories.services.MyEntityService;
import org.nrg.framework.annotations.XnatPlugin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@XnatPlugin(value = "myXnatPlugin", name = "My XNAT Plugin",
entityPackages = "edu.miskatonic.xnat.foo.entities")
@ComponentScan("edu.miskatonic.xnat.foo.repositories")
public class MyXnatPlugin {
@Bean
public MyEntityService myEntityService() {
final MyEntityService service = new MyEntityService();
// You could do some configuration here.
return service;
}
}
This demonstrates a fairly common pattern, where entity classes are integrated into your plugin through the entityPackages attributes on the @XnatPlugin annotation, repositories are integrated through the @ComponentScan annotation (repositories are usually fairly simple and require no configuration), and services are constructed and injected directly (services are often integrated through component scans as well, but require configuration more often than repositories on average).
Configuring Custom Logging for Plugins
The @XnatPlugin annotation lets you specify the path to a logging configuration file, which you can use to configure logging for your plugin classes. Adding a logging configuration to your plugin differs a bit between XNAT versions:
- Versions prior to 1.7.6 use the log4jPropertiesFile attribute
- Versions 1.7.6 and later use the logConfigurationFile attribute
In both cases, the value set for the attribute should be the full path and filename to your logging configuration file within the packaged plugin. Generally, this means the relative path to the file starting at the source root of your project. The source root is almost always src/main/resources, so if your log configuration is in src/main/resources/edu/miskatonic/xnatx/plugin/plugin-logback.xml, your @XnatPlugin annotation would look something like this:
Adding logging configuration to @XnatPlugin
@XnatPlugin(value = "myXnatPlugin", name = "My XNAT Plugin",
logConfigurationFile = "edu/miskatonic/xnatx/plugin/plugin-logback.xml")
You can find guidance on writing a configuration file on the Logback web site. The only real restrictions on custom logging configurations are:
- Don't name it logback.xml and place it in the source root folder, e.g. src/main/resources/logback.xml. Or at least don't do that unless you want to override the primary XNAT logging configuration, since that's what that would do.
- Don't replicate logger or appender names from the primary XNAT logging configuration in your custom configuration. This will conflict when loading and cause your logging configuration to fail.
If you have an existing plugin that uses a log4j-style properties file that is referenced by the log4jPropertiesFile attribute, don't worry! XNAT still loads your configuration and translates the log4j properties settings to the logback format. It also writes the logback-formatted configuration to a file that you can use to migrate your plugin. The generated files are placed in a folder named logback-xxxx, where xxxx is a timestamp, located in the work folder in your xnat.home folder, and are named pluginId-logback.xml. You can copy the appropriate configuration file into your plugin, delete the existing log4j-based configuration, and change your @XnatPlugin annotation to reference the new logback-based configuration.