Name

sn_mab_api.ConfigRetrievalService

Description

The script include used for mobile configuration retrievals

Script

var ConfigRetrievalService = Class.create();
ConfigRetrievalService.prototype = {
  initialize: function () {
      this.configKeyFactory = new sn_mab_api.ConfigKeyFactory();
      this.daoCache = new sn_mab_api.MobileDAOCache();
      this.configKeyStackCreator = new sn_mab_api.ConfigKeyStackCreator();
      this.configResponse = new sn_mab_api.ConfigResponse();
      this.validationHandler = new sn_mab_api.ValidationHandler();
      this.errorHandler = new sn_mab_api.ErrorHandler();
      this.alertHandler = new sn_mab_api.AlertHandler();
      this.configKeyStack = [];
  },

  retrieveConfig: function (tableName, sysId, externalNodes) {
      this.validateInput(tableName, sysId);

      var isFloor = false;
      if (externalNodes && externalNodes.length) {
          this.configResponse = new sn_mab_api.ConfigResponse(externalNodes);
          this.configKeyStack = this.createStackFromNodes(externalNodes);
          isFloor = externalNodes[externalNodes.length - 1].isFloor;
      }

      this.processConfiguration(tableName, sysId, isFloor);
      this.configResponse.preSerialize();
      return this.configResponse;
  },


  validateInput: function (tableName, sysId) {
      if (!tableName || !this.validationHandler.isValidTable(tableName))
          this.errorHandler.throwBadRequestError('Table name missing or does not exist: ' + tableName);

      if (!this.validationHandler.isGeneratedOrNormalSysId(sysId))
          this.errorHandler.throwBadRequestError('SysId missing or does not exist: ' + sysId);

      var dao = new sn_glide_ms_api.MobileAppBuilderDAO(tableName);
      var record = dao.getRecord(sysId);
      if (record == null || !record.isValidRecord())
          this.errorHandler.throwNotFoundError('Record does not exist for table: ' + tableName + ' and id: ' + sysId);
  },

  createStackFromNodes: function (externalNodes) {
      return this.configKeyStackCreator.createStackFromNodes(externalNodes);
  },

  //TODO: extra lookups are being performed because we can't pass in a GR directly to this function
  //need to consider modifying the java dao so that we can pass it around with a loaded 'curr' object similar to a normal GR
  processConfiguration: function(tableName, sysId, isFloor) {
      // handles the case where a we want to get the tableName of a child table
      tableName = this.getSysClassName(tableName, sysId);

      //Have we already processed this node?
      var wasProcessed = this.configResponse.exists(tableName, sysId);
      //Create our output node and add it to response, otherwise return on error
      var currConfigNode = this.addConfigNodeResponse(tableName, sysId);

      if (isFloor) {
          this.addConfigFloorNode(tableName, sysId);
      }
      this.addConfigNodeLink(tableName, sysId, isFloor);
      if (!currConfigNode || wasProcessed || isFloor)
          return;

      //Get our configMetadata
      var configMetadata = this.getConfigMetadata(tableName, sysId);

      var curNode = new ConfigKeyStackEntry(configMetadata.getConfigKey(), tableName, sysId);

      //Push ourselves onto the configKeyStack
      this.configKeyStack.push(curNode);

      this.processCurrentNode(configMetadata, currConfigNode);

      //Pop ourselves off the configKeyStack
      this.configKeyStack.pop();
  },

  getSysClassName: function (tableName, sysId) {
      var record = this.daoCache.getDAO(tableName).getRecord(sysId);

      if (!record || !record.sys_class_name)
          return tableName;

      return record.sys_class_name.value;
  },

  processCurrentNode: function (configMetadata, currConfigNode) {
      //Handle our references first
      configMetadata.references.forEach(function (currReference) {
          this.processReferences(currReference, currConfigNode);
      }, this);

      //Handle our relationships
      configMetadata.relationships.forEach(function (currRelationship) {
          this.processRelationships(currRelationship, currConfigNode);
      }, this);
  },

  getConfigMetadata: function (tableName, sysId) {
      var configMetada = this.configKeyFactory.getGenerator(tableName).getTreeConfigurationViaSysId(sysId);
      if (!configMetada) {
          gs.warn(gs.getMessage('ConfigRetrievalService was unable to retrieve the config metadata for table: {0}, id: {1}', tableName, sysId));
          //get empty config metadata
          configMetada = new sn_mab_api.TreeConfigurationNode();
      }
      return configMetada;
  },

  processReferences: function (currReference, configNode) {
      //Verify that the referenced field exists and extract its tableName
      var refTableName = undefined;
      var refSysId = undefined;
      var nodeData = configNode.data;
      if (nodeData && nodeData[currReference.name]) {
          refTableName = nodeData[currReference.name].referenceTable;
          refSysId = nodeData[currReference.name].value;
      }

      if (!refTableName || !refSysId) {
          gs.warn(gs.getMessage('ConfigRetrievalService was unable to retrieve the reference: {0} for table: {1}, id: {2}', currReference.name, configNode.tableName, configNode.sysId));
          return;
      }
      refSysId = refSysId.split(',');
      refSysId.forEach(function (referenceSysId) {
          if (!referenceSysId || referenceSysId.length <= 0)
              return;

          // no conditional so process next configuration
          if (!currReference.conditional) {
              this.processConfiguration(refTableName, referenceSysId, false);
          } else {
              // conditional evaluated to true so process next node
              if (this.evaluateConditional(currReference.conditional, refTableName, referenceSysId)) {
                  this.processConfiguration(refTableName, referenceSysId, false);
                  // conditional evaluated to false but a conditional existed so we process direct children
              } else {
                  this.processConfiguration(refTableName, referenceSysId, true);
              }
          }
      }, this);
  },

  evaluateConditional: function (conditional, tableName, sysId) {
      var configMetadata = this.getConfigMetadata(tableName, sysId);
      var evaluation = false;

      //If we find the metadata we can try the conditional evaluation
      if (configMetadata && this.evaluateCondition(conditional, this.configKeyStack, configMetadata))
          evaluation = true;

      return evaluation;
  },

  evaluateCondition: function (conditional, configKeyStack, configMetadata) {
      return ConditionalEvaluator.evaluate(conditional, configKeyStack, configMetadata);
  },

  getConfigData: function (tableName, sysId) {
      return this.daoCache.getDAO(tableName).getRecord(sysId);
  },

  processRelationships: function (currRelationship, configNode) {
      //Verify that the reference fields exist and extract them
      var refTableName = currRelationship.remoteTableName;
      var refLocalSysIdField = currRelationship.localRefFieldName;
      var refRemoteSysIdField = currRelationship.remoteRefFieldName;
      var isM2MTable = refLocalSysIdField ? true : false;

      if (!refTableName || !refRemoteSysIdField) {
          gs.warn(gs.getMessage('ConfigRetrievalService was unable to proess the relationship: {0} for table: {1}, id: {2}', currRelationship.name, configNode.tableName, configNode.sysId));
          return;
      }

      var encodedQuery = (isM2MTable ? refLocalSysIdField : refRemoteSysIdField) + '=' + configNode.sysId;
      var relationshipEntries = this.daoCache.getDAO(refTableName).getRecordsByEncodedQuery(encodedQuery);
      //No records were found that match - we return
      if (!relationshipEntries)
          return;

      //Iterate through each M2M or 12M table's entries
      relationshipEntries.forEach(function (currEntry) {
          var resolvedTableName = refTableName;
          var resolvedSysId = currEntry.sys_id.value;
          var m2mTableName = resolvedTableName;
          var m2mTableSysId = resolvedSysId;

          if (isM2MTable) {
              //If we have an M2M table we need to follow the refRemoteSysIdField to get our node
              if (currEntry[refRemoteSysIdField]) {
                  resolvedTableName = this.getRefTableName(currEntry, refRemoteSysIdField);
                  resolvedSysId = currEntry[refRemoteSysIdField].value;
              } else {
                  //this else block handles an m2m node without a child so the currEntry(refRemoteSysIdField) check fails because it is undefined
                  this.addConfigNodeResponse(m2mTableName, m2mTableSysId);
                  this.addConfigNodeLink(m2mTableName, m2mTableSysId);
                  this.addEmptyM2MAlert(m2mTableName);
                  return;
              }
          }

          //If we cant figure out the resolved tableName/sysId we break out
          if (!resolvedTableName || !resolvedSysId)
              return;

          var isFloor = false;
          if (currRelationship.conditional) {
               if (!this.evaluateConditional(currRelationship.conditional, resolvedTableName, resolvedSysId)) {
                   isFloor = true;
               }
          }

          //If we have a M2M push the M2M onto the stack and output
          if (isM2MTable) {
              this.addConfigNodeResponse(m2mTableName, m2mTableSysId);
              this.addConfigNodeLink(m2mTableName, m2mTableSysId);
              //Get our configMetadata
              var m2mConfigMetadata = this.getConfigMetadata(m2mTableName, m2mTableSysId);
              //Push ourselves onto the configKeyStack
              this.configKeyStack.push(new ConfigKeyStackEntry(m2mConfigMetadata.getConfigKey(), m2mTableName, m2mTableSysId));
          }

          //Process our main node
          this.processConfiguration(resolvedTableName, resolvedSysId, isFloor);

          //Pop the M2M from the stack if it exists
          if (isM2MTable)
              this.configKeyStack.pop();

      }, this);
  },

  getParentNode: function () {
      if (this.configKeyStack.length) {
          return this.configKeyStack[this.configKeyStack.length - 1];
      }
  },

  getRefTableName: function(record, refFieldName) {
      var refField = record[refFieldName];
      var refTable = null;
      if (refField.type === 'reference')
          refTable = refField.referenceTable;
      else if (refField.type === 'document_id') {
          var dependentField = this.getDepFieldRefTableName(record.sys_class_name.value, refFieldName);
          refTable =  dependentField ? record[dependentField].value : null;
      }

      if (!refTable)
          gs.warn(gs.getMessage('ConfigRetrievalService was unable to get reference table for field {0} on table {1}', refFieldName, record.sys_class_name.value));

      return refTable;
  },

  getDepFieldRefTableName: function(table, fieldName) {
      var dependent = null;
      var dict = new GlideRecordSecure('sys_dictionary');
      dict.addQuery('name', table);
      dict.addQuery('element', fieldName);
      dict.query();

      if (dict.next())
          dependent = dict.getValue('dependent');

      return dependent;
  },

  addEmptyM2MAlert: function (m2mTableName) {
      //logging a warning if m2m table name isn't provided but continue to provide a warning for the FE
      var childTable = '';
      if (!m2mTableName)
          gs.warn(gs.getMessage('Returning addEmptyM2MAlert with incomplete data for alertData.childTable'));
      else
          childTable = m2mTableName;

      var parentNode = this.getParentNode();
      if (parentNode) {
          var missingM2MChildAlertFlag = this.alertHandler.generateMissingM2MChildAlert(parentNode.tableName, parentNode.sysId, m2mTableName);
          this.configResponse.addAlert(missingM2MChildAlertFlag);
      }
  },

  //Find the node or it is not accessible then return and log it
  validateConfigData: function (tableName, sysId) {
      var configData = this.getConfigData(tableName, sysId);
      if (!configData) {
          gs.warn(gs.getMessage('ConfigRetrievalService was unable to retrieve the node for table: {0}, id: {1}', tableName, sysId));
          return false;
      }
      return true;
  },

  addConfigNodeLink: function(tableName, sysId, isFloor) {
      var configNode = this.configResponse.getConfigNode(tableName, sysId);
      if (!configNode && !this.validateConfigData(tableName, sysId)) {
          return;
      }
      //If we are not the root node we add the link
      var parentNode = this.getParentNode();
      if (parentNode)
          this.configResponse.validateAndAddNodeLink(parentNode.tableName, parentNode.sysId, tableName, sysId, isFloor);
  },

  addConfigFloorNode: function(tableName, sysId) {
      var configNode = this.configResponse.getConfigNode(tableName, sysId);
      if (!configNode && !this.validateConfigData(tableName, sysId)) {
          return;
      }
      //If we are not the root node we add the link
      var parentNode = this.getParentNode();
      if (parentNode)
          this.configResponse.addFloorNode(parentNode.tableName, parentNode.sysId, tableName, sysId);
  },

  addConfigNodeResponse: function (tableName, sysId) {
      var configNode = this.configResponse.getConfigNode(tableName, sysId);
      //Check our response to see if we already have output this node
      if (!configNode) {
          var configData = this.getConfigData(tableName, sysId);

          if (!configData) {
              if (!this.validationHandler.isRecordGloballyScoped(tableName, sysId)) {
                  this.addUnretrievableNodeAlert(tableName, sysId);
                  gs.warn(gs.getMessage('ConfigRetrievalService was unable to retrieve the node for table: {0}, id: {1}', tableName, sysId));
              }

              return;
          }

          this.configResponse.addConfigNode(tableName, sysId, configData);
          configNode = {
              'data': configData,
              'tableName': tableName,
              'sysId': sysId
          };
      }

      return configNode;
  },

  addUnretrievableNodeAlert: function (tableName, sysId) {
      var unretrievableNodeAlert = this.alertHandler.generateUnretrievableNodeAlert(tableName, sysId);
      this.configResponse.addAlert(unretrievableNodeAlert);
  },

  type: 'ConfigRetrievalService'
};

function ConfigKeyStackEntry(configKey, tableName, sysId) {
  if (!configKey || !tableName || !sysId)
      new ErrorHandler().throwInternalError('Error - NodeStackEntry not defined correctly');

  this.configKey = configKey;
  this.tableName = tableName;
  this.sysId = sysId;
}

Sys ID

a5d7615653122010c722ddeeff7b12c2

Offical Documentation

Official Docs: