The Structured Narrative Dataset (SND) module was built as an open data module to gather, organize and store data of any type. To allow customization of the business logic in data gathering, the module has a trigger mechanism that allows customized code in other LabKey modules to validate, update or perform other custom actions when data is entered via the SND saveEvent API.

Example Use Cases

  • A physical exam might differ depending on the gender of the subject. On saving this data via the saveEvent API, a trigger can inspect the incoming data or query the database for the gender of the subject and verify the correct physical exam data is being entered. The trigger can then accept or reject the data and provide an error message back to the API caller.
  • Blood draws might need to follow specific guidelines based on the health of the subject and history of blood draws. A trigger script could query the database for health information and perform the necessary calculations for the blood draw data being entered and provide a warning if approaching or exceeding any limits.
  • Data taken with different instrumentation could have different units of measurement. A trigger could inspect incoming data to see the units of measurement and convert the values to a different unit of measurement before saving in the database.
  • To provide additional auditing beyond the normal SND audit logs, a trigger could insert or log additional data about the event data being entered.

Event Trigger Mechanism

The event triggers are called in the SND saveEvent API, right after verifying category permissions (see SND security) and before any base SND validation. This ensures no information is passed to unauthorized users in warning or error messages, but allows the triggers to perform business logic or validation before the base SND validation and before the data is persisted in the database.

The triggers are mapped to package categories. When event data is entered, the packages associated with the data are inspected for categories that map to triggers. Only those triggers for the incoming package categories are executed. This allows package designers to add or remove business logic and validation just by adding or removing categories in the package designer UI.

Order of Execution

Because event triggers can be defined at any level of a super package and multiple triggers can be defined for a single package, the following rules apply to the order of execution.

  1. Execution starts at the deepest leaf nodes and then works up the tree of event datas (super package hierarchy), performing a reverse breadth first search. This allows business logic at the most basic package level to be performed first before the higher super package levels.
  2. Sibling event datas (packages) will execute triggers in the order of their sort order in the super package table. This is the order they are listed in the Assigned Subpackage part of the package UI.
  3. Top level packages will execute triggers in order of superPkgId.
  4. A developer can manually define the order of execution of triggers within a package. For example, if a package contains multiple categories that align with triggers, the user can implement a numerical order of those in the triggers themselves (getOrder function). See Creating Triggers below.

Trigger Factory

Create and Register Trigger Factory (First time setup)

The first step in setting up the SND triggers is creating a trigger factory. This factory class will map category names to trigger classes. This should be created in the module that will be using the SND module (created outside the SND module) and should implement the EventTriggerFactory interface from the SND module. The only function that is required in this factory is the createTrigger function which takes a String that is a category name and returns an Object that implements interface EventTrigger (more on this below). The mapping of categories to event triggers is completely independent of the SND module, and can be customized to fit each usage. A simple way is just to use a case statement mapping the strings to new instances of the triggers.

The lifecycle of each SND trigger is intended to be for one insert/update occurrence, so the SND module will call the factory createTrigger function for each package category in the incoming event data for every insert/update occurrence. Because of this, and to prevent issues with artifact data, it is recommended to return a new instance of the EventTrigger classes from the createTrigger function. See SNDTestEventTriggerFactory for an example.

Once the factory has been created, it will need to be registered in the SND module. SNDService has the function registerEventTriggerFactory which takes the module containing the trigger factory and a new instance of the trigger factory itself as parameters. Multiple factories can be registered, but only one per module. The trigger factories will be executed in dependency order starting at the leaf nodes.

Creating Triggers

Once the factory has been set up, triggers can be created and added to the factory. The trigger classes should be created in the same module as the trigger factory and implement the EventTrigger interface. Two functions must be implemented from the EventTrigger interface and a third optional function is available,

  • onInsert (Required): This function is called on fired triggers when new event data is being entered.
  • onUpdate (Required): This function is called on fired triggers when updating existing event data.
  • getOrder (Optional): Used when multiple triggers are defined on the same package (multiple categories mapped to triggers assigned to same package). Triggers with order defined will take precedent over those with no order. If getOrder returns null, triggers execute in alpha numeric order of the trigger class. Default is null.
The parameters for these two functions are:
  • Container: The project or folder being used for the data entry. Use this for container scoped queries or any other container scoping.
  • User: The user performing the data entry. Use this for permission checks.
  • TriggerAction: This contains the data that might be of interest for validation and business logic. Those fields are:
    • Event: Event class in the SND API which is a java representation of the entire event.
    • EventData: EventData class in the SND API. This is the event data associated specifically with the package which contains the category that this trigger is associated with. While this data is part of the Event, this is provided for convenience and performance to not have to search the Event for the applicable EventData.
    • TopLevelSuperPackages: This is a map of SuperPackages, from the SND API, that represents the top level super packages in the event. The super packages contain their full hierarchies. The key in the map is the EventData id associated with each package.
    • SuperPackage: SuperPackage containing the category that mapped to the trigger. This is another convenience and performance field so searching the TopLevelSuperPackages is not required.
  • ExtraContext: This is a map that can be used to pass data between triggers of the same event insert/update occurrence.
Once the triggers are created, their mapping to a category should be added to the trigger factory in the respective module. Now the triggers are associated with the category and will fire for any packages using that category.

Trigger Validation

Custom validation can be done in event triggers, and JSON passed back to the saveEvent API caller will indicate how many validation issues there were as well as exactly where in the data the validation failed. Assuming the JSON passed into the saveEvent API is parseable, syntactically correct, has required data values and correct types, the saveEvent API will always return the event JSON data back to the caller whether the saveEvent call succeeds or fails. On failure, the JSON data will have an exception at the event level either explaining the reason for failure or giving a count of exceptions within the JSON. These exceptions within the JSON will be exception fields within the data object that caused the exception. For example:

event: {
eventId: 1,

exception: { // Event level exception
severity: “Error”,
message: “1 error and 1 warning found”
eventData: [{
superPkgId: 1,
sortOrder: 1,
exception: { // EventData level exception
severity: “Warning”,
message: “This is a warning”
attributes: [
propertyName: “amount”
value: “200”
exception: { // AttributeData level exception
severity: “Error”,
message: “This is an error”

If there is not an exception specifically set at the Event level but there are EventData or AttributeData exceptions, the Event level exception will be set to the highest severity of the EventData or AttributeData level exceptions and the exception message with be the counts of the types of exceptions (as shown above).

In the trigger, if validation is deemed to have failed or any kind of message needs to be returned to the caller of the SND saveEvent API, then Event.setException, EventData.setException or AttributeData.setException can be used to set an exception at those levels in the JSON (see JSON example above). The exceptions take a ValidationException which can be three different types:

  • Error: This is the most severe. If an error is found on any of the data types then the saveEvent will not proceed beyond the trigger validation and will return the input event with the errors.
  • Warning: This will not cause the saveEvent API call to fail but will return a warning exception with the returned event.
  • Info: This is the least severe. This will not cause the saveEvent API call to fail but will return an info exception with the returned event.
See example triggers in the SND module, sndsrcorglabkeysndtriggertesttriggers

Related Topics

Was this content helpful?

Log in or register an account to provide feedback

expand allcollapse all