Using node type inheritance for custom file validation

Use the Metrici forum to ask questions about Metrici.

To get started, read the About MetriciGetting started with Metrici and About the forum topics.

Signed in as Guest. Join

Inheritance lets you add additional functionality to existing components to build customer-specific capabilities.

We had an interesting requirement from a customer today.

The customer has a data collection application based on our Skipjack framework. Skipjack records significant events in an event log, and this customer uses event attachments to pass additional files to and from their partners.

A new requirement arose to create standard attachments that can be associated with multiple data collection activities. While they are setting up projects, the customer wants to be able to overwrite attachments, but once attachments have been associated with events, overwrites need to be prevented.

We could have written a whole bunch of new code to deal with these requirements, but in the end we managed it with a small extension to standard types.

The attachments are in a folder based on the File Package type. This is parameterised by inheriting from a separate Node Options node, setting two options:

  • Set the fileType to the custom file type (see below).
  • Set the overwrite option to true.

The requirement also required a custom file type, which is an inherited copy of the standard File type, with an additional field to manage the overwrite rules. The field is a text field, with a text type of single line text.

On this field, we added two scripts.

The first script, a Member Type Node Type Derivation Script (which adds code from the field to the node type), we used the Modify Type Script to add a trigger to the type to copy the standard file storage key to this field.

/**
 * Set a trigger on the node type to set this field
 */

application.include(application.getBinding('scriptModifyType'));

ModifyType.modifyScript({
  method: 'system.TYPE_TRIGGER_SCRIPT',
  fn: function(contextMemberType) {
    var trigger = application.get('trigger');
    if ( trigger == 'update' || trigger == 'insert' ) {
      var contextWritable = application.getNodeWritable(application.getContext());
      var fileStorageKey = contextWritable.getValue('system.FILE_STORAGE_KEY');
      if ( fileStorageKey == contextWritable.getValue(contextMemberType) ) {
        return;
      }  
      if ( fileStorageKey == null ) {
        contextWritable.deleteAll(contextMemberType);
      } else {
        contextWritable.setValue(contextMemberType,fileStorageKey);
      }
      if ( !contextWritable.apply() ) {
        contextWritable.prefixError('Unable to copy the file storage key to detect changes: ');
        application.mergeError(contextWritable);
      }  
    }  
  }  
});

Remember if you're writing a trigger script that updates the context node, you need a check to make sure the update hasn't already happened (in our case, this is the condition fileStorageKey == contextWritable.getValue(contextMemberType)). If you don't, the trigger will call itself recursively until its get an overflow condition.

The second script, a Member Type Validation Script, checks to see if the standard field storage key matches the stored copy. If it does, it checks to see if the file is in use (in this case, using an inbound links list on the Skipjack event attachment field), and if it is in use, raises a user error.

/**
 * Check that user is not attempting to update the file storage key if the file is in use as
 * an attachment.
 */

var mtEventAttachment = application.getBinding('mtEventAttachment');

var context = application.getContext();
var contextMemberType = application.get('contextMemberType');

function main() {
  var fileStorageKey = context.getValue('system.FILE_STORAGE_KEY');
  if ( fileStorageKey == null ) {
    return;
  }
  if ( fileStorageKey == context.getValue(contextMemberType) ) {
    return;
  }
  if ( context.listInboundLinks(mtEventAttachment).length == 0 ) {
    return;
  }
  application.mergeUserError('You may not upload this attachment again because it is already in use');
}

if ( !application.errorFound() ) {
  main();
}

Although we try to ensure the standard libraries cover the vast majority of requirements, there are always going to be some customer-specific needs that we can't meet in standard code. Often even complex customer requirements can be met using type inheritance and a small amount of scripting.