Cool tricks using XPATH on nodesets

1 Likes
Novell Identity Manager is a very powerful system that has many components. There is the core engine, that watches for events in eDirectory (dxevent) and sends them out to the drivers according to what the filters for each driver are listening for, processing the rules along the way (vrdim). There are driver shims for many different connected systems. There are well over a dozen different drivers for well known systems, some like the JDBC, LDAP, Delimited Text, and SOAP drivers are sufficiently generic to support dozens of different systems each. (For example the JDBC driver can talk to Oracle, MS SQL, Postgress, MySQL, or DB2 databases and that counts as a single driver). These drivers watch for events in their connected systems and send them to the engine for processing. Then there is the User Application which is a whole other world, almost a completely different product.

At the heart of all this is the engine, and in addition to seeing events in eDirectory it processes the rules, style sheets, and other objects that are part of the drivers.

There are many tools at your disposal for use by the engine. You can write your rules using XSLT style sheets, the original approach. This is probably the most complex approach, unless XSLT is something you are very comfortable with. NSure Identity Manager 2.0 introduced DirXML Script, which is an XML based language with nouns, verbs, tokens, actions, condition tests, and what not that allow you to process events in a much more user friendly manner. The tool used to build rules in DirXML Script, Policy Builder is very powerful, and is exposed in iManager's plugins for Identity Manager, and Designer for Identity Manager.

Within both XSLT style sheets and DirXML Script it is possible to use XPATH, the XML Path language to do a whole host of other things. XPATH includes a number of string processing functions like contains(), subtring-before(), and many more functions, see http://www.w3.org/TR/1999/REC-xpath-19991116 for the full definition. Note that only version 1.0 of XPATH is supported, not 2.0. Last I heard XPATH 2.0 was not fully backwards compatible and can break XPATH 1.0 rules. Thus upgrading could be very complex and the developers for Novell Identity Manager are still working out how to handle this issue.

One of the very cool things you can do with XPATH that greatly extends the power of Novell Identity Manager is the ability to call a Java function (I suppose any Java function you happen to have installed on the server). With Identity Manager 3.5 and higher they added the ability to call ECMAScript functions as well from XPATH. This way if you cannot do it in DirXML Script, you can write your own function in either Java and call it, or in ECMAScript and call it via XPATH.

In this article, http://www.novell.com/communities/node/4833/some-thoughts-xpath-novell-identity-manager I discussed a couple of general uses for XPATH, and the basic approaches you can take to using it.

Another function that came up recently for me, that turns out to be extremely powerful is using the DirXML Script action, do-strip-xpath action. Now I always thought of using this action to remove a value from the current XDS document. For example, you want to remove a <remove-all-values> node from the current operation, you could do-strip-xpath with an XPATH expression of something like <do-strip-xpath expression='modify-attr[@attr-name="SomeAttribute"]/remove-all-values'/> and this is a pretty useful thing to be able to do. Especially in the case where you know a remove all values is going to come from the connected system on a particular event, but your REALLY do not want it to do that. This way you can remove it from the XDS event document before it gets sent to eDirectory (or vica versa, depending on the channel you are currently in).

What I recently was pointed at, is the fact that you can use the do-strip-xpath action against any nodeset, not just the current operational document. After all, both are just DOM objects in memory.

The most common nodeset I deal with is the result of a Query token. For more on the Query token you could read the article:
http://www.novell.com/communities/node/4906/the-query-token-identity-manager

A particular example I ran into was querying the file and print eDirectory tree for ACL values. I was trying to find users with excessive amounts of rights. Turns out there were all sorts of optimization tricks I used, but this one is still my favorite. I queried back for all Organizational Units with ACL attributes on them, and that generated a relatively large document that I stored in a local variable as a nodeset. Now a nodeset in this context is really a DOM object stored in memory.

The thing is, I knew there were a stack of ACL's returned by my query that I could care less about, and really did not want to waste time processing.

