I am currently working on developing an after operation rule for account aggregation. This is the code I have prepared for it.
import org.json.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import connector.common.JsonUtil;
import connector.common.JsonUtil.*;
import sailpoint.object.*;
import connector.common.Util;
/*
* CREATED BY: Zeel Sinojia
* Date: 01/21/2024
* Purpose: Code to fetch the last login information from the custom url
*/
//setting a log prefix allows us to easily filter ccg.log for relevant items
String logPrefix = "WSAO_OracleERP";
//retrieve the transient values, if they exits, from application storage. will be Null if not set.
Map transientValues = (Map) application.getAttributeValue("transientValues");
//build our return object structure
List returnObj = new ArrayList();
Map processedData = new HashMap();
log.info(logPrefix + " starting to process");
//only call additional APIs if we do not already have them in memory
if(transientValues == null) {
///////// START LOOKUPS ///////////
Map transientValues = new HashMap();
//set up the requirements for making API calls
List<String> allowedStatuses = new ArrayList();
allowedStatuses.add( "2**" );
Map header = requestEndPoint.getHeader();
//call the last login API, build a key/value Map of login_id : last_access_in_days
Map LastLoginLookupMap = new HashMap();
try{
String lastLoginUrl = "";
//executeGet operates in the context of the source restClient.
//will follow Authentication on the source/endpoint.
String lastLogins = restClient.executeGet(lastLoginUrl, header, allowedStatuses );
JSONObject responseJsonObject = new JSONObject(lastLogins);
JSONArray lastLoginJsonArray = responseJsonObject.get( "items" );
for (int i = 0; i < lastLoginJsonArray.length(); i++) {
JSONObject lastLoginJsonObject = lastLoginJsonArray.getJSONObject(i);
String emailId = lastLoginJsonObject.getString("login_id");
String lastAccessInDays = lastLoginJsonObject.getString("last_access_in_days");
LastLoginLookupMap.put(emailId,lastAccessInDays);
}
//store the resultant map into transient values
transientValues.put("LastLoginLookupMap", LastLoginLookupMap);
} catch (Exception e) {
log.error(logPrefix + "********ERROR AFTER RULE: " + e.getMessage(), e );
}
//store "our" transient values into the application (until the aggregation completes, wherein it will erase)
application.setAttribute("transientValues", transientValues);
log.error(logPrefix + "setting transients");
}
/////// END LOOKUPS //////////
// ensure that we have data to actuall operate on
if(rawResponseObject != null && processedResponseObject != null){
//retrieve the transientValues from application storage
transientValues = (Map) application.getAttributeValue("transientValues");
Map resultMap = JsonUtil.parseJson( rawResponseObject );
//iterate over every Record in the response
// processedResponseObject will have the JSONPath values defined on the source endpoint.
for (Map userEntry : processedResponseObject ) {
log.error(logPrefix + "updating processed data");
// grab key values from the record, from the JSONPath'd output of the endpoint itself
String emailId = null;
if ( userEntry.containsKey("Email") )
emailId = userEntry.get( "Email" );
userEntry.put( "displayName", "after operation test" );
//null safety. dont look it up if it doesnt exist.
if (transientValues.containsKey("LastLoginLookupMap") && Util.isNotNullOrEmpty( emailId )){
HashMap LastLoginLookupMap = (HashMap) transientValues.get("LastLoginLookupMap");
//only try to add the value if it actually exists.
if (LastLoginLookupMap.containsKey(emailId)){
userEntry.put("lastLogin",LastLoginLookupMap.get(emailId));
} else{
log.error(logPrefix + "lookup failed for jobcode: " + emailId);
}
}
//add the modified record to our new return result
returnObj.add(userEntry);
}
//add the data to the reply, and send it
processedData.put("data", returnObj);
log.info(logPrefix + "finished running");
return processedData;
} else{
log.error(logPrefix + "Response Object was empty, standard endpoint data will be passed.");
}
I have also updated the response mapping accordingly for it.
I have written the same kind rules in the IIQ as well when I used to work on it. I used to create new object and return that and update the response mapping accordingly.
This is how my response from the API looks like. If I give $.Resources as my root path then the aggregation will be successful and it fetch the accounts. However the login I have written in rule to fetch the last login information would not be fetched.
@ZeelSinojia01 I have written similar afterOperation rule it works perfectly fine for me.
Looking at your JSON response you should use $.Resources[*] in your root path mapping.
Rule looks fine to me. Make sure you map the response mapping as per the JOSN path.
As per your account schema map the response mapping for e.g. if your schema attribute for id is accountId then map it as accountId = $.id , just do not put id.
One thing I noticed is the rule is getting executed as I had some errors while developing it earlier so SailPoint UI was showing those issues. However I don’t feel like the object I am updating and returning is getting picked up for the response mapping.
I resorted to using rawResponseObject instead of processedResponseObject
One more thing I am unaware of is in the custom response object the keys are named as “Display Name”, “First Name”, “Last Name”, etc. however if you see in the response mapping then the attribute paths are like “$.displayName”, “$.firstName”, “$.lastName” etc.
Still the data is getting aggregated successfully and all the values I in target system are correctly fetched into the SailPoint.
import org.json.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import connector.common.JsonUtil;
import connector.common.JsonUtil.*;
import sailpoint.object.*;
import connector.common.Util;
/*
* CREATED BY: Zeel Sinojia
* Date: 01/21/2024
* Purpose: Code to fetch the last login information from the custom url
*/
//setting a log prefix allows us to easily filter ccg.log for relevant items
String logPrefix = "WSAO_OracleERP";
//retrieve the transient values, if they exits, from application storage. will be Null if not set.
Map transientValues = (Map) application.getAttributeValue("transientValues");
//build our return object structure
List returnObj = new ArrayList();
Map processedData = new HashMap();
log.info(logPrefix + " starting to process");
//only call additional APIs if we do not already have them in memory
if(transientValues == null) {
///////// START LOOKUPS ///////////
Map transientValues = new HashMap();
//set up the requirements for making API calls
List<String> allowedStatuses = new ArrayList();
allowedStatuses.add( "200" );
//call the last login API, build a key/value Map of login_id : last_access_in_days
Map LastLoginLookupMap = new HashMap();
try{
String lastLoginUrl = "";
//executeGet operates in the context of the source restClient.
//will follow Authentication on the source/endpoint.
String lastLogins = restClient.executeGet(lastLoginUrl, new HashMap(), allowedStatuses );
JSONObject responseJsonObject = new JSONObject(lastLogins);
JSONArray lastLoginJsonArray = responseJsonObject.get( "items" );
for (int i = 0; i < lastLoginJsonArray.length(); i++) {
JSONObject lastLoginJsonObject = lastLoginJsonArray.getJSONObject(i);
String emailId = lastLoginJsonObject.getString("login_id");
String lastAccessInDays = String.valueOf( lastLoginJsonObject.getInt("last_access_in_days") );
LastLoginLookupMap.put(emailId,lastAccessInDays);
}
//store the resultant map into transient values
transientValues.put("LastLoginLookupMap", LastLoginLookupMap);
} catch (Exception e) {
log.error(logPrefix + "********ERROR AFTER RULE: " + e.getMessage(), e );
}
//store "our" transient values into the application (until the aggregation completes, wherein it will erase)
application.setAttribute("transientValues", transientValues);
log.error(logPrefix + "setting transients");
}
/////// END LOOKUPS //////////
// ensure that we have data to actuall operate on
if(rawResponseObject != null && processedResponseObject != null){
//retrieve the transientValues from application storage
transientValues = (Map) application.getAttributeValue("transientValues");
Map resultMap = JsonUtil.parseJson( rawResponseObject );
//iterate over every Record in the response
// processedResponseObject will have the JSONPath values defined on the source endpoint.
for (Map userEntry : resultMap.get( "Resources" ) ) {
Map newEntry = new HashMap();
log.error(logPrefix + "updating processed data");
// grab key values from the record, from the JSONPath'd output of the endpoint itself
String emailId = null;
if ( userEntry.containsKey("emails") ) {
emailId = userEntry.get( "emails" ).get(0).get( "value" );
// put the email id is present
newEntry.put( "Email", emailId );
}
Map faUserEntry = userEntry.get( "urn:scim:schemas:extension:fa:2.0:faUser" );
if ( userEntry.containsKey("displayName") )
newEntry.put( "Display Name", userEntry.get( "displayName" ) );
if ( userEntry.containsKey("active") )
newEntry.put( "Status", userEntry.get( "active" ) );
if ( userEntry.containsKey("meta") ) {
newEntry.put( "Last Modified Date", userEntry.get( "meta" ).get( "lastModified" ) );
newEntry.put( "Created Date", userEntry.get( "meta" ).get( "created" ) );
}
if ( faUserEntry.containsKey("workerInformation") ) {
newEntry.put( "Business Unit", faUserEntry.get( "workerInformation" ).get( "businessUnit" ) );
newEntry.put( "Person Number", faUserEntry.get( "workerInformation" ).get( "personNumber" ) );
newEntry.put( "Manager", faUserEntry.get( "workerInformation" ).get( "manager" ) );
newEntry.put( "Department", faUserEntry.get( "workerInformation" ).get( "department" ) );
}
if ( userEntry.containsKey("userName") )
newEntry.put( "User Name", userEntry.get( "userName" ) );
if ( userEntry.containsKey("name") ){
newEntry.put( "First Name", userEntry.get( "name" ).get( "givenName" ) );
newEntry.put( "Last Name", userEntry.get( "name" ).get( "familyName" ) );
}
newEntry.put( "ID", userEntry.get( "id" ) );
if ( faUserEntry.containsKey("userCategory") )
newEntry.put( "User Category", faUserEntry.get( "userCategory" ) );
if ( faUserEntry.containsKey("accountType") )
newEntry.put( "Account Type", faUserEntry.get( "accountType" ) );
//null safety. dont look it up if it doesnt exist.
if (transientValues.containsKey("LastLoginLookupMap") && Util.isNotNullOrEmpty( emailId )){
HashMap LastLoginLookupMap = (HashMap) transientValues.get("LastLoginLookupMap");
//only try to add the value if it actually exists.
if (LastLoginLookupMap.containsKey(emailId)){
newEntry.put("Last Login",LastLoginLookupMap.get(emailId));
} else{
log.error(logPrefix + "lookup failed for jobcode: " + emailId);
}
}
//add the modified record to our new return result
if ( !newEntry.isEmpty() )
returnObj.add(newEntry);
}
} else{
log.error(logPrefix + "Response Object was empty, standard endpoint data will be passed.");
}
//add the data to the reply, and send it
processedData.put("data", returnObj);
log.info(logPrefix + "finished running");
return processedData;