JavaScript Extensions API




IBM Security






© Copyright International Business Machines Corporation 2003, 2012. All rights reserved.
US Government Users Restricted Rights – Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.



Purpose of this Document
Overview
FESI API Description
Platform Specific API Description
API Example

Purpose of this Document

The Identity Governance provisioning platform is designed with extensibility as a primary goal. Many of the architectural components have been built using frameworks that can make use of custom extensions. This document describes the framework used within the scripting component of Identity Governance and how it can be extended. The part of the framework that will be extended by a client will be called the Application Programming Interface (API). The APIs for extending the scripting component are specific to the scripting language configured with the product. This document specifically addresses extending the JavaScript configuration.

Overview

The scripting API is based on the use of the Identity Governance script framework. The purpose of extending the script framework is not necessarily to alter the capabilities of the script interpreter but to add additional objects and functions to the interpreter so clients can use a richer set of capabilities than provided by default.

The API provided by the script framework allows clients to create and register these additional objects and functions with the interpreter so they can be executed at runtime. Although the script framework internally uses the Apache Bean Script Framework (BSF) the APIs are not tied to BSF or any other third party framework. This decoupling of responsibilites means that changes to BSF, the script interpreter, or many of the other framework components will not break script extensions written for the script framework.

The script framework API consists of a set of interfaces, abstract classes, and classes that provide integration with the script interprter for creating and retrieving information available in the runtime context of the interpreter, as well as for registering new types of objects and functions that user scripts can use. The general set of script framework classes are found in the com.ibm.itim.script and com.ibm.itim.script.wrapper packages.

Script Framework API Description

The ScriptExtension interface is the natural place to start when learning about the script framework. The script framework uses ScriptExtensions to add new script objects and functions. ScriptExtension defines two methods that must be implemented: initialize() and getContextItems().

initialize() will always be called by the script framework before getContextItems(). initialize() is passed two parameters, a ScriptInterface object, which is discussed in more details later, and a ScriptContextDAO object. The ScriptContextDAO object provides the extension methods to interact with other script objects. The ScriptExtension should maintain a reference to the ScriptContextDAO if the extension needs to communicate with other objects.

The ScriptInterface object passed to initialize() provides some information about the host component that is using the extension. ScriptInterface itself contains no methods that must be implemented, but other interfaces that subclass ScriptInterface do. For example, ModelScriptInterface specifies three methods that must be implemented to provide information to the model extensions, and SubjectScriptInterface contains a single method. If a new extension requires information from both the ModelScriptInterface and SubjectScriptInterface then the initialize() method should check that the provided ScriptInterface class implements both.

public void initialize(ScriptInterface si, ScriptContextDAO dao)
                throws ScriptException, IllegalArgumentException {
        if (!(si instanceof ModelScriptInterface) && 
            !(si instanceof SubjectScriptInterface)) {
                 // Best to include a more detailed message.
                 throw new IllegalArgumentException("bad si");
        }
        ...
}

A list of all of the ScriptInterface subclasses can be found in the ScriptInterface JavaDoc.

The initialize() method will also create the ContextItems that the extension wants exposed to scripts. A ContextItem is a simple object that stores a name with an object. The name is what the object is called in scripts. Currently there are three types of ContextItems: SimpleContextItem, ConstructorContextItem, and GlobalFunctionContextItem. Each of these ContextItem types are named to follow their function; for instance, to create a new global function use a GlobalFunctionContextItem. ContextItem has several static methods to create the different types of ContextItems.

To add a constructor to the script context, use a ConstructorContextItem. To create a ConstructorContextItem use the method ContextItem.createConstructor(String name, Class prototype). The first argument is the name of the constructor and the second argument is the Class object of the type to create. Due to a limitation in a supported scripting engine the type that you are constructing must have a default, no-argument constructor.

public void initialize(ScriptInterface si, ScriptContextDAO dao)
                throws ScriptException, IllegalArgumentException {
        // allItems is a private member data of the class.
        allItems = new ArrayList<ContextItem>();

        ContextItem constructor = ContextItem.createConstructor("Builder",
                StringBuilder.class);
        allItems.add(constructor);
}

Now scripts can create a new DistinguishedName object like:

var builder = new Builder("ou=mycorp")
builder.append(",ou=org");
builder.append(",dc=com");

To create a global function, use the GlobalFunctionContextItem. To create a GlobalFunctionContextItem use the method ContextItem.createConstructor(String name, GlobalFunction function). The GlobalFunction object is a special type of java object with a method named call. Whenever a global function added to the script environment is called, the call method associated with it gets called.

public class MyExtension implements ScriptExtension {
        static class EchoFunction implements GlobalFunction {
                public Object call(Object[] parameters)
                                throws ScriptEvaluationException {
                        if (parameters == null || parameters.length != 1) {
                                System.err.println("no args!");
                                return null;
                        }
                        System.out.println("echo: " + parameters[0].toString());
                        return null;
                }
        }

        ...
        private List<ContextItem> allItems;
        ...

        public void initialize(ScriptInterface si, ScriptContextDAO dao)
                        throws ScriptException, IllegalArgumentException {
                ...
                allItems = new ArrayList<ContextItem>();
                ...

                ContextItem globalFunc = ContextItem.createGlobalFunction("echo",
                                new EchoFunction());
                allItems.add(globalFunc);
        }
}

Now scripts can use the echo global function like this:

echo("Echo this sentence");

Notice in the code snippet above that EchoFunction is a static inner class of MyExtension.

As nice as global functions can be, they are not the preferred method to add functions to the script environment. The call method is passed an array of Objects as parameters, and the code must check the parameters, both in number and in type, to make sure that everything is correct. This is mistake prone. The preferred method is to create a Java class that contains the methods you want to expose, and then place that object into the scripting environment using a SimpleContextItem.

public class MyExtension implements ScriptExtension {
        static class EchoBean implements ExtensionBean {
                public void echo(String msg) {
                        System.out.println("echo: " + msg);
                }
        }

        ...
        private List<ContextItem> allItems;
        ...

        public void initialize(ScriptInterface si, ScriptContextDAO dao)
                        throws ScriptException, IllegalArgumentException {
                allItems = new ArrayList<ContextItem>();
                ...
                
                ContextItem echoBean = ContextItem.createItem("bean", new EchoBean());
                allItems.add(echoBean);
                ...
        }
}

After these changes, script can now call echo like this:

bean.echo("Echo this sentence");

So for the price of adding "bean" (or any other name you like) in front of the call you get to write less code to implement the functionality.

Notice that the class EchoBean implements ExtensionBean. ExtensionBean is a marker interface that tells the script framework that this class is adding methods to the scripting environment, and to not hide those methods. If EchoBean does not implement ExtensionBean, then when a script calls bean.echo() there will be an error about the method not existing and the script will fail.

Sometimes you want to wait until right before the script runs to add a ContextItem to the script environment. To do this, you can still create the ContextItem during the initialize() call, but you use the ContextItem.createItem(String name) method.

public class MyExtension implements ScriptExtension {
        ...
        private List<ContextItem> allItems;
        private ContextItem subject;
        ...

        public void initialize(ScriptInterface si, ScriptContextDAO dao)
                        throws ScriptException, IllegalArgumentException {
                ...
                allItems = new ArrayList<ContextItem>();

                subject = ContextItem.createItem("subject");
                allItems.add(subject);
                ...
        }

}

The value for the ContextItem gets set in the getContextItems() call. The getContextItems() returns a List of ContextItem objects and is called right before a script runs. To continue on with the example from above:

public class MyExtension implements ScriptExtension {
        ...
        private List<ContextItem> allItems;
        private ContextItem subject;
        ...

        public List getContextItems() {
                subject.setContextObject(new Object());

                return allItems;
        }

        public void initialize(ScriptInterface si, ScriptContextDAO dao)
                        throws ScriptException, IllegalArgumentException {
                ...
                allItems = new ArrayList<ContextItem>();
                ...
        }
}

In a more typical case, instead of setting the context object to a new object, the code would make use of the ScriptInterface passed into initialize:

public class MyExtension implements ScriptExtension {
        ...
        private List<ContextItem> allItems;
        private ContextItem subject;
        private SubjectScriptInterface subjectI;

        public List getContextItems() {
                subject.setContextObject(subjectI.getSubject());

                return allItems;
        }

        public void initialize(ScriptInterface si, ScriptContextDAO dao)
                        throws ScriptException, IllegalArgumentException {
                if (si instanceof SubjectScriptInterface) {
                        subjectI = (SubjectScriptInterface) si;
                } else {
                        throw new IllegalArgumentException("bad si");
                }

                allItems = new ArrayList<ContextItem>();
                ...
        }
}

Notice how we save a reference to the ScriptInterface in initialize, and then use it to get the current subject right before the script runs. That is all there is to creating a script extension. Below is the full source for an example that uses all of the different examples.

package com.ibm.itim.script;

import java.util.ArrayList;
import java.util.List;

import com.ibm.itim.dataservices.model.DistinguishedName;
import com.ibm.itim.script.extensions.SubjectScriptInterface;