Things like rights granted to WM:Workstation Registration. Turns out this tree had Zenworks 2.0 installed in it years ago, and in Zenworks 2.0 to register a workstation, the container object get granted W to itself on the attribute WM:Workstation Registration. This allowed the Zen workstation registration tool (wsreg32.exe was it?) to write a string with all the info about the workstation on the container. Then in NWAdmin (who remembers that anymore?) you could use a plugin to create Workstation objects from the registration info. Overall, this was an approach to the problem. Probably not a GOOD approach, in hindsight, but hey that was almost ten years ago now, so I will cut them some slack. (The problem was that you could easily get hundreds to thousands of registration values on a container object that had to be synchronized and really had no long term value. Also, even after you were done using it, old clients would continue populating the values onto the container object. At the same time, NDS (this was before they renamed it to eDirectory) had some issues with hugely multi valued attributes. Now a days it would not be a big deal. (Worth noting that Dsrepair still reports objects with over 1000 object references, even though it does not really matter that much today).

Anyway, every container in the tree had WM:Workstation Registration set, and normally that would be the type of ACL I would want to notice, except that it really does not matter to me. I considered looping through the nodeset in a for-each loop, and testing that it was not WM:Workstation Registration, but that turned out to take a reasonable amount of time. I.e. Looping through a few thousands nodes can take more time than you might like.

Then I had all the other default ACL's that I did care about like R to Login Script on every container. Again, that is one more test inside that loop through all the ACL values. More time.

Father Ramon from the Novell Support Forums (You do read and search the Forums when you need help, right? Of course you do! There is a web interface at http://forums.novell.com where you can login with your Novell ID that you use for opening incidents and downloading patches, and there is an NNTP interface at the same IP name. nntp://forums.novell.com and look for the novell.support.identity-manager.engine-drivers forum. Lots of excellent content there), suggested that I use do-strip-xpath against the local variable nodeset.

Thus for the local variable ACLS the line below accomplishes what I needed all by its lonesome.
<do-strip-xpath expression="$ACLS//value[(component[@name = 'protectedName'] = 'WM:Registered Workstation')]"/>

This is even more powerful when you realize that ACL is structured attribute with three components. If you want to set such a value, you need to provide all three components, like in this action:

<do-add-dest-attr-value class-name="User" name="ACL">
<arg-value type="structured">
<arg-component name="protectedName">
<token-text xml:space="preserve">CN</token-text>
</arg-component>
<arg-component name="trustee">
<token-text xml:space="preserve">\ACME\lab\users</token-text>
</arg-component>
<arg-component name="privileges">
<token-text xml:space="preserve">33</token-text>
</arg-component>
</arg-value>
</do-add-dest-attr-value>




The ACL is written to an object that the right is granted upon. I.e. If the ACL is on the Tree Root, then the right specified by the value of the ACL is being granted at the Tree Root level. (This could be very bad, if you are not careful! This right will potentially inherit all the way down the tree! So please think twice, then a third time if doing something like this at the Tree Root level.)

Then the component 'protectedName' is the attribute that the trustee is being granted for. This would be the Attribute name in the eDirectory schema. The example above is for CN, but any attribute is possible. There are some special ones like [All Attributes], [Entry Rights], \[Inheritance Mask] (for Inherited Rights Filters), and possibly others.

The component 'trustee' is the user who is being granted the ACL to the object the ACL is written on. So in our case, it looks like a container named users.lab in the ACME tree is getting the right to the CN attribute, on wherever our destination object happens to be. (The example is a simple snippet, so that detail is not specified, but it could be!)

Finally the 'privileges' component is a bit mask of the values you set in the NDS Rights tab in Console 1, or via iManager, (or via NWAdmin32 even! Any tool than can manipulate eDirectory ACL's can do this. Technically that is what our set destination attribute example above is doing as well). The mapping of the values depends if it is an Entry or Attribute right, but for simplicity in the Attribute case, 33 is 32 for Supervisor rights plus 1 for Compare rights.

Alas, you cannot query for components of a structured attributes, which also would have solved my problem.

So instead, what we do is strip by XPATH the expression:
$ACLS//value[(component[@name = 'protectedName'] = 'WM:Registered Workstation')]

$ACLS means in the context of the variable $ACLS (which is conveniently a nodeset), //value means find any occurrence of a value node, with the predicate of component[@name = 'protectedName'] = 'WM:Registered Workstation'). Within that predicate, look for the component named 'protectedName', whose value is WM:Registered Workstation.

So this removes all value nodes where the trustee of the ACL is WM:Registered Workstation. Cool.

Your variable goes in with hundreds or thousands of values, and in one line of DirXML Script, hundreds of values I want removed, are removed! Yay!

Then do it again for Login Script:
<do-strip-xpath expression="$ACLS//value[(component[@name = 'protectedName'] = 'Login Script')]"/>

Then do it again for SAS:Login Configuration and SAS:Login Configuration Key, and now your thousands of nodes in your nodeset is a much smaller set that when you loop through it, takes hugely less time!

Looking at the trace on a SUSE Linux box, running eDir 8.8.1 FT2, on modern server hardware, it took about 15 milliseconds to remove all the ACL's with a privileges value below 4 (which would remove values of 1, 2, or 3 which would be Read and Compare or just Read or just compare, it is a bit mask) and there were TONS of those. The rest of do-strips took between 1-5 milliseconds.

You just cannot run through a for-each loop that fast.

So for my example I did 6 do-strip-xpath operations, and it took about 19 milliseconds total.

My initial brute force approach took over 40 minutes looping through all the values and testing and doing much more work. This approach cut the total time down to under 2 minutes. That is pretty darn fine! (10,000 object tree with all sorts of weird historical ACL values set all over the place).

I never would have thought that a local variable nodeset can be treated in a similar way to the current document nodeset and tweaked this way, but boy did it work well!

Now that I have this hammer of do-strip-xpath, everything is starting to look like a nodeset nail... Every time I have a nodeset variable I keep trying to think of a way to use this on it somehow.

Labels:

How To-Best Practice
Comment List
  • in reply to MigrationDeletedUser
    Hey geoffc - I do want to remove - I generated a node set based off of a query to see if any one is using a particular email address - the problem, is that I get 'myself' back in the results - so I want to clean up the result set so that I don't see these -

    For example:
    Say Aaron Kynaston wants the email address of Bob.johnson@novell.com, so I do the query, and see that Aaron Kynaston, Sue Johnson, and Bobbert JOhnson already have the bob.johson@novell.com email; which would be represented as a result set of three instances. After I do the strip, or the resetting of the local variable, I should have only Sue and Bobbert in the result set.

    Does that make any sense?

    BTW: your articles are wicked awesome, I used them all the time - Thanks for your help!!
  • in reply to MigrationDeletedUser
    If Father Ramon says it, do it. :)

    Sure, best place to discuss is in the Support Forum. I post under this name there as well.

    I wonder if it is a context thing... Do you want to select? Or do you want to remove?

    I wanted to remove values from a nodeset. It is possible you wanted to select only certain nodes?
  • I can't get the do-strip-xpath to work on a variable - however, token-xpath on an xpath to a node-set variable, works fantastic - and has made my code much more readable -

    Thanks for the cool solution, but I'd love to talk more about do-strip-xpath on a variable - and how it should work.

    Thanks for your time writing this!!!
Related
Recommended