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