...
We are proposing to use JSON Schema 03 to define both an Entity and the Annotations of an Entitytype. The JSON Schema breaks an object definition into two major categories; properties and additional properties.
...
Code Block |
---|
{ "name":"Product", "properties":{ "id":{ "type":"number", "description":"Product identifier", "required":true }, "name":{ "description":"Name of the product", "type":"string", "required":true }, "price":{ "required":true, "type": "number", "minimum":0, "required":true }, "tags":{ "type":"array", "items":{ "type":"string" } } }, "additionalProperties":{ "releaseStatus":{ "type":"string", "description":"The release status of a product", "enum":[ "PROTOTYPE", "RELEASED", "RECALLED", "DEPRECIATED"] } }, # not used... "additionalProperties":{ } } |
In the above example, we can seen an how various types of data can be defined for a Product using the JSON Schema. For example, "id" is a number and required, while "releaseStatus" is an enumeration of strings.
The following pseudo-code snippet shows a partial implementation of the json-schema-03 represented as a Model class:
Code Block |
---|
// This class represents a JSON Schema as defined by: http://tools.ietf.org/html/draft-zyp-json-schema-03
class ObjectSchema {
...
// This map defines the primary fields of an Object
Map<String, ObjectSchema> properties;
// This map defines the additional Annotations of an Object.
Map<String, ObjectSchema> additionalProperties;
...
}
|
In the above class each property has a name and schema that defines the property. We are proposing to use the ObjectSchema.properties to define the primary fields of a Synapse Entity, and the ObjectScheam.additinalProperties to define the Annotations of a Synapse Entity.
There is a lot more detail to the JSON Schema definition that we will not cover here.
Schema Life-cycle
For the We are proposing to use the "properties" to define the primary fields of a Synapse Entity. These primary fields can be considered the expected data of all instances of a given entity. Using the Product example from above, this implies that all instances of Product would have "id", "name", "price" and "tags.
Initially we were planning to use "additinalProperties" to define the Annotations of a Synapse Entity, but this raised a fundamental issue. If the Annotations of an entity are provided for ad-hock user data, then formally defining them in the entity schema for all instances of a type seems like a poor fit. That said, we still have many use cases where we want to constrain the data of an annotation when they are added to an instance of an entity. Therefore, we are positioning that these annotation types are set on a per-instances basis rather than at the entity schema level. Annotation types are covered in a separate document: Proposal for Annotation Types
Schema Life-cycle
For the initial implementation we are proposing that an Entity Schema can only be defined and edited as part of the compile of synapse. This means run-time edits or additions to each schema will not be possible. The reason for this limitation is to keep the Life-cycle of the schema as simple as possible. As we will see, the life-cycle is already complicated even with this limitation.
...
Code Block |
---|
/lib-auto-generated/src/main/resource/org/sagebionetworks/annotation/types/BioOntologyTissueTypeVertebrateOrganType.json |
Before we look at the definition of our Example.json let's first look at the definition of our new BioOntologyTissueTypeVertebrateOrganType.json. For this example we want to use an external the Basic Vertebrate Anatomy ontology to control define the valid values for Organs:
BioOntologyTissueTypeVertebrateOrganType.json
Code Block |
---|
{ "type":"string", "format":"uri", "enum":["NCBO "XQUERY":" SELECT fullId FROM doc(http://rest.bioontology.org/bioportal/concepts/4531?conceptid=tbio:Organ&ligthlight=1&apikey=2fb9306a-7f3f-477a-821e-e3ccd7356a18 "] )/success/data/classBean/relations/entry[string=Subclass]/list/classBean/fullId" ] } |
In this example, we can see that we expect the enumeration values to be URI strings limited to the values returned from a SPARQL query. In this case, the SPARQL query translates to the following:
Code Block |
---|
Find all URIs from the http://purl.bioontology.org/ontology that are classified as "Is A" Tissue (Tissue is defined as http://purl.bioontology.org/ontology/MCCL/FMA_9637).
|
Now that we have defined an Annotation Type for Tissue's defined by the http://purl.bioontology.org/ ontology, we can now use this type to define an entity.
Here is our definition of our example Entity:
Example.json
...
are defined by an XQuery that is used to get the "fullId" (URIs) of all Sub-classes of the Term "Organ" using the XML returned from NCBO's BioPortal Term services. Here is the XML returned by the term service for this exampl: http://rest.bioontology.org/bioportal/concepts/4531?conceptid=tbio:Organ&light=1&apikey=2fb9306a-7f3f-477a-821e-e3ccd7356a18.
Assuming the XQuery is setup correctly, the effective enum definition for this type would be"
Code Block |
---|
"enum":[
"http://www.co-ode.org/ontologies/basic-bio/basic-vertebrate-gross-anatomy.owl#Heart",
"http://www.co-ode.org/ontologies/basic-bio/basic-vertebrate-gross-anatomy.owl#Pericardium",
"http://www.co-ode.org/ontologies/basic-bio/basic-vertebrate-gross-anatomy.owl#Brain",
"http://www.co-ode.org/ontologies/basic-bio/basic-vertebrate-gross-anatomy.owl#Stomach",
"http://www.co-ode.org/ontologies/basic-bio/basic-vertebrate-gross-anatomy.owl#Lung",
"http://www.co-ode.org/ontologies/basic-bio/basic-vertebrate-gross-anatomy.owl#Liver",
]
|
Now that we have defined an Annotation Type for Organ using the ontology we can use this type in the definition of the entity.
Here is our definition of our example Entity:
Example.json
Code Block |
---|
{ "extends":"org/sagebionetworks/entity/type/Entity.json""name":"Product", "properties":{ "id":{ "descriptiontype":"number"Name, of the Example", "typedescription":"stringExample identifier", "required":true }, }, "additionalPropertiesname":{ "tissue":{ "type":"object", "$ref":"org/sagebionetworks/annotation/types/BioOntologyTissueType.json" "description":"Name of the Example", "type":"string", } "required":true } } |
...
,
"organ":{
"$ref":"org/sagebionetworks/annotation/types/VertebrateOrganType.json"
}
}
}
|
The first thing to point out about our Example.json is that it extends Entity.json, which makes it a Synapse Entity. This implies it inherits all of its values from the base Entity. The second thing to point out is that it defines a type called "tissue" in its additional properties that is based on the "organ" property is defined using the annotation type we defined created earlier.
Compile JPJOs (first time)
Since we still want Java POJOs to represent all entities, we will use the schema-to-pojo-maven-plugin to build these POJOs. This is done by simply added the following to the lib-auto-generated/pom.xml file:
Code Block |
---|
<!-- This plugin builds the POJOs from JSON schemas. --> <plugins> <plugin> <plugin> <groupId>org.sagebionetworks</groupId> <artifactId>schema-to-pojo-maven-plugin</artifactId> <version>${schema-to-pojo.version}</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <sourceDirectory>src/main/resources</sourceDirectory> <packageName>org.sagebionetworks</packageName> <outputDirectory>target/auto-generated-pojos</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> |
The plugin will automatically create a POJOs class for each JSON schema found in the resource directory. These POJOs will be placed in the target/auto-generated-pojos directory.
...
Code Block |
---|
root/schemas/org/sagebionetworks/entity/type/Example.json root/schemas/org/sagebionetworks/annotation/types/BioOntologyTissueTypeVertebrateOrganType.json |
Folder entities will be created as need to create each path. By giving each SchemaEntity a unique path, we can use this path to reference a schema before we have an entity to represent it.
...
We want to add a new required primary field called "status". Since "status" is required, we must provide a default value. This is a requirement because we already have instances of Example entities deployed to Synapse, and each of these must be given a default value. We will cover how these default values are applied shortly. Here is our new Example.json:
Example.json
...
Example.json:
Example.json
Code Block |
---|
{ "extends":"org/sagebionetworks/entity/type/Entity.json""name":"Product", "properties":{ "id":{ "type":"number", "description":"Example identifier", "required":true }, "name":{ "description":"Name of the Example", "type":"string", "required":true }, "extends":"org/sagebionetworks/entity/type/Entity.json"organ":{ "name$ref":"Product", "properties":{org/sagebionetworks/annotation/types/VertebrateOrganType.json" "id":{ }, "typestatus":"number",{ "descriptiontype":"Example identifierstring", "required":true, }, "enum":[ "name":{ "description":"Name of the Example"PROTOTYPE", "type":"string", "requiredRELEASED":true , }, "status":{ "type":"stringRECALLED", "required":true, "enum":[ "PROTOTYPE", "RELEASED", "RECALLED", "DEPRECIATED"], "default":"PROTOTYPE" }], }, "additionalProperties":{ "tissue":{ "type"default":"objectPROTOTYPE", "$ref":"org/sagebionetworks/annotation/types/BioOntologyTissueType.json" } } } |
Compile POJOs (Nth Time)
...