Name

global.TaskSLAController

Description

Controller for the TaskSLA task - test the SLA conditions, and update the TaskSLA, and related workflows. - schedule an asynchronous job if property com.snc.sla.engine.async == true (default true), otherwise complete the work synchronously // run the TaskSLAController for a GlideRecord, task new TaskSLAController(task).run(); // run the TaskSLAController with a given task (or subclass) record sys_id, and subclass name new TaskSLAController(task_sys_id, sys_class_name).run(); // optional methods // -- set the task GlideRecord taskSLAController.setTaskGR(task); // -- get the current task GlideRecord var taskGR = taskSLAController.getTaskGR(); // -- set a special flag, for replaying a task (to prevent insertion of fresh breach timers by TaskSLA) taskSLAController.setReplaying(true); // -- get task s associated task_sla records, as a GlideRecord result set var taskGRquery = taskSLAController.queryTaskSLAs(); while (taskGRquery.next()) ...

Script

var TaskSLAController = Class.create();

TaskSLAController.prototype = {

  // sys_properties
  SLA_TASK_SLA_CONTROLLER_LOG: 'com.snc.sla.task_sla_controller.log',
  SLA_TASK_SLA_CONTROLLER_TIMERS: 'com.snc.sla.task_sla_controller.timers',
  SLA_TASK_SLA_DEFAULT_CONDITIONCLASS: 'com.snc.sla.default_conditionclass',
  SLA_DATABASE_LOG: 'com.snc.sla.log.destination',
  SLA_SERVICE_OFFERING: 'com.snc.service.offering.field',
  SLA_GET_TASK_FROM_DB: 'com.snc.sla.get_task_from_db',
  SLA_CONDTION_CLASS_CACHING: 'com.snc.sla.condition_class.caching.enabled',
  SLA_CONDTION_CLASS_CACHING_LIMIT: 'com.snc.sla.condition_class.caching.limit',

  // constants
  BASE_CONDITIONCLASSNAME: 'SLAConditionBase',
  RESET_ACTION_CANCEL: 'cancel',
  RESET_ACTION_COMPLETE: 'complete',
  DEFAULT_SLA_CONDITION_CLASS_KEY: 'DEFAULT_SLA_CONDITION_CLASS',

  initialize : function(taskGR, taskType, modCount) {
  	this.taskSysId = null;
  	this.taskClass = null;
  	this.taskLatestModCount = -1;
  	this.taskGR = null;
  	this.taskHistoryWalker = null;
  	this.calledFromAsync = false;
  	this.asyncTaskWalked = false;
  	this.asyncModCount = null;
  	this.gScheduleRecordId = null;
  	this.getTaskFromDB = false;
  	this.taskWalked = false;

  	this.conditionClassCachingEnabled = gs.getProperty(this.SLA_CONDTION_CLASS_CACHING, 'false') + '' === 'true';
  	this.conditionClassCachingLimit = parseInt(gs.getProperty(this.SLA_CONDTION_CLASS_CACHING_LIMIT, 10));
  	if (isNaN(this.conditionClassCachingLimit) || this.conditionClassCachingLimit < 1)
  		this.conditionClassCachingLimit = 10;

  	if (taskGR) {
  		if (!taskType)
  			// normal initialization
  			this.taskGR = taskGR;
  		else {
  			// entry point for Async sys_trigger execution
  			// (taskGR is a sys_id, and taskType is its sys_class)
  			this.taskGR = this._getTask(taskType, taskGR);
  			this.calledFromAsync = true;
  		}
  	}

  	// trace, debug, logging
  	this.timers = (gs.getProperty(this.SLA_TASK_SLA_CONTROLLER_TIMERS, 'false') == 'true');
  	this.lu = new GSLog(this.SLA_TASK_SLA_CONTROLLER_LOG, 'TaskSLAController');
  	this.lu.includeTimestamp();

  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('initialize: for Task ' + (this.taskGR !== null ? this.taskGR.getDisplayValue() : ""));

  	this.slaAsyncUtils = new SLAAsyncUtils();
  	this.slaAsyncQueue = new SLAAsyncQueue();
  	this.runningAsync = this.slaAsyncUtils.isAsyncProcessingActive();

  	if (this.taskGR !== null) {
  		this.taskClass = this.taskGR.getRecordClassName();
  		this.taskSysId = this.taskGR.getValue('sys_id');
  		this.taskLatestModCount = parseInt(this.taskGR.getValue('sys_mod_count'), 10);

  		/* If we're not running asynchronously check if our Task's type is in the system property that forces
  		certain task types to be fetched from the database instead of using "current" */
  		this.getTaskFromDBTables = gs.getProperty(this.SLA_GET_TASK_FROM_DB, '').split(",");
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('initialize: getTaskFromDBTables=' + this.getTaskFromDBTables);
  		this.getTaskFromDB = this.runningAsync === false && this.getTaskFromDBTables.indexOf(this.taskClass) >= 0;
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('initialize: getTaskFromDB=' + this.getTaskFromDB);

  		if (this.getTaskFromDB)
  			this.taskGR = this._getTask(this.taskClass, this.taskSysId);

  		if (this.calledFromAsync) {
  			this.asyncModCount = isNaN(modCount) ? this.taskLatestModCount : parseInt(modCount, 10);
  			if (typeof g_schedule_record !== "undefined" && g_schedule_record.sys_id)
  				this.gScheduleRecordId = g_schedule_record.sys_id;
  		}
  	}

  	// SLAConditionClass default override
  	this.SLADefaultConditionClassName = gs.getProperty(this.SLA_TASK_SLA_DEFAULT_CONDITIONCLASS, 'SLAConditionBase');
  	// for TaskSLAreplay
  	this.replayingTask = false;
  	//Default Service Offering field
  	this.serviceOfferingField = gs.getProperty(this.SLA_SERVICE_OFFERING, 'cmdb_ci');
  	if (gs.getProperty(this.SLA_DATABASE_LOG, "db") == "node")
  		this.lu.disableDatabaseLogs();
  	this.newSLADefIds = [];
  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('newSLADefIds initialized to length=' + this.newSLADefIds.length);
  	this.slalogging = new TaskSLALogging();
  	this.slaContractUtil = new SLAContractUtil();
  	this.slaUtil = new SLAUtil();
  	this.breakdownsPluginActive = GlidePluginManager.isActive("com.snc.sla.breakdowns");
  	this.domainsPluginActive = GlidePluginManager.isActive("com.glide.domain");

  	this._slaConditionClassCache = {};
  },

  // run TaskSLAController once with the task's current state
  run: function() {
  	if (this.runningAsync) {
  		this._queueAsync();

  		if (this.lu.atLevel(GSLog.INFO) && this.slaAsyncUtils.isSLAAsyncOverride())
  			this.lu.logInfo("run: SLA Async override enabled so update number " + this.taskLatestModCount + " of " + this.taskGR.sys_meta.label + " " +
  							this.taskGR.getDisplayValue() + " will be processed asynchronously");

  		return;
  	}

  	if (this.slaAsyncQueue.isTaskQueued(this.taskSysId)) {
  		if (this.lu.atLevel(GSLog.INFO))
  			this.lu.logInfo("run: there are unprocessed records in the SLA Async queue for " + this.taskGR.sys_meta.label + " " +
  							this.taskGR.getDisplayValue() + " so this update (" + this.taskLatestModCount + ") will be processed asynchronously");
  		this._queueAsync();
  		return;
  	}

  	this.runNow();
  },

  // run once synchronously, even if the property says otherwise
  runSync: function() {
  	this.runningAsync = false;
  	this.runNow();
  },

  // also called when running from asynchronous job
  runNow: function() {
  	if (this.taskGR === null || !this.taskSysId || !this.taskClass) {
  		this.lu.logWarn('runNow: no Task supplied so TaskSLAController cannot continue');
  		return;
  	}

  	var sw;
  	if (this.timers)
  		sw = new GlideStopWatch();
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('runNow: starting now (sys_updated=' + this.taskGR.sys_updated_on.getDisplayValue() + ')');
  	if (this.lu.atLevel(GSLog.DEBUG)) {
  		this.lu.logDebug('runNow: ' + this.slalogging.getBusinessRuleStackMsg());
  		this.lu.logDebug('runNow: previous and current values\n' + this.slalogging.getRecordContentMsg(previous, '"previous"') + '\n' + this.slalogging.getRecordContentMsg(current, '"current"'));
  	}

  	var currentDomain = null;
  	if (this.domainsPluginActive)
  		currentDomain = gs.getSession().getCurrentDomainID();

  	try {
  		if (this.calledFromAsync) {
  			this.slaAsyncUtils.setSLAAsyncProcessing(true);
  			if (this.domainsPluginActive && !this.taskGR.sys_domain.nil())
  				gs.getSession().setDomainID(this.taskGR.getValue("sys_domain"));
  		}

  		this._processNewSLAs();
  		this._processExistingSLAs();
  	} catch (e) {
  		if (this.lu.atLevel(GSLog.ERROR)) {
  			var stack = e.getStackTrace();
  			var stackErr = "";
  			for (var i = 0; i < stack.length; i++)
  				stackErr += "\t" + stack[i] + "\n";

  			this.lu.logError("runNow: error occurred during SLA processing of task " + this.taskGR.getDisplayValue() + "\n" + e + "\n" + stackErr);
  		}
  	} finally {
  		if (this.calledFromAsync) {
  			this.slaAsyncUtils.setSLAAsyncProcessing(false);
  			if (this.domainsPluginActive)
  				gs.getSession().setDomainID(currentDomain);
  		}
  	}

  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('runNow: finished');

  	if (this.timers)
  		sw.log('TaskSLAController.runNow complete');
  },

  setTaskGR: function(taskGR) {
  	this.taskGR = taskGR;
  },

  getTaskGR: function() {
  	return this.taskGR;
  },

  setReplaying: function(enable) {
  	this.replayingTask = enable;
  },

  // Enable Stopwatch timers
  // (used for profiling performance)
  setTimers: function(enable) {
  	this.timers = enable;
  },

  // return GlideRecord result set of task's active task_sla records
  queryTaskSLAs: function() {
  	var taskSLAgr = new GlideRecord('task_sla');
  	taskSLAgr.addActiveQuery();
  	taskSLAgr.addDomainQuery(this.taskGR);
  	taskSLAgr.addQuery('task', this.taskGR.sys_id);
  	taskSLAgr.orderBy('sla.sys_id');
  	taskSLAgr.query();
  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('queryTaskSLAs: #' + taskSLAgr.getRowCount());
  	return taskSLAgr;
  },

  queryContractSLAs: function() {
  	var contractSLAgr = new GlideRecord('contract_sla');
  	contractSLAgr.addDomainQuery(this.taskGR);
  	var taskSLAJoin = contractSLAgr.addJoinQuery('task_sla', 'sys_id', 'sla');
  	taskSLAJoin.addCondition('task', this.taskGR.sys_id);
  	taskSLAJoin.addCondition('active', true);
  	contractSLAgr.orderBy('sys_id');
  	contractSLAgr.query();
  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('queryContractSLAs: #' + contractSLAgr.getRowCount());
  	return contractSLAgr;
  },

  /////////////////////////////

  // mutex names
  MUTEX_NEW: 'Process New SLAs Mutex ',
  MUTEX_UPDATE: 'Process Existing SLAs Mutex ',

  // internal methods

  _queueAsync: function() {
  	var taskSLAGr = this.queryTaskSLAs();

  	if (!(new SLACacheManager().hasDefinitionForRecord(this.taskGR)) && !taskSLAGr.hasNext()) {
  		if (this.lu.atLevel(GSLog.INFO))
  			this.lu.logInfo('_queueAsync: no active SLA definitions or active Task SLAs defined for this Task - ' + this.taskClass + ":" + this.taskSysId);
  		return;
  	}

  	this.slaAsyncQueue.queueTask(this.taskGR);
  },

  _processNewSLAs: function() {
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('_processNewSLAs');

  	if (!(new SLACacheManager().hasDefinitionForRecord(this.taskGR))) {
  		if (this.lu.atLevel(GSLog.INFO))
  			this.lu.logInfo('_processNewSLAs: no active SLA definitions defined for this Task - ' + this.taskClass + ":" + this.taskSysId);
  		return;
  	}

  	// if we've been called from a "sys_trigger" record because SLA processing is running asynchronously
  	// then we use HistoryWalker to get the Task record at the current update number so we've got the
  	// "changes" information for each field
  	if (this.calledFromAsync) {
  		var earlyReadGR = this._getSLAsQueryCheckingContracts(); // We need to do this for async and HW with Journal support
  		earlyReadGR.addDomainQuery(this.taskGR);
  		earlyReadGR.addQuery("adv_condition_type", "advanced_journal");
  		earlyReadGR.setLimit(1);
  		earlyReadGR.query();

  		this._walkTaskForAsync(earlyReadGR.hasNext());
  	}

  	var sw;
  	if (this.timers)
  		sw = new GlideStopWatch();

  	var slaGR = this._getSLAsQueryCheckingContracts();

  	SelfCleaningMutex.enterCriticalSectionRecordInStats(this.MUTEX_NEW + this.taskGR.sys_id, this.MUTEX_NEW, this, this._processNewSLAs_criticalSection, slaGR);
  	// TODO: optionally attach work-notes
  	if (this.timers)
  		sw.log('TaskSLAController: Finished _processNewSLAs part 1');

  	// and active Service Offering SLA definitions
  	// (TODO: merge this contract_sla query with the previous one, to process all of them in one go)
  	if (!this._allowProcessingServiceCommitment())
  		return;

  	var socGR = new GlideRecord('service_offering_commitment');
  	if (!socGR.isValid())
  		return;

  	var commitmentFieldTest = new GlideRecord('service_commitment');
  	if (!commitmentFieldTest.isValidField("sla"))
  		return;

  	if (this.timers)
  		sw = new GlideStopWatch();
  	// (using contract_sla GlideRecord to easily avoid
  	//  those that are currently active and assigned to the task)
  	slaGR.initialize();
  	slaGR.addActiveQuery();
  	slaGR.addQuery('collection', this.taskGR.getRecordClassName());
  	// service_commitment.type='SLA'
  	slaGR.addQuery('JOINcontract_sla.sys_id=service_commitment.sla!type=SLA');
  	// service_offering_commitment.service_offering=cmdb_ci
  	slaGR.addQuery('JOINservice_commitment.sys_id=service_offering_commitment.service_commitment!service_offering=' + this.taskGR.getValue(this.serviceOfferingField));

  	SelfCleaningMutex.enterCriticalSectionRecordInStats(this.MUTEX_NEW + this.taskGR.sys_id, this.MUTEX_NEW, this, this._processNewSLAs_criticalSection, slaGR);
  	// TODO: optionally attach more work-notes
  	if (this.timers)
  		sw.log('TaskSLAController: Finished _processNewSLAs part 2');
  },

  // (called after obtaining SelfCleaningMutex: '<<<--Process New SLAs Mutex ' + this.taskGR.sys_id + '-->>>')
  // NB. adds to slaGR query, before executing it.
  _processNewSLAs_criticalSection: function(slaGR) {
  	var newTaskSLA;
  	var newTaskSLAs = [];
  	this.fieldValuesLogged = false;
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('_processNewSLAs_criticalSection: ' + this.taskGR.sys_id);
  	var sw;
  	if (this.timers)
  		sw = new GlideStopWatch();

  	// Log the field values in the Task record
  	if (this.lu.atLevel(GSLog.DEBUG)) {
  		this.lu.logDebug('_processNewSLAs_criticalSection:\n' + this.slalogging.getRecordContentMsg(this.taskGR));
  		this.fieldValuesLogged = true;
  	}

  	// skip any active SLAs already (indirectly) attached to this task -- must be done inside of mutex
  	slaGR.addQuery('sys_id', 'NOT IN', this._getSLAsString(this.queryTaskSLAs()));
  	slaGR.addDomainQuery(this.taskGR);
  	slaGR.query();
  	while (slaGR.next()) {
  		var oldLogLevel = this.lu.getLevel();
  		// if enable logging has been checked on the SLA definition up the log level to "debug"
  		if (slaGR.enable_logging) {
  			this.lu.setLevel(GSLog.DEBUG);
  			if (!this.fieldValuesLogged) {
  				this.lu.logDebug('_processNewSLAs_criticalSection:\n' + this.slalogging.getRecordContentMsg(this.taskGR));
  				this.fieldValuesLogged = true;
  			}
  		}

  		newTaskSLA = this._checkNewSLA(slaGR);
  		if (newTaskSLA)
  			newTaskSLAs.push(newTaskSLA);

  		this.lu.setLevel(oldLogLevel);
  	}

  	if (newTaskSLAs.length > 0) {
  		this._adjustPauseTime(newTaskSLAs);
  		for (var i = 0; i < newTaskSLAs.length; i++) {
  			newTaskSLA = newTaskSLAs[i];
  			var taskSLAgr = newTaskSLA.taskSLA.getGlideRecord();
  			if (newTaskSLA.needsAdjusting && !newTaskSLA.adjusted)
  				newTaskSLA.taskSLA.updateState(TaskSLA.STATE_IN_PROGRESS);
  			// if currently processing asynchronously we need to call breakdown processing as this is not handled by business rule "Process SLA Breakdowns"
  			if (this.calledFromAsync && this.breakdownsPluginActive && sn_sla_brkdwn.SLABreakdownProcessor.hasBreakdownDefinitions(taskSLAgr.getValue("sla")))
  				this._processSLABreakdowns(taskSLAgr, null, "insert");
  		}
  	}

  	if (this.timers)
  		sw.log('TaskSLAController._processNewSLAs_criticalSection complete');
  },

  // Gets the SLA definitions to process taking the contracts into account
  _getSLAsQueryCheckingContracts: function() {
  	var collection = this.taskGR.getRecordClassName();
  	// if contract is not active for this table, we process all SLAs
  	if (this.slaContractUtil.ignoreContract(collection))
  		return this.slaContractUtil.getAllSLAsQuery(collection);

  	var contractGR = this.taskGR.contract;
  	// if the task has a contract attached we process the SLAs linked to the contract and, if enabled, the non-contractual SLAs

  	function isExtension(baseTable, extensionTable) {
  		var tu = new TableUtils(baseTable);
  		var tabArrLst = tu.getTableExtensions();
  		return tabArrLst.contains(extensionTable);
  	}

  	var contractGr = null;
  	var isASTService = false;
  	var isASTContract = false;

  	var hasContract = !this.taskGR.contract.nil();
  	if (hasContract) {
  		contractGr = this.taskGR.contract.getRefRecord();
  		var className = contractGr.getRecordClassName() + "";
  		isASTService = className === "ast_service";
  		isASTContract = className === "ast_contract";
  		if (!isASTService && !isASTContract) // Check for ast_service extensions
  			isASTService = isExtension("ast_service", className);
  		if (!isASTService && !isASTContract) // Check for ast_contract extensions not in ast_service tree
  			isASTContract = isExtension("ast_contract", className);
  	}

  	var processContract = false;
  	// Original behaviour.  If it has a contract as it's an ast_service (or extension) record, process the contract
  	if (hasContract && isASTService)
  		processContract = true;

  	// New behaviour.  If it's an ast_contract (or extension which isn't in the ast_service branch)
  	// check the attach_sla field on the contract model
  	if (!processContract && hasContract && isASTContract)
  		if (!contractGr.contract_model.nil() && contractGr.contract_model.attach_sla + "" === "true")
  			processContract = true;

  	if (processContract) {
  		var includeNonContractual = this.slaContractUtil.processNonContractualSLAs(contractGR);
  		var slaGR = this.slaContractUtil.getContractualSLAs(contractGR, collection, includeNonContractual);
  		return slaGR;
  	}

  	// if the task doesn't have a contract, we process non-contractual (but only if the contract table property doesn't exist to preserve legacy behavior)
  	if (!this.slaContractUtil.hasContractProperty())
  		return this.slaContractUtil.getNonContractualSLAs(collection);

  	// if nothing of the above matches, return an empty query
  	var emptySlaGR = new GlideRecord('contract_sla');
  	emptySlaGR.setLimit(0);
  	return emptySlaGR;
  },

  _allowProcessingServiceCommitment: function() {
  	var collection = this.taskGR.getRecordClassName();
  	// if contract does not allow to process non-contractual SLAs, Service Offering SLAs are not processed either
  	return this.slaContractUtil.ignoreContract(collection)
  	|| (!this.taskGR.contract && !this.slaContractUtil.hasContractProperty())
  	|| this.slaContractUtil.processNonContractualSLAs(this.taskGR.contract);
  },

  /*
  Check the Attach Conditions of the specified contract_sla (or service_offering_commitment) definition
  If (SLACondition).attach returns true then attach it to this task

  pre-conditions: by this point, we have confirmed that it isn't currently attached to the task,
  and we have the Mutex for "Process New SLAs Mutex " + this.taskGR.sys_id) to prevent it being added by another TaskSLAController
  */
  _checkNewSLA: function(slaGR) {
  	var sw;
  	if (this.timers)
  		sw = new GlideStopWatch();
  	var slac = this._newSLACondition(slaGR, this.taskGR);
  	var startMatches = slac.attach();
  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_checkNewSLA: checking ' + slaGR.name + ', start condition matched=' + startMatches);

  	if (!startMatches)
  		return null;

  	this.newSLADefIds.push(slaGR.getValue('sys_id'));
  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_checkNewSLA newSLADefIds=[' + this.newSLADefIds.join() + ']');

  	// this object will contain properties to indicate if this new Task SLA needs retroactive pause time calculated,
  	// if the adjust pause was succesful and also the TaskSLA object itself
  	var newTaskSLA = {
  		"needsAdjusting": false,
  		"adjusted": false
  	};

  	var taskSLA;
  	// Check if this TaskSLA needs retroactive pause calculation...
  	if (this._needsAdjustPause(slaGR)) {
  		/* If retroactive pause calculation is required we need to create a copy of the "contract_sla" record
  		   so we have a copy to pass as an argument when creating the TaskSLA object.
  		   We can't just use "slaGR" as this is changing as we next through it */
  		newTaskSLA.needsAdjusting = true;
  		newTaskSLA.contractSLAgr = this.slaUtil.copyContractSLA(slaGR);
  		taskSLA = new TaskSLA(newTaskSLA.contractSLAgr, this.taskGR, /* deferInsert */ true, {calledFromAsync: this.calledFromAsync});
  		this.slaUtil.setSLAUpdateSourceEngineParameter(taskSLA.getGlideRecord(), this.type);
  	} else {
  		// If there's no retroactive pause calculation we can just go ahead and create the Task SLA
  		taskSLA = new TaskSLA(slaGR, this.taskGR, /* deferInsert */ true, {calledFromAsync: this.calledFromAsync});
  		this.slaUtil.setSLAUpdateSourceEngineParameter(taskSLA.getGlideRecord(), this.type);
  		taskSLA.updateState(TaskSLA.STATE_IN_PROGRESS); // adds task_sla record, initiates model state machine, starts notification workflow
  		if (this.lu.atLevel(GSLog.INFO))
  			this.lu.logInfo('_checkNewSLA: added SLA "' + slaGR.name + '"');
  	}

  	// add the TaskSLA object to our exising newTaskSLA object
  	newTaskSLA.taskSLA = taskSLA;

  	if (this.timers)
  		sw.log('TaskSLAController: Finished _checkNewSLA');

  	return newTaskSLA;
  },

  _checkNewSLAsFromReset: function(resetSLAs) {
  	this._checkNewSLAsForDefs(resetSLAs);
  },

  _checkNewSLAsForDefs: function(slaDefIds) {
  	if (!slaDefIds || slaDefIds.length == 0)
  		return null;

  	var slaGR = new GlideRecord('contract_sla');
  	slaGR.addActiveQuery();
  	slaGR.addQuery('sys_id', slaDefIds);
  	slaGR.addDomainQuery(this.taskGR);
  	slaGR.query();

  	var newTaskSLAs = [];
  	var newTaskSLADefIds = [];
  	var newTaskSLA;
  	while (slaGR.next()) {
  		newTaskSLA = this._checkNewSLA(slaGR);
  		if (!newTaskSLA)
  			continue;
  		if (!newTaskSLA.contractSLAgr)
  			newTaskSLA.contractSLAgr = this.slaUtil.copyContractSLA(slaGR);
  		if (newTaskSLA) {
  			newTaskSLAs.push(newTaskSLA);
  			newTaskSLADefIds.push(slaGR.getValue('sys_id'));
  		}
  	}

  	// Perform the retroactive pause adjustment for the ones that need it
  	if (newTaskSLAs.length > 0) {
  		this._adjustPauseTime(newTaskSLAs);
  		for (var i = 0; i < newTaskSLAs.length; i++) {
  			newTaskSLA = newTaskSLAs[i];
  			var taskSLAgr = newTaskSLA.taskSLA.getGlideRecord();
  			var previousTaskSLAgr;
  			// if currently processing asynchronously we need to call breakdown processing as this is not handled by business rule "Process SLA Breakdowns"
  			var processBreakdowns = this.calledFromAsync && this.breakdownsPluginActive && sn_sla_brkdwn.SLABreakdownProcessor.hasBreakdownDefinitions(taskSLAgr.getValue("sla"));
  			if (newTaskSLA.needsAdjusting && !newTaskSLA.adjusted)
  				newTaskSLA.taskSLA.updateState(TaskSLA.STATE_IN_PROGRESS);
  			if (processBreakdowns) {
  				previousTaskSLAgr = this.slaUtil.copyTaskSLA(taskSLAgr);
  				this._processSLABreakdowns(taskSLAgr, null, "insert");
  			}

  			// and just in case it should need to transition to Paused state immediately upon creation
  			// make sure the "updateTime" is correct as it may not be if we've been doing a retroactive calculation
  			newTaskSLA.taskSLA.setUpdateTime(this.taskGR.sys_updated_on);
  			var conditionResults = this._pauseUnpause(newTaskSLA.taskSLA, newTaskSLA.contractSLAgr);
  			if (processBreakdowns && conditionResults.stageChangedTo)
  				this._processSLABreakdowns(taskSLAgr, null, "insert");
  		}
  	}

  	return newTaskSLADefIds;
  },

  // process all of a task's active, attached task_sla records
  _processExistingSLAs: function() {
  	this.lu.logInfo('_processExistingSLAs');

  	// Get the current set of active Task SLAs for this record
  	var taskSLAgr = this.queryTaskSLAs();
  	if (!taskSLAgr.hasNext()) {
  		if (this.lu.atLevel(GSLog.INFO))
  			this.lu.logInfo('_processExistingSLAs: no active Task SLAs found for Task - ' + this.taskClass + ":" + this.taskSysId);
  		return;
  	}
  	var contractSLAgr = null;
  	if (GlidePluginManager.isActive("com.glide.domain"))
  		contractSLAgr = this.queryContractSLAs();

  	SelfCleaningMutex.enterCriticalSectionRecordInStats(this.MUTEX_UPDATE + this.taskGR.sys_id, this.MUTEX_UPDATE, this, this._processExistingSLAs_criticalSection, taskSLAgr, contractSLAgr);
  },

  // (called after obtaining SelfCleaningMutex MUTEX_UPDATE: '<<<--Process Existing SLAs Mutex ' + this.taskGR.sys_id + '-->>>',
  // to prevent simultaneous/overlapping updates of the task_sla records)
  _processExistingSLAs_criticalSection: function(taskSLAgr, contractSLAgr) {
  	this.fieldValuesLogged = false;
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('_processExistingSLAs_criticalSection: ' + this.taskGR.sys_id);
  	var sw;
  	if (this.timers)
  		sw = new GlideStopWatch();

  	// if we've been called from a "sys_trigger" record because SLA processing is running asynchronously
  	// then we use HistoryWalker to get the Task record at the current update number so we've got the
  	// "changes" information for each field
  	if (this.calledFromAsync) {
  		var withJournal = false;
  		var withSysFields = false;
  		var withVariables = false;

  		if (contractSLAgr !== null) { //Domains is active and we should have records to iterate over
  			while ((!withJournal || !withSysFields || !withVariables) && contractSLAgr.next()) {
  				if (this.slaUtil.hasAdvancedJournalCondition(contractSLAgr))
  					withJournal = true;
  				if (this.slaUtil.hasAdvancedSysFieldCondition(contractSLAgr))
  					withSysFields = true;
  				if (this.slaUtil.hasVariablesCondition(contractSLAgr))
  					withVariables = true;
  			}
  			contractSLAgr.setLocation(-1);
  		} else {
  			while ((!withJournal || !withSysFields || !withVariables) && taskSLAgr.next()) {
  				var tempContractSLARecord = taskSLAgr.sla.getRefRecord();
  				if (this.slaUtil.hasAdvancedJournalCondition(tempContractSLARecord))
  					withJournal = true;
  				if (this.slaUtil.hasAdvancedSysFieldCondition(tempContractSLARecord))
  					withSysFields = true;
  				if (this.slaUtil.hasVariablesCondition(tempContractSLARecord))
  					withVariables = true;
  			}
  			taskSLAgr.setLocation(-1);
  		}

  		this._walkTaskForAsync(withJournal, withSysFields, withVariables);
  	}

  	// Log the field values in the Task record
  	if (this.lu.atLevel(GSLog.DEBUG)) {
  		this.lu.logDebug('_processExistingSLAs_criticalSection:\n' + this.slalogging.getRecordContentMsg(this.taskGR));
  		this.fieldValuesLogged = true;
  	}

  	var resetTaskSLAs = [];
  	var contractSLA;
  	var taskSLA;
  	var conditionResults;
  	var previousTaskSLAGr;

  	while (taskSLAgr.next()) {
  		var hasBreakdowns = this.breakdownsPluginActive && sn_sla_brkdwn.SLABreakdownProcessor.hasBreakdownDefinitions(taskSLAgr.getValue("sla"));

  		// if we need to process breakdowns that create our own "previous" copy of the Task SLA record
  		if (hasBreakdowns)
  			previousTaskSLAGr = this.slaUtil.copyTaskSLA(taskSLAgr);

  		contractSLA = this._getContractSLA(taskSLAgr, contractSLAgr);

  		if (this.getTaskFromDB) {
  			var slaConditionClass = this._newSLACondition(contractSLA, this.taskGR, taskSLAgr);
  			if (typeof slaConditionClass.hasAdvancedConditions !== "function" || slaConditionClass.hasAdvancedConditions()) {
  				if (this.lu.atLevel(GSLog.DEBUG))
  					this.lu.logDebug("_processExistingSLAs_criticalSection: getTaskFromDB is true and SLA Definition \"" + contractSLA.getDisplayValue() + "\" has advanced conditions");
  				this._walkTaskToLatest(this.slaUtil.hasAdvancedJournalCondition(contractSLA), this.slaUtil.hasAdvancedSysFieldCondition(contractSLA), this.slaUtil.hasVariablesCondition(contractSLA));
  			}
  		}

  		var oldLogLevel = this.lu.getLevel();
  		// if enable logging has been checked on the SLA definition up the log level to "debug"
  		if (contractSLA.enable_logging) {
  			this.lu.setLevel(GSLog.DEBUG);
  			if (!this.fieldValuesLogged) {
  				this.lu.logDebug('_processExistingSLAs_criticalSection:\n' + this.slalogging.getRecordContentMsg(this.taskGR));
  				this.fieldValuesLogged = true;
  			}
  		}

  		taskSLA = this._getTaskSLA(taskSLAgr, contractSLA);
  		this.slaUtil.setSLAUpdateSourceEngineParameter(taskSLA.getGlideRecord(), this.type);
  		conditionResults = this._checkExistingSLA(taskSLA, contractSLA);
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug("Condition results for Task SLA " + contractSLA.getDisplayValue() + " on task " + this.taskGR.number +
  							":\n" + JSON.stringify(conditionResults));

  		if (conditionResults.stopCancel.reset)
  			resetTaskSLAs.push(taskSLAgr.getValue('sla'));

  		// SLA Breakdown processing
  		if (hasBreakdowns) {
  			/* if we're running synchronously and the stage hasn't changed on the Task SLA and it's not one we've just created
  			   then call breakdown processing in case a breakdown field on the Task has changed */
  			if ((!this.calledFromAsync && !conditionResults.stageChangedTo && this.newSLADefIds.indexOf(taskSLAgr.getValue("sla") < 0))
  				||
  				(this.calledFromAsync && (this.newSLADefIds.indexOf(taskSLAgr.getValue("sla") < 0) || conditionResults.stageChangedTo)))
  				this._processSLABreakdowns(taskSLA.getGlideRecord(), previousTaskSLAGr, "update");
  		}

  		/* if we're running asynchronously but nothing changed on the Task SLA for this update and there are no records in the
  		   async queue for this task then call SLACalculator to make sure all the values are up to date including "has_breached" */
  		if (this.calledFromAsync && !conditionResults.stageChangedTo && taskSLA.getCurrentState() === TaskSLA.STATE_IN_PROGRESS &&
  			!(this.slaAsyncQueue.isTaskQueued(taskSLAgr.getValue("task"), null, ["ready", "queued"])))
  			SLACalculatorNG.calculateSLA(taskSLA.getGlideRecord(), false, new GlideDateTime(), taskSLA.getContractSLA());

  		this.lu.setLevel(oldLogLevel);
  	}

  	if (resetTaskSLAs.length > 0)
  		SelfCleaningMutex.enterCriticalSectionRecordInStats(this.MUTEX_NEW + this.taskGR.sys_id, this.MUTEX_NEW,
  			this, this._checkNewSLAsFromReset, resetTaskSLAs);

  	if (this.timers)
  		sw.log('TaskSLAController._processExistingSLAs_criticalSection complete');
  },

  _getTaskSLA: function(taskSLAgr, slaDefGR) {
  	return new TaskSLA(taskSLAgr, this.taskGR, null, {calledFromAsync: this.calledFromAsync}, slaDefGR);
  },

  _processSLABreakdowns: function(currentTaskSLAGr, previousTaskSLAGr, operationOverride) {
  	var gru = new GlideRecordUtil();
  	var fields1 = {};
  	var fields2 = {};
  	if (previousTaskSLAGr)
  		gru.populateFromGR(fields1, previousTaskSLAGr);
  	gru.populateFromGR(fields2, currentTaskSLAGr);

  	var breakdownProcessor = new sn_sla_brkdwn.SLABreakdownProcessor(currentTaskSLAGr, previousTaskSLAGr);
  	if (operationOverride)
  		breakdownProcessor.setTaskSLAOperationOverride(operationOverride);
  	breakdownProcessor.setTaskGr(this.taskGR);
  	breakdownProcessor.setUpdateTime(this.taskGR.sys_updated_on);
  	breakdownProcessor.processBreakdowns();
  },

  _checkExistingSLA: function(taskSLA, contractSLA) {
  	var sw;
  	if (this.timers)
  		sw = new GlideStopWatch();

  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_checkExistingSLA: ' + taskSLA.getGlideRecord().sys_id);

  	if (this.replayingTask)
  		taskSLA.setBreachTimer(false); // disable breach timers on the task_sla, for replay

  	var conditionResults = {
  		stopCancel: {

  		},
  		pauseResume: {

  		}
  	};

  	// (stop/cancel takes precedence over pause/unpause also matching in the same update to the task record)
  	conditionResults.stopCancel = this._stopCancel(taskSLA, contractSLA);

  	if (!conditionResults.stopCancel.conditionMatched)
  		conditionResults.pauseResume = this._pauseUnpause(taskSLA, contractSLA);

  	conditionResults.stageChangedTo = conditionResults.stopCancel.stageChangedTo || conditionResults.pauseResume.stageChangedTo;

  	// TODO: work-notes
  	if (this.timers)
  		sw.log('TaskSLAController: Finished _checkExistingSLA');
  	return conditionResults;
  },

  _pauseUnpause: function(taskSLA, contractSLA) {
  	var conditionResults = {};

  	var taskSLAgr = taskSLA.getGlideRecord();

  	if (!contractSLA)
  		contractSLA = taskSLAgr.sla;

  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_pauseUnpause: task = ' + this.taskGR.getValue("number") + ", sla = " + contractSLA.getDisplayValue());

  	// a "relative-duration" SLA cannot pause, whatever conditions might be in the SLA Definition record
  	if (contractSLA.duration_type != '')
  		return conditionResults;

  	var slaConditionClass = this._newSLACondition(contractSLA, this.taskGR, taskSLAgr);

  	conditionResults.pause = slaConditionClass.pause();
  	conditionResults.resume = slaConditionClass.resume();
  	conditionResults.conditionMatched = conditionResults.pause || conditionResults.resume;

  	if (this.lu.atLevel(GSLog.DEBUG)) {
  		this.lu.logDebug('_pauseUnpause: current SLA state=' + taskSLA.getCurrentState() + ', pause condition matched=' + conditionResults.pause);
  		this.lu.logDebug('_pauseUnpause: current SLA state=' + taskSLA.getCurrentState() + ', resume condition matched=' + conditionResults.resume);
  	}

  	if (taskSLA.getCurrentState() == TaskSLA.STATE_IN_PROGRESS && conditionResults.pause && !conditionResults.resume) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_pauseUnpause: Pausing SLA ' + taskSLAgr.getUniqueValue());
  		conditionResults.stageChangedTo = TaskSLA.STATE_PAUSED;
  		taskSLA.updateState(TaskSLA.STATE_PAUSED);
  	}
  	else if (taskSLA.getCurrentState() == TaskSLA.STATE_PAUSED && !conditionResults.pause && conditionResults.resume) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_pauseUnpause: Resuming SLA ' + taskSLAgr.getUniqueValue());
  		conditionResults.stageChangedTo = TaskSLA.STATE_IN_PROGRESS;
  		taskSLA.updateState(TaskSLA.STATE_IN_PROGRESS);
  	}

  	return conditionResults;
  },

  _stopCancel: function(taskSLA, contractSLA) {
  	var conditionResults = {
  		conditionMatched: true
  	};

  	var taskSLAgr = taskSLA.getGlideRecord();

  	if (!contractSLA)
  		contractSLA = taskSLAgr.sla;

  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_stopCancel: task = ' + this.taskGR.getValue("number") + ", sla = " + contractSLA.getDisplayValue());

  	var slaConditionClass = this._newSLACondition(contractSLA, this.taskGR, taskSLAgr);

  	conditionResults.complete = slaConditionClass.complete();
  	conditionResults.reset = slaConditionClass.reattach(this.newSLADefIds);

  	if (this.lu.atLevel(GSLog.DEBUG)) {
  		this.lu.logDebug('_stopCancel: current SLA state=' + taskSLA.getCurrentState() + ', stop condition matched=' + conditionResults.complete);
  		this.lu.logDebug('_stopCancel: current SLA state=' + taskSLA.getCurrentState() + ', reset condition matched=' + conditionResults.reset);
  	}

  	if (conditionResults.complete) {
  		taskSLA.updateState(TaskSLA.STATE_COMPLETED);
  		conditionResults.stageChangedTo = TaskSLA.STATE_COMPLETED;
  		return conditionResults; // state was changed
  	}

  	// Re-evaluate conditions for this specific taskSLA,
  	//      to allow a 'Complete and reapply' mode of operation
  	if (conditionResults.reset) {
  		var resetExistingSLATo = contractSLA.reset_action == this.RESET_ACTION_CANCEL ? TaskSLA.STATE_CANCELLED : TaskSLA.STATE_COMPLETED;
  		taskSLA.updateState(resetExistingSLATo);
  		conditionResults.stageChangedTo = resetExistingSLATo;
  		taskSLA.isReset = true; //flag to detect reset in the taskSLA object
  		return conditionResults; // state was changed
  	} else {
  		conditionResults.cancel = slaConditionClass.cancel();
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_stopCancel: ' + taskSLA.getCurrentState() + ', cancel condition matched=' + conditionResults.cancel);
  		if (conditionResults.cancel) {
  			taskSLA.updateState(TaskSLA.STATE_CANCELLED);
  			conditionResults.stageChangedTo = TaskSLA.STATE_CANCELLED;
  			return conditionResults; // state was changed
  		}
  	}

  	conditionResults.conditionMatched = false;
  	return conditionResults;
  },

  // method to assist overriding the SLA Condition class
  // -- returns an instance of an SLA Condition script include
  _newSLACondition: function(slaGR, taskGR, taskSLAgr) {
  	var conditionClassCacheKey = this.DEFAULT_SLA_CONDITION_CLASS_KEY;
  	if (!JSUtil.nil(slaGR) && !slaGR.condition_class.nil())
  		conditionClassCacheKey = slaGR.getValue("condition_class");
  
  	return this._getConditionClassFromCache(conditionClassCacheKey, slaGR, taskGR, taskSLAgr);
  },

  /* creates, if necessary, and then returns the appropriate SLA condition class object
     from the cache (object "this._slaConditionClassCache")
     
     if the object contains an instance of the associated script include then this is returned
     otherwise we fall back to the old behaviour of creating a new instance of the script include
     and returning it */
  _getConditionClassFromCache: function(cacheKey, slaGR, taskGR, taskSLAgr) {
  	if (JSUtil.nil(cacheKey))
  		cacheKey = this.DEFAULT_SLA_CONDITION_CLASS_KEY;

  	if (!this._slaConditionClassCache.hasOwnProperty(cacheKey))
  		this._cacheSLAConditionClass(cacheKey);

  	var slaConditionClass = this._slaConditionClassCache[cacheKey];
  	// if this slaConditionClass object is marked as inactive or invalid we'll use the default instead
  	if (!slaConditionClass.active || !slaConditionClass.valid)
  		slaConditionClass = this._slaConditionClassCache[this.DEFAULT_SLA_CONDITION_CLASS_KEY];

  	/* if the slaConditionClass already has an instance of the script include we can set just
  	   set the SLA Definition, Task and Task SLA records in it and return it for condition testing */
  	if (slaConditionClass.scriptIncludeInstance !== null) {
  		slaConditionClass.scriptIncludeInstance.setSLA(slaGR, taskGR);
  		slaConditionClass.scriptIncludeInstance.setTask(taskGR);
  		slaConditionClass.scriptIncludeInstance.setTaskSLA(taskSLAgr);
  		return slaConditionClass.scriptIncludeInstance;
  	}

  	// ...otherwise we have to simply create a new instance of the script include and return it
  	return new slaConditionClass.scriptInclude(slaGR, taskGR, taskSLAgr);
  },

  _cacheSLAConditionClass: function(cacheKey) {
  	// if no cache key provided assume the default one
  	if (JSUtil.nil(cacheKey))
  		cacheKey = this.DEFAULT_SLA_CONDITION_CLASS_KEY;

  	// initially set the script include name to be the default one...
  	var scriptIncludeName = this.SLADefaultConditionClassName;
  	// ...but check if our cache key is the sys_id of a condition class record
  	if (cacheKey !== this.DEFAULT_SLA_CONDITION_CLASS_KEY) {
  		/* if we can't get the SLA Condition Class record or it doesn't reference a valid script include
  		   fall back to the default condition class */
  		var slaConditionClassGR = new GlideRecord("sla_condition_class");
  		if (!slaConditionClassGR.get(cacheKey) || slaConditionClassGR.class_name.nil())
  			cacheKey = this.DEFAULT_SLA_CONDITION_CLASS_KEY;
  		var scriptIncludeGR = slaConditionClassGR.class_name.getRefRecord();
  		if (!scriptIncludeGR.isValidRecord())
  			cacheKey = this.DEFAULT_SLA_CONDITION_CLASS_KEY;
  		
  		// otherwise we now have the name of the script include referenced by the SLA Condition Class
  		scriptIncludeName = scriptIncludeGR.getValue("name");
  	}

  	// if we've had to revert back to the default check to see if it's already cached
  	if (this._slaConditionClassCache.hasOwnProperty(cacheKey))
  		return;
  	
  	// Fetch the script include object from the global scope
  	var slaConditionClass = this._getConditionClassScriptInclude(scriptIncludeName);
  	
  	/* if we're caching the default make sure the default is valid by checking for required methods
  	   (this is the script include name specified in system property "com.snc.sla.default_conditionclass")
  	   if invalid we'll use a fixed value of "SLAConditionBase" */
  	if (cacheKey === this.DEFAULT_SLA_CONDITION_CLASS_KEY && !this._isValidSLAConditionClass(slaConditionClass)) {
  		this.SLADefaultConditionClassName = this.BASE_CONDITIONCLASSNAME;
  		slaConditionClass = this._getConditionClassScriptInclude(this.SLADefaultConditionClassName);
  	}
  	
  	/* if we've got a valid SLA Condition Class record and associated script include create a cache entry
  	   using the values from the condition class record...
  	   
  	   ...otherwise we're storing the default class which has fixed values */
  	if (cacheKey !== this.DEFAULT_SLA_CONDITION_CLASS_KEY) {
  		var isActive = slaConditionClassGR.active + "";
  		var isValid = this._isValidSLAConditionClass(slaConditionClass);
  		this._createSLAConditionClassCacheEntry(
  			slaConditionClassGR.getUniqueValue(),
  			slaConditionClassGR.getValue("name"),
  			isActive,
  			isValid,
  			slaConditionClass
  		);

  		// if this cache entry is inactive or invalid create the default one as it will be needed instead
  		if (!isValid || !isActive)
  			this._cacheSLAConditionClass(this.DEFAULT_SLA_CONDITION_CLASS_KEY);
  	} else {
  		this._createSLAConditionClassCacheEntry(
  			this.DEFAULT_SLA_CONDITION_CLASS_KEY,
  			"",
  			true,
  			true,
  			slaConditionClass
  		);			

  		// "this._SLADefaultConditionClass" has always pointed to the default condition class script include 
  		this._SLADefaultConditionClass = slaConditionClass;			
  	}
  },

  /* add a new object to "this._slaConditionClassCache" object with a reference of the supplied cacheKey
     the object created will contain details of SLA Condition rule along with a copy of the script include that it references.
     If this script include has the newer methods of "setSLA", "setTask" and "setTaskSLA", an instance of the script include
     will be created and stored within the new object to allow for re-use of this during condition testing */
  _createSLAConditionClassCacheEntry: function(cacheKey, conditionClassName, active, isValid, scriptIncludeObject) {
  	/* if system property "com.snc.sla.condition_class.caching.enabled" doesn't exist or is not set to true then caching
  	   of instances of each condition class script include is disabled so return without storing it 

  	   this effectively reverts back to the previous behaviour of creating a new instance of the appropriate 
  	   SLA condition class script include for each SLA Definition being tested */
  	if (!this.conditionClassCachingEnabled)
  		return;
  	
  	if (JSUtil.nil(cacheKey))
  		return;

  	// Before we create a new cache entry check if we've reached the limit and if so remove one (excluding the default cache entry)
  	var existingCacheKeys = Object.keys(this._slaConditionClassCache).filter(function(key) {
  		 return key !== this.DEFAULT_SLA_CONDITION_CLASS_KEY;
  		 }, this);
  	if (existingCacheKeys.length >= this.conditionClassCachingLimit)
  		delete this._slaConditionClassCache[existingCacheKeys[0]];

  	isValid = isValid + "" === "true";
  	active = active + "" === "true";

  	this._slaConditionClassCache[cacheKey] = {
  		"className": conditionClassName,
  		"scriptIncludeType": scriptIncludeObject.prototype.type,
  		"scriptInclude": scriptIncludeObject,
  		"scriptIncludeInstance": null,
  		"active": active,
  		"valid": isValid
  	};

  	if (this._SLAConditionClassHasSetMethods(this._slaConditionClassCache[cacheKey].scriptInclude))
  		this._slaConditionClassCache[cacheKey].scriptIncludeInstance = new this._slaConditionClassCache[cacheKey].scriptInclude();
  },

  _getConditionClassScriptInclude: function(scriptIncludeName) {
  	if (JSUtil.nil(scriptIncludeName))
  		return null;

  	// Make sure we only fetch the Global scope once
  	if (!this._outerScope)
  		this._outerScope = JSUtil.getGlobal();

  	return this._outerScope[scriptIncludeName];
  },

  // does klass look valid as an SLAConditionClass?
  _isValidSLAConditionClass: function(klass) {
  	return this._doesClassHaveMethods(klass, ['attach', 'pause', 'complete', 'reattach', 'cancel']);
  },

  _SLAConditionClassHasSetMethods: function(klass) {
  	return this._doesClassHaveMethods(klass, ['setSLA', 'setTask', 'setTaskSLA']);
  },

  _doesClassHaveMethods: function(klass, methods) {
  	if (JSUtil.nil(klass) || !(klass.hasOwnProperty("prototype")))
  		return false;

  	if (JSUtil.nil(methods))
  		return true;

  	if (!Array.isArray(methods))
  		methods = [methods];

  	return methods.every(function(method) {
  		return klass.prototype.hasOwnProperty(method);
  	});
  },

  // methods to manipulate the task's associated task_sla and contract_sla records.

  _getContractSLA: function(taskSLAgr, contractSLAgr) {
  	if (!taskSLAgr || !taskSLAgr.isValidRecord()) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_getContractSLA: taskSLAgr is not valid -> taskSLAgr=' + (taskSLAgr === null ? 'null' : taskSLAgr));

  		return null;
  	}

  	/* if domains isn't active then we won't have contractSLAgr and can just use "getRefRecord"
  	   to get the SLA Definition */
  	if (!this.domainsPluginActive) {
  		var sla = taskSLAgr.sla.getRefRecord();

  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_getContractSLA: domains is not installed -> this.domainsPluginActive=' + this.domainsPluginActive + ', sla=' + (sla === null ? 'null' : sla.getUniqueValue()));

  		return sla;
  	}

  	/* if we haven't got a "contractSLAgr" for some reason fall back to returning the GlideElement
  	   for the SLA Definition from the "task_sla" record - then SLAConditionBase will take care of
  	   fetching the SLA Definition from the DB */
  	if (!contractSLAgr || !contractSLAgr.isValid()) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_getContractSLA: contractSLAgr is not a vaid GlideRecord -> contractSLAgr=' + (contractSLAgr === null ? 'null' : contractSLAgr) + ', taskSLAgr.sla=' + taskSLAgr.sla);

  		return taskSLAgr.sla;
  	}

  	/* Otherwise get the next record from "contractSLAgr" which should match the "sla" value in the
  	   "taskSLAgr" as they should be in synch with each other */
  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_getContractSLA: contractSLAgr.getEncodedQuery()= ' + contractSLAgr.getEncodedQuery() + ', contractSLAgr.getUniqueValue()=' + contractSLAgr.getUniqueValue());

  	// if "contractSLAgr" is already the correct record then use it
  	if (taskSLAgr.getValue('sla') === contractSLAgr.getUniqueValue()) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_getContractSLA: taskSLAgr sla value and contract SLA match -> taskSLAgr.getValue(\'sla\')=' + taskSLAgr.getValue('sla') + ', contractSLAgr.getUniqueValue()=' + contractSLAgr.getUniqueValue());

  		return contractSLAgr;
  	}

  	// if we don't have any more records, again fall back to the "sla" element in taskSLAgr
  	if (!contractSLAgr.next()) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_getContractSLA: no more records in contractSLAgr -> taskSLAgr.getValue(\'sla\')=' + taskSLAgr.getValue('sla') + ', contractSLAgr.getUniqueValue()=' + contractSLAgr.getUniqueValue());

  		return taskSLAgr.sla;
  	}

  	/* finally just check the 2 record sets are in synch in terms of the SLA definition and if not
  	   use the "sla" GlideElement */
  	if (taskSLAgr.getValue('sla') !== contractSLAgr.getUniqueValue()) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_getContractSLA: taskSLAgr sla value and contract SLA are NOT in sync -> taskSLAgr.getValue(\'sla\')=' + taskSLAgr.getValue('sla') + ', contractSLAgr.getUniqueValue()=' + contractSLAgr.getUniqueValue());

  		return taskSLAgr.sla;
  	}

  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug('_getContractSLA: taskSLAgr and contract SLA are in sync -> taskSLAgr.getValue(\'sla\')=' + taskSLAgr.getValue('sla') + ', contractSLAgr.getUniqueValue()=' + contractSLAgr.getUniqueValue());

  	// If we've got to here we've got the correct SLA Definition
  	return contractSLAgr;
  },

  // return comma-separated list of contract_sla sys_ids, given GlideRecord result set from taskSLAgr.query()
  // suitable for 'sys_idIN' or 'sys_idNOT IN' queries
  _getSLAsString: function(taskSLAgr) {
  	var slaList = [];
  	//taskSLAgr.query();
  	while (taskSLAgr.next())
  		slaList.push(taskSLAgr.getValue("sla"));
  	var queryString = slaList.join(',');
  	return queryString;
  },

  _needsAdjustPause: function(slaGR) {
  	// nothing to adjust for, as the task can have no previous updates
  	if (this.taskGR.operation() === 'insert')
  		return;

  	// relative duration SLAs, and those without pause conditions, cannot pause
  	if (slaGR.duration_type != '' || !slaGR.pause_condition)
  		return false;

  	// (shouldn't have been called for a non retroactive pause SLA)
  	if (!slaGR.retroactive || !slaGR.retroactive_pause)
  		return false;

  	return true;
  },

  // generate & replay task history, to adjust pause duration, pause time of retroactive-start SLAs
  // (runs from within the _processNewSLAs_criticalSection)
  _adjustPauseTime: function(slasToAdjust) {
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('_adjustPauseTime: Adjusting pause time for retroactive SLAs on ' + this.taskGR.number);

  	if (!slasToAdjust || slasToAdjust.length === 0) {
  		this.lu.logWarning('There are no SLAs to be adjusted');
  		return;
  	}

  	// nothing to adjust for, as the task can have no previous updates
  	if (this.taskGR.operation() === 'insert') {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug("_adjustPauseTime: ends - task is being inserted so there can't be any retroactive pauses");
  		return;
  	}

  	if (this.taskGR.isNewRecord()) {
  		this.lu.logWarning('Cannot adjust SLA pause time on a new task record');
  		return;
  	}

  	if (!(new GlideAuditor(this.taskGR.getTableName(), null).auditTable())) {
  		if (this.lu.atLevel(GSLog.ERR))
  			this.lu.logError('Cannot adjust SLA pause time for a retroactive start - auditing not enabled on ' + this.taskGR.getTableName());
  		return;
  	}

  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo('_adjustPauseTime: at ' + this.taskGR.getValue("sys_updated_on"));

  	var hasRetroSLAs = false;
  	for (var i = 0; i < slasToAdjust.length; i++) {
  		if (slasToAdjust[i].needsAdjusting) {
  			hasRetroSLAs = true;
  			break;
  		}
  	}

  	if (!hasRetroSLAs) {
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug("_adjustPauseTime: ends - there are no retroactive Task SLAs requiring retroactive pause time calculation");
  		return;
  	}

  	var taskSLA;
  	var contractSLA;
  	var withJournals = false;
  	var withSysFields = false;
  	var withVariables = false;

  	// Do we need to add journal or system fields to the history walker?
  	for (var i = 0; (i < slasToAdjust.length && (!withJournals || !withSysFields || !withVariables)); i++) {
  		if (!slasToAdjust[i].needsAdjusting)
  			continue;

  		taskSLA = slasToAdjust[i].taskSLA;
  		contractSLA = slasToAdjust[i].contractSLAgr;
  		if (!contractSLA)
  			contractSLA = taskSLA.getGlideRecord().sla;

  		if (this.slaUtil.hasAdvancedJournalCondition(contractSLA))
  			withJournals = true;

  		if (this.slaUtil.hasAdvancedSysFieldCondition(contractSLA))
  			withSysFields = true;

  		if (this.slaUtil.hasVariablesCondition(contractSLA))
  			withVariables = true;
  	}

  	var hw = this.slaUtil.getHistoryWalker(
  		this.taskGR.getRecordClassName(),
  		this.taskGR.getValue('sys_id'),
  		/*recordLevelSecurity*/ false,
  		/*fieldLevelSecurity*/ false,
  		/*withVariables*/ withVariables,
  		/*walkToFirstUpdate*/ true,
  		/*withJournalFields*/ withJournals,
  		/*withSysFields*/ withSysFields
  	);

  	if (!hw) {
  		this.lu.logError("_adjustPauseTime: failed getting HistoryWalker to initial update");
  		return;
  	}

  	// save a reference to the current task record before we start stepping through the updates
  	var originalTaskGR = this.taskGR;

  	var initialUpdateProcessed = false;
  	var taskSLAgr;
  	var walkedRecordUpdatedOnMs;
  	var startTimeMs;

  	var currentModCount = this.taskGR.getValue('sys_mod_count');
  	do {
  		this.taskGR = hw.getWalkedRecord();
  		if (this.taskGR.getValue('sys_mod_count') == currentModCount)
  			break;

  		walkedRecordUpdatedOnMs = this.taskGR.sys_updated_on.dateNumericValue();

  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('_adjustPauseTime: [' + this.taskGR.getValue('sys_mod_count') + '] history update time: ' + this.taskGR.getValue('sys_updated_on'));

  		for (var j = 0; j < slasToAdjust.length; j++) {
  			if (!slasToAdjust[j].needsAdjusting)
  				continue;

  			taskSLA = slasToAdjust[j].taskSLA;
  			contractSLA = slasToAdjust[j].contractSLAgr;
  			if (!contractSLA)
  				contractSLA = taskSLA.getGlideRecord().sla;

  			if (!initialUpdateProcessed) {
  				slasToAdjust[j].adjusted = true;
  				taskSLA.setRetroactiveAdjusting(true);
  				taskSLA.updateState(TaskSLA.STATE_IN_PROGRESS);
  				taskSLA.starting = false;
  			}

  			taskSLAgr = taskSLA.getGlideRecord();
  			startTimeMs = taskSLAgr.start_time.dateNumericValue();
  			if (startTimeMs <= walkedRecordUpdatedOnMs)
  				taskSLA.setUpdateTime(this.taskGR.getValue('sys_updated_on'));
  			else
  				taskSLA.setUpdateTime(taskSLAgr.getValue('start_time'));

  			var conditionResults = this._pauseUnpause(taskSLA, contractSLA);

  			if (this.lu.atLevel(GSLog.DEBUG)) {
  				this.lu.logDebug("Condition Results for Task SLA " + contractSLA.getDisplayValue() + ": " + JSON.stringify(conditionResults));
  				this.lu.logDebug("Business elapsed: " + taskSLAgr.business_duration.getDurationValue() + ", Pause duration: " + taskSLAgr.pause_duration.getDurationValue() +
  								", Pause time: " + taskSLAgr.pause_time.getDisplayValue());
  			}
  		}

  		initialUpdateProcessed = true;
  	} while (hw.walkForward());

  	for (var k = 0; k < slasToAdjust.length; k++) {
  		if (!slasToAdjust[k].adjusted)
  			continue;

  		taskSLA = slasToAdjust[k].taskSLA;
  		contractSLA = slasToAdjust[k].contractSLAgr;
  		if (!contractSLA)
  			contractSLA = taskSLA.getGlideRecord().sla;

  		//If current taskSLA stage is In Progress then keep the duration fields up to date with now timesamp
  		if(taskSLA.getCurrentState() === TaskSLA.STATE_IN_PROGRESS)
  			taskSLA.calculate(true, new GlideDateTime());

  		taskSLA.starting = true;
  		taskSLA.setRetroactiveAdjusting(false);
  		if (this.lu.atLevel(GSLog.INFO)) {
  			taskSLAgr = taskSLA.getGlideRecord();
  			this.lu.logInfo('Finished adjusting pause time for retroactive SLA ' + contractSLA.name + ' on ' + this.taskGR.getDisplayValue() + ': pause_duration=' + taskSLAgr.pause_duration + ', pause_time='
  			+ taskSLAgr.pause_time.getDisplayValue());
  		}
  		if (this.lu.atLevel(GSLog.DEBUG))
  			this.lu.logDebug('taskSLA: starting=' + taskSLA.starting);
  	}

  	// set back to the real task
  	this.taskGR = originalTaskGR;

  	return;
  },

  _walkTaskToUpdate: function(updateNumber, withJournalFields, withSysFields, withVariables) {
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo("_walkTaskToUpdate: update number: " + updateNumber);

  	if (isNaN(updateNumber)) {
  		if (this.lu.atLevel(GSLog.ERR))
  			this.lu.logError("_walkTaskToUpdate: invalid update number requested: " + updateNumber);
  		return false;
  	}

  	if (!this.taskHistoryWalker) {
  		this.taskHistoryWalker = this.slaUtil.getHistoryWalker(
  			this.taskGR.getRecordClassName(),
  			this.taskGR.getValue('sys_id'),
  			/*recordLevelSecurity*/ false,
  			/*fieldLevelSecurity*/ false,
  			/*withVariables*/ withVariables,
  			/*walkToFirstUpdate*/ false,
  			/*withJournalFields*/ withJournalFields,
  			/*withSysFields*/ withSysFields
  		);

  		if (!this.taskHistoryWalker) {
  			if (this.lu.atLevel(GSLog.ERR))
  				this.lu.logError("_walkTaskToUpdate: failed to get HistoryWalker");
  			return false;
  		}
  	}

  	if (!this.taskHistoryWalker.walkTo(parseInt(updateNumber, 10)))
  		return false;

  	this.taskGR = this.taskHistoryWalker.getWalkedRecord();

  	if (this.lu.atLevel(GSLog.DEBUG))
  		this.lu.logDebug("_walkTaskToUpdate: " + this.taskGR.sys_meta.label + " " + this.taskGR.getDisplayValue() + " walked to update number " + this.taskGR.getValue("sys_mod_count"));

  	return true;
  },

  _walkTaskForAsync: function(withJournalFields, withSysFields, withVariables) {
  	// if we've already walked the Task to the latest update no need to do it again
  	if (this.asyncTaskWalked)
  		return;

  	if (this._walkTaskToUpdate(this.asyncModCount, withJournalFields, withSysFields, withVariables))
  		this.asyncTaskWalked = true;
  },

  _walkTaskToLatest: function(withJournalFields, withSysFields, withVariables) {
  	// if we've already walked the Task to the latest update no need to do it again
  	if (this.taskWalked)
  		return;

  	if (this._walkTaskToUpdate(this.taskLatestModCount, withJournalFields, withSysFields, withVariables))
  		this.taskWalked = true;
  },

  _getTask: function(taskType, taskSysId) {
  	if (this.lu.atLevel(GSLog.INFO))
  		this.lu.logInfo("_getTask: called with taskType=" + taskType + ", taskSysId=" + taskSysId);

  	if (!taskType || !taskSysId) {
  		if (this.lu.atLevel(GSLog.ERR))
  			this.lu.logError("_getTask: either task type or sys_id where not provided");

  		return null;
  	}

  	if (!GlideTableDescriptor.isValid(taskType)) {
  		if (this.lu.atLevel(GSLog.ERR))
  			this.lu.logError("_getTask: invalid task type requested \"" + taskType + "\"");

  		return null;
  	}

  	// Try and retrieve the Task from the supplied task table
  	var taskGR = new GlideRecord(taskType);
  	if (taskGR.get(taskSysId))
  		return taskGR;

  	// If we didn't find it log a warning...
  	if (this.lu.atLevel(GSLog.WARNING))
  		this.lu.logWarning("_getTask: failed to retrieve record from table \"" + taskType + " with sys_id \"" + taskSysId + "\"");

  	/* ... and then see if a Task with this sys_id exists as it may exist but in a different task extension
  		because "sys_class_name" has changed */
  	taskGR = new GlideRecord("task");
  	if (!taskGR.get(taskSysId)) {
  		this.lu.logError("_getTask: Task record with sys_id " + taskSysId + " does not exist");
  		return null;
  	}

  	// If we've managed to find a Task then we expect it to have a different Task Type so fetch the record from that table
  	var taskTypeInDB = taskGR.getValue("sys_class_name");
  	if (this.lu.atLevel(GSLog.WARNING))
  		this.lu.logWarning("_getTask: expected Task type is \"" + taskType + "\" but actual is \"" + taskTypeInDB +
  							".  Task will be fetched from \"" + taskTypeInDB + "\"");

  	taskGR = new GlideRecord(taskTypeInDB);
  	if (taskGR.get(taskSysId))
  		return taskGR;
  	else {
  		if (this.lu.atLevel(GSLog.ERR))
  			this.lu.logError("_getTask: failed to retrieve record from table \"" + taskTypeInDB + " with sys_id \"" + taskSysId + "\"");

  		return null;
  	}
  },

  type: 'TaskSLAController'
};

Sys ID

24a759e30a0a2c3960e024ad3b60d9e8

Offical Documentation

Official Docs: