+

Search Tips   |   Advanced Search

Working with hierarchical Entry objects

An alternative way of working with hierarchically structured data is to take advantage of the support for hierarchical objects in the Security Directory Integrator entry object.

Different from earlier versions, IBM Security Directory Integrator Version 7.1.1 and later support the concept of the hierarchical Entry object. The Entry object represents the root of the hierarchy and each Attribute represents a node in that hierarchy. Following this logic the values of every Attribute are leafs in the hierarchy. An API for traversing the hierarchy exists as well. This API is a partial implementation of the DOM 3 specification. Only a few classes from that specification have been implemented:

These classes are the minimum set of classes provided by the DOM specification that are needed to represent a hierarchical data. The pre-v7.0 API is not hierarchy aware (for example, it cannot access/modify/remove child Elements) for backward compatibility reasons. This is why only the DOM API can manipulate a hierarchical structure.

To keep the Entry structure backward compatible by default the Entry always uses flat Attributes. The Entry only becomes hierarchical on demand – after you call one of the newly provided DOM APIs. This allows only components aware of the hierarchical nature of the Entry to make use of it, the rest of the components don't need to be changed in order to keep running. Starting from IBM Security Directory Integrator v7.0, a new name notation is introduced in order for users to have an easier way of creating hierarchical trees. Every name containing a dot in it is thought to be a composite name compound of simple names; these names are separated by dots. When such a composite name is passed to a hierarchical Entry, it breaks it down to simple names and builds the hierarchy that is described by that composite name.

For example if you run the following JavaScript code:

// create a new empty Entry object
var entry = new com.ibm.di.entry.Entry(true);
// create a new branch of 2 levels
entry.setAttribute("firstLevelChild.secondLevelChild", "level2Value");
// finds the already existing branch and creates a new node on level 3
entry.setAttribute("firstLevelChild.secondLevelChild.thirdLevelChild", "level3Value");

the following structure will be created: Simple hierarchical entry

Note: It is important to know that the names are not broken down to simple names until the Entry structure is not converted to a hierarchical one. For example if the entry is a flat one and only the old methods are used that would still create the same old flat structure:

Traditional, flat entry

In some cases it may be necessary to have dots in the names of the Entry's attributes, so the Entry object also understands escape characters (currently only \\ and \. are supported). Note: When working from script the backslash should be properly escaped with another backlash! For example if the following hierarchy is needed: Another simple hierarchical entry

the following script would create it:

var entry = new com.ibm.di.entry.Entry(true);
entry.setAttribute("first\\.level\\.child.second\\.level\\.child", "level2Value");
entry.setAttribute("first\\.level\\.child.second\\.level\\.child.third\\.level\\.child", "level3Value");

In order to maintain backward compatibility with previous releases of IBM Security Directory Integrator when implicitly working with hierarchical Entries, all the old methods exposed by both the Attribute and the Entry classes have changed slightly. For example, the Entry.getAttributeNames() method will return an array of full paths to the leafs that are available in the tree. In reference to the above structure the getAttributeNames method returns the array:

["first\.level\.child.second\.level\.child", "first\.level\.child.second\.level\.child.third\.level\.child"]

The Entry.size() method returns the total number of elements in the array returned by getAttributeNames() method. In our case the size() method would return 2 (the number of leafs in the whole tree) instead of 1 (as you might expect, considering that the Entry object has only one attribute as a child).

This is because both getAttributeNames() and size() methods only work with flat structures. In order to get the real size of the children you would need to use the DOM API as follows: Entry.getChildNodes().getLength();

If the entry is a flat one the old API will behave as in previous releases.


The Attribute object

The Attribute object is enhanced with the ability to create hierarchical structures. These structures follow the DOM specification and that is why the Attribute object is aware of XML Namespace concepts.

The Attribute class also had to be expanded in order to provide new methods for the hierarchical functionality. The getValue/setValue/addValue methods are also backward compatible and will return only the values the particular element has. The difference here is when each value is accessed through the DOM API it will be wrapped in an AttributeValue class and will be treated as a Text node. In order to get the child elements of an Attribute (for example, Attributes and AttributeValues) the DOM API has to be used.

The Attribute child of an Entry is also able to switch the Entry's structure to hierarchical one when any of its DOM methods are accessed. Unlike the Entry class the Attribute class does this implicitly and does not provide a way to do the switching explicitly.

IBM Security Directory Integrator v7.0 supports extensions to scripting capabilities of the Server to easily access complex structures. Please see the section Navigation within scripts for more details.


The AttributeValue object

This class represents a value in the hierarchical tree. As per DOM specification the values in the tree are always Strings. The AttributeValue class however, has held objects of any kind for the last few releases. This is valid in the current version as well. The only difference for the AttributeValue object is that when accessed through DOM it will return a String representation of the contained object. In order for the AttributeValue class to represent a Node and have a value at the same time in the terms that the DOM specification defines, it must implement either the org.w3c.dom.Text or org.w3c.dom.CDATASection interfaces. The AttributeValue implements them both, and can represent either of these Nodes depending on your needs.


The Property object

Attributes can have zero, one or more Property objects. The Property class implements the org.w3c.dom.Attr interface and thus represents the attributes in terms of DOM concepts. Using properties you may declare prefixes/namespaces in terms of XML concepts.


Transferring Objects

Mapping an Attribute from one entry to another will always copy the source Attribute. For example:

entry.appendChild(conn.getFirstChild());
// or
entry.setAttribute("name", conn.getFirstChild());
Even when the Attribute is not a first-level child of the Entry it is still copied. This can also be accomplished by the script:
entry.a.b.c.d.appendChild(conn.e.f.g);
In order to move an Attribute object between entries without cloning it you will need to first detach it from its old parent and then attach it to a new parent. For example:
var src = entry1.b.source;
entry1.b.removeChild(src);
entry2.a.target.appendChild(src); 
When moving an Attribute object from one parent to another parent in the same Entry the Attribute is automatically moved. No cloning is done. For example:
entry.a.target.appendChild(entry.b.source);
In this example the "source" Attribute is detached from its parent ("entry.b") and then attached to the "entry.a.target" Attribute. No cloning is done. If you do not want to remove the Attribute object from the source then we can append a copy of the Attribute like this:
entry.a.target.appendChild(entry.b.source.clone());


Navigation within scripts

The IBM Security Directory Integrator ScriptEngine enables you to easily access the attributes of an entry just by referring to them by name; for example, entry.attrName returns the attribute with name attrName.

  1. The JavaScript Engine resolves names based on the context object the name was requested on. For example, if the call entry.a is performed, the entry name is the context object and a is the name of the child object to resolve. The JavaScript Engine uses left-to-right interpretation to evaluate each context object until the final one is resolved. Based on the diagram below the following call, entry.a.b.c, is resolved using this procedure: Find the entry object to use it as the context object for the first step.
  2. Search the context object for a name a. The entry object has only one child with name a. Consider that child to be the next context object for the next step.
  3. Search the context object for a name b. The context object has two children named b. Put them in a list and return that list.
  4. The final operation searches the list returned in the previous operation for the name c. Each element in the list has at least one such child. Get them all and put them in a list, which is the actual result of resolving the full expression.

The following entry object example diagram illustrates this: Hierarchical Entry object example

The script engine provided by IBM Security Directory Integrator allows more arbitrary names to be used in child resolving process. For example if the name of the child contains dots then we can refer to it using the square-bracket syntax as shown below:

work["{namespace}name:Containing\.invalid\.charaters"]
Notice that the dots are being escaped to denote that they are part of the local name and that they must not be treated as path separators by the script engine.

Depending on the current context object on which an operation is performed the end result might differ. IBM Security Directory Integrator v7.2 adheres to the standard object manipulation the JavaScript Engine provides by implementing several enhancements to the following objects:

You now have the following options:

  1. Ability to access all the d elements by referring to them starting from the top; for example, entry.a.b.c.d – this returns an object of type NodeList with all the d attributes that match that path. In our example this will return all the three d elements.
  2. Ability to access attributes by specifying both prefix and local name; for example, entry[”a.b.c.pref1:d”] – this will return a single Attribute only, the one with a prefix of "pref1".
  3. Ability to access each Attribute of an NodeList using the [ ] notation, for example, entry.a.b.c.d[0] – this returns a single Attribute, namely the first d element of the structure above.
  4. Ability to navigate through elements using the [ ] notation; for example, entry.a.b[0].c.d – this returns a List of Attributes, but this time it contains all the d attributes form the first b branch.
  5. Ability to get the property of an attribute (that is, an Attribute of an Element using the DOM naming convention) using the @ notation; for example, entry.a.b.c.d[0]["@propPrefix:propName"] – this returns us a String object containing the value of that property (that is, the Attribute's value according to DOM). Note here that the propPrefix and the propName are separated by the colon sign (":"), and that if the property has a prefix, it is mandatory that both the prefix and the name be specified in order for the property to be found. Alternatively the namespace and the propertyName can be used to find a property that has a prefix.
  6. Ability to call methods of an Attribute before searching for child with the name of the method that the user wants to execute; for example, entry.a.b.[0].getChildNodes() – this returns a NodeList object that holds all the Attribute values that the first b Attribute contains.
  7. Ability to access entry attributes by name; for example, entry.attrName – this will return the Attribute mapped to the attrName key.
  8. Ability to access entry properties using the .@ notation; for example, entry.@propName – this will return the Object mapped as property to the propName key.
  9. Ability to access child nodes by specifying the namespace those children belongs to. For example work.a.b[{someNamespace}c] – this will return all the c objects that belong to the "someNamespace" namespace.

Note:

  1. If the name of an attribute is the same as the name of a method of an Entry/Attribute then the method will be called. If you want to access the attribute, you must use the object's methods like before (that is, Entry.getAttribute("getAttribute");.)
  2. If a flat entry is used the script engine will not convert the entry to a hierarchical unless the namespace of the attribute to find is specified. For example: entry["{ns}element"]. The script engine will automatically convert the entry to a hierarchical one if the context object is of type Attribute. For example in this case: entry.attr.child.
  3. If the attribute to find contains dots in its name and the entry is flat the you must use the bracket notation instead of the dot notation, or force the entry to become hierarchical before resolving the child node. For example if the entry is flat we cannot access the attribute http.body using the call entry.http.body. For this to happen you will have to use this instead: entry["http.body"] or call entry.enableDOM() prior to calling entry.http.body.
  4. If you intend to use the reference retrieved from the expression, for example, a.b.c.d more than once, then it is good practice to assign that reference to a local variable since each repeating evaluation of the same expression will result in additional overhead for retrieving the same reference.
  5. If, for example, the expression entry.a.b.c[0].d is used, then this will refer to an Attribute object; but if entry.a.b[0].c.d is used, then this will refer to a NodeList object. In order to recognize the referenced object we can check the name of the object, for example:
    var obj = a.b.c.d;
    if (obj.getClass().getSimpleName().equals("Attribute") ) {
    		// handle this as Attribute
    } else {
    		// handle this as a list of Attributes
    }
    If, for example, you need to handle each element returned by an expression, even if only one element is returned then we can use a for/in loop structure like this:
    for (obj in entry.a.b.c.d) {
    		// the obj will be an object of type Attribute
    }
    The same usage is now available with the Entry object, for example
    for (obj in work) {
    		// the obj will be an Entry's attribute
    }
  6. ScriptEngineOptions also allows assigning values. For example the following expressions are valid:

      entry.a.b.c.d[0]["@propPrefix:propName"] = "new value";
      This changes the value of the property. If the property does not exist then it will be created.
      entry.a.b.c.d[0][0] = "new value";
      This replaces the attribute value on position 0.
      entry.a.b.c.d[0][1] = "another value";
      This adds another value to the attribute (if the index is the same as the size of the array of values.)
      entry.a.b.c.d[0][a.b.c.d[0].size()] = "appended value";
      If you need to append a new value to the list of objects, but do not know the last element position we can use Attribute#size() to get the number of children this element has.
      new com.ibm.di.entry.Entry().@propName = "someValue";
      This will create a new property in the Entry object with name propName if it does not exist, and will set its value to the String "someValue".
      entry.a.b.c[1] = "value";
      This will resolve entry.a.b.c as a NodeList and will automatically add as a value the new String object to the second element of the resolved NodeList object. If, for example, entry.a.b.c resolves to an Attribute, then the new String value will replace the second value of the resolved c Attribute.
      entry.a.b.c[2]["pref3:d"] = "value";
      This will add a new pref3:d child to the third c attribute from the entry.a.b.c list. Note that entry.a.b.c[2] resolves to an Attribute and to a list.
      entry.a.b.c["{namespaceURI}:d"] = "myTextValue";
      This sets a value to the first child Attribute of c that has a local name equals to d and belongs to the namespace namespaceURI. If no such attribute is found, a new one is created and the value is assigned to it. When creating an attribute, you might as well associate the namespace with a prefix. In our case this could be done with this: entry.a.b.c["{namespaceURI}prefix:d"] = "myTextValue";.
      entry["{http://ibm.com/xmlns/}first.second.third"] = null;
      This will first try to find the element with local name "first" from the namespace "http://ibm.com/xmlns/". When it fails it will create the element and then will try to resolve its child element "second". When it fails it will create it and set its namespace to the one of the first element. Finally an attempt to resolve the third element will be made. When it fails the last element will be created with no values. This is equivalent to entry["{http://ibm.com/xmlns/}first.{http://ibm.com/xmlns/}second.{http://ibm.com/xmlns/}third"] = null;


Create the above structure with script

// create a new entry that will hold the structure
var entry = new com.ibm.di.entry.Entry();
// create the first branch
var d = entry.newAttribute("a.b.c.d");
// create a new property and assign it a value
d["@propPrefix:PropName"] = "value";
// create a new value of the d Attribute
d[0] = "D1";
// append a new value for the entry.a.b attribute
entry.a.b.appendChild(entry.createElement("c"));
// create a new Attribute d with a prefix on the second c attribute, 
// and assign the string "D2" as a first value
entry.a.b.c[1]["pref1:d"] = "D2";
// create a new child of the a Attribute
entry.a.appendChild(entry.createElement("b"));
// choose the second attribute from the entry.a.b NodeList and create a new child named c
entry.a.b[1].appendChild(entry.createElement("c"));
// create the d child of the c Attribute
entry.a.b[1].c["pref2:d"] = "D3";


Navigation using XPath

The Entry class provides convenient methods for navigating and retrieving data based on XPath expressions. Using XPath for querying data from an Entry is much more advanced than using any simple navigation within scripts. It is much easier to implement a searching and/or a value matching logic in a single expression than writing multi-lined scripts in order to achieve the same results.

The Entry class provides the following methods:


Flattening hierarchical structures

When working with hierarchical data it is sometimes necessary to flatten the nodes of the hierarchy. To do that we can either write a script for traversing the tree, or use one of the methods:

These methods traverse the tree and search for elements with the specified name and namespace. The DOM API allows these methods to accept the character "*" for both names and namespaces. That character represents a wildcard, which is used to match any element name/namespace. Using that character for name flattens the tree by returning all the Attribute nodes in a NodeList. It should be noted that the structure of the hierarchy has not been changed. The returned NodeList is just a container for the Element nodes that were found in the tree. If you run the following script on the entry from the structure in section Navigation within scripts:

var list = entry.getElementsByTagName("*");
then the list variable will hold the following structure:
list 
   |
  + a
   |
  + b
   |
  + c
   |
  + d
   |
  + c
   |
  + pref:d
   |
  + b
   |
  + c
   |
  + pref2:d


Exceptions

An exception is thrown in the following cases:


Parent topic:

Internal data model: Entries, Attributes and Values