Name

global.CIRelatedList

Description

Utility class for working with CI related lists.

Script

// Discovery class
gs.include("PrototypeServer");

/**
* Utility class for working with CI related lists. Each instance of this class contains the details of a single list 
* that is related to a particular CI. The details of this list AND the contents of the list are included.
* 
* Tom Dilatush tom.dilatush@service-now.com
*/
var CIRelatedList = Class.create();

CIRelatedList.prototype = { 
  /*
   * Initializes a new instance of this class.  With no arguments, simply constructs an empty instance.
   * 
   * table: the table containing the related list (if many-to-many, the many-to-many table)
   * ref_field: the reference field in the table for this related list
   * cmdb_ci: The sys_id of the CI this list is related to.
   * debug_flag: true if debug logging is enabled.
   */
  initialize: function(table, ref_field, cmdb_ci, debug_flag) {
      this.GlideRecordUtil = new GlideRecordUtil();
      this.ignoreFields = {
          'sys_created_by' : true,
          'sys_updated_by' : true,
          'sys_mod_count'  : true
          };

      // array of hashmaps, one for each record in the related list...
      this.records = [];

      // array of hashmaps, one for each record in the m2m list (if there is one)...
      this.m2m_records = [];

      this.table_name = (arguments.length >= 1) ? table : null;
      this.field_name = (arguments.length >= 2) ? ref_field : null;
      this.cmdb_ci =    (arguments.length >= 3) ? cmdb_ci : null;
      this.debug_flag = (arguments.length >= 4) ? debug_flag : null;
  	
  	// Fields and flag to be set if the table is m2m
      this.isM2M = false;
      this.target_table_name = null;
      this.target_ref_field_name = null;
  	
      // false for one-to-many; true for many-to-many...
  	// this.isM2M, target_table_name and target_ref_field_name are all set in the following two calls
      if (!this.checkSysM2M()) 
          this.checkSysCollection();
  	
  	// If there's no cmdb_ci record, don't populate!
  	if (this.cmdb_ci == null)
  		return;
  	
      // now get our data...
      this.populate();
  },

  /*
   * Populates the this.records field of this instance.
   */
  populate: function() {
  	if (this.cmdb_ci == null) {
  		gs.log("Unable to load records from CIRelatedList script include due to invalid ci");
  		return;
  	}
  	
      // no matter what happens, we need our related list...
      var relTable = new GlideRecord(this.table_name);
      relTable.addQuery(this.field_name, this.cmdb_ci);
      relTable.query();

      // if this isn't a many-to-many list, just load the records and skedaddle...
      if (!this.isM2M) {
          this._loadRecords(relTable, this.records);
          return;
      }

      // it's a many-to-many list, so first load the m2m records...
      this._loadRecords(relTable, this.m2m_records);

      // now load the target records, in chunks so our query doesn't get too darned big...
      var sys_ids = new Packages.java.util.ArrayList();
      for (var i = 0; i < this.m2m_records.length; i++) {
          sys_ids.add(this.m2m_records[i][this.target_ref_field_name]);
          if (sys_ids.size() >= 50)
              this._loadChunk(sys_ids);
      }
      if (sys_ids.size() > 0)
          this._loadChunk(sys_ids);
  },

  /*
   * Remove all the related list items.
   */
  remove: function() {
      var rlgr = new GlideRecord(this.table_name);
      rlgr.addQuery(this.field_name, this.cmdb_ci);
      rlgr.deleteMultiple();
      this.records = [];
      this.m2m_records = [];
  },

  /*
   * Adds the given record to the related list.
   */
  addRec: function(record) {
      this.records.push(record);
  },

  /*
   * Sets the given records to the related list.
   */
  addRecs: function(records) {
      this.records = records;
  },

  /*
   * Populates a chunk of the target table when it's a many-to-many table.
   * 
   * sys_ids: a Java ArrayList of the sys_ids of the target table to load
   */
  _loadChunk: function(sys_ids) {
      // query our real related list for the record in sys_ids...
      var relTable = new GlideRecord(this.target_table_name);
      relTable.addQuery('sys_id', sys_ids);
      relTable.query();

      // finally - load our records...
      this._loadRecords(relTable, this.records);
      sys_ids.clear();
  },

  /*
   * Loads the related list records in the given GlideRecord instance into the given array.
   * 
   * relTable: a GlideRecord initialized to the records for a related list.
   * records: the array to add records to.
   */
  _loadRecords: function(relTable, records) {
      while (relTable.next()) {
          var hm = {};
          this.GlideRecordUtil.populateFromGR(hm, relTable, this.ignoreFields);
          records.push(hm);
      }
  },

  /*
   * Check to see if this is a Sys many-to-many table.  If it is, set the target table name and reference field name.
   */
  checkSysM2M: function() {
      // use some existing Java code to do the actual check...
      var my_many = new GlideSYSMany2Many();
      this.isM2M = my_many.isMtm(this.table_name);
      if (!this.isM2M)
          return false;

      // ok, we are a Sys many-to-many -- so set the target table name and reference field name...
      if (my_many.getFromField() == this.field_name) {
          this.target_table_name = my_many.getTargetTable();
          this.target_ref_field_name = my_many.getTargetField();
      } else {
          this.target_table_name = my_many.getFromTable();
          this.target_ref_field_name = my_many.getFromField();
      }

      // and skedaddle indicating what we are...
      return true;
  },

  /*
   * Check to see if this is a SysCollection table.  If it is, set the target table name and reference field name.
   */
  checkSysCollection: function() {
      // see if any record in the sys_collection table has this table's name...
      var scGR = new GlideRecord('sys_collection');
      scGR.addQuery('collection', this.table_name);
      scGR.query();
      var tables = [];
      var fields = [];
      while (scGR.next()) {
          tables.push(scGR.getValue('name'));
          fields.push(scGR.getValue('join_field'));
      }

      // if we didn't find exactly two records (one parent, one child), then bail out...
      if (tables.length != 2)
          return false;

      // ok, we are a SysCollection table -- so set the target table name and reference field name...
      this.isM2M = true;
      var src = (fields[0] == this.field_name) ? 1 : 0;
      this.target_table_name = tables[src];
      this.target_ref_field_name = fields[src];

      // and skedaddle indicating what we are...
      return true;
  },

  /*
   * Initializes this instance from the given XML element.
   */
  fromXML: function(element) {
      var xml_util = GlideXMLUtil;

      // look through our children for fields or list records...
      var it = xml_util.childElementIterator(element);
      while (it.hasNext()) {
          var el = it.next();
          var el_name = '' + el.getTagName();
          switch (el_name) {
              case 'fld':
                  var val = XMLUtilJS.stringToValue(xml_util.getText(el));
                  var fld_name = el.getAttribute('name');
                  if (fld_name == 'isM2M')
                      val = (val == 'true');
                  this[fld_name] = val;
                  break;
              case 'rl_rec':
                  this.xmlToRecord(this.records, el);
                  break;
              case 'm2m_rec':
                  this.xmlToRecord(this.m2m_records, el);
                  break;
          }
      }
  },

  xmlToRecord: function(records, el) {
      var xml_util = GlideXMLUtil;
      var record = {};
      var flds = xml_util.getChildrenByTagName(el, 'fld');
      for (var i = 0; i < flds.size(); i++) {
          var fld_el = flds.get(i);
          var fld_name = fld_el.getAttribute('name');
          var fld_value = XMLUtilJS.stringToValue(xml_util.getText(fld_el));
          record[fld_name] = fld_value;
      }
      records.push(record);
  },

  /*
   * serializes this instance to XML in the given document and <rl> element.
   */
  toXML: function(doc, el) {
      // first we dump our singleton fields...
      this.appendXMLChildFld(el, 'isM2M',                 this.isM2M                );
      this.appendXMLChildFld(el, 'table_name',            this.table_name           );
      this.appendXMLChildFld(el, 'field_name',            this.field_name           );
      this.appendXMLChildFld(el, 'target_table_name',     this.target_table_name    );
      this.appendXMLChildFld(el, 'target_ref_field_name', this.target_ref_field_name);

      // now our array of related list records...
      for (var i = 0; i < this.records.length; i++)
          this.appendXMLChildRecord(el, 'rl_rec', this.records[i]);

      // and finally, our list of m2m records (only, of course, if this is an m2m list)...
      for (var i = 0; i < this.m2m_records.length; i++)
          this.appendXMLChildRecord(el, 'm2m_rec', this.m2m_records[i]);
  },

  appendXMLChildRecord: function(parent, name, rec) {
      var xml_util = GlideXMLUtil;
      var rec_el = xml_util.newElement(parent, name);
      for (var name in rec)
          this.appendXMLChildFld(rec_el, name, rec[name]);
  },

  appendXMLChildFld: function(parent, name, datum) {
      var xml_util = GlideXMLUtil;
      var el = XMLUtilJS.valueToString(datum);
      var data_el = xml_util.newElement(parent, 'fld');
      data_el.setAttribute('name', name);
      xml_util.setText(data_el, el);
  },

  /*
   * Makes a string representation of this instance, pushing each line onto the end of the given result array. This 
   * method is called from CIData.toString(), and assumes related list lines are to be indented two spaces.
   */
  toString: function(result) {
      result.push('  CIRelatedList: ' + this.table_name + ':' + this.field_name + '; ' + this.records.length 
              + ' entries');
      for (var i = 0; i < this.records.length; i++) {
          var record = this.records[i];
          result.push('    Record ' + i);
          for (var name in record)
              result.push('      ' + name + ': ' + record[name]);
      }
  },

  type: 'CIRelatedList'
}

Sys ID

b09a6c4b0a0a0b0200b8e7e041e95496

Offical Documentation

Official Docs: