Friday 17 July 2015

FIM2010: Writing Advanced Attribute Flows

Intro

Once in a while you will come across very complex business requirements while implementing FIM in a large environment. These requirements often require a classic architecture (with VB or C# extensions), but can create very messy code that is hard to maintain. This article does not start another discussion on whether or not you should (try to) use 100% declarative (codeless) or a classic architecture when implementing such large scenarios. A good article on this topic: codeless architecture and when you are not able to use declarative configuration. Instead, this article will focus on how you should implement a proper classic architecture, in a way that is performant, readable, agile and easy to maintain.

Advanced flow rules

A basic map attributes for import method is written as follows:
void IMASynchronization.MapAttributesForImport(
  string flowRuleName, CSEntry csentry, MVEntry mventry)
{
    switch (flowRuleName)
    {
        case "SomeFlowRuleName":
            {
                // Some code ...
            }
            break;
        default:
            {
                throw new EntryPointNotImplementedException(
                   string.Concat("Flow rule name not found: ", 
                   flowRuleName));
            }
    }
}
Imagine you have 12 advanced import rules, which is not that many in a big environment. A rule has an average of 16 lines of code. Because you have already 4 lines of code for each case statement, you get a method of 240 lines. You could create a method for each case statement. That way the switch statement becomes more readable. What if we want to take this one step further? That is where reflection comes into place.

Reflection

Reflection is a programming concept that enables you to inspect (or even change) source code. You can apply reflection on a class itself, but also on other classes. Reflection enables you to use the same implementation of the method MapAttributesForImport in all your rules extensions. Similar code can also be used for MapAttributesForExport, MapAttributesForJoin and ResolveJoinSearch. Each attribute flow is implemented in its own method, eg importMustChangePassword. By using a simple naming convention, import/export target attribute name, readability is very good. Because each attribute can only be target of one export attribute flow, this convention also ensures uniqueness.

Code using reflection

First line constructs an array of objects. These are the parameters that will be passed to our advanced attribute flow method. Second line constructs a BindingFlags object. This object determines what kind of method we will call. The binding flags describes the method signature. InvokeMethod indicates we will not call a constructor. NonPublic indicates the access modifier: we will call a private, internal or protected method. Instance specifies that instance members are to be included in the search.

The third line does the actual call. It will invoke the method on the current class. The reflection mechanism will search in the current class for methods matching the flowRuleName, bindingFlags and the number and type of parameters. If one and only one match is found, the method is invoked.

void IMASynchronization.MapAttributesForImport(string flowRuleName, 
       CSEntry csentry, MVEntry mventry)
{
    try
    {
        object[] parameters = { mventry, csentry };
        BindingFlags bindingFlags = BindingFlags.InvokeMethod | 
            BindingFlags.NonPublic | BindingFlags.Instance;
        this.GetType().InvokeMember(flowRuleName, bindingFlags, 
            null, this, parameters);
    }
    catch (MissingMethodException)
    {
        throw new EntryPointNotImplementedException(
          string.Concat("Flow rule name not found: ", 
          flowRuleName));
    }
}

Example attribure flow rule

An example to illustrate the mechanism is this rule to set a flag on the metaverse person object whether he has to change his password.
/// Imports the must change password flag. 
/// This flag is true if the pwdLastSet timestamp
/// attribute is set to 0.
/// </summary>
/// <param name="mventry">Destination metaverse entry.</param>
/// <param name="csentry">Source connector space entry.</param>
private void importMustChangePassword(MVEntry mventry, CSEntry csentry)
{
    if (csentry["pwdLastSet"].IsPresent)
    {
        mventry["mustChangePassword"].BooleanValue = 
            csentry["pwdLastSet"].IntegerValue == 0;
    }
}

References