Walking through the Blackboard driver – Part 3

With the release of Identity Manager 4 Support Pack 1, (IDM 4.01) there were three new drivers added. I have previously written about the Google Apps drivers:

Series: Walking through the IDM 4 Google Apps Driver

In this series I want to do the same for the Blackboard driver.

I am collecting all these types of driver walk throughs on a Wiki page, and if you feel up to trying the same on your own, please let me know so I can link to it. I think this is an important thing we all need, but is unlikely to ever appear in the documentation.

In the first article I discussed the filter and some of the attributes in play.

In the second article I worked through some of the subscriber channel.


This driver does not actually have any placement rules which is interesting as often I have seen the engine kick out an event and veto it since no Destination DN was generated by the Placement policy. I wonder if that is specific to the Publisher channel, since eDirectory always requires some kind of placement, where as not every application feels the same way.

There are often applications with flat naming, like Unix or Linux with NIS or NIS where there is a totally flat name space and thus the concept of placement is irrelevant.

Databases can often be the same. Once you have a class mapped to a table, there really is no placement. The data is going where it is going, as a row in the table. Though the placement in a database is really schema, table, primary key, as you can see from the DirXML-Association value the JDBC driver will add on an object. But I think my point is, that since the class itself is mapped to the table, in the schema, all that remains is to name the object with its primary key value, since the rest is kind of set already and sort of flat.

Command Transform Policy Set

There are four policy objects here, so lets work through them.

  1. OBNDBKBDUE-sub-ctp-EntitlementProcessing

  • OBNDBKBDGBE-sub-ctp-TransformGorupAttrsToEnrollmentObjects

  • OBNDBKBDDC-sub-ctp-SetClassnameForGroups

  • OBNDBKBDDC-sub-ctp-TransformLoginDisabled

1. OBNDBKBDUE-sub-ctp-EntitlementProcessing

Handle Removed Entitlement

There is only one rule in this policy object, and for DirXML-BB-Person BBObjectType's, where the GCV use_entitlements is true, and the bbAccount entitlement is changing, then it runs the policies that implement the entitlements.

Now this is the second driver I have seen lately that does not use the Implement Entitlement token. I need to track down more information as to whether this token is deprecated or not. (The other driver is the AD driver, and I have a bug entered, so maybe I will find out that way, that you no longer need to use it? I am not sure what the story here is). Basically the Implement Entitlement is supposed to report results back to the engine and get the DirXML-EntitlementResult attribute updated.

Having said that, you will see some extra XML at the bottom of the <status> event, even when this token is not used, about the <entitle-impl> which may indicate at some point that the engine just got smart enough to do the Implement Entitlement process implicitly, anytime it sees a DirXML-EntitlementRef of relevance in the document. However I have not found clear confirmation of either of these approaches is preferred, so I will note that these guys do not use the Implement Entitlement token either.

If you were to use it, it wants a nodeset as input, which would be the current-node inside the two loops I will discuss in a moment) that is basically the nodeset containing the entitlement of interest, and then the actions would be to delete, disable, or enable the user as the rules then generate.

By wrapping it in the Implement Entitlement token it allowed you to clarify to the engine what event was the entitlements and thus what part has to succeed or fail to be logged.

Anyway, there are two loops, over the Removed Entitlement and Added Entitlement noun tokens. These list off all the changing entitlements in the document.

For each Removed entitlements, then inside the loop does a check of the GCV bbAccount_action and if it is set to disable_user, a DirXML-BB-p-row-status attribute is sent in as DISABLED. I suppose this ought to be mapped to Login Disabled in eDirectory. Otherwise, the assumption is that if not a disable user on entitlement being removed, you want to delete them.

In which case the destination object is deleted, and the current eDirectory objects Association value is removed. It is good practice to remember to clean up associations as you delete users. In this case it may not matter than much since there is no Publisher channel to generate a possible event on a user being added into Blackboard, but for later modifies on the Subscriber channel, if you were to decide to give them a Blackboard account again you would run into an issue. On the Publisher channel this would cause pain, as the Matching rule would likely find a either that the association is already in use (Actually that would be the Association processor, before the Match rule) and error or else get into the Match rule, find the correct matching user, and report it is already associated and error. None of these are good outcomes.

In the second loop over added entitlement, the DirXML-BB-p-row-status is set to ENABLED to reactivate the user.

2. OBNDBKBDGBE-sub-ctp-TransformGorupAttrsToEnrollmentObjects

Transform add and remove events on group attributes defined in the Group Attribute Map GCV

This is the only rule in the policy object and it is a somewhat interesting one. The Comment field explains that this changes Group attributes listed in the attribute_role_map GCV to DirXML-BB-Enrollment objects.

Looking at the GCV, I see that there are two values, Member and Owner. Member is mapped to Student as the Blackboard Role Name and Owner is mapped to Instructor. There are a number of possible Blackboard roles you could change those too, like:

  • Student

  • Instructor

  • Teaching Assistant

  • Grader

  • Course Builder

  • Guest

But it seems like this is generic for the whole driver, whereas most use case for those other roles seem like they would be very specific examples. That is, it seems like I might want some way to indicate that a class of groups, maybe based on a DN use a different mapping, than all the others. I suppose since this rule is what does, I could rework it to do what is needed. Might be nice if there were some better facility to allow this.

This fires on all groups, so as you can see, some kind of scoping based on either some other attribute or maybe container could be added pretty easily, I would think. Not sure how you generalize it yet.

The GCV attribute_role_map is stored into a nodeset variable so we can treat it like a nodeset and do some fun XPATH on the Structured GCV. You can read more about how much I like Structured GCVs in this article: Structured Global Configuration Values in IDM

Now in a Structured GCV, you define a template, and then as many <instance> nodes as needed for each instance of the GCV. That is, I can say my structured GCV shall consist of a integer GCV, a DN GCV, and a string. Then after I have defined and named everything, I click the sign button to add an instance. Then I add as many (or as few) as I require. But each one is an <instance> in the GCV XML.

Thus if you loop over the nodeset, you get one loop iteration per instance.

In this case, it is really just a matched pair of values that are Enum (Enumerated list) type GCV, and XPATH is used to get them into a variable for attrName and another for roleName. The XPATH looks like:

$current-node//definition[@display-name='Group Attribute Name']/value/text()

That means, within the current loop value (current-node variable, thus the leading $ to indicate it is a variable name) look for any (the // does a search and is frowned upon by most XPATH folk for being inefficient, but the XML in a GCV is sufficiently short that it ought not to matter too much) definition node, whose display-name XML attribute (the [] mean a predicate and thus a 'where', and the @class-name is used to select the XML attribute class-name as the condition of the predicate), and then get the text() of the value node under it.

Now they use a clever use of variable replacement in the If Operation Attribute condition token, where instead of specifying each possible attribute and having many such conditions, they use the $attrName$ notation to use the variable to provide the attribute name.

If it is changing (which means either an add or remove of a value, as opposed to available which would mean only an add of a value, and thus you can detect only remove cases, (if op attr changing and not available) with just two tests, which is much easier than the XPATH way I used to do it) then we loop over the Removed attribute values.

Now as it turns out they used the XPATH equivalent of the Removed Attribute noun token.

The XPATH is:

In fact I would argue that the use of current-op is gratuitous as well. As is the double use of // as an Policy and XPATH snob, (at least in the IDM context) I must take umbrage. (I will nag the developer, since he is a friend, though maybe NOT after this article) with a much simpler approach).

Anyway this uses the current-op attribute which is still a really freaky attribute to me, since if you want, you can clone the contents out to a variable to work on (cool!) but if you write changes (set local variable current-op) you can overwrite the current event itself! Which when you need to do it is great. I probably might have considered using this to replace the current event in my Salesforce.com driver with the SOAP call instead of adding it and then stripping the old one, had I thought about this approach back then. However, I still find this variable a little freaky to use.

Then it looks for the modify-attr node whose attr-name XML attribute is our $attrName variable value. Then for remove-value nodes, and gets the text() of the value node.

In reality, so long as the attributes are not structured, it would be sufficient to just use a simpler XPATH of:

However, I do not doubt that this works, it is just that the Removed Attribute token would work in a much simpler to read and comprehend fashion. Also, since its counterpart is Operation Attribute would also work on an <add> event, they could handle adds and modifies with no changes needed, at least to the loop portion. The XML for an <add> event looks different and the XPATH in my simplified example above would look more like:


They trace out the current-node value, with an XPATH of $current-node, of course, Local Variable noun token would do the same thing, but to level 5, and since most people run at level 3 at most, this would nicely NOT clutter up the trace. Nice to troubleshoot though.

Now, when working with DN references, since the Groups main attributes are DN's, that is Member and Owner, which refer to other objects by their DN's, but since we are looking at a remove-value case, it is possible that the Group had a value removed, because the user was deleted. In which case eDirectory would clean up and generate the event.

I suppose elsewhere a delete in eDirectory would be mapped to a delete in Blackboard, and thus no need to clean up anything else here.

However, since there is no easy way to tell from the current event if that is the case, they do a query into the source to see if the DN returns a value into a nodeset variable linkedUser, and they test with an XPATH of count($linkedUser//association/text()) = 0 which means that the resulting nodeset has an association node with a length of 0, then they veto the event.

First off, it turns out, the most efficient test here would just be an XPATH of $linkedUser which means that then nodeset has anything in it at all, else it would be false. No need to do any XPATH path selecting or counting of nodes, just test if the nodeset exists. However that is a quibble as unless you do this thousands of times every minute, you may never see any performance difference between the two approaches.

But I was actually thinking of the veto. It is within the realm of possibility that a replication event would send more than one member change, including one where the case of a user is deleted. I concede it is probably rare and unlikely, but if your replicas were set up right it could happen. I would probably just have wrapped the rest of them in this test and moved on to the next iteration of the loop, rather than vetoing, since that assumes on the one bad user is being cleaned up in the entire event, which I am not certain is guaranteed. I agree it is unlikely, but I am not certain it can never happen.

Anyway a variable doRemove is set to true, then another loop over the attribute_role_nodeset variable (Which is a copy of the GCV so we can XPATH on it), gets the attrNameForTest from the same basic XPATH as before for attrName, and userDN is set to $linkedUser/@src-dn, which is how you get the DN of an object you found in a search, you query for it into a nodeset, and the src-dn XML attribute is the DN.

If the attrNamefortest is the same as the DN, which is an interesting test, and the current attrName value is not the same as this inner loops iteration of the attrNameForTest, then they set doRemove to false. I get what the attrName comparison is for, i.e. only make sure the current attribute is being compared. (Though I am still not 100% on the reasoning here) but the userDN test I do not get? Perhaps, you can include specific users in the Role GCV to exclude them from this? Seems like an odd case, as this would be the full DN of a user, specified as an entry in that GCV.

So the only conditions where they do NOT remove the Enrollment object in Blackboard.

Thus if doRemove is still true, then they delete the destination object. They use the association value in the destination system that is quite interesting.

<do-delete-dest-object class-name="DirXML-BB-Enrollment" when="before">
<token-replace-first regex="[^-]*-" replace-with="">
<token-xpath expression="$linkedUser//association/text()"/>
<token-text xml:space="preserve">|enrolled_in|</token-text>
<token-replace-first regex="[^-]*-" replace-with="">

This reads to me that they remove everything that is not a dash (a Regular expression of [^-] means the set (the [] defines a set) of dashes, but negated (the ^ caret) symbol then any number of characters up to the next dash. So remove everything before the first dash in the User's association, which is read from the linkedUser variable, getting the association nodes text() value.

Then the string |enrolled in| and the same is done on the Groups association value, replace everything before the first dash with nulls. Which I had confirmed by one of the authors that the Association value for the Enrollment object is the user association, the literal string |enrolled in| then the group association. Thus they are able to contain the target, and the group in one association value. Clever.

Now having looped over the <remove-values> nodes (to see which users had been removed from the course) the rule loops over the <add-value> nodes. Thus we check who was added to the course in this event.

Using a Query for the specified user object against eDirectory to make sure it exists (I guess this assumes it is possible to use a non-DN syntax attribute in the attribute map GCV. If there is no such user, then do nothing. If there is, where the linkedUser variable not equal to null, there is some work to do.

Now I actually think that if you sent in a remove 2 values, add 3 values event for this object, and one of those three add values was not a real user you could sneak past this rule. That is because linkedUser would be initialized and have a value in it from the remove processing. But I concede that is probably a pretty rare case to worry about.

Next up is to check if the enrollment already exists, and if so convert to a modify, or at least that is what the inline comment, using a Trace token suggests. To do that a variable enrollmentObject is set to a query the destination, looking for values in the DirXML-BB-enr-c-ext-key which is basically the Group objects association modified with a Replace First that is the regular expression of:

This means replace the first group of non-dash characters, up to the first dash with nothing.

That is, 123-123abcdfhfhfh as a value would have the leading 123- removed from it. I am not entirely sure why this is, but that is pretty consistent in their code.

Then the enrollment object also has to match with a DirXML-BB-enr-p-ext-key with the Users association value, with the same Regular Expression applied. They get the association value with an XPATH from the linkedUser variable at the beginning of the loop. The XPATH is:


Now we have a big fork, depending on if we find an enrollment object and thus this is a modify, or not and this is an add event.

First up is the add, when the enrollment object does not exist.

Add the destination object, DirXML-BB-Enrollment as the class, using the Source DN of this object as the destination DN, and use the when = before, to make it happen before this event.

Set the DirXML-BB-enr-c-ext-key value to the association of this object with the leading stuff before the first dash removed via Regular Expression as above.

Set the DirXML-BB-enr-p-ext-key to the association of the user processed the same way via Regular Expression.

The DirXML-BB-enr-role-name is set from the roleName variable set earlier in this rule.

Then we enable the enrollment by setting the DirXML-BB-enr-row-status to ENABLED.

If this was a modify event (i.e. An existing Enrollment object was found in BlackBoard via the query) then the same four attributes are modified using the same values. Just the target of the change is done by the Association of the current event.

Finally the Attribute we have been looking at this initial loop over the GCV schema mapping table, is stripped from the event document, since we of course have since mapped it in a way the schema map rule is not quite capable of handling.

The end result of all of that is when a Group object changes, we manage the Enrollment objects in Blackboard to match the changes.

3. OBNDBKBDDC-sub-ctp-SetClassnameForGroups

There are two rules here, both simple. If the class name is not DirXML-BB-Enrollment (recall that in the previous rule we either added a modify or add of a DirXML-BB-Enrollment object, as a secondary event and this following rule will process the original Group modify, and then the added DirXML-BB-Enrollment event separately) and the BBObjectType op property is set to DirXML-BB-Course then the operation class is set to DirXML-BB-Course. This is a pretty cool token, and you can use it to morph the class of the current event. I am pretty sure that Set XML Attribute @class-name to some value would do the same, but nicer having a token to do it specifically.

Then for Organizations, where the BBObjectType is DirXML-BB-Organization, set the operation class to match.

4. OBNDBKBDDC-sub-ctp-TransformLoginDisabled

Transform Login Disabled to values for DirXML-BB-p-row-status and DirXML-BB-p-available-ind

This is the only rule, and serves to manage the fact that a disabled or enabled user object in Blackboard has two attributes that indicate active or disabled. DirXML-BB-p-row-status (which groups and enrollments seem to share as well) and DirXML-BB-p-available-ind.

So if Login Disabled is being set, change both these values, if it is clear or being cleared, then set them both to the enabled version.

Output Transform Policy Set:


This policy object has four rules.

  1. Veto if required attributes not available for DirXML-BB-Person

  • Veto if required attributes not available for DirXML-BB-Course

  • Veto if required attributes not available for DirXML-BB-Organization

  • Veto if required attributes not available for DirXML-BB-Enrollment

All four basically do the same thing. They basically do what would normally be done in the Create Policy and look to make sure all the mandatory attributes made it in. Now some of these are eDirectory attributes mapped like Given Name mapped to DirXML-BB-p-firstname and could be trapped in the Create policy. But others are calculated like DirXMl-BB-p-ext-key.

I probably would not have done it this way, but this is meant to avoid unnecessary errors on sending an event to the shim.

Input Transform Policy Set:


This rule simply vetoes the case where a Group on the Subscriber channel has a member and this spawns an event for creating a DirXML-BB-Enrollment object, which is basically a glue object to link the user to a course. (It would appear there will be many of these such objects since each user gets one for each course they are enrolled in.

But this is not an object mapped back into eDirectory, rather it is calculated.

Therefore after a successful add, there will be an <add-association> event sent for it, and that needs to be blocked since it would probably come with the target DN of the group that spawned the event, and thus a course with 30 students would potentially thus try to add 30 associations from each Enrollment that would generate, which of course will not work in IDM. The engine will have a minor fit if you try it. Thus this event is caught here and vetoed. When I say the engine will have a minor fit, I wonder what it would really do. On the object side, if you add multiple Associations for a driver on a single object, then any following events, generate one event per association. That is, if you had 5 associations to this driver on a User, and you modified an attribute, you would get 5 identical modify events, each with one of the five association values.

What is interesting is they let the shim send back a Destination DN (dest-dn) of pseudo-enrollment-object and use that to detect the event to veto. As opposed to just hard coding the shim not to send back an add-association for it. I suppose this allows some flexibility if you decided you did want to handle this case with your own object, you could modify the policies to allow it.

That is about it policy wise. You can see much of the approach taken in terms of handling events for this driver.

There is no use of the standard password manipulation rules to call a <modify-password> event, instead nspmDistributionPassword is mapped straight to the password attribute in BlackBoard. There is a sort of Pseudo object to track course enrollments using the DirXML-BB-Enrollment object, that ultimately is a representation of Group Membership, expanded to be an object (I suppose column in the database really) for each enrollment for each user.

There are Users, Courses, and Organizations as basic standard objects.

The filter has a lot more attributes disabled than are used in the policy, making it relatively easy, if you wanted to send more data into BlackBoard than the basic stuff you can easily do so. The Omnibond guys are good about mapping all the possible attributes. They also do the AS400, mainframe (Top Secret, ACF2, RACF) and Unix drivers. If you look at Schema, you can see they added all the AS400 attributes (starting with DirXML-i5os and man there are a bunch of them) and they did the same for BlackBoard. Alas the downside is a cluttered schema but hey, schema is cheap. Not like it uses up a lot of space. Of course they all start with DirXML-BB so it is easy to ignore them if you are not interested.

That about wraps up this driver. Hope you enjoyed. I have many more driver walk throughs completed, and in the works, so please let me know if this is helpful or if you have a particular driver you would like reviewed this way next.


How To-Best Practice
Comment List