Introducing the Rule Development Kit

I was watching this presentation ( Getting started with the SailPoint Rule Development Kit ) and thought I would share some changes that I’ve made to the RDK to make my life easier.

Two things bothered me that I could solve:

My solution was to use a plugin to generate the XML code from the Java classes at compile time.
Using the plugin com.igormaznitsa:jcp in pom.xml:

                            <!-- <keepComments>REMOVE_JCP_ONLY</keepComments> -->

and creating the Java class like this:

//#outname "Rule - AttributeGenerator - UserPrincipalNameGenerator.xml"
//$$<?xml version='1.0' encoding='UTF-8'?>
//$$<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
//$$<Rule name="UserPrincipalNameGenerator" type="AttributeGenerator">
//$$    <Description>Generate a unique UserPrincipalName for Active Directory.</Description>
//$$    <Source><![CDATA[
package rules;

import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import sailpoint.object.Application;
import sailpoint.object.Identity;
import sailpoint.server.IdnRuleUtil;

//#if false

 * ...
@SuppressWarnings({"rawtypes", "FieldMayBeFinal"})   // BeanShell doesn't support generics
public class UserPrincipalNameGenerator {

    Logger log = LogManager.getLogger(rules.UserPrincipalNameGenerator.class);
    Identity identity = new Identity();
    Application application = new Application();
    IdnRuleUtil idn;

    private static String RULE_CONFIG_ATTRIBUTE = "userPrincipalNameGeneratorConfig";
    private static int MAX_USERNAME_LENGTH = 64;

    public void loadConfiguration() {

    public String generateUserPrincipalName(String firstName, String lastName, String domain) {

    String generateUserPrincipalName(String firstName, String lastName, String domain, int maxUsernameLength) {

    String getCanonicalFirstName(String firstName) {

    String getCanonicalLastName(String lastName) {

    private static String capitalize(String lastName) {

    private static String replaceUnsupportedCharsWithSpace(String firstName) {

    private Map ASCII_REPLACEMENTS = Map.of(

    private String normalizeDiacritics(String text) {

    public boolean isUpnUnique(String upn) {

    String getDomain(Identity identity) {

    String beanshellEntry() {
        String domain = getDomain(identity);
        if (domain == null) {
            return null;
        return generateUserPrincipalName(identity.getFirstname(), identity.getLastname(), domain);
//$$  ]]></Source>

This allows to exclude some lines from XML (e.g. importing of org.apache.log4j.* classes, the beanshell entry function declaration), or blocks of code (documentation, class declaration and objects already present in context - log, identity, idn, application).
This code can be than broken in individual functions which can be easily tested independently, without having to set up identities or the BeanShell environment.

For my personal taste I would also try to reduce the duplicate code in the tests, I find such tests easier to read than rule-development-kit/src/test/java/sailpoint/ at a4fdf22221b1d5a0cd8f4f4f9db414c511c4b84a · sailpoint-oss/rule-development-kit · GitHub

    public void testRule_uniqueLogic() {
        mockIdentity("John", "Doe");

        mockAccountExists("[email protected]");
        mockAccountExists("[email protected]");

        assertEquals("[email protected]", evalRule());

Next step would be to use AssertJ as testing library instead of the plain JUnit.

1 Like