An alternative way of working with hierarchically structured data is to take advantage of the support for hierarchical objects in the Tivoli Directory Integrator entry object.
Different from earlier versions, Tivoli Directory Integrator v7.0 supports 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 Tivoli 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 we 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
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).
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 Tivoli 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 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.
Tivoli 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.
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.
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 we 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 we 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());
The Tivoli 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.
The following entry object example diagram illustrates this:
Hierarchical Entry object example
The script engine provided by IBM Tivoli 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. Tivoli Directory Integrator V7.1 adheres to the standard object manipulation the JavaScript Engine provides by implementing several enhancements to the following objects:
If a prefix is provided, it will be ignored and the name resolving mechanism will only look for the specified namespaceURI and localName. The resolved object could be either null or an Attribute object.
If a prefix is provided it will be ignored and the name resolving mechanism will only look for the specified namespaceURI and localName. The resolved object could be either null or a Property object.
If a prefix is provided it will be ignored and the name resolving mechanism will only look for the specified namespaceURI and localName. The resolved object could be null, a single Attribute object or a NodeList containing all of the Attributes that match the search name.
If a prefix is provided it will be ignored and the name resolving mechanism will only look for the specified namespaceURI and localName. The resolved object could be either null or a Property object (if only one found) or a NodeList of all the Property objects found in the NodeList.
If a prefix is provided it will be ignored and the name resolving mechanism will only look for the specified namespaceURI and localName. The resolved object could be null, a single Attribute object or a NodeList containing all of the Attributes that match the search name.
You now have the following options:
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, we 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 }
// 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";
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:
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 we 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
An exception is thrown in the following cases:
Parent topic: Internal data model: Entries, Attributes and Values