OpenText product name changes coming to the community soon! Learn more.

Wikis - Page

Open Call for useful ECMA functions to use in Identity Manager

3 Likes

An article that David Gersic just wrote Adding Regular Expression Matching to XPath in Identity Manager reminded me of something I had wanted to do for a while...



We should get an article going that lists off useful ECMA functions to use in Identity Manager. I have a bunch that I have been using, and possibly would be useful to others.



I found out after this was written that there is a Wiki page for ECMA function, and I have put a link there, back to this article. Lets try and keep both in mind.



I found that this approach was really successful when trying to gather up all the possible values for DirXML-Associations, as you can see in this article: Open Call - IDM Association Values for eDirectory Objects



Lets see if we can do as well with ECMA Script functions as well!



With Novell Identity Manager 3.5 and higher, Novell added the ability to call ECMA Script functions from policy.



This is a great feature as anything you cannot do with DirXML Script, as powerful as it is, you can usually do in ECMA Script. Before you could have written some Java class to do what was needed, but ECMA Script is somewhat easier and samples are a little more common. ECMA Script is the new name (or possibly correct name now) for JavaScript.



David was pretty detailed in how you go about using an ECMA Script in his article, so I won't repeat his instructions other than to point you at his article for those details. In short, you store the ECMA Function in a ECMA object somewhere in the tree, you link it to the Driver you are using, add it to the namespace, and when you need to call it, you prefix the function name with es: in your XPATH call.



David's example was great, using Regular expressions inside an XPATH statement, since the XPATH equality test (= sign) is an exact compare and does not use anything more fun like regular expressions.



Here is his matches(arg1, arg2) function that uses some of the ECMA Regular Expression functionality, and makes it available in XPATH.



/** Return true or false based on a Regular Expression match
* @param {String} s1 the string to test
* @param {String} s2 the regex string to test it with
* @type Boolean
* @return a boolean true or false
*/
function matches(s1,s2)
{
// Build search and replace options.
var options = "";

var re = new RegExp(s2, options);
if (s1.match(re)) {
return true;
} else {
return false;
}
}


David gets first billing, only because he inspired me to write this, but otherwise first billing would have to go to Lothar Haeger and his Password Notification Driver, http://www.novell.com/communities/node/2277/idm-35-password-notification-service-driver which if you have not yet looked at you should.



My theory is that if you examine the driver in detail there are at least three things you will learn from it. Even pretty experienced Identity Manager developers can possibly learn something from this driver. You can read this article: An even easier way to call a Job from policy for my thoughts on what you will learn if you look at his driver in detail.



One of those items is the use of an ECMA Script LDAP search function. This has some real advantages over a regular query token in DirXML Script. The primary one, is that for LDAP searching, the standard allows for the use of a filter. In other words, you can search for all objects, that meet some filter. The token query can specify the object class as a type of filter, like only return User objects, but cannot do other filter things like only return objects that have values for an attribute. Lets say you want to find all user objects in your tree that have say a value for uidNumber, since you want to be sure you have unique uidNumber values, so as not to freak out your Unix or Linux system that is using pam_ldap to authenticate.



With a DirXML Token, you could query for all object class User objects, but you could not say only return objects with a value for uidNumber. Rather you would get an empty node set for users without one. Thus you would have to clear up all those empty node sets in order to work through them. With an LDAP query you could use a filter of (uidNumber=*) to only get objects that have a value populated for uidNumber.



The filter can be more complex than that, and have multiple predicates in it, which is much more powerful to control the results than the Query token.



On the other hand, in defense of the query token, it is very powerful and useful, and has the ability to do paged queries (query-ex) which is controlled by the max number returned parameter in Policy Builder. This is great, as you can really save memory by only looking at 50 node set results (lets say) at a time. Rather than trying to load all thousands of objects into a node set variable to then process.



Regardless, there are great use cases for both, and here you can see how to use ldapSearch via ECMA.



importPackage(Packages.com.novell.ldap);

/**
* ldapSearch
*
* @param (String} host LDAP Server, either DNS or IP-Address
* @param (Number} port LDAP listening port
* @param (String} user user account, full distinguished name, LDAP syntax
* @param (String} password the cleartext LDAP userpassword
* @param (String} base search base
* @param (String} scope (base | one | sub)
* @param (String} filter LDAP search filter according to RFC2254 (see {@link #ldapCount(DirContext, String, String)}
* @param (String} attrList comma separated list of attributes to return
* @param (Number} maxResultSet maximum result set size (0=unlimited)
* @type Nodeset
* @return NodeSet containing instances from search result, or status element with error message
*/
function ldapSearch(host, port, user, password, base, scope, filter, attrList, maxResultSet)
{

var nodeSet = new Packages.com.novell.xml.xpath.NodeSet();
var document = Packages.com.novell.xml.dom.DocumentFactory.newDocument();
var ndsElement = document.createElement("nds");
document.appendChild(ndsElement);
ndsElement.setAttributeNS(null, "dtdversion", "3.5");
var outputElement = document.createElement("output");
ndsElement.appendChild(outputElement);

var searchScope = LDAPConnection.SCOPE_ONE;
if (scope == "base")
{
searchScope = LDAPConnection.SCOPE_BASE;
} else if (scope == "sub")
{
searchScope = LDAPConnection.SCOPE_SUB;
}

var attrSplit = attrList.split(',');
var attrArray = java.lang.reflect.Array.newInstance(java.lang.String, attrSplit.length);
for (var attrIndex in attrSplit)
{
attrArray[attrIndex] = attrSplit[attrIndex];
}


var lc = new LDAPConnection();

// set search result set size limit
var searchConstraints = new LDAPSearchConstraints(lc.getSearchConstraints());
searchConstraints.setMaxResults(maxResultSet);
lc.setConstraints(searchConstraints);

try
{
// connect and bind to the server
lc.connect( host, port );
lc.bind( LDAPConnection.LDAP_V3, user, new java.lang.String(password).getBytes("UTF8") );

var searchResults = lc.search(base,
searchScope,
filter,
attrArray, // return all attributes
false); // return attrs and values

/* process the results
* -- The first while loop goes through all the entries
* -- The second while loop goes through all the attributes
* -- The third while loop goes through all the attribute values
*/
while (searchResults.hasMore())
{
var nextEntry = searchResults.next();

// create the instance node
var instanceElement = document.createElement("instance");
instanceElement.setAttributeNS(null,"src-dn", nextEntry.getDN());

var attributeSet = nextEntry.getAttributeSet();
var allAttributes = attributeSet.iterator();

// create the attr nodes
while(allAttributes.hasNext())
{
var attribute = allAttributes.next();
var attrElement = document.createElement("attr");
attrElement.setAttributeNS(null, "attr-name", attribute.getName());

var allValues = attribute.getStringValues();

// create the value nodes

if( allValues != null)
{
while(allValues.hasMoreElements())
{
var valueElement = document.createElement("value");
valueElement.appendChild(document.createTextNode(allValues.nextElement()));
attrElement.appendChild(valueElement);
}
instanceElement.appendChild(attrElement);
}
}
outputElement.appendChild(instanceElement);
nodeSet.add(instanceElement);
}
}
catch(e)
{
var statusElement = document.createElement("status");
statusElement.setAttributeNS(null, "level", "error");
statusElement.appendChild(document.createTextNode(e.toString()));
outputElement.appendChild(statusElement);
nodeSet.add(statusElement);
}
finally
{
// disconnect with the server
lc.disconnect();
}
return nodeSet;
}

function serialize(nodeSet)
{
var stringWriter = new java.io.StringWriter();
for (var node = nodeSet.first(); node; node=nodeSet.next())
{
var domWriter = new Packages.com.novell.xml.dom.DOMWriter(node, stringWriter);
domWriter.setIndent(true);
domWriter.write();
domWriter.flush();
stringWriter.write('\n');
}
return stringWriter.toString();
}


Next up, we have one I got from Father Ramon in the support forums, append to file, which is a great way to write out to a log file, in the drivers local file system, via ECMA Script.



While we have a couple of send email tokens in DirXML Script, there is no simple way to log stuff into a file. Well now there is, courtesy of ECMA Script!



function appendToFile(fileName, text)
{
var fileWriter = new java.io.FileWriter(fileName, true);
try
{
fileWriter.write(text);
fileWriter.write("\n");
}
finally
{
fileWriter.close();
}
}


The next series turn out to be astonishingly fast and powerful implementations of a unique() function, a duplicate() function, and a blanks() function.



If you need to test if a value matches any single value in a node set, then DirXML Script is great for that. The equality test of a node set against a single value will test to see if that single value occurs anywhere in the values in the node set. The problem comes up when you have a node set with lots of values and you want to clean it up, so that any duplicates are removed, or else just want the duplicates, then these functions can really help.



Now you could loop through one of those node sets, and then test the current node value against the entire node set, but it turns out that when you have a couple of thousand of objects in the node set it can take a really long time to do this kind of a compare. Now there turn out to be a number of tricks to make this more efficient and I discuss a number of them in my series on toolkit rules:










Things like disabling trace is a big one, but there are all sorts of interesting other things that can be done, to specifically tone down trace when it is painful to performance.



My first iteration of a rule working with about 6000 nodes using a brute force approach ran for over 2 hours before I killed it and gave up. Using this set of functions, I got the run time down to under two minutes total (and it had to do a lot more than just this one function).



importPackage(Packages.com.novell.xml.xpath);
importPackage(Packages.com.novell.xsl.util);
importClass(java.util.HashSet);

/** Create a NodeSet that eliminates nodes from the input nodeset with duplicate string values
* @param {String} input the input NodeSet
* @type NodeSet
* @return a NodeSet that eliminates nodes from the input nodeset with duplicate string values
*/
function unique(input)
{
// create the output node set
var nodeSet = new InsertionOrderNodeSet();

// create HashSet to keep track of duplicates
var set = new HashSet();

// loop through the Nodes in the NodeSet
for (var node = input.first(); node != null; node = input.next())
{
var value = Util.getXSLStringValue(node).toUpperCase();

if (!set.contains(value))
{
set.add(value);
nodeSet.add(node);
}
}
return nodeSet;
}


Next up is sort of the converse of unique() which is duplicates() which will return only the duplicate values in the nodeset it is passed. This is useful if you need to find out if any values in a node set are duplicates.



Note, you will need the import commands from the beginning of the unique() function if you want to use just this function.



/** Create a NodeSet that contains nodes from the input nodeset with duplicate string values
* @param {String} input the input NodeSet
* @type NodeSet
* @return a NodeSet that contains only nodes from the input nodeset with duplicate string values
*/
function duplicate(input)
{
// create the output node set
var nodeSet = new InsertionOrderNodeSet();

// create HashSet to keep track of duplicates
var set = new HashSet();

// loop through the Nodes in the NodeSet
for (var node = input.first(); node != null; node = input.next())
{
var value = Util.getXSLStringValue(node).toUpperCase();
if (set.contains(value))
{
nodeSet.add(node);
}
else
{
set.add(value);
}
}
return nodeSet;
}


Next up is bittest() which is great for when you want to look at bit mask attributes. For example the ACL attribute uses bit mask values to represent object rights as a decimal number. DirXML Script has no binary operators included, which normally is not an issue. However for times when you need it, here it is to use! You pass it a value, the bit in decimal that you want to test for, and it returns true or false.



/** Perform a binary compare for a bitmask
* @param {int} value binary value, as a decimal representation
* @param {int} bits binary value, as a decimal representation
* @type int
* @return boolean true or false if the tested VALUE contains the BIT specified.
*/
function bittest(value, bits)
{
return ((value & bits) == bits);
}


Well that is my collection so far, what can you contribute? Try and post it into the comments, and I can try and edit the article to add them. Or feel free to email me directly with your functions and I will add them to the article. My email is geoffreycarman@gmail.com so send away.



Update Jun 11, 2009: And the updates start coming in! Yay! Thanks to those who emailed me with samples.



Justin Birt referenced a bunch of other articles out there with nice ECMA examples in them:






Nathan Spears sent me this one, in the name of Father Ramon (The partonizing saint of DirXML as he used to say).




importClass(Packages.com.novell.xml.util.Base64Codec);

/**
* Convert a Base64 encoded GUID attribute value to an ASCII string
* in the format the NDS2NDS driver uses as its association.
*
* @param {String} s Base64 encoded GUID attribute value
*
* @type String
* @return ASCII string
*
* @throws IOException -
*/
function base64conversion(s)
{
var bytes = Base64Codec.decode(s);
var s1 = encodeAsciiHex(bytes);
return s1;
}

var digits = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F"
];

function encodeAsciiHex(abyte0)
{
var buffer = "";
for(var i = 0; i > 4 & 0xf];
buffer = digits[byte0 & 0xf];
}

return buffer;
}


This is useful to convert GUID's which are Base64 encoded, back into strings.

Updated Oct 29, 2009



Michael Brian Hansen, from Denmark, from KMD A/S consulting, which you can find at their website http://www.kmd.dk sent me the following samples:



Read froma file:

function ReadFromFile(fileName)
{
// Open an input stream
var fileReader = new java.io.FileInputStream(fileName);
try
{
// Read a line of text
var Indhold = new DataInputStream(fileReader).readLine();
return Indhold;
}
finally
{ // Close our input stream
fileReader.close();
}
}


He also sent: Decrypt PGP password key:



function PGP(UserPasswordGPG)
{
var path = "/tmp/idmlog/";
var fileName = "log.txt";
var fileName_err = "log_err.txt";
var password_enc = UserPasswordGPG;
var pgpcmd = "gpg --homedir /var/novell --decrypt --batch";
var pgpmsg = "-----BEGIN PGP MESSAGE-----\n\n" password_enc "\n-----END PGP MESSAGE-----";
var cmd = new Array();
cmd[0] = "/bin/sh";
cmd[1] = "-c";
cmd[2] = "echo \"" pgpmsg "\"" " | " pgpcmd;
var rt = java.lang.Runtime.getRuntime();
try {
var pr = rt.exec(cmd);
}
catch(err) {
//print(err)
appendToFile(fileName_err,err);
quit(1);
}
var input = new java.io.BufferedReader(new java.io.InputStreamReader(pr.getInputStream()));
var input_err = new java.io.BufferedReader(new java.io.InputStreamReader(pr.getErrorStream()));
var line = null;
var output_txt = "";
while((line=input.readLine()) != null) {
//output_txt = output_txt "\n" line
//print(line);
return line;
}
var returnVal = pr.waitFor();
}


Note:
‘/var/novell’ path, is where you place the ‘pubring.gpg secring.gpg trustdb.gpg’ file.



Michael also included a library file, attached as libAJC.txt (since I cannot attach a .xml file) that has a whole stack of ECMA functions! (2000 lines of ECMA!). Great stuff, so much I have not even had time to process and work through them all yet.



Update Jan 13, 2010: I found out more of the origins of the libAJC library file. It looks like Novell Consulting had a custom JAR file, full of useful Java classes. This got ported to ECMA and is now included with most of the Resource Kit inspired driver configurations. This helps clarify what this library is for. There are some 80 functions in there, and many of them have since been superceeded by DirXML Script tokens (like many of the time format conversion ones) but are still there for compatability).



Keep them coming! What I think I will do is add all the examples I get into a Library file in my scratchbook Designer project and attach it as a file so you can download a single file with all the examples so far.



I attached it as ECMALibrary.txt, but rename it to XML (Since Cool Solutions does not want me to upload XML files to it) and import it into your Designer to see all the ECMA examples so far! Stick it in a Library in your project and you should be good to use any of them as needed! (Libraries are cool!)





Tags:

Labels:

How To-Best Practice
Comment List
  • Great collection!

    Found one perfomance issue with ldapSearch.

    Every result will be added to the nodeSet and in addition to the outputElelement. But beside its name, the outputElement is never put out nore used in any way.

    So this line

    outputElement.appendChild(instanceElement);

    is useless and can be removed / commented out.

    The benefit of removing it is a huge performance improvement, especially on ldap queries which return a lot of objects.

    Below graph shows results of an ldap query, which returns 90k objects with 4 attributes each, each attribute has only one value.

    The blue curve without the above mentioned line, the orange curve with the above mentioned line.

    x-axis are seconds ( please note the logrithmic scale )

    y-axis are number of objects

    Here are some numbers

    objects, seconds fast, seconds slow

    100, 0.144, 0.148

    500, 0.301, 0.343

    1000, 0.393, 0.556

    2000, 0.533, 1.277

    5000, 0.887, 7.239

    10000, 1.415, 28.42

    20000, 2.485, 109.9

    50000, 5.226, 711.0

    90000, 8.856. 2404

  • Used to figure the context (topmost node) of a nodeset local variable:

    /**
    * @param {com.novell.xml.xpath.NodeSet} nodeset Local Variable of type nodeset
    * @return {string} result of .first().getNodeName() applied against the nodeset
    */
    function nodeName( nodeset ) {
    return nodeset.first().getNodeName();
    }
  • I use this as a poor man's ParseDN function in workflows. I needed to find out what the root domain is for an AD domain. It could be one of many;

    .indexOf('dc=com') > 0

    A typical use;

    if (flowdata.get(someVariable).indexOf('someSubString')>0)
    doSomething
    else
    doSomethingElse;

    the .indexOf will return zero if the string is not found. So, if the root domain is dc=com, then it will be a non-zero return. Substitute according to your needs.
  • A useful function to get the byte length of a string. I use this for length restrictions on PeopleSoft fields. PeopleSoft has a byte length restriction and not a character length restriction. xpath string-length() only counts the number of characters, so with multibyte characters it gets nasty.

    /**
    * Returns the byte count of a string.
    *
    * @param {String} str input text string
    *
    * @type Number
    * @return byte length
    */
    function getByteCount(str)
    {
    var s = 0;
    for(var i = 0; i
    {
    var c = str.charCodeAt(i);
    if(c
    if(c
    if(c
    s += 4;
    }
    return s;
    }
  • I think you might find this useful - it converts text to markup.
    www.textism.com/.../index.php

    The best I could find in terms of an EBCDIC to ASCII converter was this one.
    www.pacsys.com/ebccnv.htm

    I'd love to see someone write a plugin to Notepad++ to do conversions like these. :)
  • I agree fully. Lets keep updating both the Wiki and the article. The more copies of the data, the easier people will find it when they search.

    You are welcome to copy all the examples here into the Wiki if you feel like it. I just don't like editing text in a format other than raw ASCII and then using markup on it. (Though I suppose LROFF would be ok... Or maybe TeX. )

    ASCII rules! EBCDIC sucks! (Oh no, IBM'ers with pitchforks at the door!)

    As a funny aside, I was working on an AS400 driver, and the log file, is actually written in EBCDIC! And if you did not transfer it properly, it would look like gooble-de-gook.

    Very funny! Otherwise I would have said I am young enough never to have needed to touch/see EBCDIC. But alas that is no longer true!

    Darn you IBM! Darn you to sock heck! You have ruined my single basic encoding method streak! (Like my streak on going up the Empire State Building (86 stories, once), CN Tower (100+ stories, 5 times), and Rockfeller Center (66 stories, once)! Only ever taken elevators DOWN from them! Stairs up all the way so far! )
  • Thanks for kicking this off and contributing so much! I've seen and used a lot of JavaScript that can be used in ECMAScript for IDM but there are situations where it just won't work. I think it may be useful at some point to have an article written with guidelines for exactly what can and can't be done within IDM's ECMAScript. I've been meaning to look into it but haven't had the chance yet - perhaps you will before me? ;)
Related
Recommended