Name

sn_cmp.ResponseMapping

Description

Class to help translate a given JSON encoded string worth of raw attributes from a cloud provider / Cloud API to an collection of attributes defined by a set of mappings per type and service provider.

Script

var ResponseMapping = Class.create();
ResponseMapping.prototype = {
  initialize: function(datasource, table) {
  	this.datasource = datasource;
  	this.table = table;
  	
  	this.shouldDebug = 'true' == gs.getProperty('cmp.response_processor.debug', 'false');
  },
  
  getAttributeGr: function(idOnly) {
  	var gr = new GlideRecord('sn_cmp_response_mapping');
  	gr.addQuery('datasource.name', this.datasource);
  	gr.addQuery('ci_class', this.table);
  	if (!gs.nil(idOnly) && idOnly){
  		var qc = gr.addQuery('used_for_identification', true);
  		qc.addOrCondition('additional_identifier',true);
  	}
  		
  	gr.query();
  	
  	return gr;
  },	
  
  getIdAttributes: function() {
  	var idAttributes = {};
  	var gr = this.getAttributeGr(true);
  	if (!gs.nil(gr))
  		while (gr.next()){
  			var table;
  			var table_field;
  			if (gr.used_for_identification){
  				table = gr.ci_class + '';
  				table_field = gr.ci_class_field + '';
  			}else{
  				table = gr.identifier_ci_class + '';
  				table_field = gr.identifier_ci_class_field + '';
  			}
  			if (!idAttributes[table]){
  				idAttributes[table] = [];
  			}
  				
  			idAttributes[table].push(table_field);
  		}
  	return idAttributes;
  },
  
  getRelatedObjectsAttributeGr: function() {
  	var gr = new GlideRecord('sn_cmp_response_mapping');
  	gr.addQuery('datasource.name', this.datasource);
  	gr.addQuery('ci_class', this.table);
  	gr.addQuery('attribute_value_type', 'related_object');
  	gr.query();
  	
  	return gr;
  },
  
  getBindingObjectsAttributeGr :function() {
  	var gr = new GlideRecord('sn_cmp_response_mapping');
  	gr.addQuery('datasource.name', this.datasource);
  	gr.addQuery('ci_class', this.table);
  	gr.addQuery('attribute_value_type', 'binding');
  	gr.query();
  	
  	return gr;
  },
  
  getTagValues: function(responseObject, type) {
  	if (gs.nil(responseObject))
  		return null;
  	
  	var rtGr = new GlideRecord('sn_capi_resource_type');
  	if (rtGr.get('name',type)) {
  		var tagField = new CmpTagUtil().getProviderProperty(rtGr.product.provider,
  																   'tag_response_field');
  		if (!gs.nil(tagField) && !gs.nil(responseObject[tagField]))
  			return global.JSON.stringify(responseObject[tagField]);
  	}
  	
  	return null;
  },
  
  getAttributes: function(responseObject) {
  	var attributes = {};
  	
  	if (gs.nil(responseObject))
  		return attributes;
  	
  	attributes.$additional_identifier = {};
  	var gr = this.getAttributeGr();
  	
  	if (gr.getRowCount() == 0)
  		return {};

  	this.debug('Response mappings found: ' + gr.getRowCount());
  	
  	var attributeValue = '';
  	while (gr.next()) {
  		var attributeValueType = '' + gr.attribute_value_type;

  		this.debug('Process attribute type: ' + attributeValueType);

  		switch(attributeValueType){
  			case 'mapped':
  				attributeValue = this._getMappedValue(responseObject, gr);
  				break;
  			case 'fixed':
  				attributeValue = this._getFixedValue(responseObject, gr);
  				break;
  			case 'scripted':
  				attributeValue = this._getScriptedValue(responseObject, gr);
  				break;
  			case 'array_count':
  				attributeValue = this._getArrayCountValue(responseObject, gr);
  				break;
  			case 'first_array_element':
  				attributeValue = this._getFirstArrayElement(responseObject, gr);
  				break;
  			case 'mapped_first_array_element':
  				attributeValue = this._getMappedFirstArrayElement(responseObject, gr);
  				break;
  			case 'translation':
  				attributeValue = this._getTranslationValue(responseObject, gr);
  				break;
  			default:
  				continue;
  		}

  		// If the attribute value is null, something was wrong and we didn't
  		// find a valid value in the response object - so don't put anything
  		// into the payload
  		if (attributeValue !== null){
  			if (gr.additional_identifier) {
  				var table = '' + gr.identifier_ci_class;
  				if (!attributes.$additional_identifier[table])
  					attributes.$additional_identifier[table] = {};
  				attributes.$additional_identifier[table]['' + gr.identifier_ci_class_field] = attributeValue;
  			}		
  			else
  				attributes['' + gr.ci_class_field] = attributeValue;
  		}
  			
  	}
  	return attributes;
  },
  
  getRelatedObjects: function(responseObject, cloudAccount, ldc) {
  	var relatedObjects = {};
  	if (gs.nil(responseObject))
  		return relatedObjects;
  	
  	var gr = this.getRelatedObjectsAttributeGr();
  	
  	if (gr.getRowCount() > 0)
  		this.debug('Related object mappings found: ' + gr.getRowCount());
  	
  	while (gr.next()) {
  		this.debug('Related object datasource: ' + gr.datasource_for_mappings.name);

  		var sourceValue = responseObject;
  		if (!gs.nil(gr.source_field)) {
  			sourceValue = this._getProperty(responseObject, '' + gr.source_field);
  			
  			// If there isn't anything in the source field, no sense to try to process
  			// anything - skip processing
  			if (gs.nil(sourceValue))
  				continue;
  		}
  		
  		var table = '' + gr.relationship_ci_class;
  		
  		var processor = new ResponseProcessor(cloudAccount, 
  											  ldc, 
  											  gr.datasource_for_mappings.name);

  		this.debug('Source passed to response processor for related object processing: ' +
  				   global.JSON.stringify(sourceValue));
  		
  		var obj = processor.processResponseArray(sourceValue, 
  											'',
  											null,
  										    null,
  										    table, responseObject);
  		
  		this.debug('Related object payload: ' + global.JSON.stringify(obj));
  		
  		if (!this._isArray(obj))
  			obj = [obj];
  		
  		var result = [];
  		for (var index = 0; index < obj.length; index++){
  			result.push(obj[index][table]);
  		}
  		
  		relatedObjects[table] = result;
  	}
  	
  	return relatedObjects;
  },
  
  getBindingObjects : function(responseObject, cloudAccount, ldc) {
  	var bindingObjects = [];
  	if (gs.nil(responseObject))
  		return bindingObjects;
  	
  	var gr = this.getBindingObjectsAttributeGr();
  	
  	if (gr.getRowCount() > 0)
  		this.debug('Binding object mappings found: ' + gr.getRowCount());
  	
  	while (gr.next()){
  		this.debug('Binding object datasource: ' + gr.datasource_for_mappings.name);
  		
  		var sourceValue = responseObject;
  		if (!gs.nil(gr.source_field)) {
  			sourceValue = this._getProperty(responseObject, '' + gr.source_field);
  			
  			// If there isn't anything in the source field, no sense to try to process
  			// anything - skip processing
  			if (gs.nil(sourceValue))
  				continue;
  		}
  		
  		
  		var bindingType = '' + gr.binding_type;
  		var bindingEndpoint = '' + gr.binding_endpoint;
  		var processor = new ResponseProcessor(cloudAccount, 
  											  ldc, 
  											  gr.datasource_for_mappings.name);
  		
  		var obj = processor.processResponseForBindings(sourceValue, responseObject, bindingType, bindingEndpoint);
  		
  		this.debug('Binding object payload: ' + global.JSON.stringify(obj));
  		
  		bindingObjects = bindingObjects.concat(obj);
  	}
  	return bindingObjects;
  },
  
  _getMappedValue: function(responseObject, gr) {
  	return this._getProperty(responseObject, '' + gr.source_field);
  },
  
  _getProperty: function(responseObject, property) {	
  	var propertyKey = '' + property;
  	var value = null;
  	if (gs.nil(responseObject) || gs.nil(property))
  		return value;

  	var parts = propertyKey.split('.');
  	var length = parts.length;

  	// Walk the object until we get to the one being asked for
  	// If at any point the dot-walking fails because the property being asked to
  	// walk into doesn't exist, bail out and return null
  	var workingPart = responseObject;

  	for (var i = 0; i < length; i++ ) {
  		if (i == length - 1)
  			value = workingPart.hasOwnProperty(parts[i]) ? workingPart[parts[i]] : null;
  		else {
  			if (workingPart.hasOwnProperty(parts[i]) && 
  					this._isObject(workingPart[parts[i]]))
  				workingPart = workingPart[parts[i]];
  			else {
  				this.debug(gs.getMessage('Property \'{0}\' not found, or is not an object in \'{1}\'', [parts[i], global.JSON.stringify(workingPart)]));
  				return null;
  			}
  		}			
  	}

  	return value;
  },

  _getFixedValue: function(responseObject, gr) {
  	return '' + gr.fixed_value;
  },
  
  _getArrayCountValue: function(responseObject, gr) {
  	var arrayField = '' + gr.array_field;
  	var arr = this._getProperty(responseObject, arrayField);
  	return this._isArray(arr) ? arr.length : null;
  },
  
  _getFirstArrayElement: function(responseObject, gr) {
  	var arrayField = '' + gr.array_field;
  	var arr = this._getProperty(responseObject, arrayField);
  	return this._isArray(arr) ? arr[0] : '';
  },
  
  _getMappedFirstArrayElement: function(responseObject, gr) {
  	var arrayField = '' + gr.array_field;
  	var sourceField = '' + gr.source_field;
  	
  	var arr = this._getProperty(responseObject, arrayField);
  	if (this._isArray(arr) && arr.length > 0) {
  		var o = arr[0];
  		if (this._isObject(o))
  			return o[sourceField];
  	}

  	return '';
  },
  
  /**
   * Attempts to translate a value from the response object to another value
   * by looking in a translation table using the key and source value...if the
   * source value is not found, don't translate it to anything and just return
   * it as is
   */
  _getTranslationValue: function(responseObject, gr) {
  	var key = '' + gr.translation_key;
  	var sourceValue = this._getProperty(responseObject, '' + gr.source_field);
  	
  	if (null === sourceValue)
  		return null;
  	
  	var translationGr = new GlideRecord('sn_cmp_response_value_mapping');
  	translationGr.addQuery('key', key);
  	translationGr.addQuery('source_value', sourceValue);
  	translationGr.query();
  	
  	return translationGr.next() ? '' + translationGr.translated_value : sourceValue;
  },
  
  _getScriptedValue: function(responseObject, gr) {
  	var value;
  	try {
  		var responseStr = global.JSON.stringify(responseObject);
  		var sourceField = '' + gr.source_field;
  		var sourceValue = gs.nil(sourceField) ? '' : 
  							this._getProperty(responseObject, sourceField);
  		var sourceValueStr = global.JSON.stringify(sourceValue);

  		var evaluator = new GlideScopedEvaluator();
  		//evaluator = evaluator.withInterpretedMode(true);
  		evaluator.putVariable('rawObject', responseStr);
  		evaluator.putVariable('sourceFieldValue', sourceValueStr);
  		value = evaluator.evaluateScript(gr, 'script');
  	} catch(e) {
  		gs.error(gs.getMessage('Failed to evaluate scripted mapping'));
  		gs.error(e);
  	}

  	return value;
  },	

  _isObject: function(testObject) {
  	return !gs.nil(testObject) && typeof testObject === 'object';
  },
  
  _isArray: function(testObject) {
  	return !gs.nil(testObject) && !(testObject.propertyIsEnumerable('length')) &&
  		typeof testObject === 'object' && typeof testObject.length === 'number';
  },	
  
  log: function(message) {
  	gs.info(message, 'ResponseMapping');
  },
  
  debug: function(message) {
  	if (this.shouldDebug)
  		gs.info('DEBUG: ' + message, 'ResponseMapping');
  },

  type: 'ResponseMapping'
};

Sys ID

76ae7b319f07220048111f80a57fcf54

Offical Documentation

Official Docs: