Name

global.DiscoveryCIRelationReconciler

Description

Reconciles records that are related by CI relations.

Script

// Discovery
var DiscoveryCIRelationReconciler = Class.create();

DiscoveryCIRelationReconciler._INSTALL_STATUS = 'install_status';
DiscoveryCIRelationReconciler._INSTALL_STATUS_ABSENT = 100;

DiscoveryCIRelationReconciler.prototype = {
  /**
   * Optionally, pass a single parameters object using the same names.
   * Extended options:
   *   string direction: Specifies whether the fromId is the 'downstream' or 'upstream'. Default 'upstream'.
   * 
   * @param string fromId The sys_id of the CI to be related from (the 'one' in one:many)
   * @param string nature The relationship type descriptor
   * @param string toTable The table that of CIs to be related
   * @param CIData[] cidata The array of CIs to create/update and relate to. (the 'many' in one:many)
   * @param string[] keys Fields that make up a unique identity when related to the fromCi.
   * @param boolean retireMissing If TRUE, CIs of the same type that are related, but do not match the CIs specified, will be marked absent.
   * @param string reason Used as the update() message for GlideRecord writes.
   */
  initialize: function(fromId, nature, toTable, cidata, keys, retireMissing, reason) {
  	if (typeof nature !== 'undefined') {
  		this._fromId = fromId;
  		this._nature = nature;
  		this._relationTypeId = g_disco_functions.findCIRelationshipType('cmdb_rel_type', nature);			
  		this._table = toTable;
  		this._keys = keys;
  		this._retireMissing = retireMissing;
  		this._reason = reason;
  		this._originalCiDataArray = cidata;
  		this._direction = 'downstream'
  	} else {
  		var options = fromId;
  		this._fromId = options.fromId;
  		this._nature = options.nature;
  		this._relationTypeId = g_disco_functions.findCIRelationshipType('cmdb_rel_type', options.nature);			
  		this._table = options.toTable;
  		this._keys = options.keys;
  		this._retireMissing = options.retireMissing;
  		this._reason = options.reason;
  		this._originalCiDataArray = options.cidata;
  		this._direction = ( typeof options.direction !== 'undefined' ? options.direction : 'downstream' );
  	}
  	
  	if (gs.nil(this._relationTypeId))
  		throw new IllegalArgumentException('Unable to reconcile for specified relation type ' + nature);
  	
  	this._cidataArray = []; // may contain null values
  	
  	// copy so that we can skip invalid cis without altering original array
  	for (var i = 0, n = this._originalCiDataArray.length; i < n; ++i) {
  		if (this._originalCiDataArray[i] === null || !this._cidataHasKeys(this._originalCiDataArray[i]))
  			this._cidataArray.push(null); // prevent this ci from being found in other steps
  		else
  			this._cidataArray.push(this._originalCiDataArray[i]);
  	}
  },

  reconcile: function() {
  	this._writeCIs();
  	this._retireMissingCIs();
  },
 
  /**
  * Inserts/Updates all CIs provided, updating the original CI object with the appropriate ID.
   */
  _writeCIs: function() {
  	for (var i = 0, n = this._cidataArray.length; i < n; ++i) {
  		if (this._cidataArray[i] === null)
  			continue;
  	   
  		// insert/update the ci
  		var cidata = this._cidataArray[i];
  		var ci = this._findCi(cidata);
  		this._write(cidata, ci);
  		this._originalCiDataArray[i].data.sys_id = cidata.data.sys_id; // update the passed reference with the sysid
  		var parentId = ( this._direction === 'downstream' ? this._fromId : cidata.data.sys_id );
  		var childId = ( this._direction === 'downstream' ? cidata.data.sys_id : this._fromId );
  		new DiscoveryFunctions().createRelationshipIfNotExists(parentId, childId, this._nature);
  	}
  },
 
  /**
   * Scans all relationships of the same type and classname from the origin for CIs that are not in our list
   * and retires those missing CIs.
   */
  _retireMissingCIs: function() {
  	if (!this._retireMissing)
  		return;
     
  	var fromField = ( this._direction === 'downstream' ? 'child' : 'parent' );
  	var toField = ( this._direction === 'downstream' ? 'parent' : 'child' );
  	
  	var relations = new GlideRecord('cmdb_rel_ci');
  	relations.addQuery(fromField, this._fromId);
  	relations.addQuery(toField + '.sys_class_name', this._table);
  	relations.addQuery('relation', this._relationTypeId);
  	relations.query();

  	while(relations.next()) {
  		var ci = new GlideRecord(this._table);
  		if (!ci.get(''+relations[toField]))
  			continue;
  	   
  		if (this._findIndex(ci) === null)
  			this._retire(ci);
  	}
  },
    
  /**
  * Expects cidata to have all keys necessary.
  * @param CIData cidata
   * @returns GlideRecord|null
  */
  _findCi: function(cidata) {
  	var query = new GlideRecord(this._table);
  	for (var ki = 0, kn = this._keys.length; ki < kn; ++ki) {
  		var key = this._keys[ki];
  		var value = cidata.data[key];
  		query.addQuery(key, value);
  	}
     
  	query.query();
  	if (query.next())
  	   return query;
     
     return null;
  },

 
  /**
   * Verifies that CIData has all of the specified keys to match on.
   * @param cidata CIData
   * @param [string] keys A list of keys to check
   * @returns TRUE if valid, FALSE if no keys are set
   */
  _cidataHasKeys: function(cidata, keys) {
  	var data = cidata.data;
  	for (var i = 0, n = keys.length; i < n; ++i) {
  		if (gs.nil(data[keys[i]]))
  			return false;
  	}
     
  	return true;
  },
  	
  _findIndex: function(ci) {
  	var values = []; // build values. will be Java strings, so we use equals() later
  	for (var ki = 0, kn = this._keys.length; ki < kn; ++ki) // kn is reused later
  		values.push(ci.getValue(this._keys[ki])); // Java String

  	var data = null;
  	for (var i = 0, n = this._cidataArray.length; i < n; ++i) {
  		if (this._cidataArray[i] === null) // already done
  			continue;
  		
  		data = this._cidataArray[i].data;
  		var skipped = false;
  		for (ki = 0; ki < kn; ++ki) { // reusing kn
  			if (gs.nil(values[ki]) || !values[ki].equals(data[this._keys[ki]])) {
  				skipped = true;
  				break;
  			}
  		}		
  	   
  		if (!skipped)
  			return i;
  	}
  	
  	return null;
  },

  _retire: function(ci) {
  	ci.setValue(DiscoveryCIRelationReconciler._INSTALL_STATUS, DiscoveryCIRelationReconciler._INSTALL_STATUS_ABSENT);
  	ci.update(this._reason);
  },
  
  /**
   * Expects cidata for index to have all keys.
   * @param ci GlideRecord|null
   */
  _write: function(cidata, ci) {
  	var isNew = ( ci === null );
  	if (isNew)
  		ci = new GlideRecord(this._table);
     
  	var data = cidata.data;
  	for (var name in data)
  		ci.setValue(name, data[name]);

  	if (isNew) {
  		data.sys_id = ''+ci.insert(this._reason);
  	} else {
  		ci.update(this._reason);
  		data.sys_id = ''+ci.sys_id;
  	}
  },

  type: 'DiscoveryCIRelationReconciler'
};

Sys ID

c9c2b99c3711200032ff8c00dfbe5de4

Offical Documentation

Official Docs: