Can't replace strings with transform, or apply Apache Velocity logic

Hi all,

The following transform works:

{
    "name": "Angelo test transform",
    "type": "dateFormat",
    "attributes": {
        "inputFormat": "yyyy-MM-dd",
        "input": "2023-04-23"
    }
}

But the following transform does not work (replace the string by a static transform that outputs the same string):

{
    "name": "Angelo test transform",
    "type": "dateFormat",
    "attributes": {
        "inputFormat": {
            "type": "static",
            "attributes": {
                "value": "yyyy-MM-dd"
            }
        },
        "input": "2023-04-23"
    }
}

We then get the following error: “There was an exception while calculating the value for this attribute. class com.sailpoint.seaspray.transform.StaticTransform cannot be cast to class java.lang.String (com.sailpoint.seaspray.transform.StaticTransform is in unnamed module of loader ‘app’; java.lang.String is in module java.base of loader ‘bootstrap’)”

We also tried this (inserting Apache Velocity in the string):

{
    "name": "Angelo test transform",
    "type": "dateFormat",
    "attributes": {
        "inputFormat": "yyyy-MM-dd#if(1 eq 2) HH-mm-ss#end",
        "input": "2023-04-23"
    },
    "internal": false
}

This transforms returns nothing when we map the transform to an identity attribute.
A usecase for this would be when we need to assign the inputFormat dynamically, maybe because we want to transform an identity attribute that has values in different date formats depending on the country of the identity. Or maybe we want to reference a transform, and pass the inputFormat from the transform that calls the reference.

According to Transforms | SailPoint Developer Community, every string value in a Seaspray transform can contain templated text and will run through the template engine. It looks like this is not the case.

We also noticed the following transform failing:

"name": "Angelo test static transform",
    "type": "static",
    "attributes": {
        "value": "#if(2 eq 2)Hi#end$foo.$bar",
        "foo": "#if(1 eq 1)ONE#{else}TWO#end",
        "bar": "#if('a' eq 'b')A#{else}B#end"
    },

It gives as output: Hi#if(1 eq 1)ONE#{else}TWO#end.#if('a' eq 'b')A#{else}B#end, instead of evaluating the velocity code.

Kind regards,
Angelo

I’ve got an update from SailPoint

Still it looks like a bug to me. The documentation says it supports templated text in each string value in a transform after all.

Kind regards,
Angelo

@tyler_mairose can you look into this and see what we can find out about how SailPoint says it’s supposed to work (and let them know it differs from what users expect).

1 Like

@jordan_violet @angelo_mekenkamp

Yes, I will take a look and see what I can find out!

SailPoint Support did send me this prior to the other message:

We have got following suggestion form our Expert team, please check if it helps.

  • You can have this built a different way where you can create static transforms and have condition if [(1eq2)invoke x else invoke y] x and y you can define in respective input format they want.

  • Instead of velocity, You MIGHT be able to get away with a firstvalid transform of (dateformat yyyy-MM-dd), (dateformat yyyy-MM-dd HH-mm-ss) .

We know that workarounds exists for this issue I am pointing out here, but this means adding unnecessary complexity to our transforms.

Assume that the manual of your car says you can turn in both directions. You try to turn left and succeed, you try to turn right and fail. After calling the car manufacturer you get told that there is no issue, it is working as designed and in order to go right, you should go left three times to achieve the same results. To me the response from SailPoint Support feels slightly like this.

I’ve got an update from SailPoint Support.

They say that the PM team treats it as an enhancement request and not as a defect.
Given that the documentation does mention it should already be working, I don’t agree with this standpoint.

@tyler_mairose, were you able to take a look at this? (all the best for 2024!)

Kind regards,
Angelo

Hey Angelo!

I’ve had some time to dig into this and found some interesting results.

Looking at your first example:

I can confirm that this will not work other than specifying the corresponding date format as a simple string. I looked at the code and it is reading in the input provided and does not evaluate any template logic.

With this example, it seems to be expecting a $ symbol when evaluating the velocity logic. If I use something I know is available in the template context like $identity.getId() then the logic evaluates.

For example:

{
    "attributeName": "personalEmail",
    "type": "static",
    "attributes": {
        "value": "#if(2 eq 2)Hi#end$foo.$bar",
        "foo": "#if(1 eq 1)Test#{else}Words#end",
        "bar": "#if('a' eq 'a')$identity.getId()#{else}NoId#end"
    }
}

This gives me the result Hi#if(1 eq 1)Test#{else}Words#end.2c9180867dfe694b017e208e26565795. Since the bar atttribute has the $ symbol in $identity.getId() it will evaluate. The foo attribute stays as is and does not evaluate and instead acts as a string.

You can trick the velocity engine to evaluate by adding the $ symbol to the string. See below:

{
    "attributeName": "personalEmail",
    "type": "static",
    "attributes": {
        "value": "#if(2 eq 2)Hi#end$foo.$bar",
        "foo": "$#if(1 eq 1)Test#{else}Words#end",
        "bar": "#if('a' eq 'a')$identity.getId()#{else}NoId#end"
    }
}

This evaluates both foo and bar giving us the result: HiTest.2c9180867dfe694b017e208e26565795.

Another detail that is worth noting: You can’t use an attribute created inside another attribute other than the value key. If you create a variable foo and try and use it in the bar variable you will receive a template error.

{
    "attributeName": "personalEmail",
    "type": "static",
    "attributes": {
        "value": "#if(2 eq 2)Hi#end$foo.$bar",
        "foo": "$#if(1 eq 1)Test#{else}Words#end",
        "bar": "#if($foo eq 'Test')$identity.getId()#{else}NoId#end"
    }
}

I found out this is because of the apache velocity context. A new context instance is created for the static transform and the keys under the attributes foo and bar are then added to this new context which is then provided when velocity evaluates the value key provided from the transform. However, when the foo and bar keys are added to the context the code uses the original context which does not have these keys added.

I think this should be updated. I could see this being implemented two ways.

  1. Create a new transform that allows you to create a new variable and add it to the velocity context so that it is available everywhere in the transform.

  2. Update the static transform code to allow any attributes outside of the value key to be added to the global context to be used elsewhere in the transform.

1 Like

Thank you very much @tyler_mairose for taking the time to dive into this subject. You have discovered some interesting behaviors here!

Regarding the dateFormat. Since you have confirmed that the inputFormat can only be a simple string and not a transform or string with apache logic, this behavior seems to be conflicting with this documentation, which states that every string value can contain templated text. Shouldn’t this be considered as a bug than? Because it is not behaving as documented and we now need a strange workaround to make it work.

Regarding that strings on additional attributes in a static transform (not sure about other transforms) do listen to the apache logic if the string contains a $-sign. This is interesting and probably worth documenting, because the current documentation says that it is always run through the template engine.

I have noticed myself that if you have an attribute be a nested static transform, you are not able to reach the variables from the top transform. I believe the other way also hold, that the top transform can’t reach variables defined in the nested transform.

I would say it sounds most intuitive if it would behave like this:
We define variables a and b in a top transform,
We define variables b and c in a nested transform. In the nested transform, we can call a, b and c. The value for b and c is as defined in the nested transform, the value for a is as defined in the top transform. Also, in the top transform we can call variables a and b, both of which have a value as defined in the top transform. c would not be callable from the top transform, because there might be multiple nested transforms next to each other (for example in a concat transform) and it would be ambiguous which value to take.