Passing data from a Transform to a Rule

Hello Community,

I have implemented a Rule which performs some complex logic and I want to make it as dynamic as possible. To accomplish this I want to pass these variables from a Transform allowing me to reuse the same Rule on different sources and different environments.

Snippet of the Rule:

import sailpoint.api.*;
import sailpoint.connector.*;
import sailpoint.object.*;
import sailpoint.rule.*;
import sailpoint.tools.*;
import java.util.*;
import org.apache.commons.lang.StringUtils;
import java.util.Iterator;
import java.util.Map;
import com.google.gson.Gson;
String transformInputs_SourceID = transformInputs_SourceID;
String transformInputs_PersonType_domainPrefix_mapping = transformInputs_PersonType_domainPrefix_mapping;
Map personType_domainPrefix_mapping = new Gson().fromJson(transformInputs_PersonType_domainPrefix_mapping, Map.class);
// Some string manipulation and Map searches performed in the code

Snippet of the Transform:

{
	"name": "userPrincipalName",
	"transform": {
		"attributes": {
			"name": "UPN Generator",
			"transformInputs_PersonType_domainPrefix_mapping": {
				"Regular": "",
				"Agency": "external.",
				"Apprentice": "external.",
				"default": "external."
			},
			"transformInputs_SourceID": "eeb7a5f386bc4d8bb5cfc02e3a9ec78d"
		},
		"type": "rule"
	},
	"isRequired": false,
	"type": "string",
	"isMultiValued": false
}

Rule and Transform codes have been adapted from the documentation Transform Rule > Example - Name Normalizer

The Transform is used in “Create Account” policy however when it runs it fails with the following error:

java.lang.RuntimeException: 
sailpoint.tools.GeneralException: The application script threw an exception: 
com.google.gson.JsonSyntaxException: 
com.google.gson.stream.MalformedJsonException: 
    Expected ':' at line 1 column 47 path $. 
See https://github.com/google/gson/blob/2549ba93fc48f4c29f1a5c82553ce666712f183a/Troubleshooting.md#malformed-json 
BSF info: UPN Generator at line: 0 column: columnNo

I have checked the code multiple times and I can’t find any typos or misplaced commas. The validator is not showing any errors and the Rule has been correctly deployed in my tenant.

Do you have any insights to help me fix the issue?
Thank you in advance!

Your transform does not look like the transform in the documentation. You have an additional layer of nesting. The documentation has:

  "name": "Normalize Name",
  "type": "rule",
  "attributes": {
    "name": "Name Normalizer",
    "delimiters": ["-", " ", "\\'"],
    "patterns": "\\b(Mc|Mac)",
    "input": {
      "type": "trim"
    }
  }

Yours has:

{
	"name": "userPrincipalName",
	"transform": {
		"attributes": {
			"name": "UPN Generator",
			"transformInputs_PersonType_domainPrefix_mapping": {
				"Regular": "",
				"Agency": "external.",
				"Apprentice": "external.",
				"default": "external."
			},

Hello @agutschow,

my second nesting layer is due to where the transform is. I need to set this transform in a provisioning policy, it means that it will be used as a Generator to produce an account attribute.
The entire provisioning policy around the userPrincipalName is the following:

{
			"name": "userPrincipalName",
			"transform": {
				"type": "rule",
				"attributes": {
					"name": "UPN Generator",
			        "transformInputs_PersonType_domainPrefix_mapping": {
				        "Regular": "",
				        "Agency": "external.",
				        "Apprentice": "external.",
				        "default": "external."
			            },
			     }
			},
			"attributes": {
				"cloudRequired": "true"
			},
			"isRequired": false,
			"type": "string",
			"isMultiValued": false
		}

If I try to delete the additional nesting level it won’t work.
A similar code works for many other rules in the same provisioning policy but with the only difference that in this Transform+Rule I need to use a Map.

Generally, all variables within a Transform’s attributes will be converted to Strings. If you include a JSON Array or Object variable, it will first be interpreted as a Java List or Map respectively, but then converted to String via the corresponding toString() method. So your transformInputs_PersonType_domainPrefix_mapping variable will not contain stringified JSON as you are trying to parse, but instead the Java Map toString() representation. Something like:

{default=external., Agency=external., Regular=, Apprentice=external.}

However, since you are using this rule within a Provisioning Policy, you can move that configuration to the field attributes, and it will be available to you as a Java Map:

{
    "name": "userPrincipalName",
    "transform": {
        "type": "rule",
        "attributes": {
            "name": "UPN Generator"
        }
    },
    "attributes": {
        "cloudRequired": "true",
        "transformInputs_PersonType_domainPrefix_mapping": {
            "Regular": "",
            "Agency": "external.",
            "Apprentice": "external.",
            "default": "external."
        }
    },
    "isRequired": false,
    "type": "string",
    "isMultiValued": false
}

and in your rule code:

Object mappingObject = field.getAttribute("transformInputs_PersonType_domainPrefix_mapping");
if (mappingObject instanceof Map) {
  Map mapping = (Map) mappingObject;
  // ...
}

Thank you @nsorlien, your solution has worked like a charm!
If possible, I would like to further enhance the dynamism of a specific Transform and Rule by using a nested Map structure. It would be something like this:

{
			"name": "email",
			"transform": {
				"type": "rule",
				"attributes": {
					"name": "Email Generator",
			        // Some attributes here
			     }
			},
			"attributes": {
				"cloudRequired": "true",
				"transformInputs_legalEmployerAlias_mapping": {
					"company1": {
						"Regular": "@company1.com",
						"default": "@external.company1.com"
					},
					"company2": {
						"Regular": "@company2.com",
						"default": "@ext-company2.com"
					}
				}
			},
			"isRequired": false,
			"type": "string",
			"isMultiValued": false
		}

Would this work with the following Rule code?

Map nestedMaps = null;
Object mappingObject = field.getAttribute("transformInputs_legalEmployerAlias_mapping");
if (mappingObject instanceof Map) {
  nestedMaps  = (Map) mappingObject;
  // ...
}

Map companyMap = nestedMaps.get(companyX); // Do I need to explicitly cast it to map?
Map companyMap = (Map) nestedMaps.get(companyX); // Is this cast necessary?
String targetDomain = companyMap.get(employeeType); 

The “companyMap” attribute in the Rule should be casted to (Map) or would it be implicit?

Yes, nesting Maps and Lists does work!

Beanshell isn’t as strict as Java, so casting is optional in this case. I normally write rules in Java and copy the code to Beanshell, so I included the cast in my example. This code does work in Beanshell:

Map nestedMaps = field.getAttribute("transformInputs_PersonType_domainPrefix_mapping");
Map companyMap = nestedMaps.get(companyX);
String targetDomain = companyMap.get(employeeType);

I would recommend including at least some validation on the type of the outermost variables, though. Beanshell errors can be pretty cryptic.

Thank you very very much @nsorlien!

1 Like