Novell is now a part of Micro Focus

Advanced Features of DirXML, Part 1: Queries

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 Jul 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 query feature, which enables you to search either Novell eDirectory or the application connected to DirXML for information that meets certain criteria. This AppNote takes you through the basics of performing a query from a stylesheet and leveraging the information returned in the business logic built into the stylesheet.
Topics DirXML, XSLT stylesheets, directory-enabled applications
Products DirXML 1.0, 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 first 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.

This AppNote will focus specifically on the query feature of DirXML. It will first examine a simple query, then look at some more complex queries.

Overview of DirXML Queries

The query feature enables DirXML to search either eDirectory or the application connected to DirXML. We will refer to the application connected to DirXML as the remote datastore. The queries can be performed from any stylesheet and leverage the information returned in the business logic built into the stylesheet.

Why would you need to query from within a stylesheet? When an event is triggered by a modification to an object in eDirectory, that event is sent through the DirXML engine for processing. Based on the rules and stylesheets, DirXML will transform that event and pass it on to the remote datastore. However, often the business logic is dependent upon information external to this modification. For example, suppose your DirXML driver replicates Employees but not Contractors. If both Employees and Contractors are represented with User objects, the driver rules must filter based on an attribute value. Each time a User object is modified, a stylesheet may need to query the "EmpType" attribute of that object to determine whether DirXML should process that event.

Another example is to use the query feature in a Create Stylesheet to determine if the name of the User object you are about to create is unique the target datastore. If that username is already in use, DirXML can try some other variants of the name to find a unique username. Clearly, the ability to query enhances the functionality of DirXML and enables developers to accomplish more with DirXML.

While the query feature is available in DirXML 1.0, there have been some minor changes to this feature in DirXML 1.1. One is the ability to query from the Input and Output Transformation rules. Another change is that it is no longer required to wrap the Query Document inside <input> and <nds> elements.

A Simple Query

Before we delve into the complexities of queries, it will be helpful to go through the steps of writing a simple query to illustrate how it is done. While this example query has little functional value, it does illustrate the basic structure of a query. Assume that your business logic requires that any time a user is modified in eDirectory, their workforceID must be listed in the DirXML Trace Log. Given these requirements, you will use the Event Transformation Stylesheet on your Subscriber Channel to implement this logic.

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 Query 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 queries 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:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.
XdsQueryProcessor" >

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

<xsl:param name="srcQueryProcessor"/>
<xsl:param name="destQueryProcessor"/>

Once these changes are in place, you can now construct your <xsl:template>. Since you are going to query every time a user is modified, start the <xsl:template> with the following line, which will match on every Modify event to User objects:

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

Now you can construct a Query Document. The Query Document (as will be explained in detail later) contains your search criteria and which attributes you want returned in the Query Response. Since you've already determined that you want to query the same user that was modified, you can simply take the src-dn attribute from the <modify> document and insert it into the Query Document. In addition, since you know that the only information you want returned is the workforceID, you list that in the Query Document as well. Lastly, you need to store the Query Document inside an <xsl:variable> called $query-doc.

Here's the XML code to do this:

<xsl:variable name="query-doc">
<query dest-dn="{@src-dn}" scope="entry">
<read-attr attr-name="workforceID"/>
</query>
</xsl:variable>

Once you've constructed the Query Document and stored it in a variable, you can submit the query to the Query Processor. In doing so, you must notify the Query Processor which datastore you want to search. This is done by specifying either the srcQueryProcessor or destQueryProcessor (these parameters will be explained in detail later on). For this example, choose the srcQueryProcessor. Lastly, you instruct the DirXML engine to store the results of the query in an <xsl:variable> named result. The XML code for this is:

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

Now you can analyze the $result variable and see if a workforceID was returned. If so, you write that workforceID to the DirXML Trace Log. If a workforceID is not returned (possibly because this user object did not have its workforceID attribute populated), you do nothing. Here is the relevant XML code for this:

<xsl:if test="$result//attr[@attr-name='workforceID']">
<xsl:message>
The WorkforceID is <xsl:value-of select="$result//attr[@attr-name='workforceID']"/>
</xsl:message>
</xsl:if>

Lastly, you need to copy the Modify event through so that it gets processed by the remote datastore:

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

Here is the stylesheet in its entirety.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="query" version="1.0" 
   xmlns:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.
      XdsQueryProcessor" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <!--These are the two parameters that are needed for Queries -->
   <xsl:param name="srcQueryProcessor"/>
   <xsl:param name="destQueryProcessor"/>

   <!-- 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 Modifies and writes the WorkforceID 
      of the modified user to the DirXML Trace File -->
   <xsl:template match="modify[@class-name='User']">

      <!--Build the Query Document to send to the Query Processor -->
      <xsl:variable name="query-doc">
         <query dest-dn="{@src-dn}" scope="entry">
            <read-attr attr-name="workforceID"/>
         </query>
      </xsl:variable>

      <!-- Instruct the Query Processor to send our Query to the
         srcQueryProcessor -->
      <xsl:variable name="result" select="query:query($srcQueryProcessor,
      $query-doc)"/>

      <!-- Check if the user returned has a workforceID -->
      <xsl:if test="$result//attr[@attr-name='workforceID']">

         <!--If it does then write that ID to the DirXML Log file -->
         <xsl:message>
            The WorkforceID of the user modified was <xsl:value-of select=
            "$result//attr[@attr-name='workforceID']"/>
         </xsl:message>
      </xsl:if>
      
      <!-- Copy the <modify> event through -->
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

Queries In-Depth

Now that you've seen a simple query, let's dig in deeper and understand how it all works. There are five items that queries use in any stylesheet:

  • Name Space Declarations

  • Parameter Declarations

  • Query Document

  • Query Execution

  • Query Response

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 "query" to a Java class that implements the query 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:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.
XdsQueryProcessor"

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 queries 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:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.
XdsQueryProcessor" >

Parameter Declaration

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

  • srcQueryProcessor

  • destQueryProcessor

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 queried. 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 your query to the srcQueryProcessor. If you need to query the datastore that will be receiving the event, you send your query to the destQueryProcessor.

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 destQueryProcessor is used to query 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 srcQueryProcessor is used to query eDirectory. For example, if you are writing a Create Stylesheet for your Publisher Channel and you want to query into eDirectory, you would send the query to the destQueryProcessor.

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="srcQueryProcessor"/>
<xsl:param name="destQueryProcessor"/>

The Query Document

Now that you've properly prepared your stylesheet for queries, you can now delve into the actual queries themselves. The Query Document is the XML document that describes the actual query-that is, what is it you are looking for, where do you want to search, and what information do we want returned? The full details of the Query 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 Query Document is constructed, it is typically stored in an <xsl:variable> so that the it can be passed to the Query Processor.

The first element in a Query Document is the <query> element. This is the top- most required element in a Query Document.

Note: DirXML 1.0 requires the <query> element to be enclosed in <nds> and <input> elements. DirXML 1.1 does not.

There are a few attributes of the <query> element that you may want to specify. They include:

  • class-name

  • dest-dn or dest-entry-id

  • scope

The class-name Attribute. This attribute is not required. It is used to specify how attribute names are mapped in the body of the query. Recall that DirXML mapping rules can specify class-specific mappings for attribute names. For example, Telephone Number might be mapped one way for User objects and another way for Organizational Unit objects. Specifying the class-name attribute on the <query> element tells DirXML which mapping to use for an attribute name if there is more than one mapping specified for a particular attribute name.

The dest-dn and dest-entry-id Attributes. These attributes are used to specify either the object to read or the object at which a subtree- or subordinate-scoped query will start. An alternative way to specify the object is with an <association> element. For entry-scoped queries (which are actually reads), one of the dest-dn or dest-entry-id attributes or the association element must be specified. For subtree- or subordinate-scoped queries, dest-dn, dest-entry-id, and the association element are all optional; if not specified, the root of the query is defined to be the root of the query space. For eDirectory this is the root-most object of the root-most partition contained on the server hosting DirXML. For applications, this is dependent on the application driver.

To populate the dest-dn or dest-entry-id attribute on the query element in a stylesheet, you normally copy the value of the src-dn or src-entry-id attribute.

Note: The dest-entry-id only works if you are querying eDirectory.

The scope Attribute. This attribute is not required. It can be used to specify the range of the query. There are three possible values: subtree, entry, and subordinates.

  • Subtree instructs the query processor to start at the object specified in the dest-dn or dest-entry-id attributes or specified by the <association> element and search the entire subtree below. If no dest-dn attribute is specified, the entire tree will be searched. If no scope is specified, subtree is assumed.

  • Entry instructs the query processor that the object specified in the dest-dn or dest-entry-id attribute or specified by the <association> element is the object that you want to query.

  • Subordinates instructs the query processor to search the children of the object specified in the dest-dn or dest-entry-id attribute or specified by the <association> element, but not the entire subtree below those children.

If no value is specified, the scope of the query defaults to subtree. Once you've built the <query> element, you may want to further refine your search. This can be done with any of the following elements:
  • The <association> element specifies the object to read (for entry-scoped queries) or the object at which to begin the search (for subtree- and subordinate-scoped queries). For a given query, only one of the <association> element, dest-dn, or dest-entry-id attributes is specified.

  • Specifying the <search-class> will limit the search to a given class. If the scope="entry" is used in the <query> element, using the <search-class> element has no effect. However, if the scope="subtree" or scope= "subordinates" is used, one or more <search-class> elements limits the class of objects searched.

  • Specifying the <search-attr> will limit the results to objects that match the criteria specified in the <search-attr> element.

  • The <read-attr> element is used to instruct the query processor what information you want returned, once you've defined your search criteria clearly. This element can be used in the following manner:

    • <read-attr/> specifies that no attributes should be returned. This is typically used to check whether a specific entry exists, rather than to determine the value of a specific attribute.

    • <read-attr attr-name="Surname"/> specifices that only the attributes named should be returned.

    If no <read-attr> is specified, all attributes are returned.

Sample Query Documents

Below are some sample query documents to better illustrate how to properly construct a query document.

Sample 1. This is a query to determine if there is already a user with the distinguished name of "\tree-name\acme\users\fredjones".

<xsl:variable name="query-doc">
<query dest-dn="\tree-name\acme\users\fredjones" scope="entry">
<read-attr/>
</query>
</xsl:variable>

Sample 2. This is a query to read the Surname of "\tree-name\acme\users\ fredjones".

<xsl:variable name="query-doc">
<query dest-dn="\tree-name\acme\users\fredjones" scope="entry">
<read-attr attr-name="Surname">
</query>
</xsl:variable>

Sample 3. This is a query to find all users in the entire tree with a Surname of "Jones" and read all attributes.

<xsl:variable name="query-doc">
<query scope="subtree">
<search-attr attr-name="Surname">
<value type="string">Jones</value>
</search-attr>
</query>
</xsl:variable>

Sample 4. This is a query to find all users in the "\tree-name\acme\users" container and read the Given Name and Surname attributes.

<xsl:variable name="query-doc">
<query dest-dn="\tree-name\acme\users" scope="subordinates">
<read-attr attr-name="Given Name"/>
<read-attr attr-name="Surname"/>
</query>
</xsl:variable>

The Query Execution

Once you've properly constructed your query document and stored it in an <xsl:variable> called $query-doc, you need to submit it to the Query Processor for execution. There are a few components to the execution:

  • The name of the variable that will contain the results

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

  • A reference to either the srcQueryProcessor or the destQueryProcessor to instruct the query engine as to which datastore should be queried

  • A reference to the variable in which you stored the query document

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

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

$result is the variable name you will store the query results in. The value of $result will be the result of the select statement. The select statement begins with query: to indicate that it should be passed to the query engine for processing. The command that is sent to the query engine is query($srcQueryProcessor, $query-doc) which instructs the query engine to send the $query-doc to the srcQueryProcessor for processing.

The Query Response

Once the query is executed and the results are stored in the $result variable, you can now review those results by navigating the XML document stored in the $result variable. It is important to understand the structure of a Query Response document. The full details of the <instance> document structure can be found in the NDS.DTD located at http://developer.novell.com/ndk/doc/dirxml/dirxmlbk/ api/ndsdtd/instance.html.

There are generally three types of Query Responses:

  • Zero Instances found

  • One Instance found

  • Multiple Instances found

Zero Instances Found. If no objects are found based on your search criteria, the following document will be returned:

<nds dtdversion="1.1" ndsversion="8.5">
<source>
<product version="1.1">DirXML</product>
<contact>Novell, Inc.</contact>
</source>
<output>
<status event-id="0" level="success"/>
</output>
</nds>

Notice that there is no <instance> element in the document above, since no matches were found. In addition, depending on the structure of your Query Document, you may see an error message in the <status> element.

One Instance Found. If a single object is found based on your search criteria, the following document will be returned:

<nds dtdversion="1.1" ndsversion="8.5">
<source>
<product version="1.1">DirXML</product>
<contact>Novell, Inc.</contact>
</source>
<output>
<instance class-name="User" event-id="0" src-dn="\TEST_TREE\TEST\Users\JDoe">
<association state="associated">JDoe@test.com</association>
<attr attr-name="Surname">
<value timestamp="1016241258#16" type="string">Doe</value>
</attr>
</instance>
<status event-id="0" level="success"></status>
</output>
</nds>

Notice that since only a single object matched your search criteria, there is a single <instance> element in the document above.

Multiple Instances Found. If multiple objects are found based on your search criteria, the following document will be returned:

<nds dtdversion="1.1" ndsversion="8.5">
<source>
<product version="1.1">DirXML</product>
<contact>Novell, Inc.</contact>
</source>
<output>
<instance class-name="User" event-id="0" src-dn="\TEST_TREE\TEST\Users\JDoe">
<association state="associated">JDoe@test.com</association>
<attr attr-name="Surname">
<value type="string">Doe</value>
</attr>
</instance>
<instance class-name="User" event-id="0" src-dn="\TEST_TREE\TEST\Users\JDoe2">
<association state="associated">Jdoe2@test.com</association>
<attr attr-name="Surname">
<value type="string">Doe</value>
</attr>
</instance>
<status event-id="0" level="success"></status>
</output>
</nds>

Notice that since multiple objects matched your search criteria, there are multiple <instance> elements in the document above.

Navigating the Query Result Document

Once a Query Result document is returned, you must navigate that document to retrieve the information you are interested in. Depending on the purpose of your query, that navigation will vary. There are generally three types of result tree navigation:

  • Checking for the presence of a matching object

  • Checking for the presence of a specific attribute value

  • Using the attribute value returned elsewhere in the stylesheet

Checking for the Presence of a Matching Object. This can be accomplished in one of two ways: either check for the presence of an <instance> element or count the number of <instance> elements. If you need to ensure that only a single object was returned (this is typically required in a Matching Stylesheet), you can count the number of <instance> elements using the following code:

<xsl:if test="count($result//instance) = 1">

If you need to test for the presence of any number of matches, you can use the following code:

<xsl:if test="$result//instance">

Checking for the presence of an attribute value can be done using the following code:

<xsl:if test="$result//instance/attr[@attr-name='Surname']/value">

If you need to test for a specific value in that attribute, you can do so using the following code:

<xsl:if test="string($result//instance/attr[@attr-name='Surname']/value) = 'Jones'">

You can use the value returned elsewhere in the stylesheet by storing that value in an <xsl:variable> using the following code:

<xsl:variable name="user-sn" select="$result//instance/attr[@attr-name='Surname'] /value"/>

A Complex Query

Now that you've seen some of the details of DirXML queries, let's construct a more complex query to better illustrate these details. Assume that your business logic requires that only users who are members of the Managers and Directors groups should have their "directReports" attribute replicated across to the remote datastore. To accomplish this, you will need to query the User object's Group Membership each time the object's "directReports" attribute is modified to determine if the modification should flow to the remote datastore from eDirectory. Since you want this logic applied to both Add and Modify events, you can place this login in a Command Transformation Stylesheet on the Subscriber Channel.

Once again, you need to enable queries in your stylesheet by adding the Name Space and Parameter Declarations:

<xsl:stylesheet exclude-result-prefixes="query" version="1.0" 
xmlns:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.
XdsQueryProcessor" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="srcQueryProcessor"/>
<xsl:param name="destQueryProcessor"/>

Next, build your <xsl:template> match statement. Since you need to match every time the "directReports" attribute is modified or added, you can use that as your trigger. The following template will match on any element that affects the "directReports" attribute:

<xsl:template match="*[@attr-name='directReports']">

Once you've determined that the "directReports" attribute has been modified, you need to construct a query to go out and read the Group Membership of the User object that was modified. The one complication is that the <xsl:template> matched on the element that contained the modification and you would need to get the Distinguished Name (DN) of the object to build the query. Since the DN of the object is in the Parent element, you can build an <xsl:variable>, store the user's DN in it, and then build your Query Document:

<xsl:variable name="user-dn" select="../@src-dn"></xsl:variable>
<xsl:variable name="query-doc">
<query dest-dn="{$user-dn}" scope="entry">
<read-attr attr-name="Group Membership"/>
</query>
</xsl:variable>

Once again, execute the query and pass the srcQueryProcessor parameter along with the Query Document. Store the results in a variable called $result:

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

Now you need to navigate the $result variable and determine if the user is a member of either the Managers or Directors group. You can use the <xsl:choose> to check whether the user is a member of either of these two groups and if so, copy the <modify> element through:

<xsl:choose>
<xsl:when test="$result//value[./text()='\W2K-NDS\novell\Groups\Managers']">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:when>
<xsl:when test="$result//value[./text()='\W2K-NDS\novell\Groups\Directors']">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:when>
</xsl:choose>

Here is the stylesheet in its entirety.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="query"
xmlns:query="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.XdsQueryProcessor" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="destQueryProcessor"/>
<xsl:param name="srcQueryProcessor"/>
<!-- This is the identity template that is needed in all stylesheets -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="modify-attr[@attr-name='directReports']">
<!-- Store the User's DN in a variable -->
<xsl:variable name="user-dn" select="../@src-dn"></xsl:variable>
<!-- Build the Query Document -->
<xsl:variable name="query-doc">
<query dest-dn="{$user-dn}" scope="entry">
<read-attr attr-name="Group Membership"/>
</query>
</xsl:variable>
<!-- Execute query and store results in a variable called $result -->
<xsl:variable name="result" select="query:query($srcQueryProcessor,
$query-doc)"/>
<xsl:choose>
<!-- If user is a member of Managers copy the Modify event through -->
<xsl:when test="$result//value[./text()='\W2K-NDS\novell\Groups\
Managers']">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:when>
<!-- If user is a member of Directors copy the Modify event through -->
<xsl:when test="$result//value[./text()='\W2K-NDS\novell\Groups\
Directors']">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Conclusion

Hopefully, this AppNote has provided some insight into how queries can be used inside DirXML stylesheets. In the next AppNote in this series, you will learn how to leverage queries together with the Write-Back Channel and see how both features can be used together.

* 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