Name

global.CSMRelationshipServiceSNC

Description

Service script-include for CSM Relationship framework, only for OOB implementation.

Script

var CSMRelationshipServiceSNC = Class.create();
CSMRelationshipServiceSNC.prototype = {
  initialize: function() {
  	this.context = {};
  },
  
  /*
   * Return true if this SI is able to handle the services required for given relationship.
  */
  canProcess: function(relationship, additionalParams) {
  	return false;
  },
  
  /*
   * 
   * @relationship: String - The table that represents this relationship 
   * @accessLevel: String - The level of access; READ/WRITE/CREATE/DELETE/NOTIFY/FULL
   *
  */
  hasAccess: function(current, relationship, accessLevel, additionalParams) {
  	if(!this.__isValidRelationship(relationship) || !this.canProcess(relationship, additionalParams))
  		return false;

  	this.__setContext(current, relationship, null, accessLevel, additionalParams);

  	if(!this._isAllowedToFetchEntities())
  		return false;

  	return this.__hasAccess(current, relationship, accessLevel);
  },

  /*
   * 
   * @relationship: String - The table that represents this relationship 
   * @accessLevel: String - The level of access; READ/WRITE/CREATE/DELETE/NOTIFY/FULL
   * @targetEntity: String - The entity where the the given 'accessLevel' operation to be performed
   * @applicableTo - filter responsibilities by limiting them to specific personas. 
   * @restrictAccessTo - filter by restricting access to specific set of records (contact/consumer) of accessible entities.
   * 
  */
  getResponsibilities: function(relationship, accessLevel, targetEntity, applicableTo, restrictAccessTo) {
  	targetEntity = targetEntity || this.__getAdditionalParam(global.CSMRelationshipConstants.TARGET_ENTITY);
  	applicableTo = applicableTo || this.__getAdditionalParam(global.CSMRelationshipConstants.APPLICABLE_TO);
  	restrictAccessTo = restrictAccessTo || this.__getAdditionalParam(global.CSMRelationshipConstants.RESTRICT_ACCESS_TO);
  	var skipRoleCheck = this.__getAdditionalParam(global.CSMRelationshipConstants.SKIP_ROLE_CHECK);
  	return CSMResponsibilityModelUtil.getResponsibilities(
  		relationship, accessLevel, null, skipRoleCheck, targetEntity, applicableTo, restrictAccessTo
  	);
  },
  
  /*
   * 
   * @current: GlideRecord -  Current glide record; Mostly it would be an entity glide-record on which 
   *                         1) the dynamic filter is being applied OR 2) this method invoked.
   * @relationship: String - The table that represents this relationship 
   * @entityName: String - The field name in the m2m table that represents the required entity 
   * @accessLevel: String - The level of the access; READ/WRITE/NOTIFY/FULL/AUDIT
   * @additionalParams: JSON Object - Object of additional params
   * @additionalParams.targetEntity - The entity where the the given 'accessLevel' operation to be performed
   * @additionalParams.applicableTo - filter responsibilities by limiting them to specific personas. 
   * @additionalParams.restrictAccessTo - filter by restricting access to specific set of records (contact/consumer) of accessible entities.
   * 
  */
  
  getMyEntities: function(current, relationship, entityName, accessLevel, additionalParams) {
  	if(!this.__isValidRelationship(relationship) || !this.canProcess(relationship, additionalParams))
  		return [];
  	
  	this.__setContext(current, relationship, entityName, accessLevel, additionalParams);
  	
  	if(!this._isAllowedToFetchEntities())
  		return [];
  	
  	return this.__getMyEntities(current, relationship, entityName, accessLevel, additionalParams);
  },
  
  

  	
  /*** Private Methods (Do not invoke these methods out side of this SI/child SI) ***/
  
  __isValidRelationship: function(relationship) {
  	if (this.__hasValidAccessConfig(relationship))
  		return true;
  		
  	// fallback to SI configurations
  	return (Object.keys(global.CSMRelationshipConstants.RELATIONSHIPS)|| []).indexOf(relationship) > -1;
  },

  __hasValidAccessConfig: function(relationship) {
  	if (gs.nil(relationship))
  		return false;

  	return !gs.nil(new global.ResponsibilityAccessConfigCacheUtil().getAppliesToRelationshipFromCache(relationship));
  },

  __setContext: function(current, relationship, entityName, accessLevel, additionalParams) {
  	this.context.current = current;
  	this.context.relationship = relationship;
  	this.context.entityName = entityName;
  	this.context.accessLevel = accessLevel;
  	this.context.additionalParams = additionalParams;	
  },
  
  __updateContext: function(key, value) {
  	if (!gs.nil(key))
  		this.context[key] = value;
  },
  
  __getAdditionalParam: function(key) {
  	if(!key || !this.context || !this.context.additionalParams)
  		return null;

  	return this.context.additionalParams[key];
  },

  __getMyEntities: function(current, relationship, entityName, accessLevel, additionalParams) {
  			
  	var entities = {};
  	
  	var relationshipGR = new GlideRecord(relationship);
  	if (!relationshipGR.isValid()) return;
  	if (!this.__getAdditionalParam(global.CSMRelationshipConstants.NO_PERSONA_QUERY))
  		this._addPersonaCriteria(relationshipGR);
  	if(this._enforceResponsibilities())
  		this._addResponsibilitiesCriteria(relationshipGR);
  	this._addEncodedQuery(relationshipGR);
  	this._addAdditionalCriteria(relationshipGR);
  	
  	if(this.__getAdditionalParam(global.CSMRelationshipConstants.SKIP_BEFORE_QUERY_FILTER)){
  		relationshipGR._skip_before_query_filter = true;
  	}
  	
  	relationshipGR.query();
  	
  	while(relationshipGR.next()) {
  		var entity = this._getEntity(relationshipGR);
  		if(!gs.nil(entity)){
  			entities[entity] = relationshipGR.getValue(global.CSMRelationshipConstants.RESPONSIBILITY);
  		}
  	}
  	
  	this._addAdditionalEntities(entities);
  	return Object.keys(entities); //set of entity sys-ids
  },
  
  __hasAccess: function(current, relationship, accessLevel) {
  	var relationshipGR = new GlideRecord(relationship);
  	if (!relationshipGR.isValid()) return;
  	if (!this.__getAdditionalParam(global.CSMRelationshipConstants.NO_PERSONA_QUERY))
  		this._addPersonaCriteria(relationshipGR);
  	if(this._enforceResponsibilities())
  		this._addResponsibilitiesCriteria(relationshipGR);

  	this._addAdditionalCriteria(relationshipGR);

  	relationshipGR.setLimit(1);
  	relationshipGR.query();

  	return relationshipGR.hasNext();
  },

  /*
  * This method inserts/updates/deletes given value in Glide List field.
  *
  * Only addPersonaId is passed -> Insert operation
  * Only removePersonaId is passed -> Delete operation
  * Both are passed -> Replace operation
  * 
  * Example: 
  * syncRelationshipGlideListWithEntity(SOLD_PRODUCT_TABLE, SYS_ID, 'additional_consumers', consumerId, null)
  * 
  * Above example adds given consumer Id to additional consumers glide list field in Sold Product table
  */
  syncRelationshipGlideListWithEntity: function (tableName, sysId, glideListFieldName, addPersonaId, removePersonaId){
  	return this.__mutateGlideList(tableName, sysId, glideListFieldName, addPersonaId, removePersonaId, 3);
  },
  
  /*
  * This method is util method for _updatePersonaInReadAccessList
  * This method tries to mutate glide list while avoiding collisions.
  * makes given number of attempts
  */
  __mutateGlideList: function (tableName, sysId, glideListFieldName, addPersonaId, removePersonaId, attempts){
  	if(gs.nil(attempts) || attempts <= 0)
  		return false;

  	if(gs.nil(tableName) || gs.nil(sysId) || gs.nil(glideListFieldName))
  		return false;
  	if(gs.nil(addPersonaId) && gs.nil(removePersonaId))
  		return false;

  	addPersonaId = addPersonaId || '';
  	removePersonaId = removePersonaId || '';
  	var gr = new GlideRecord(tableName);
  	if(!gr.get(sysId))
  		return false;

  	var values = (gr.getValue(glideListFieldName)) || '';
  	var addIndex = -1;
  	var removeIndex = -1;

  	if(gs.nil(removePersonaId)){
  		addIndex = values.indexOf(addPersonaId+'');
  	} else if(gs.nil(addPersonaId)){
  		removeIndex = values.indexOf(removePersonaId+'');
  	} else{
  		removeIndex = values.indexOf(removePersonaId+'');
  		addIndex = values.indexOf(addPersonaId+'');
  	}

  	
  	if(removeIndex == -1 && addIndex > -1)
  		return true;
  	else if(removeIndex == -1){
  		//Insert
  		if(gs.nil(values))
  			values = addPersonaId;
  		else
  			values = values + "," + addPersonaId;
  	}
  	else if(removeIndex > -1 && (gs.nil(addPersonaId) || addIndex > -1)){
  		//Remove
  		// if persona id we are going to remove is a middle element - 
  		// it would leave ',,'. If it is a first element or last -
  		// it would leave a trailing or starting comma.
  		// The regex replaces first or last comma
  		values = values.replace(removePersonaId, '').replace(',,', ',').replace(/^,|,$/g, '');
  	}
  	else 
  		//Replace
  		values.replace(removePersonaId, addPersonaId);

  	//trying to persist read access list with latest value
  	var rec = new GlideRecord(tableName);
  	rec.addQuery("sys_id", sysId);
  	//we are querying sys_mod_count to be sure that we are not overwriting the other changes made to the same record.
  	rec.addQuery("sys_mod_count", gr.sys_mod_count);
  	rec.query();
  	if(rec.next()){
  		rec.setValue(glideListFieldName, values+'');
  		rec.update();

  		//Checking if change is done
  		gr.initialize();
  		gr.addQuery("sys_id", sysId);
  		if(removeIndex == -1)
  			gr.addQuery(glideListFieldName, "CONTAINS", addPersonaId);
  		else if(removeIndex > -1 && (gs.nil(addPersonaId) || addIndex > -1))
  			gr.addQuery(glideListFieldName, "DOES NOT CONTAIN", removePersonaId);
  		else{
  			gr.addQuery(glideListFieldName, "CONTAINS", addPersonaId);
  			gr.addQuery(glideListFieldName, "DOES NOT CONTAIN", removePersonaId);
  		}
  		gr.query();
  		if(gr.hasNext())
  			return true;
  	}

  	//Change did not persist, may be because of collision, reattempting
  	attempts--;
  	return this._mutateGlideList(tableName, sysId, glideListFieldName, addPersonaId, removePersonaId, attempts);
  },

  /*** Protected Methods (Are subject to override only, do not invoke these methods out side of this SI/child SI) ***/
  
  //Note: use this.context object to get the required parameters.

  _addPersonaCriteria: function(relationshipGR) {
  	relationshipGR.addQuery(global.CSMRelationshipConstants.DEFAULT_PERSONA_FIELD, gs.getUserID());
  },
  
  _enforceResponsibilities: function() {
  	return true;
  },

  _addResponsibilitiesCriteria: function(relationshipGR) {
  	var responsibilities = this.getResponsibilities(this.context.relationship, this.context.accessLevel);

  	relationshipGR.addQuery(global.CSMRelationshipConstants.RESPONSIBILITY, "IN", responsibilities);
  },

  _addAdditionalCriteria: function(relationshipGR) {

  },

  _addEncodedQuery : function(relationshipGR) {
  	var additionalEncodedQuery = this.__getAdditionalParam(global.CSMRelationshipConstants.ADDITIONAL_ENCODED_QUERY);
  	if (!gs.nil(additionalEncodedQuery))
  		relationshipGR.addEncodedQuery(additionalEncodedQuery);
  },

  _addAdditionalEntities: function(entitiesMap) {

  },
  
  _getEntity: function(relationshipGR) {
  	return relationshipGR.getElement(this.context.entityName) + '';
  },
  
  _isAllowedToFetchEntities: function() {
  	return true;
  },

  _isChildOf: function(parent, tableName) {
  	return new global.CSMRelationshipUtils().isChildOf(parent, tableName);
  },

  _getAccountHelper: function() {
  	return new global.Account();
  },

  _getSOHelper: function() {
  	return new global.ServiceOrganizationUtil();
  },

  type: 'CSMRelationshipServiceSNC'
};

Sys ID

0ce4b42d77427010d3ef07dc7d5a997a

Offical Documentation

Official Docs: