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:

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>3.2.5</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>com.igormaznitsa</groupId>
                <artifactId>jcp</artifactId>
                <version>7.1.1</version>
                <executions>
                    <execution>
                        <id>preprocessSources</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>preprocess</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${basedir}/src/main/java/rules</source>
                            </sources>
                            <target>${project.basedir}/src/main/resources/rules</target>
                            <!-- <keepComments>REMOVE_JCP_ONLY</keepComments> -->
                            <keepComments>false</keepComments>
                            <keepLines>false</keepLines>
                            <dontOverwriteSameContent>true</dontOverwriteSameContent>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

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;
//#endif

    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() {
        loadConfiguration();
        String domain = getDomain(identity);
        if (domain == null) {
            return null;
        }
        return generateUserPrincipalName(identity.getFirstname(), identity.getLastname(), domain);
        //#//
    }
//#//
}
//$$  ]]></Source>
//$$</Rule>

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/UsernameGeneratorTest.java at a4fdf22221b1d5a0cd8f4f4f9db414c511c4b84a · sailpoint-oss/rule-development-kit · GitHub

    @Test
    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