var sp_CMDBIntegrationUtils = Class.create(); sp_CMDBIntegrationUtils.prototype = { /********************** * updateCIDatasource() * * Records the date/time that a given CI was last seen on a particular feed or * data source, using the table u_ci_datasource. If no record exists, creates one. * * Normally we identify a CI Datasource record by Feed ID, which is assumed to be * unique for a particular datasource. If one CI appears multiple times on a feed * then this gives a CI Datasource record per Feed ID, which is felt to be better * than one CI Datasource record on which the Feed ID oscillates. * * But if the supplied Feed ID is empty then the datasource isn't providing Feed IDs * on some or all records. Instead we identify the CI Datasource record by the CI & * empty Feed ID, so that at least we can have a CI Datasource record even if the * Feed ID is empty. * * On the u_ci_datasource table, the first seen and last seen dates default to now, * so only need here to set the last seen date when updating an existing record. * * Amended July 2020 - allow Feed ID to be empty, to cater for feeds where not all * import records have Feed IDs, but we still want to have a CI Datasource record. * * @param {string} iCI - The sys_id of the CI * @param {string} iDatasource - The name of the datasource * @param {string} [iFeedId] - the unique record ID in the feeding system corresponding to this CI * @param {string} [iFeedSourceName] - the name on the record in the feeding system corresponding to this CI * @param {string} [iFeedSourceLifecycleStatus] - the lifecycle status on the record in the feeding system corresponding to this CI **********************/ updateCIDatasource: function(iCI, iDatasource, iFeedId, iFeedSourceName, iFeedSourceLifecycleStatus) { try { var ok = true; var logSource = 'sp_CMDBIntegrationUtils/updateCIDatasource'; var feedId = (typeof iFeedId !== 'undefined' ? iFeedId : ''); var feedSourceName = (typeof iFeedSourceName !== 'undefined' ? iFeedSourceName : ''); var feedSourceLifecycleStatus = (typeof iFeedSourceLifecycleStatus !== 'undefined' ? iFeedSourceLifecycleStatus : ''); if (iCI == '' || iDatasource == '') { gs.logError( 'Parameter(s) missing: CI=' + iCI + ', datasource=' + iDatasource, logSource); ok = false; } if (ok) { var grCIDS = new GlideRecord('u_ci_datasource'); grCIDS.addQuery('u_datasource', iDatasource); grCIDS.addQuery('u_feed_id', feedId); if (feedId == '') { grCIDS.addQuery('u_ci', iCI); } grCIDS.setLimit(1); // there should only be 1 grCIDS.query(); if (grCIDS.next()) { grCIDS.u_ci = iCI; // just in case CI has changed for this Feed ID grCIDS.u_feed_id = feedId; grCIDS.u_last_seen = gs.nowDateTime(); grCIDS.u_source_name = feedSourceName; grCIDS.u_source_lifecycle_status = feedSourceLifecycleStatus; grCIDS.update(); } else { grCIDS.initialize(); grCIDS.u_ci = iCI; grCIDS.u_datasource = iDatasource; grCIDS.u_feed_id = feedId; grCIDS.u_last_seen = gs.nowDateTime(); grCIDS.u_source_name = feedSourceName; grCIDS.u_source_lifecycle_status = feedSourceLifecycleStatus; grCIDS.insert(); } } } catch (err) { gs.logError( 'Error caught in updateCIDatasource in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'updateCIDatasource'); } }, /********************** * updateDatasourceLastUpdate() * * Records the last seen date/time for specified fields on a given CI, from a given datasource. * * Updates records on the cmdb_datasource_last_update table, or creates if they do not exist. * Excludes any attributes with name starting 'sys_' because data sources are not going to be * updating those fields, even if they are in the list of fields provided. * * @param {string} iCI - The sys_id of the CI * @param {string} iDatasource - The name of the datasource * @param {array} iFieldsToUpdate - Array of names of attributes to update or insert records for **********************/ updateDatasourceLastUpdate: function(iCI, iDatasource, iFieldsToUpdate) { try { var ok = true; var logSource = 'updateDatasourceLastUpdate'; var fieldsUpdated = []; var grDSLU; var thisFieldName; if (iCI == '' || iDatasource == '') { gs.logError( 'ERROR: In sp_CMDBIntegrationUtils/updateDatasourceLastUpdate - parameter(s) missing: CI=' + iCI + ', Datasource=' + iDatasource, logSource); return; } var debug = 'In sp_CMDBIntegrationUtils/updateDatasourceLastUpdate' + '\nCI=' + iCI + '\nDatasource=' + iDatasource + '\nField list=' + iFieldsToUpdate; if (iFieldsToUpdate.length == 0) { debug += '\n\nNo fields to update'; ok = false; } if (ok) { //-------------------------------------------------------- // Update cmdb_datasource_last_update records that already exist //-------------------------------------------------------- debug += '\n\nUpdating records that exist...'; grDSLU = new GlideRecord('cmdb_datasource_last_update'); grDSLU.addQuery('cmdb_ci', iCI); grDSLU.addQuery('attribute', 'IN', iFieldsToUpdate.join(',')); grDSLU.query(); while (grDSLU.next()) { if (!grDSLU.attribute.startsWith('sys_')) { thisFieldName = grDSLU.attribute.toString(); debug += '\n- Field: ' + thisFieldName; grDSLU.updated_on = gs.nowDateTime(); grDSLU.discovery_source = iDatasource; grDSLU.update(); fieldsUpdated.push(thisFieldName); } } //-------------------------------------------------------- // Insert records which didn't already exist //-------------------------------------------------------- if (fieldsUpdated.length != iFieldsToUpdate.length) { debug += '\n\nInserting records that do not exist...'; grDSLU = new GlideRecord('cmdb_datasource_last_update'); for (var iField = 0; iField < iFieldsToUpdate.length; iField++) { thisFieldName = iFieldsToUpdate[iField]; if (fieldsUpdated.indexOf(thisFieldName) == -1) { if (!thisFieldName.startsWith('sys_')) { debug += '\n- Field: ' + thisFieldName; grDSLU.initialize(); grDSLU.cmdb_ci = iCI; grDSLU.attribute = thisFieldName; grDSLU.discovery_source = iDatasource; grDSLU.updated_on = gs.nowDateTime(); grDSLU.insert(); } } } } } if (this.ifDoDebugLogging()) { gs.log(debug, logSource); } } catch (err) { gs.logError( 'Error caught in updateDatasourceLastUpdate in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'updateDatasourceLastUpdate'); } }, /********************** * findCIFromFeedID() * * Uses the CI Datasource table to find the CI corresponding to a given ID from a particular * data source. * * @param {string} iDatasource - The name of the datasource * @param {string} iFeedId - the unique record ID in the feeding system corresponding to this CI * @returns {string} - The sys_id of the CI found, or an empty string if none **********************/ findCIFromFeedID: function(iDatasource, iFeedId) { try { var ok = true; var logSource = 'sp_CMDBIntegrationUtils/findCIFromFeedID'; var retVal = ''; if (iDatasource == '' || iFeedId == '') { gs.logError( 'Parameter(s) missing: Datasource=' + iDatasource + ', Feed ID=' + iFeedId, logSource); ok = false; } if (ok) { var grCIDS = new GlideRecord('u_ci_datasource'); grCIDS.addQuery('u_datasource', iDatasource); grCIDS.addQuery('u_feed_id', iFeedId); grCIDS.setLimit(1); // there should only be 1 grCIDS.query(); if (grCIDS.next()) { retVal = grCIDS.u_ci.toString(); } } return retVal; } catch (err) { gs.logError( 'Error caught in findCIFromFeedID in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'findCIFromFeedID'); } }, /********************** * findCIFromNameAndClass() * * Find a CI on a specified table with a specified name. If multiple matches, picks the * one created earlies. * * @param {string} iDatasource - The name of the datasource (not used, but available for logging) * @param {string} iName - the name to find a CI with * @param {string} iClass - the name of the table on which to look for the CI * @returns {string} - The sys_id of the CI found, or an empty string if none **********************/ findCIFromNameAndClass: function(iDatasource, iName, iClass) { try { var ok = true; var logSource = 'sp_CMDBIntegrationUtils/findCIFromNameAndClass'; var retVal = ''; if (iDatasource == '' || iName == '' || iClass == '') { gs.logError( 'Parameter(s) missing: Datasource=' + iDatasource + ', Name=' + iName + ', Class=' + iClass, logSource); ok = false; } if (ok) { var grCI = new GlideRecord(iClass); grCI.addQuery('name', iName); grCI.orderBy('sys_created_on'); // get the oldest if there are duplicates grCI.setLimit(1); grCI.query(); if (grCI.next()) { retVal = grCI.sys_id.toString(); } } return retVal; } catch (err) { gs.logError( 'Error caught in findCIFromNameAndClass in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'findCIFromNameAndClass'); } }, /********************** * applyDatasourcePrecedence() * * Applies precedence to identify fields to block prior to a CI being updated in the database. * * Prior to an updated CI record being committed to the database, applies data source precedence * to identify any fields which cannot be updated by the current data source, because the data * source that last updated this field on this CI has greater precedence than the current source * which is attempting the CI update. For any fields which are found to need the update blocked, * the value of that field in the current GlideRecord is reverted back to the the value for that * field in the previous GlideRecord. * * If this is being called in a transform map, then the GlideRecords will be 'target', and the * CI as it exists now in the database. * * If this is being called outside of a transform map i.e. in a before business rule, then * the GlideRecords will be 'current' and 'previous'. * * @param {GlideRecord} iGrCurrent - The GlideRecord about to be committed to the database * @param {GlideRecord} iGrPrevious - The previous CI state, before the current source made any field changes * @param {string} iFieldList - Comma-separated list of fields to be considered for precedence * @param {string} iDatasource - The name of the datasource attempting the CI update * @param {string} iTargetTable - Name of CI table that the current CI is on * * @return {array} - Array of field names that were not blocked **********************/ applyDatasourcePrecedence: function(iGrCurrent, iGrPrevious, iFieldList, iDatasource, iTargetTable) { var targetFieldName; var targetCITable = iGrCurrent.getRecordClassName(); var targetFields; var iField; var logSource = 'sp_CMDBIntegrationUtils/applyDatasourcePrecedence'; var authorisedFields; var allowedFields = []; var currentFieldValue; var previousFieldValue; var foundWhetherToBlock; var previousSource; var grLastUpdate; var lastUpdateDatasourcePrecedence; var isManualEntry = (iDatasource == 'Manual Entry'); var grDataSourcePrecedence; var logInfo = iGrCurrent.getDisplayValue() + '|' + iGrCurrent.sys_id + '|' + iGrPrevious.getDisplayValue() + '|' + iGrPrevious.sys_id + '|' + iFieldList + '|' + iDatasource + '|' + iTargetTable; var debug = 'In sp_CMDBIntegrationUtils/applyDatasourcePrecedence' + '\niGrCurrent=' + iGrCurrent.getDisplayValue() + ' (' + iGrCurrent.sys_id + ')' + '\niGrPrevious=' + iGrPrevious.getDisplayValue() + ' (' + iGrPrevious.sys_id + ')' + '\niFieldList=' + iFieldList + '\niDatasource=' + iDatasource + '\niTargetTable=' + iTargetTable; try { //======================================================== // Get a list of the allowed fields for this data // source and CI class, from the Reconciliation // definition. If no Reconciliation definition, or it has // no attributes defined on it, use the full list of fields // for this CI's table. But for manual entry, we allow // updates to any fields, so no need to check reconciliation // records. //======================================================== debug += '\n\nGET LIST OF FIELDS FOR THIS CI'; authorisedFields = []; if (isManualEntry) { debug += '\nManual Entry => automatically authorised for all fields'; } else { authorisedFields = this.getAuthorisedAttributes(iDatasource, targetCITable); debug += '\nAllowed fields from reconciliation record: ' + authorisedFields.toString(); } //======================================================== // Find the precedence level for the current datasource // and target table. //======================================================== debug += '\n\nGETTING THIS SOURCE\'S PRECEDENCE LEVEL'; var thisDatasourcePrecedence = this.getPrecedenceForDatasource(iDatasource, targetCITable); if (thisDatasourcePrecedence != '') { debug += '\nPrecedence level found: ' + thisDatasourcePrecedence; } else { debug += '\nNo precedence level found'; } //======================================================== // For each field to consider, decide whether or not to // block the field update. //======================================================== debug += '\n\nCHECKING PRECEDENCE LEVELS ON FIELD LAST UPDATE SOURCES'; targetFields = iFieldList.split(','); for (iField = 0; iField < targetFields.length; iField++) { targetFieldName = targetFields[iField]; currentFieldValue = iGrCurrent.getValue(targetFieldName); previousFieldValue = iGrPrevious.getValue(targetFieldName); blockUpdate = false; foundWhetherToBlock = false; previousSource = ''; debug += '\nField #' + (iField + 1) + ' of ' + targetFields.length + ' : Name=' + targetFieldName; //-------------------------------------------------------- // If the field isn't being changed - nothing to block! //-------------------------------------------------------- if (currentFieldValue == previousFieldValue) { debug += '\n- Field unchanged => allowed'; blockUpdate = false; foundWhetherToBlock = true; } //-------------------------------------------------------- // If the field name starts with 'sys_' then don't block it // But we'll ignore it subsequently anyway. //-------------------------------------------------------- if (!foundWhetherToBlock) { if (targetFieldName.startsWith('sys_')) { debug += '\n- System field => allowed (but will ignore later)'; blockUpdate = false; foundWhetherToBlock = true; } } //-------------------------------------------------------- // Is this field one of the ones that the datasource is // allowed to update? If no, then block the update. //-------------------------------------------------------- if (!foundWhetherToBlock) { if (authorisedFields.length > 0 && authorisedFields.indexOf(targetFieldName) == -1) { debug += '\n- Source not authorised to update field => blocked'; blockUpdate = true; foundWhetherToBlock = true; } } //-------------------------------------------------------- // If the field is allowed to be updated by this datasource, // go ahead to check precedence levels. Find the data source // which last updated this field on the target CI, and get // its precedence level. //-------------------------------------------------------- if (!foundWhetherToBlock) { previousSource = ''; lastUpdateDatasourcePrecedence = ''; grLastUpdate = new GlideRecord('cmdb_datasource_last_update'); grLastUpdate.addQuery('cmdb_ci', iGrCurrent.sys_id.toString()); grLastUpdate.addQuery('attribute', targetFieldName); grLastUpdate.query(); if (grLastUpdate.next()) { debug += '\n- Found cmdb_datasource_last_update record, discovery_source=' + grLastUpdate.discovery_source; previousSource = grLastUpdate.discovery_source.toString(); lastUpdateDatasourcePrecedence = this.getPrecedenceForDatasource(previousSource, targetCITable); debug += '\n- Precedence level for last update: discovery_source=' + previousSource + ', level=' + lastUpdateDatasourcePrecedence; } //-------------------------------------------------------- // Only allow the update if the current source has a // higher precedence (i.e. lower order) than the last // source had. If the current is lower precedence (i.e. // higher order) then block it. If the current source // is the same precedence as the last source, then // block it - partly to prevent field oscillation. Also, // take account of one or both of the precedence levels being // empty, meaning no precedence record defined. //-------------------------------------------------------- debug += '\n- Checking whether to block or allow update: ' + 'Last precedence=' + lastUpdateDatasourcePrecedence + ', This precedence=' + thisDatasourcePrecedence; blockUpdate = this.ifBlockUpdateByPrecedence( iDatasource, thisDatasourcePrecedence, previousSource, lastUpdateDatasourcePrecedence); debug += '\n- Block update based on precedence=' + blockUpdate; } //-------------------------------------------------------- // If we need to block the field update, revert the value // in the current GlideRecord. If blocked due to // precedence (rather than not appearing in the Attributes // list) then log to system log, and display a message if // an interactive session. //-------------------------------------------------------- if (blockUpdate) { debug += '\n- Blocking update...'; debug += '\n- Reverting field on target CI: currently=' + iGrCurrent.getValue(targetFieldName) + ', reverting to=' + iGrPrevious.getValue(targetFieldName); if (previousSource != '') { gs.log( 'Precedence blocked attempted CI field update: ' + 'CI=' + iGrCurrent.getDisplayValue() + ' (' + iGrCurrent.sys_class_name + '/' + iGrCurrent.sys_id + '), ' + 'Field=' + targetFieldName + ', ' + 'Datasource=' + iDatasource + ' (order=' + thisDatasourcePrecedence + '), ' + 'Last update datasource=' + previousSource + ' (order=' + lastUpdateDatasourcePrecedence + ')', logSource); if (gs.getSession().isInteractive()) { gs.addErrorMessage( 'Cannot modify ' + iGrCurrent[targetFieldName].getLabel() + ' on CI \'' + iGrCurrent.getDisplayValue() + '\'' + ', update ignored - this field was most recently updated by ' + (previousSource == 'Manual Entry' ? 'manual entry' : 'the ' + previousSource + ' source or integration') + ', which takes precedence over ' + (iDatasource == 'Manual Entry' ? 'manual updates' : iDatasource)); } } iGrCurrent.setValue(targetFieldName, iGrPrevious.getValue(targetFieldName)); } else { allowedFields.push(targetFieldName); } } if (this.ifDoDebugLogging()) { gs.log(debug, logSource); } return allowedFields; } catch (err) { gs.logError( 'Error caught in _applyDatasourcePrecedence in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'applyDatasourcePrecedence'); gs.log(debug, 'sp_CMDBIntegrationUtils/applyDatasourcePrecedence'); } }, /********************** * getPrecedenceForDatasource() * * Finds the precedence order for a given datasource and CI table. Looks * for data source precedence records for the given table, and tables in * the class hierarchy, selecting the first one found in that order. This * means that a catch-all data source precedence record can be set up for * a given datasource, applying to cmdb_ci. * * @param {Object} iDatasource - The name of the datasource * @param {String} iTargetCITable - The table name * @return {Number} - Precedence order, or empty string if none found **********************/ getPrecedenceForDatasource: function(iDatasource, iTargetCITable) { try { var datasourcePrecedence = ''; var isManualEntry = (iDatasource == 'Manual Entry'); var logSource = 'getPrecedenceForDatasource'; var debug = 'In sp_CMDBIntegrationUtils/getPrecedenceForDatasource : ' + 'iDatasource=' + iDatasource + ', iTargetCITable=' + iTargetCITable; var javaTableHierarchy = new TableUtils(iTargetCITable).getTables(); gs.include("j2js"); var tableHierarchy = j2js(javaTableHierarchy); // convert Java ArrayList to Javascript array debug += '\nLooking for precedence records for tables in ' + tableHierarchy; var grDataSourcePrecedence = new GlideRecord('cmdb_datasource_precedence'); grDataSourcePrecedence.addQuery('applies_to', 'IN', tableHierarchy.toString()); grDataSourcePrecedence.addQuery('discovery_source', iDatasource); grDataSourcePrecedence.addQuery('active', 'true'); grDataSourcePrecedence.query(); debug += '\nQuery results count=' + grDataSourcePrecedence.getRowCount(); for (var i = 0; i < tableHierarchy.length; i++) { var tableNameInHierarchy = tableHierarchy[i].toString(); if (grDataSourcePrecedence.find('applies_to', tableNameInHierarchy)) { datasourcePrecedence = Number(grDataSourcePrecedence.order.toString()); debug += '\nFound precedence record for ' + tableNameInHierarchy + ' with Order ' + datasourcePrecedence; break; } } if (this.ifDoDebugLogging()) { gs.log(debug, logSource); } return datasourcePrecedence; } catch (err) { gs.logError( 'Error caught in getPrecedenceForDatasource in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'getPrecedenceForDatasource'); return ''; } }, /********************** * getAuthorisedAttributes() * * Finds the list of attributes which a given datasource is authorised * to update on a given table, based on the reconciliation record. If * no Reconciliation definition exists, or it has no attributes defined * on it, use the full list of fields for this table. * * Ideally this function would do the same cleverer logic that * IRE does, where the Ci table hierarchy is inspected to work out which * attributes can be updated. But currently we don't do that. * * @param {Object} iDatasource - The name of the datasource * @param {String} iTargetCITable - The table name * @return {Array} - Array of attribute names, with empty meaning all **********************/ getAuthorisedAttributes: function(iDatasource, iTargetCITable) { try { var attributeList = ''; var authorisedFields = []; var logSource = 'getAuthorisedAttributes'; var debug = 'In sp_CMDBIntegrationUtils/getAuthorisedAttributes' + '\nDatasource=' + iDatasource + '\niTargetCITable=' + iTargetCITable; var grReconDef = new GlideRecord('cmdb_reconciliation_definition'); grReconDef.addQuery('discovery_source', iDatasource); grReconDef.addQuery('applies_to', iTargetCITable); grReconDef.addQuery('active', 'true'); grReconDef.query(); if (grReconDef.next()) { attributeList = grReconDef.attributes; if (attributeList != '') { authorisedFields = attributeList.split(","); debug += '\nGot field list from Reconciliation Definition'; } } else { debug += '\nNo attributes defined in Reconciliation Definition'; gs.log( 'WARNING: in getAuthorisedAttributes in sp_CMDBIntegrationUtils - ' + 'no reconciliation record found for data source=' + iDatasource + ', table=' + iTargetCITable, logSource); } debug += '\n\nAllowed fields from reconciliation record: ' + authorisedFields.toString(); if (this.ifDoDebugLogging()) { gs.log(debug, logSource); } return authorisedFields; } catch (err) { gs.logError( 'Error caught in getAuthorisedAttributes in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'getAuthorisedAttributes'); return []; } }, /********************** * ifBlockUpdateByPrecedence() * * Decides whether to block or allow a field update on a CI, based on the * current source and its precedence order, and the source which last * updated the field, and its precedence order. Any of the parameters * might be empty, depending on whether the calling function found * precedence records etc. * * @param {String} iThisDatasource - current datasource trying to update the CI * @param {Number} iThisPrecedence - the precedence order for the current source * @param {String} iLastDatasource - the datasource which last updated the field * @param {Number} iLastPrecedence - the precedence order for the last source * @return {Boolean} - true if the field update should be blocked **********************/ ifBlockUpdateByPrecedence: function(iThisDatasource, iThisPrecedence, iLastDatasource, iLastPrecedence) { try { var blockUpdate; var logSource = 'ifBlockUpdateByPrecedence'; var logInfo = iThisDatasource + '|' + iThisPrecedence + '|' + iLastDatasource + '|' + iLastPrecedence; var debug = 'In sp_CMDBIntegrationUtils/ifBlockUpdateByPrecedence : ' + 'iThisDatasource=' + iThisDatasource + ', ' + 'iThisPrecedence=' + iThisPrecedence + ', ' + 'iLastDatasource=' + iLastDatasource + ', ' + 'iLastPrecedence=' + iLastPrecedence; if (iLastPrecedence == '') { // Last datasource precedence empty : allow update blockUpdate = false; debug += '\n- Update allowed: Last datasource precedence empty'; } else if (iThisPrecedence == '') { // This datasource precedence empty, Last datasource precedence not empty : block update blockUpdate = true; debug += '\n- Update to be blocked: This datasource precedence empty, Last datasource precedence not empty'; } else if (iLastDatasource == iThisDatasource) { // Same data source as last time : allow update (regardless of precedence level) blockUpdate = false; debug += '\n- Update allowed: Same datasource as last time'; } else if (iThisPrecedence < iLastPrecedence) { // This datasource precedence < Last datasource precedence : allow update blockUpdate = false; debug += '\n- Update allowed: This datasource precedence < Last datasource precedence'; } else if (iThisPrecedence == iLastPrecedence) { // Precedence level unchanged, but different source than before : block update blockUpdate = true; debug += '\n- Update to be blocked: Precedence level unchanged, but from different source'; } else if (iThisPrecedence > iLastPrecedence) { // This datasource precedence > Last datasource precedence : block update blockUpdate = true; debug += '\n- Update to be blocked: This datasource precedence > Last datasource precedence'; } else { // Unrecognised case - shouldn't get here, but allow the update blockUpdate = false; debug += '\n- Update allowed: Precedence & datasource scenario unhandled, but allow update'; gs.logError( 'ERROR: in ifBlockUpdateByPrecedence in sp_CMDBIntegrationUtils - ' + 'unhandled precedence & datasource scenario - ' + logInfo, logSource); } if (this.ifDoDebugLogging()) { gs.log(debug, logSource); } return blockUpdate; } catch (err) { gs.logError( 'Error caught in blockUpdateBasedOnPrecedence in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'ifBlockUpdateByPrecedence'); return false; } }, /********************** * getTransformMapTargetFields() * * Returns an array of target field names from the field maps in a specified transform map. * If the transform map has Copy Empty Fields false, then omit any fields which are empty * in the source record. * * @param {Object} map - The transform map to get the fields from * @param {GlideRecord} source - The source record from the Transform Map * @return {array} - Array of field names from the fields maps **********************/ getTransformMapTargetFields: function(map, source) { try { var targetFields = []; var mapCopyEmptyFields = (map.getValue('copy_empty_fields') == true); var addThisField; var td = GlideTableDescriptor.get(map.target_table); var entryGr = new GlideRecord('sys_transform_entry'); entryGr.addQuery('source_table', map.source_table); entryGr.addQuery('map', map.sys_id); entryGr.query(); //-------------------------------------------------------- // Loop over the field maps defined for the transform map // If the transform map has 'Copy Empty Fields' not // checked, then we need to see whether the source record // is supplying an empty value. If so, the transform won't // copy the value to the target record, so ignore it here // too. For each field, if there is a source script then // we need to evaluate that to get the source value. //-------------------------------------------------------- while (entryGr.next()) { addThisField = true; if (!mapCopyEmptyFields) { var sourceValue = ''; if (entryGr.getValue('use_source_script') == true) { var evaluator = new GlideScopedEvaluator(); evaluator.putVariable('answer', null); evaluator.putVariable('source', source); evaluator.putVariable('error', false); evaluator.putVariable('error_message', ''); evaluator.evaluateScript(entryGr, 'source_script'); if (evaluator.getVariable('error') != true) { sourceValue = evaluator.getVariable('answer').toString(); } } else { var sourceField = entryGr.getValue('source_field'); sourceValue = source.getValue(sourceField); if (sourceValue == null) { sourceValue = ''; } } if (sourceValue == '') { addThisField = false; } } if (addThisField) { targetFields.push(entryGr.getValue('target_field')); } } return targetFields; } catch (err) { gs.logError( 'Error caught in getTransformMapTargetFields in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'getTransformMapTargetFields'); return []; } }, /********************** * ifDoDebugLogging() * * Decides if debug logging is currently needed in the various scripts involved * in datasource precedence and management. * * @return {Boolean} - true if logging needed, false otherwise **********************/ ifDoDebugLogging: function() { try { var needLogging = gs.getProperty('sp.cmdb.datasources.precedence_management_logdebug') == 'true'; return needLogging; } catch (err) { gs.logError( 'Error caught in ifDoDebugLogging in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'ifDoDebugLogging'); return false; } }, /********************** * applyReconciliationForTransformMap() * * Carries out reconciliation/precedence for a transform map, when IRE cannot be used. * Blocks updates to certain fields based on precedence of current source vs last * source for the field, * * @param {GlideRecord} source - The source record from the Transform Map * @param {GlideRecord} target - The target record from the Transform Map * @param {Transform Map} map - The map object from the Transform Map * @param {Map log object} log - The log object from the Transform Map * @param {Boolean} isUpdate - true if is updating rather than inserting * @param {String} dataSource - Current data source (from choice list for discovery_source on cmdb_ci) * @return {Array} - names of fields which were not blocked by precedence **********************/ applyReconciliationForTransformMap: function(source, target, map, log, isUpdate, dataSource) { try { var strCount = ("000000" + source.sys_import_row).slice(-6); var targetTable = target.sys_class_name; var allowedFields = []; var logSource = 'applyReconciliationForTransformMap'; var ok = true; var mapFields; var debug = 'In sp_CMDBIntegrationUtils/applyReconciliationForTransformMap : ' + '\nmap.name=' + map.name + '\nrow=' + strCount + '\ntarget.sys_class_name=' + targetTable + '\ntarget.sys_id=' + target.sys_id + '\nDatasource=' + dataSource; var outputRecordSysId = target.sys_id.toString(); debug += '\noutputRecordSysId=' + outputRecordSysId; if (outputRecordSysId == '') { debug += '\n\noutputRecordSysId is empty - aborting'; // means transform map is skipping this record ok = false; } //-------------------------------------------------------- // Get the fields defined on the transform map. If this // is an update operation, apply precedence to remove any // fields which datasource precedence blocks (this will // revert the field in the target record). //-------------------------------------------------------- if (ok) { mapFields = this.getTransformMapTargetFields(map, source); debug += '\nMap fields=' + mapFields.toString(); if (isUpdate) { var mapFieldList = mapFields.join(','); debug += '\nAction = update => apply precedence to fields: ' + mapFieldList; var grCI = new GlideRecord(target.sys_class_name); if (grCI.get(outputRecordSysId)) { debug += '\nApplying datasource precedence...'; allowedFields = this.applyDatasourcePrecedence(target, grCI, mapFieldList, dataSource, targetTable); debug += '\nFields allowed by precedence=' + allowedFields.toString(); } else { debug += '\n\nError, could not get current CI - aborting'; gs.logError( 'ERROR: in main script in Transform Map ' + map.name + ' - could not get current CI with sys_id=' + outputRecordSysId, logSource); ok = false; } } else { debug += '\nAction = insert => allow all fields'; allowedFields = mapFields; } } if (this.ifDoDebugLogging()) { gs.log(debug, logSource); } return allowedFields; } catch (err) { gs.logError( 'Error caught in applyReconciliationForTransformMap in sp_CMDBIntegrationUtils: ' + err.name + ' - ' + err.message, 'applyReconciliationForTransformMap'); return []; } }, type: 'sp_CMDBIntegrationUtils' };