public class MyExtension implements ScriptExtension {
        static class EchoFunction implements GlobalFunction {
                public Object call(Object[] parameters)
                                throws ScriptEvaluationException {
                        if (parameters == null || parameters.length != 1) {
                                System.err.println("no args!");
                                return null;
                        }
                        System.out.println("echo: " + parameters[0].toString());
                        return null;
                }
        }

        static class EchoBean implements ExtensionBean {
                public void echo(String msg) {
                        System.out.println("echo: " + msg);
                }
        }

        private List<ContextItem> allItems;
        private ContextItem subject;
        private SubjectScriptInterface subjectI;

        public List getContextItems() {
                subject.setContextObject(subjectI.getSubject());

                return allItems;
        }

        public void initialize(ScriptInterface si, ScriptContextDAO dao)
                        throws ScriptException, IllegalArgumentException {
                if (si instanceof SubjectScriptInterface) {
                        subjectI = (SubjectScriptInterface) si;
                } else {
                        throw new IllegalArgumentException("bad si");
                }

                allItems = new ArrayList<ContextItem>();

                subject = ContextItem.createItem("subject");
                allItems.add(subject);

                ContextItem echoBean = ContextItem.createItem("bean", new EchoBean());
                allItems.add(echoBean);

                ContextItem globalFunc = ContextItem.createGlobalFunction("echo",
                                new EchoFunction());
                allItems.add(globalFunc);

                ContextItem constructor = ContextItem.createConstructor("DN",
                                DistinguishedName.class);
                allItems.add(constructor);
        }
}

Platform Specific API Description

Beyond the basic script extension capabilities provided by the Script Framework API, there are additional capabilities provided by the Identity Governance provisioning platform to ensure a more cohesive integration with the provisioning business logic executed by the platform. For example, the interpreter is constructed by the platform itself, so how are the extension classes provided to the interpreter?

Script interpreters are constructed in various areas of the platform’s business logic, each with a different context. For example, an interpreter is used when evaluating Provisioning Policy parameters, when evaluating Identity Policies, when determining the placement of identities in the platform’s org chart from an identity feed, and a few others. Because the context in which the platform constructs the interpreters can be quite different, they each have a different set of extensions that can be loaded. These extensions are defined within the scriptframework.properties file. The property file is formatted with the standard Java Properties <key>=<value> format, where the key is the interpreter context and the value is the full class name of the Script Extension to register. The following line is an example of registering an extension for the Identity Policy context:

ITIM.extension.IdentityPolicy=com.ibm.itim.policy.script.IdentityPolicy

There is one slight twist to this file format. Since all keys in the Java Properties format must be unique, and because the platform supports multiple extension classes to be registered per context, the key used in the file really just needs to be prefixed with the platform context name. These context names, or prefixes, are listed below:

Script Class Key Description
ITIM.extension.PostOffice Extensions registered using this key are loaded by Post Office templates.
ITIM.extension.ProvisioningPolicy Extensions registered using this key are loaded by Provisioning parameters.
ITIM.extension.AccountTemplate Extensions registered using this key are loaded by Account Template parameters.
ITIM.extension.HostSelection Extensions registered using this key are loaded by Service Selection policies.
ITIM.extension.PersonPlacementRules Extensions registered using this key are loaded by placement rules during identity feeds.
ITIM.extension.Workflow Extensions registered using this key are loaded by workflow scripts.
ITIM.extension.Reminder Extensions registered using this key are loaded by email reminder templates.
ITIM.extension.IdentityPolicy Extensions registered using this key are loaded by Identity Policies.
ITIM.extension.OrphanAdoption Extensions registered using this key are loaded by adoption scripts.
ITIM.extension.Notification Extensions registered using this key are loaded by notification templates.

Warning: The platform is deployed with a set of extensions for each context already defined in scriptframework.properties. Please do not remove these from the file, as this will cause the platform to behave improperly. Simply add additional extensions if needed using the context names as prefixes. For example, to add the extension examples.javascript.SampleExtension to the Identity Policy context without removing the installed extension, add the following line to the scriptframework.properties file:

ITIM.extension.IdentityPolicy.sample=examples.javascript.SampleExtension

Another opportunity for a more cohesive integration with the platform is when throwing exceptions from extensions. When an extension is executed and errors are detected, most often when the script author misuses the extension, exceptions should be thrown from the extension business logic. These exceptions are generally converted into error messages that end users of the provisioning platform will see. The signatures provided by the script framework API’s identify a ScriptException as the exception object to be thrown. However, Identity Governance supports internationalization (I18N), so that all messages to the end user can be translated to the locale of the end user. The ScriptException class is an extension of the ITIMException class, which supports the identification of a resource bundle key and a set of parameters that can be used to generate error messages using the correct locale.

API Example

Please see the accompanying example code with this package.