Novell is now a part of Micro Focus

Advanced Features of DirXML, Part 2: Channel Write-Back

Articles and Tips: article

Israel Forst
Net Solutions Support
Novell, Inc.
iforst@novell.com

Hicham Mourad
Net Solutions Support
Novell, Inc.
hmourad@novell.com

Garth Williamson
Net Solutions Support
Novell, Inc.
gwilliamson@novell.com

01 Aug 2002


Special thanks to Perrin Blanchard and Shon Vella of Novell for their comments and suggestions.

One of the more advanced features of Novell's DirXML is the Channel Write- Back feature, which enables you to send commands from a rule to either eDirectory or an application connected to DirXML, regardless of whether the Subscriber or Publisher Channel is used. This AppNote takes you through the basics of performing Channel Write-Back from a stylesheet.


Topics

DirXML, XSLT stylesheets, directory-enabled applications

Products

DirXML 1.1

Audience

application developers

Level

intermediate

Prerequisite Skills

familiarity with XML and XSLT stylesheets

Operating System

n/a

Tools

none

Sample Code

yes

Introduction

Many people have been introduced to DirXML and implemented it in some fashion. However, some have not been exposed to many of the advanced features of DirXML. This AppNote is the second in a series that will describe some of these advanced features in depth. The focus of this series is on DirXML 1.1, as many enhancements have been introduced in that version. However, we will also reference DirXML 1.0 when appropriate.

The first AppNote in this series discussed queries. This AppNote will focus on the Channel Write-Back feature of DirXML. This feature is used to send commands from a rule on either the Subscriber or Publisher Channel to eDirectory or to the application connected to DirXML. We will refer to an application connected to DirXML as the remote datastore.

Overview of Channel Write-Back

One of the challenges of DirXML 1.0 was the inability to affect changes on the datastore that generated an event. Typically, if an event is generated by eDirectory, DirXML will take that event and send it to the remote datastore along the Subscriber Channel. Various rules and stylesheets can be applied to that event to transform it as required by the business logic. However, the event that was generated by eDirectory cannot trigger changes in eDirectory since the Subscriber Channel is used exclusively for events directed to the remote datastore. Channel Write-Back provides the ability to make changes to any datastore, regardless of which channel triggered the event.

Here's a simple example of when Channel Write-Back may be used. Suppose you are synchronizing an eDirectory tree with an Active Directory Domain and your business logic requires that each User object in eDirectory list the Distinguished Name (DN) of the corresponding object in Active Directory. When a user is created in eDirectory, an event will be generated and processed by the Subscriber Channel. The Subscriber Channel placement rule will determine the proper placement in Active Directory.

Once the placement rule is executed and proper placement is determined, the "dest-dn" attribute will contain the DN of the user in Active Directory. You would then need to capture the DN generated by the placement rule and write it back to eDirectory. Using DirXML 1.0, this is not a simple task because DirXML 1.0 provides no mechanism to write information back to the source datastore. But with DirXML 1.1, this can be easily accomplished using the Channel Write-Back feature. You can instruct the Placement Rule to write the DN back to eDirectory before submitting the new user to Active Directory for creation.

The Channel Write-Back feature was introduced in DirXML 1.1 and is not available in DirXML 1.0.

Two Types of Channel Write-Back

There are two types of Channel Write-Back: the type that can be used in a Simplified Create Rule, and the type that can be used in any stylesheet that uses XSLT.

Note: While you cannot use Channel Write-Back in Simplified Rules other than the Create Rule, you can use Rule Chaining to call an XSLT stylesheet to perform Channel Write-Back after a Simplified Rule is executed. Rule Chaining will be discussed in a later AppNote.

Create Rule Channel Write-Back

As you may already know, a Simplified Create Rule can assign default values to attributes that have no values (see http://developer.novell.com/ndk/doc/dirxml/dirxmlbk/api/ndsdtd/required-attr.html for details). In DirXML 1.0, a default value can be written to the destination datastore. However, that default value was never updated in the source datastore, leaving the datastores out of sync. With DirXML 1.1, you can configure the Simplified Create Rule to update the source datastore with the default value using Channel Write-Back.

Any existing Create Rule can be modified to support Channel Write-Back by simply adding a write-back="true" attribute to the <required-attr> element. For example, the following Create Rule will assign a default value of "Employee" to the EmpType attribute, if there is no value present:

<required-attr attr-name="EmpType">

        <value>Employee</value>

    </required-attr>

To instruct the Create Rule to update the source datastore with the default value, simply modify the create rule as follows:

<required-attr attr-name="EmpType" write-back="true">

        <value>Employee</value>

    </required-attr>

With this modification, the Create Rule will update the source datastore any time it applies a default value to the EmpType attribute.

XSLT Channel Write-Back

Using the eDirectory/Active Directory example mentioned above, let's illustrate how this DN synchronization can be accomplished. We will take a standard Placement stylesheet and modify it to support Channel Write-Back. (You should recognize many of the changes as being similar to those made to support queries.)

The first changes you need to make to the stylesheet (or add to a new stylesheet if one does not already exist) are to declare the Command Name Space, and to declare two parameters (these items will be explained in detail later on). Typically, the <xsl:stylesheet> element will look something like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

To use Channel Write-Back in a stylesheet, the <xsl:stylesheet> element's start tag should look like this:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:cmd="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.

XdsCommandProcessor" >

The following "stylesheet parameter" declarations should also be added:

<xsl:param name="srcCommandProcessor"/>

    <xsl:param name="destCommandProcessor"/>

Once these changes are in place, you can now construct your <xsl:template>. Since you want to write back information every time a user is added, start your <xsl:template> with the following line, which will match on every Add event to User objects:

<xsl:template match="add[@class-name='User']">

Since this is a Placement stylesheet, you must determine the correct User Placement. This is done in three steps.

First, extract the user's CN from the DN string using the template named get-cn-from-dn and store the CN in an <xsl:variable> called $user-cn. Here's how to do that:

<xsl:variable name="user-cn">

        <xsl:call-template name="get-cn-from-dn">

            <xsl:with-param name="src-dn" select="@src-dn"/>

        </xsl:call-template>

    </xsl:variable>

This code calls the following template to extract the CN from the CN:

<xsl:template name="get-cn-from-dn" xmlns:jstring="http://www.novell.com/

            nxsl/java/java.lang.String">

<xsl:param name="src-dn"/>

        <xsl:variable name="dn" select="jstring:new($src-dn)"/>

        <xsl:variable name="index" select="jstring:lastIndexOf($dn,'\')"/>

        <xsl:if test="$index != -1">

            <xsl:value-of select="jstring:substring($dn,$index + 1)"/>

        </xsl:if>

</xsl:template>

Next, determine where to place the user by using some simple logic based on the source container. Store the placement information in an <xsl:variable> named $dest-dn for use later in the stylesheet. Here's how to do this step:

<xsl:variable name="dest-dn">

        <xsl:choose>

            <xsl:when test="starts-with(@src-dn,'\ACME-TREE\ACME\US\')">

                <xsl:value-of select="concat('CN=', $user-cn, ',OU=USA,DC=AD,DC=ACME,

                        DC=COM')"/>

            </xsl:when>

            <xsl:when test="starts-with(@src-dn,'\ACME-TREE\ACME\CA\')">

                <xsl:value-of select="concat('CN=', $user-cn, ',OU=CAN,DC=AD,DC=ACME,

                        DC=COM')"/>

            </xsl:when>

        </xsl:choose>

    </xsl:variable>

Lastly, copy the Add element to the result tree and insert a new attribute called dest-dn which indicates where the user should be placed in Active Directory:

<xsl:copy>

        <xsl:attribute name="dest-dn">

            <xsl:value-of select="$dest-dn"/>

        </xsl:attribute>

        <xsl:apply-templates select="@*|node()"/>

    </xsl:copy>

At this point, the placement portion of your stylesheet is complete. Now you can focus on the Channel Write-Back portion.

First, you must generate an XDS fragment that contains the modification you want to apply to eDirectory. Store that XDS fragment in an <xsl:variable> named $cmd-update:

<xsl:variable name="cmd-update">

        <modify class-name="User" dest-dn="{@src-dn}">

            <modify-attr attr-name="description">

                <add-value>

                    <xsl:value-of select="concat('The DN in Active Directory is ', $dest-dn)"/>

                </add-value>

            </modify-attr>

        </modify>

    </xsl:variable>

Once the XDS fragment document is generated, it is sent to the Command Processor using the following command (this command will be explained in detail later in this AppNote):

<xsl:variable name="results" select="cmd:execute($srcCommandProcessor, $cmd-update)"/>

Here is the stylesheet in its entirety:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:cmd="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.XdsCommandProcessor">



    <!--These are the two parameters that are needed for Channel Write-Back -->

    <xsl:param name="srcCommandProcessor"/>

    <xsl:param name="destCommandProcessor"/>



    <!-- This is the identity template that copies otherwise unmatched items to the result -->

    <xsl:template match="node()|@*">

        <xsl:copy>

            <xsl:apply-templates select="@*|node()"/>

        </xsl:copy>

    </xsl:template>

    

    <!-- This template matches on all User Adds and writes the Dest-DN to the user in the Source Tree -->

    <xsl:template match="add[@class-name='User']">

        <!-- Here you call a template to extract the User CN from the Source DN -->

        <xsl:variable name="user-cn">

            <xsl:call-template name="get-cn-from-dn">

                <xsl:with-param name="src-dn" select="@src-dn"/>

            </xsl:call-template>

        </xsl:variable>

    

        <!-- Here you peform some simple placement logic to generate the Dest-DN and

        store it in a variable -->

        <xsl:variable name="dest-dn">

            <xsl:choose>

                <xsl:when test="starts-with(@src-dn,'\ACME-TREE\ACME\US\')">

                    <!-- Place US Users in the USA Container -->

                    <xsl:value-of select="concat('CN=', $user-cn, ',OU=USA,DC=AD,DC=ACME,DC=COM')"/>

                </xsl:when>

                    <xsl:when test="starts-with(@src-dn,'\ACME-TREE\ACME\CA\')">

                    <!-- Place CA Users in the CAN Container -->

                    <xsl:value-of select="concat('CN=', $user-cn, ',OU=CAN,DC=AD,DC=ACME,

                            DC=COM')"/>

                </xsl:when>

            </xsl:choose>

        </xsl:variable>

        

        <!--Here you take the dest-dn variable and copy it to the dest-dn attribute of the Add element -->

        <xsl:copy>

            <xsl:attribute name="dest-dn">

                <xsl:value-of select="$dest-dn"/>

            </xsl:attribute>

            <!-- Here you copy the rest of the Add element -->

            <xsl:apply-templates select="@*|node()"/>

        </xsl:copy>

    

        <!--Now you write the dest-dn variable back to the description field in the Source Datastore -->

        <!-- First generate the XDS fragment with the modification -->

        <xsl:variable name="cmd-update">

            <modify class-name="User" dest-dn="{@src-dn}">

                <modify-attr attr-name="description">

                    <add-value>

                        <xsl:value-of select="concat('The DN in Active Directory is ', $dest-dn)"/>

                    </add-value>

                </modify-attr>

            </modify>

        </xsl:variable>



        <!-- Now send the XDS fragment generated above to the Src Directory  -->

        <xsl:variable name="results" select="cmd:execute($srcCommandProcessor, $cmd-update)"/> 

    </xsl:template>

    

    <!--This Template is used to extract the CN from a DN-->

    <xsl:template name="get-cn-from-dn" xmlns:jstring="http://www.novell.com/nxsl/java/java.lang.

            String">

        <xsl:param name="src-dn"/>

        <!-- use java string stuff to make this much easier -->

        <xsl:variable name="dn" select="jstring:new($src-dn)"/>

        <xsl:variable name="index" select="jstring:lastIndexOf($dn,'\')"/>

        <xsl:if test="$index != -1">

            <xsl:value-of select="jstring:substring($dn,$index + 1)"/>

        </xsl:if>

    </xsl:template>

</xsl:stylesheet>

XSLT Channel Write-Back In-Depth

Now that you've seen a simple example of XSLT Channel Write-Back, let's dig in deeper and understand how it all works. There are four items that Channel Write-Back uses in any stylesheet:

  • Name Space Declarations

  • Parameter Declarations

  • XDS Fragment Document

  • Channel Write-Back Execution

Name Space Declaration

The first item you need to add is a name space declaration that instructs Novell's XSLT processor to bind the prefix "cmd" to a Java class that implements the Channel Write-Back functionality. Note that the use of Java functions in a stylesheet is not part of the standard XSLT specification. We won't discuss the details of how it works in this AppNote; suffice it to say that the following line accomplishes what is needed:

xmlns:cmd="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.

XdsCommandProcessor"

Typically, the <xsl:stylesheet> element's start tag will look something like this:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

To enable Channel Write-Back in a stylesheet, the <xsl:stylesheet> element's start tag should look like this:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:cmd="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.

XdsCommandProcessor" >

Note that in terms of DirXML, the <xsl:stylesheet> and <xsl:transform> are identical and can be used interchangeably.

Parameter Declaration

In addition to adding the name space declaration, two stylesheet parameters must be declared:

  • srcCommandProcessor

  • destCommandyProcessor

These two parameters are passed by the DirXML engine to the stylesheet. They are used to allow the stylesheet to specify which datastore should be written back to (modified). The terms "source" and "destination" are used in context of the origin of the event. In other words, if you need to query the datastore that generated the event, you send the XDS Fragment query to the srcCommand- Processor. If you need to modify the datastore that will be receiving the event, you send the XDS Fragment to the destCommandProcessor.

Here's a hint that might help you. Since the Publisher Channel is always used for events coming into eDirectory, the destination datastore will always be eDirectory. Therefore, the destCommandProcessor is used to modify eDirectory on the Publisher Channel. Conversely, since the Subscriber Channel is always used for events coming from eDirectory, the source datastore will always be eDirectory so the srcCommandProcessor is used to modify eDirectory.

These two parameters should be declared towards the top of your stylesheet, after the <xsl:stylesheet> element. While it is only necessary to declare the parameters that are actually used, it's good practice to declare them both:

<xsl:param name="srcCommandProcessor"/>

<xsl:param name="destCommandProcessor"/>

The XDS Fragment Document

Now that you've properly prepared your stylesheet for Channel Write-Back, you can now delve into the actual Channel Write-Back itself. The structure of the XDS Fragment document is the same as any XDS document. This means it must comply with the NDS.DTD. The full details of the XDS Document structure can be found in the NDS.DTD file located at http://developer.novell.com/ndk/doc/dirxml/dirxmlbk/api/ndsdtd/query.html.

Once the XDS Fragment Document is constructed, it is typically stored in an <xsl:variable> so that it can be passed to the Command Processor for execution.

Sample XDS Fragment Documents

Below are some sample XDS Fragment Documents to better illustrate how to properly construct such a document.

Sample 1. This is a document to modify a user's Description attribute.

<modify class-name="User" dest-dn="\AMCE\Users\Sam">

        <association>{BC3E7155-CDF9-d311-9846}</association>

        <modify-attr attr-name="Description">

            <remove-all-values/>

            <add-value>

                <value>This is a new Description</value>

            </add-value>

        </modify-attr>

    </modify>

Sample 2. This is a document to remove an object's association.

<remove-association>{BC3E7155-CDF9-d311-9846}</remove-association>

Sample 3. This is a document to add an Alias to an object.

<add class-name="Alias" dest-dn="\ACME\Aliases\Sam">

        <add-attr attr-name="Aliased Object Name">

            <value type="dn">

                <xsl:value-of select="\ACME\Users\Sam"/>

            </value>

        </add-attr>

    </add>

Sample 4. This is a document to delete an object.

<delete class-name="User" src-dn="\ACME\Users\Sam">

        <association>{BC3E7155-CDF9-d311-9846}</association>

    </delete>

The Channel Write-Back Execution

Once you've properly constructed your XDS Fragment Document and stored it in an <xsl:variable> called $cmd-Update, you need to submit it to the Command Processor for execution. There are a few components to the execution:

  • The name of the variable that will contain the results (unlike with queries, the result variable is never used after execution; it is used to execute the command)

  • The cmd: prefix to indicate that this command will be handled by the Command Processor as declared in the Name Space Declaration

  • A reference to either the srcCommandProcessor or the destCommand- Processor to instruct the DirXML engine as to which datastore the XDS Fragment should be applied to

  • A reference to the variable in which the XDS Fragment Document is stored

When you put these elements together, you get the following command:

<xsl:variable name="result" select="cmd:execute($srcCommandProcessor, $cmd-Update)"/>

$result is the <xsl:variable> you will store the Channel Write-Back results in. This variable has no function once the command is executed.

Using Channel Write-Back with Queries

While Channel Write-Back is a powerful feature in its own right, combining it with queries provides extremely powerful functionality. To illustrate this point, let's describe a typical problem encountered when trying to enforce Data Authority with DirXML. Data Authority is when one datastore "owns" a particular attribute or set of attributes.

A simple example is connecting an eDirectory tree to an e-mail system. The e-mail system should own the e-mail addresses. If an e-mail address is updated in eDirectory, that change should not be replicated to the e-mail system. This is typically enforced by removing "Email Address" from the Subscriber Channel filter, which would prevent eDirectory from sending any modifications to the Email Address down the Subscriber Channel to the e-mail system. While this protects the e-mail system from receiving incorrect e-mail addresses, the eDirectory tree still contains an incorrect e-mail address.

Prior to Channel Write-Back, there was little you could do to correct this situation. With Channel Write-Back, you can easily correct the e-mail address in eDirectory if you can determine what the correct address should be. This is where queries come in. If you can query the e-mail system to determine what the correct e-mail address is, you can use Channel Write-Back to correct the e-mail address in eDirectory. Let's build a stylesheet that performs this functionality.

First, you must decide where to place this stylesheet. Since you only want to match on Modifications, not Adds or Synthesized Adds, the best place would be in the Command Transformation stylesheet on the Subscriber Channel. In addition, unlike the traditional methods of Data Authority, this method requires that the filters allow modifications to flow through so that your stylesheet can detect and act upon those modifications. The stylesheet will never pass the modifications to the e-mail system, but simply write the modification back to the original datastore with the correct data and ensure data integrity.

The first changes you need to make to your stylesheet (or add to a new stylesheet if one does not already exist) are to declare the Command Name Space, the Query Name Spaces, and four parameters.

Typically, the <xsl:stylesheet> element will look something like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

To use Channel Write-Back and queries in a stylesheet, the <xsl:stylesheet> element's start tag should look like this:

<xsl:stylesheet version="1.0"  exclude-result-prefixes="query cmd"

xmlns:cmd="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.

        XdsCommandProcessor" 

xmlns:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.

        XdsQueryProcessor" 

xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

The following "stylesheet parameter" declarations should also be added:

<xsl:param name="srcCommandProcessor"/>

    <xsl:param name="destCommandProcessor"/>

    <xsl:param name="srcQueryProcessor"/>

    <xsl:param name="destQueryProcessor"/>

Once these changes are in place, you can construct your <xsl:template>. Since you want to write back every time an e-mail address is modified, start your <xsl:template> with the following line:

<xsl:template match="modify-attr[@attr-name='Internet Email Address']">

Once you've detected that Email Address was modified, you will generate a query to read the correct value from the e-mail system (the destQueryProcessor is used):

<xsl:variable name="query"> 

        <query class-name="User" scope="entry">

            <association><xsl:value-of select="../association"/></association>

            <read-attr attr-name="Internet Email Address"/>

        </query>

    </xsl:variable>



    <!-- Execute the Query and store the results in a variable called $result -->

    <xsl:variable name="result" select="query:query($destQueryProcessor,$query)"/>

After the query is executed and the results are stored in the <xsl:variable> $result, select the first instance that was found (in case multiple instances were found) and store the first instance in an <xsl:variable> named $instance:

<xsl:variable name="instance" select="$result//instance[1]"/>

Once you have the correct value(s), generate the XDS Fragment that will correct the attribute in eDirectory:

<xsl:variable name="cmd-update">

        <modify class-name="User">

            <association><xsl:value-of select="../association"/></association>

            <modify-attr attr-name="Internet Email Address">

                <remove-all-values/>

                <add-value>

                    <!-- Copy the value elements returned in the query. 

                    If the attribute is multi-valued, all values will be copied -->

                    <xsl:copy-of select="$instance//value"/>

                </add-value>

            </modify-attr>

        </modify>

</xsl:variable>

Lastly, you must send the XDS Fragment to eDirectory (srcCommandProcessor is used):

<xsl:variable name="results" select="cmd:execute($srcCommandProcessor, $cmd-update)"/>

You'll notice that there is no <xsl:copy>or <xsl:apply-templates> in this template, so the modification made in eDirectory is never copied to the e-mail system. It is dropped from the Subscriber Channel and corrected in eDirectory using Channel Write-Back.

Here is the stylesheet in its entirety.

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"  exclude-result-prefixes="query cmd"

xmlns:cmd="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.XdsCommandProcessor" 

xmlns:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.XdsQueryProcessor" 

xmlns:xsl="http://www.w3.org/1999/XSL/Transform">



    <xsl:strip-space elements="*"/>

    <xsl:preserve-space elements="association add-association remove-association value component password check-password"/>

    <xsl:output indent="yes" method="xml"/>

    

    <xsl:param name="srcCommandProcessor"/>

    <xsl:param name="destCommandProcessor"/>

    <xsl:param name="srcQueryProcessor"/>

    <xsl:param name="destQueryProcessor"/>



    <!-- identity transform for everything we don't want to mess with -->

    <xsl:template match="node()|@*">

        <xsl:copy>

            <xsl:apply-templates select="@*|node()"/>

        </xsl:copy>

    </xsl:template>



    <xsl:template match="modify-attr[@attr-name='Internet Email Address']">

        <!-- Query the destination directory for the origional value-->        

        <xsl:variable name="query"> 

            <query class-name="User" scope="entry">

                <association><xsl:value-of select="../association"/></association>

                <read-attr attr-name="Internet Email Address"/>

            </query>

        </xsl:variable>



        <!-- Execute the Query and store the results in a variable called $result -->

        <xsl:variable name="result" select="query:query($destQueryProcessor,$query)"/>



        <!-- Store the first instance in a variable called instance -->

        <xsl:variable name="instance" select="$result//instance[1]"/>



        <!-- Generate the XDS fragment that will correct the changed value with the original value -->

        <xsl:variable name="cmd-update">

            <modify class-name="User">

                <association><xsl:value-of select="../association"/></association>

                <modify-attr attr-name="Internet Email Address">

                    <remove-all-values/>

                    <add-value>

                        <!-- Copy the value elements returned in the query. 

                        If the attribute is multi-valued, all values will be copied -->

                        <xsl:copy-of select="$instance//value"/>

                    </add-value>

                </modify-attr>

            </modify>

        </xsl:variable>

        <!-- Send the XDS fragment generated above to the Src Directory  -->

        <xsl:variable name="results" select="cmd:execute($srcCommandProcessor, $cmd-update)"/>

    </xsl:template>    

</xsl:stylesheet>

Conclusion

Hopefully, this AppNote has provided some insight into how Channel Write-Back can be used inside DirXML 1.1 stylesheets and how to leverage queries together with Channel Write-Back.

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates