Keeping Multivalued Attributes Under Control - [MV-Attr Clean Up Driver Example]


Recently I was asked how a multivalued attribute could best be cleaned up automatically, as it was causing issues when synchronized to a connected system. The attribute in question was being used to hold a historical record of certain changes and needed to be clean up once in a while. I have also seen similar situations with other customers who have used the Description attribute to hold a record of changes made to a given user over time.That practice caused some grief when a poorly written policy kept updating the Description with a text string and a timestamp, so every event processed generated a new value.

In this situation a different attribute was involved and the customer was managing the attribute with manual scripting to remove the attribute or remove extra values. Unfortunately, manual processing meant the process was usually forgotten until something failed and the environment needed to be fixed to get things working once more.

Note: As this solution was targeted at a single attribute it was developed with that goal in mind. I want to return to this solution and expand it to manage multiple attributes, with different maximum values allowed for each managed attribute.

Initially the desire was to do a full pass and make sure that all existing attributes are checked and reduced to the allowed maximum number of values. Additionally ongoing monitoring of changes to remove any extra values as the attribute on any user is modified.

To address the first request a Job that is configured for the appropriate scope can be used. As this was expected to be a one time event, the choice to trigger events for each user was selected. This could also have been handled by a Job that generates a single event, that then does a query for all users with the attribute in question. By using the event for each user, the Driver Filter does not have to include the object that a single event would likely be configured to trigger on. This can generate a lot of events for that initial pass, so the choice may be different for other situations. Additionally, by creating an event for each user, the same policies can be used to monitor ongoing changes.

To handle ongoing changes, a modification to the managed attribute will generate an event that can be used to check the current number of values and trim the attribute as needed.

With the above considerations sorted out the attached driver was developed as a proof of concept in a few hours and has been cleaned up further to ensure anonymity of any guilty parties. Feel free to use the concepts and code examples to meet your needs as well.

Some details on the implementation.

I like the idea of being able to provide a complete solution, so I have used the Null Driver base as a starting point and copied that to create the Multivalued Attribute Clean Up Base Driver package. This allows the Wizard to be configured to include the Multivalued Attribute Clean Up Configuration package as part of the initial install. I can also use a bit of my (limited) creative flair and develop customized icons for iManager to be included in the install. Once installed I can customize Designer with the included icon for the Designer representation of the driver as well.

To personalize the driver for the specific needs, the GCV's can be changed to select a different attribute, as 'Description' is the one used in the example packages. Unfortunately, the Filter can not be customized from that GCV setting, so it will need to be modified before the driver is deployed to match the desired attribute to manage. The provided Job will also need to be modified to select the target server and define the Scope for the initial pass. When adding the Scope, remember to also specify 'Apply job to all descendants of this container' and add 'User' to the classes list of objects that the job is to be applied to.

Once the GCVs, Filter, and Job are configured to meet your needs, Deploy the driver, setup Permissions for the Driver and the Job and you should be good to go.

Other than all the setup, the entire work is done in a single policy that goes through a series of steps to validate the event being processed has work to check and then handle any checking required by removing any excess values for the managed attribute.

Walking through the Policy by Rule:

1. First check that the event is one we want to work on. I try to scope every policy after troubleshooting too many environments where the rule, or rules, in a given policy are all executing even though the event has no work to be done. This generates a ton of overhead in the traces, especially when debugging at trace level 3. The more that can be done to determine there is no work to do and then get out of the policy as soon as possible the cleaner the code is in my view.

This first Rule checks to make sure the event is either a modify with the managed attribute included in the event, or a Trigger event from the Job for a User. If neither is true, there is no more work to be done in this Policy so Break out of it Now!

Note that the check for the managed attribute is done using XPath so that a modification with only a value removal is also processed. If this test had checked only for operational attribute being available, the removal only situation would be ignored and not cleaned up with the final Rule if needed.

<rule> <description>Break if Not a User Modify or Trigger for MV Attribute</description> <comment xml:space="preserve">Break if the event is not a User Modify or User Trigger for the Managed Multi-Valued Attribute. Use the XPath test for the managed attribute, instead of Operational Attribute Available to also handle cases where the managed attribute has one or more values being removed only.</comment> <conditions> <or> <if-class-name mode="nocase" op="not-equal">User</if-class-name> <if-operation mode="nocase" op="not-equal">modify</if-operation> <if-xpath op="not-true">modify-attr[@attr-name="~gv.mvattr.attrToManage~"]</if-xpath> </or> <or> <if-class-name mode="nocase" op="not-equal">User</if-class-name> <if-operation mode="nocase" op="not-equal">trigger</if-operation> <if-op-property mode="nocase" name="source" op="not-equal">MV Attr CleanUp</if-op-property> </or> </conditions> <actions> <do-break/> </actions> </rule>

2. The second Rule handles the situation where this is a Trigger event we want, but the referenced User does not have any values for the managed attribute. If that is true, then we can Veto the Trigger event and be done processing.

<rule> <description>Veto if Trigger and No Multi-Valued Attribute</description> <comment xml:space="preserve">If this is a trigger event and the User has No multi-valued attribute to be managed, Veto as no further action is required.</comment> <conditions> <and> <if-operation mode="nocase" op="equal">trigger</if-operation> <if-attr name="~gv.mvattr.attrToManage~" op="not-available"/> </and> </conditions> <actions> <do-veto/> </actions> </rule>

 3. The third Rule simply sets up some local variables, one node set with the list of values for the managed attribute, and the second with the count of values the managed attribute currently has.

<rule> <description>Setup Working Variables</description> <conditions> <and/> </conditions> <actions> <do-set-local-variable name="lv-AttrValues" scope="policy"> <arg-node-set> <token-src-attr name="~gv.mvattr.attrToManage~"/> </arg-node-set> </do-set-local-variable> <do-set-local-variable name="lv-ValueCount" scope="policy"> <arg-string> <token-xpath expression="count($lv-AttrValues)"/> </arg-string> </do-set-local-variable> </actions> </rule>

 4. The fourth Rule is where the real work happens if the count of values on the managed attribute exceeds the allowed number. If it does not, we can skip on to the next Rule. 

A key to this process is that the returned results of values for a given attribute are always listed in order of their age, or the timestamp on that value. Since the oldest values are listed first, we can simply generate a Remove Value for the manage attribute for any values that are listed ahead of the position the first allowed value is. If the number of allowed values is 12, when there are currently 20 values on the attribute, the allowed position can be calculated as 9 [20 - 12 1 = 9]. Then looping through a for loop we can generate the remove value for each value that exists in a position before that number:

for each




<rule> <description>Clean Up aged Values from Managed Attribute</description> <comment xml:space="preserve">Clean Up values that are older than the most recent allowed maximum values. This uses the returned multi-valued listing that is provided in aged order, oldest listed first, based on the timestamp values. So anything older than the first allowed item, counting back from the end of the list, can be removed. eg: if there are 20 values and only 12 are allowed, any values in position before position 9 [20 - 12 1 = 9] will be removed, or list items 1 through 8, leaving the 12 (newest) allowed values on the attribute.</comment> <conditions> <and> <if-xpath op="true">$lv-ValueCount > ~gv.mvattr.maxValues~</if-xpath> </and> </conditions> <actions> <do-set-local-variable name="lv-KeepValuePos" scope="policy"> <arg-string> <token-xpath expression="$lv-ValueCount - ~gv.mvattr.maxValues~ 1"/> </arg-string> </do-set-local-variable> <do-for-each> <arg-node-set> <token-xpath expression="$lv-AttrValues[position()&lt;$lv-KeepValuePos]"/> </arg-node-set> <arg-actions> <do-trace-message> <arg-string> <token-text xml:space="preserve">Remove value of: </token-text> <token-local-variable name="current-node"/> <token-text xml:space="preserve"> from Attribute </token-text> <token-global-variable name="~gv.mvattr.attrToManage~"/> </arg-string> </do-trace-message> <do-remove-src-attr-value name="~gv.mvattr.attrToManage~"> <arg-value type="string"> <token-local-variable name="current-node"/> </arg-value> </do-remove-src-attr-value> </arg-actions> </do-for-each> </actions> </rule>

 5. The fifth Rule cleans up any Trigger event we have been processing, since all the work is now done with a Veto on that event.

<rule> <description>Veto Trigger</description> <comment xml:space="preserve">Veto the Trigger event when all processed</comment> <conditions> <and> <if-operation mode="nocase" op="equal">trigger</if-operation> </and> </conditions> <actions> <do-veto/> </actions> </rule>

 6. The final Rule handles any desired clean up of the Modify events by stripping out the managed attribute. As this may not always be the desired action, especially where there are other Policies and Rules that may need to process that attribute, or this concept is being used in a driver that is synchronizing values to a connected system, the GCV setting can be used to control this option.

If desired the managed attribute is stripped out of the event and if there is nothing left in the event for processing it is cleaned up with a Veto as well.

<rule> <description>Strip Modify Attr</description> <comment xml:space="preserve">Strip the Attribute from the Modify event doc and if no further attributes are changing Veto the empty Modify. Use the XPath test for the managed attribute, instead of Operational Attribute Available to also handle cases where the managed attribute has one or more values being removed only. Note: This Rule may not be desired if there are other operations to be processed on this attribute in later policies. The GCV (gv.mvattr.stripAttr) setting can be used to allow the attribute to stay in the doc for further processing.</comment> <conditions> <and> <if-operation mode="nocase" op="equal">modify</if-operation> <if-xpath op="true">modify-attr[@attr-name="~gv.mvattr.attrToManage~"]</if-xpath> <if-global-variable mode="nocase" name="gv.mvattr.stripAttr" op="equal">true</if-global-variable> </and> </conditions> <actions> <do-strip-op-attr name="~gv.mvattr.attrToManage~"/> <do-strip-xpath expression="modify-attr[not(*)]"/> <do-if> <arg-conditions> <and> <if-xpath op="not-true">*[@attr-name]</if-xpath> </and> </arg-conditions> <arg-actions> <do-veto/> </arg-actions> <arg-actions/> </do-if> </actions> </rule>

Hopefully the above is helpful in your work and learning along the way. Please post any questions or observations as well.




How To-Best Practice
Comment List
Related Discussions