<inbound>
<expression>
<value>ExAmPLE, Inc.</value>
</expression>
<target>
<path>organization</path>
</target>
<condition>
<script>
<code>orgCode == "001"</code>
</script>
</condition>
</inbound>
Mapping Condition
Mapping condition is a mechanism for easy implementation of mappings that provide conditional values. This means a value that is present when certain condition is true, while it is not present if the condition is false. The idea is that mapping expression provides the value, while the condition controls when the value is applied and when it is not.
A very simple mapping condition would control a static value.
The mapping below sets value ExAmPLE, Inc.
to the property organization
in case that orgCode
attribute has value 001
.
The mapping will remove the ExAmPLE, Inc.
value from the organization
property in any other case.
Mapping condition is fully relative, which means it takes care of both add and removal of the value produced by the expression.
In fact, that is the whole purpose of mapping condition.
The condition is not "turning off" the mapping, it only influences the way expression results are used.
Mapping range, and most of other aspects of the mapping are applied even if the condition is evaluated to false
.
More on that below.
At most one condition can be defined in a mapping.
A clever reader may wonder why are conditions needed at all.
Complex expressions may implement all the logic that is usually placed into the condition.
And clever reader might be right for most of the cases.
But it is important to keep in mind that mapping is much more than just its expression.
There are other settings such as mapping range.
A condition that is false will inactivate all the aspects of the mapping.
Whereas mapping expression can only control the value that the mapping produces.
But it cannot control other processing of that value that takes place in the mapping.
|
Relativity of Mapping Condition
Mapping conditions are also relative.
This means that the condition is not evaluated for simple true
or false
value.
MidPoint is watching how the condition value changes:
Old value of condition | New value of condition | Effect | Description |
---|---|---|---|
|
|
No change |
Mapping is active, and there is no change to this situation.
The value that the mapping produces is added or removed as expected (based on the source delta).
No special handling for this situation. |
|
|
Value removed |
Mapping becomes inactive.
The mapping (most likely) produced some value before this change happened.
And now it is not producing any such value.
Therefore, midPoint will try to remove the value that was produced by the mapping. |
|
|
Value added |
Mapping becomes active.
The mapping produced no values before this change happened.
But now, the mapping is producing some values (most likely).
Therefore, such values should be added. |
|
|
No change |
Mapping is inactive and there is no change to this situation. Mapping values were ignored before, they are also ignored after. Nothing to do. |
This is a very intentional behavior. It is designed to enable simple mappings that behave in a relativistic way. For example:
<item>
<ref>organization<ref>
<mapping>
<source>
<path>costCenter</path>
</source>
<expression>
<value>ACME, Inc.</value>
</expression>
<condition>
<script>
<code>costCenter.startsWith('A')</code>
</script>
</condition>
</mapping>
</item>
This object template mapping will set the organization property of a user to ACME, Inc.
in case that the cost center code starts with letter A.
In this case, the expression is completely static literal value.
Yet the mapping is behaving in the usual relativistic way, because there is a condition.
When the condition becomes true
then ACME, Inc.
value is added.
When the condition becomes false
then ACME, Inc.
value is removed.
In this case, the use of condition is simple and intuitive.
What may be very confusing is when (relativistic) conditions are combines with (relativistic) expressions. Such a complex approach may be needed to implement some special cases. However, it should not be required in the common case. The rule of the thumb is to use either complex condition or complex expression, but not both - at least until you know precisely what you are doing.
Condition and Mapping Evaluation
Condition changes the way expression results are used.
As stated above, expression value is added when condition becomes true
, and removed when condition becomes false
.
Other aspects of the mapping are usually not affected by condition.
For example, mapping range is applied normally even if condition is evaluated to false
(see below).
The condition is not used to "turn mapping off". Turning mapping "off" would mean that a value is added, but it is never removed. Given the emphasis on data consistency that permeates all of midPoint, such approach does not make much sense. This is identity management, we want to provision, but we also want to deprovision. What is added, needs to be removed eventually. Mapping condition is designed to handle both actions, both adding and removal.
If the mapping is meant to provide an initial value, weak mappings should be used. If the expression is meant to be active only for some cases, it may be better to put expression is role or org.
Condition does not mean that the expression is skipped.
Expression may be evaluated even if condition is false
.
The expression may still be evaluated, e.g. to get negative values for the delta.
Condition applies entire mapping, it applies to all the values processed by a mapping. The condition does not work for individual values.
Rule of the Thumb
The rule of the thumb is to use either complex condition or complex expression, but not both - at least until you know precisely what you are doing.
If the thing that you are trying to achieve is difficult to achieve by using the condition, then perhaps condition is not the right method to achieve your goal. Try to re-think the problem, consider using roles or policy rules. Maybe the mapping strength is wrong. Maybe the mapping is not in the right place. Consider moving the mapping to inbound part of the processing. Conditions are meant to support simple cases, they are not built to handle unusual situations.
Condition and Range
Mapping range is still applied, even if condition is evaluated to false
.
Condition does not "turn off" the mapping, therefore it does not "turn off" the range either.
Condition is used to add or remove values.
Condition that evaluates to false
is still active in a sense, removing the values.
Therefore, there is no reasons for a range not to be applied.
Moreover, there may be some values in the target property that have to be removed, even though such values may not be computed by the expression.
The reason for this may be that the state of mapping target property may be inconsistent.
We still want to apply the range, even if condition is false
, to make sure that such values are removed.
This approach may seem strange.
However, this goes well with midPoint philosophy of keeping the data as consistent as possible.
Also, evaluating range when condition is false
gives more flexibility for customization, by manipulating range expression.
The range can always be "turned off" by specifying range expression that returns false
for every value, thus effectively reducing range to an empty set.
This gives you choice whether you want to apply the range or not.
Simply copying the condition into a range expression would usually do the trick.
However, this approach should be used only as a last resort, as such configuration is usually incorrect.
Good Usage And Bad Usage
Mapping condition is meant to add and remove simple values, mostly static, literal values. We have already seen a good example:
<item>
<ref>organization<ref>
<mapping>
<source>
<path>costCenter</path>
</source>
<expression>
<value>ACME, Inc.</value>
</expression>
<condition>
<script>
<code>costCenter.startsWith('A')</code>
</script>
</condition>
</mapping>
</item>
Similarly, mapping conditions are useful when automatically assigning individual roles:
<attribute>
<ref>org_code<ref>
<inbound>
<expression>
<value>
<targetRef oid="436f24b0-8816-11ec-b2a5-cf2046309455" type="OrgType"/> <!-- ExAmPLE, Inc. organization -->
</value>
</expression>
<target>
<path>assignment</path>
</target>
<condition>
<script>
<code>org_code == "EXAMPLE"</code>
</script>
</condition>
</inbound>
</item>
This is an inbound mapping for org_code
attribute of an HR system.
This mapping assigns the user to the ExAmPLE, Inc.
organization, represented as midPoint org with OID 436f24b0-8816-11ec-b2a5-cf2046309455
.
The mapping is controlled by a condition, which triggers when org_code
attribute has a value EXAMPLE
.
This mapping creates the assignment and removes the assignment as necessary.
However, mapping condition is not very useful when assigning multiple roles or orgs with a single mapping, not directly anyway.
Use of specialized expressions (e.g. assignmentTargetSearch
) and a proper application of range is the right way to do it.
However, mapping condition may be useful even in this case, e.g. in case that we want to auto-assign the roles only to active users:
<item>
<ref>assignment</ref>
<mapping>
<source>
... definition of sources for the assignmentTargetSearch expression ...
</source>
<source>
<path>activation/effectiveStatus</path>
</source>
<expression>
<assignmentTargetSearch>...</assignmentTargetSearch>
</expression>
<condition>
<script>
<code>effectiveStatus == com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType.ENABLED</code>
</script>
</condition>
</mapping>
</item>
The mapping above makes sure that the roles are automatically assigned only in case that the user is active (i.e. effectively enabled).
Yet, the mapping will also make sure that the roles assigned to the user will be removed when the user is disabled.
However, this mapping assumes that the result of assignmentTargetSearch
expression will be the same every time it is evaluated.
More specifically, it has to produce the same set of roles when it is evaluated at the times the roles are to be assigned, as at the time when they are unassigned.
This is fair assumption to make when the system is constantly kept consistent, e.g. the data are periodically reconciled.
However, if there is any risk of inconsistencies, it is recommended to supplement the expression with appropriate range definition.
Mapping condition might be used to check for sanity of input values.
For example, mapping condition may guard the inputs of a fullName
mapping:
<item>
<ref>fullName</ref>
<mapping>
<source>
<path>givenName</path>
</source>
<source>
<path>familyName</path>
</source>
<expression>
<script>
<code>givenName + ' ' + familyName</code>
</script>
</expression>
<condition>
<script>
<code>givenName != null && familyName != null</code>
</script>
</condition>
</mapping>
</item>
In this case the condition guards against producing an invalid value, such as John null
or null Smith
.
However, there is a catch.
Such mapping will not produce any value for fullName
property in case that the inputs are invalid.
While this may be formally correct, it is not very practical.
There are (at least) two opportunities for improvement. We can use the condition to select appropriate mapping from a set of several alternative mappings:
<item>
<ref>fullName</ref>
<mapping>
<source>
<path>givenName</path>
</source>
<source>
<path>familyName</path>
</source>
<expression>
<script>
<code>givenName + ' ' + familyName</code>
</script>
</expression>
<condition>
<script>
<code>givenName != null && familyName != null</code>
</script>
</condition>
</mapping>
<mapping>
<source>
<path>givenName</path>
</source>
<source>
<path>familyName</path>
</source>
<expression>
<script>
<code>familyName</code>
</script>
</expression>
<condition>
<script>
<code>givenName == null && familyName != null</code>
</script>
</condition>
</mapping>
<mapping>
<source>
<path>givenName</path>
</source>
<source>
<path>familyName</path>
</source>
<expression>
<script>
<code>givenName</code>
</script>
</expression>
<condition>
<script>
<code>givenName != null && familyName == null</code>
</script>
</condition>
</mapping>
<mapping>
<source>
<path>givenName</path>
</source>
<source>
<path>familyName</path>
</source>
<expression>
<value>John Doe</value>
</expression>
<condition>
<script>
<code>givenName == null && familyName == null</code>
</script>
</condition>
</mapping>
</item>
While this approach is formally correct, it is neither very elegant nor entirely maintainable. Much more straightforward approach is to handle all the cases inside the expression, without any need for condition:
<item>
<ref>fullName</ref>
<mapping>
<source>
<path>givenName</path>
</source>
<source>
<path>familyName</path>
</source>
<expression>
<script>
<code>
if (givenName == null) {
if (familyName == null) {
return "John Doe"
}
return familyName
}
if (familyName == null) {
return givenName
}
return givenName + ' ' + familyName
</code>
</script>
</expression>
</mapping>
</item>
There are also ways to abuse mapping conditions. Perhaps one of the perfectly clear, understandable and wrong way is to use conditions to set initial values of properties. This will not really work. Weak mappings should be used for that purpose instead.