Step 3 of 10 Adding new data types
Goal
Create a new XNAT data type and add it to your plugin.
The core XNAT data types–those that represent imaging sessions, study subjects, subject assessors, and so on–are represented in the system as XML objects. The XML that defines these objects is, in turn, defined by an XML data schema or XSD. Creating a new data type in XNAT consists of creating the XSD that defines the data type itself. Creating a new data type in an XNAT plugin consists of putting the schema file into the appropriate place in your built-out plugin jar.
For this exercise, we just want to demonstrate how data types fit into a plugin rather than getting into creating a complex data type, so we'll create something relatively simple, an assessor that records whether five separate biological specimens were collected from a research subject during a visit. We'll also include a more complex data type for recording the results of a radiology read on an imaging session, but the schema for that will be provided for you and just needs to be included in your project.
The most important thing about data-type schemas, from the point of view of including them in a plugin, is where to put the schema files in the plugin project structure. Gradle expects a standard directory layout when building a project's source. The root folder for these is named src. Under src, you can have two folders, main and test. Although writing test code for your project is a noble and admirable pursuit, we'll leave promoting and explaining that to others and focus solely on the contents of the main folder.
Within your main development folder, there can be a large number of subfolders depending on the types of data handled by the plugins configured in your build.gradle project. In the case of the java plugin, the supported subfolders are java and resources. You've already created a file under the java folder. The purpose of that folder is strictly to contain your Java classes. The purpose of the resources folder, on the other hand, is to contain pretty much everything else. XNAT data-type schemas fall under the rubric of "pretty much everything else", so that's where you'll start. Go ahead and create the resources folder next to the java folder.
If you'd like to follow along rather than create these schema on your own, you can get the workshop schemas attached to this page and unzip them into your project. If you extract these at the root of your plugin folder, they'll be extracted into the correct location.
Now you can create the data-type schema file itself. XNAT looks for schema files that are stored in this structure:
schemas/schema/schema.xsd
Let's call this schema workshop, which means you'll create the new file schemas/workshop/workshop.xsd. Remember that this is underneath src/main/resources, so you've added the file src/main/resources/schemas/workshop/workshop.xsd.
As for the contents of the schema, let's start with the most basic element of an XSD file, <xs:schema>:
Basic XSD file
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://nrg.wustl.edu/workshop"
xmlns:workshop="http://nrg.wustl.edu/workshop"
xmlns:xnat="http://nrg.wustl.edu/xnat"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
</xs:schema>
This is mostly boilerplate text for an XML XSD file. The only things that are specific to the data type we're going to define are:
- The value set for the targetNamespace attribute defines the default namespace for your schema.
- The xmlns:workshop and xmlns:xnat attributes define URIs that can be used to uniquely identify schemas that use a particular data type. This lets you change the prefix (in this case, workshop and xnat) but still be able to recognize the relationship to the same schema definition. Note that the URI used for this does not need to resolve to a working accessible web address, even though it can.
Next you need to import the core XNAT data-type schema:
Import XNAT schema into XSD
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://nrg.wustl.edu/workshop"
xmlns:workshop="http://nrg.wustl.edu/workshop"
xmlns:xnat="http://nrg.wustl.edu/xnat"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import namespace="http://nrg.wustl.edu/xnat" schemaLocation="../xnat/xnat.xsd"/>
</xs:schema>
In this step, notice that the namespace attribute uses the same URI used by the xmlns:xnat attribute in the <xs:schema> element. This tells the XML parser that references to elements within the xnat namespace should be validated against the schema found at the indicated location. In the case of your plugin, the location doesn't really indicate whether the XNAT schema can be found now (although you can copy that file there in order to let any XML editor–including the built-in XML editors in IntelliJ IDEA or Eclipse–properly resolved the xnat namespace and provide assistance in locating XML elements, attributes, and the like), but, more importantly, where the XNAT schema will be found once your schema is deployed into the XNAT application. Since all data-type schemas in XNAT are located in schemas/schema/schema.xsd, any other schema you want to reference will be located at ../schema/schema.xsd.
Now you can add the element declaration for your data type. There are a number of different ways you can do this in XSD, but the standard XNAT method declares the element, sets the element name, then references a separate element definition:
Data-type element declaration
<xs:element name="BiosampleCollection" type="workshop:biosampleCollection"/>
This just declares the BiosampleCollection element and says that it's of type workshop:biosampleCollection. The workshop at the beginning means that the type is declared here in our current namespace, while biosampleCollection indicates the actual type.
Now you can define the actual element type, which is where you'll define any base data type for the element type and all of the attributes specific to your data type. In this case, we're creating a subject assessor data type (which just means a data type that contains data about one of a research subject). The basic declaration of the element type looks like this:
Element type definition
<xs:complexType name="biosampleCollection">
<xs:complexContent>
<xs:extension base="xnat:subjectAssessorData">
<xs:sequence>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
For now, just treat this as boilerplate code and copy and paste it whenever you need to create a new data type, modifying just the type name and base data-type definition. If you're interested in understanding this structure in more detail, just ask one of the XNAT team who can help you with resources, including pointers to some extremely complex data types.
You now have a biosampleCollection data type! It may not appear to do all that much in and of itself, but it's already a subject assessor, which brings a lot of power and functionality from the start. But to make it tell you something about whether or not biological samples were collected from a subject, you need to add some properties to the data type that can store what you want to know.
The main reason the biosampleCollection data type was chosen for this exercise is because of the simplicity of the properties: they're all boolean yes/no properties: was a DNA sample collected from the subject, was an RNA sample collected, plasma, serum, craniospinal fluid? The answer in each case is just yes or no. Defining a boolean attribute for your data type looks like this:
Defining a boolean property
<xs:element name="dna">
<xs:simpleType>
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:element>
This shows the definition of the dna attribute. This basically defines a simple (i.e. atomic) type that is a boolean. Create four more of these with the names rna, plasma, serum, and csf. You'll end up with the full XSD below:
Completing the workshop XSD
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://nrg.wustl.edu/workshop" xmlns:workshop="http://nrg.wustl.edu/workshop" xmlns:xnat="http://nrg.wustl.edu/xnat" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import namespace="http://nrg.wustl.edu/xnat" schemaLocation="../xnat/xnat.xsd"/>
<xs:element name="BiosampleCollection" type="workshop:biosampleCollection"/>
<xs:complexType name="biosampleCollection">
<xs:annotation>
<xs:documentation>Indicates whether the required biosample collections have been acquired per study protocol.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="xnat:subjectAssessorData">
<xs:sequence>
<xs:element name="dna">
<xs:annotation>
<xs:documentation>DNA</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:element>
<xs:element name="rna">
<xs:annotation>
<xs:documentation>RNA</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:element>
<xs:element name="plasma">
<xs:annotation>
<xs:documentation>Plasma</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:element>
<xs:element name="serum">
<xs:annotation>
<xs:documentation>Serum</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:element>
<xs:element name="csf">
<xs:annotation>
<xs:documentation>CSF</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Now add the data-type schema radRead.xsd that's attached to this page (workshop.xsd is also attached to this page). The radRead.xsd file should be placed into src/main/resources/schemas/radRead/radRead.xsd.
Programming XNAT