Wikis - Page

Let's talk some more about Packages in Designer 4 - Part 7

0 Likes
Novell Identity Manager initially came with a Console One plugin to manage policies and stylesheets, back when it was still called DirXML. Then as the product matured and DirXML Script became available we got iManager with plugins to manage it.

When Designer for Identity Manager was released, it really was a game changing paradigm shift for Novell's product. Being able to take a project offline, work on it, come back, push out (and double check) your changes made how we work on IDM projects totally different. Documentation generation did not hurt either!

Each release of Designer has added new features and functionality. I recently worked through a series on what is new in Identity Manager 4 Advanced Edition (and compared it to what was introduced with IDM 3.5 and 3.6 releases), which you can read here:





Of course most of those features discussed included support for managing them in Designer. In fact, the series on what has changed is more focused on what has changed in Designer. But that was a high level overview and so much more has changed down low in weeds it is going to take a fair bit of work to get through them all.

With Identity Manager 4 Advanced Edition there are some major engine and Designer changes that are yet another example of a paradigm shift, and it can be summed up in one word, Packages. I started talking about Packages in these articles:








There is so much to say about packages, that I wanted to continue on to some more of the interesting features. I realized after I finished the last article that this would really just be a series regurgitating the documentation, so I actually went and checked the documentation to see how much is there. That is when I realized there is almost nothing about building packages in the docs. There is a single long page with almost no useful details in it, so hopefully this series will be a good resource for others. If you see some details in a documentation page missing please be sure to use the Submit Comment button, as there really is a person at the other end of that link. (A busy person usually, but they do often respond).

In the first article Let's talk some more about Packages in Designer 4 - Part 1 I talked about versioning, base packages, building prompts, interesting linkages, GCV and filter extensions.

In the second article Let's talk some more about Packages in Designer 4 - Part 2 I talked about more details in the nitty gritty about packages like localization, dependencies and ordering, and finally about the package catalog and what is stored there.

In the third article Let's talk some more about Packages in Designer 4 - Part 3 I discussed how the process of adding a driver changes with the new package model.

In the fourth article Let's talk some more about Packages in Designer 4 - Part 4 I discussed options about building packages and some of the states that a package might go through in terms of usage, and in terms of recommended development paths.

In the fifth article Let's talk some more about Packages in Designer 4 - Part 5 I started looking at the various resource types that can be part of a package, and how they differ from their standard representation in a driver which would include the various new features added to support the package infrastructure. I was able to cover Global Configuration objects and Filter extensions.

In the sixth article Let's talk some more about Packages in Designer 4 - Part 6 I talked about how objects are named and how a Resource can make changes to the configuration as it is being imported.

In this article I would like to continue with further examples of how the XSLT in Prompt Resources can manipulate the driver configurations. The documentation on packages is pretty light at the moment, so I have been browsing through the packages included with IDM 4, specifically the Active Directory driver, the Managed Systems Gateway driver (used by Reporting) and the Data Collection Service driver (also used by Reporting), looking for interesting items. In the previous article I found a very simple example of using XSLT in the Target Transformation section of a Prompt resource to set an Engine Control Value. This time I found something more complicated!

In fact reading it, I see I answered a question I had in the previous article. When does the 'Prompts Transformation' apply? Before or after the prompts are displayed. I assumed after. But I see from the comments in this XSLT that it is applied before displaying the prompts. Interesting.

The object I found of interest is NOVLIDMDCSB-RegistrationPrompts which is from the Package for Data Collection Service Base, which you can derive from the package short name. Novell recommends that it be up to 12 characters long, all upper cased for some reason, with three parts to the naming model.

Usually a four character vendor identifier. Novell of course uses NOVL, and this helps deconflict naming issues. Then the next segment, usually four characters long should be related to the package name. The last segment should be related to the functionality being delivered. Like BASE, ENTL, PASS, etc for Base functionality, Entitlement handling, and Password management for example. In this case IDMDCSB looks like they broke the rule, and decided to go with the last two components munged together to give us something like Identity Manager Data Collection Service Base (IDMDCSB).

Well its like the Pirates Code, its more of recommendations. (My wife loves that line in the movie). After all nothing is being enforced other than uniqueness within your package catalog. In other words a naming collision would mean you could not import a package with the same short name into your package catalog of this project. (Though I have not actually tried to see if it is really enforced on not. Let's just leave it as a bad plan). So if you are vendor building packages, make sure to use your stock code of the like in the name, and keep it unique within your products.

This was the Prompt Transformation I found, and the XSLT is below. Let's see what there is to see:
<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
Initial Settings - Prompt Style Sheet
=====================================

This style sheet handles initial settings prompts. It pre-populates the
fields with existing values.

The prompt style sheet is applied to the XML representation of the prompts
(GCV's) for this prompt resource before the prompts are displayed for
input.

Updated: 20100714
-->
<!--
defsDoc
=======
XML document containing the prompts (configuration value definitions)
including their values that were entered for this prompt page.

Sample document:
<configuration-values>
<definitions>
<header display-name="Authentication"/>
<definition display-name="SAP User ID" mandatory="true" name="shim-auth-id" type="string">
<description>The ID of the User this driver will use for SAP Logon. This is referred to as 'User' in the SAP Logon screen.</description>
<value>idmdriver</value>
</definition>
<definition display-name="SAP User Password" mandatory="true" name="shim-auth-id" type="password-ref">
<description>The User password this driver will use for SAP Logon. This is referred to as 'Password' in the SAP Logon screen.</description>
<value>shim-auth-password</value>
</definition>
</definitions>
</configuration-values>
-->
<xsl:param name="defsDoc"/>
<!--
curDoc
======
In the case of an upgrade or downgrade, this parameter contains the XML
content of the currently installed prompt target.

Sample document (only an excerpt, these docs are rather large):
<ds-attributes>
<ds-attribute ds-attr-name="shim-auth-id">
<ds-value>idmdriver</ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="shim-auth-server">
<ds-value>127.0.0.1</ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="driver-start-option">
<ds-value>2</ds-value>
</ds-attribute>
</ds-attributes>
-->
<xsl:param name="curDoc"/>
<!--
npDoc
=====
In the case of an upgrade or downgrade, this parameter contains an XML
representation of all named password available on the prompt target.

Note:
Only the names of existing passwords are available, not their values. If a
named password has been set through a prompt, both its name and value are
available.

Note2:
To set a named password, append the following structure to the target
(target must support named passwords):
<ds-attribute ds-attr-name="named-password">
<ds-value display-name="Password 1" name="pwd1">1</ds-value>
<ds-value display-name="Password 2" name="pwd2">2</ds-value>
</ds-attribute>

Sample document:
<named-passwords>
<named-password name="promptedPwd">promptedValue</named-password>
<named-password name="existingPwd"/>
</named-passwords>
-->
<xsl:param name="npDoc"/>
<!--
propertyWizard
==============
Flag (true or false) indicating if the installation is from the package
installation wizard (PIW, launched from the "Packages" property page) or
from the driver configuration wizard (DCW, launched by installing a new
driver). This flag can be useful to show or hide prompts or to set or not
set values based on which wizard is being run.

E.g. the driver name should only be prompted for in the DCW, not in the
PIW where the driver already exists.

Possible values:
'true' -> PIW
'false' -> DCW
-->
<xsl:param name="propertyWizard"/>
<xsl:template match="header[@driver-name='true']">
<xsl:if test="$propertyWizard='false'">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="definition[@driver-name='true']">
<xsl:if test="$propertyWizard='false'">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- pre-populate prompts with existing values -->
<xsl:template match="definition/value">
<xsl:variable name="name" select="../@name"/>
<xsl:variable name="curVal">
<xsl:choose>
<xsl:when test="$curDoc//ds-value[../@ds-attr-name=$name]/text()">
<xsl:value-of select="$curDoc//ds-value[../@ds-attr-name=$name]/text()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$curDoc//value[../@name=$name]/text()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<!-- backfilling from current value -->
<xsl:when test="$curVal">
<xsl:variable name="checkRemote">
<xsl:choose>
<xsl:when test="$name='shim-auth-server' or $name='shim-auth-password'">
<xsl:value-of select="'true'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'false'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$checkRemote='true' and starts-with($curVal, 'REMOTE')">
<value>
<xsl:value-of select="substring-after($curVal, ')')"/>
</value>
</xsl:when>
<xsl:otherwise>
<value>
<xsl:value-of select="$curVal"/>
</value>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- no current value found -->
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- identity transformation template -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Starting at the top we have a nice comment. Two thumbs up for that Novell! I love comments that explain what is going on as I almost always learn something from it. Then we see they did something I really recommend. They embedded a sample XML doc to show what their XPATH will be working on. That was in the defsDoc sections.
	defsDoc
=======
XML document containing the prompts (configuration value definitions)
including their values that were entered for this prompt page.

I highly recommend doing this where it makes sense. I started doing in when I was working on a SOAP driver to Salesforce.com since I was converting XML from their web service into XML in the XDS dialect (Novell's XML dialect for IDM) using DirXML Script policy instead of XSLT. I found by leaving an XML snippet in the Comment on the Rule, I could come back later to fix a bug, copy the XML out, and paste it into Simulator to see if my fix was working. Heck I even left the expected output document so a total newbie coming in to work on it would have a good chance of getting started with it. If you are interested in the SOAP driver you can read my series on building a SOAP driver to Salesforce.com in these following articles:










Then they did the same for the curDoc and npDoc. curDoc would be the current values, since Packages need to support upgrades and downgrades, in both cases you want to be able to preserve the current values. This tries to solve the settings problem, that Packages overall is trying to solve for Driver Configurations. In the case of driver configurations it was impossible to do a simple upgrade since all your changes would get over written. Thus Packages makes the default packages different, that is marked, and checksummed. If you change one it is flagged as modified, and Factory Mode can let you revert back to before the changes.

However settings are slightly different as they are expected to change on every driver, and possibly over time. (Remote Loader servers move around perhaps). Therefore you see this approach, where as an Upgrade or Downgrade is being performed the Package copies the values it is responsible for out, and copies them back into the new configuration XML thus allowing an overwrite of the configuration and a restore of the values. Along the way, you may wish for the configuration to change such that before you could run the Active Directory driver with Signing enabled but not SSL, but now if you have elsewhere selected Windows 2000 server, Signing and Sealing is not supported, so you might have to change around some settings. Silly example I know, but you get the point.

Next we have the property wizard flag. Turns out there are two different places the settings can be configured. During the install you use the Driver Configuration Wizard (DCW) to set all the properties with values, passwords and what not. Same would be true in an upgrade of downgrade example. However, once the driver is installed and running you might decide to change a configuration parameter at which point you would choose Properties of the object. It is then that the Properties Wizard (PIW, I do not know what the 'I' stands for) is presented. This allows you to ask different questions at different times in the lifecycle of a driver.

You can see that used right away, in the snippet:
<xsl:template match="header[@driver-name='true']">
<xsl:if test="$propertyWizard='false'">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>

Where they try to find the node <header driver-name="true"> whose with the test

header[@driver-name='true']

and then if the propertyWizard value is false, copy through the setting. Otherwise, do not copy them through. However the next token does the same for definitions with the same conditions.

Anyway, the next part is interesting:
<!-- pre-populate prompts with existing values -->
<xsl:template match="definition/value">
<xsl:variable name="name" select="../@name"/>
<xsl:variable name="curVal">
<xsl:choose>
<xsl:when test="$curDoc//ds-value[../@ds-attr-name=$name]/text()">
<xsl:value-of select="$curDoc//ds-value[../@ds-attr-name=$name]/text()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$curDoc//value[../@name=$name]/text()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<!-- backfilling from current value -->
<xsl:when test="$curVal">
<xsl:variable name="checkRemote">
<xsl:choose>
<xsl:when test="$name='shim-auth-server' or $name='shim-auth-password'">
<xsl:value-of select="'true'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'false'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$checkRemote='true' and starts-with($curVal, 'REMOTE')">
<value>
<xsl:value-of select="substring-after($curVal, ')')"/>
</value>
</xsl:when>
<xsl:otherwise>
<value>
<xsl:value-of select="$curVal"/>
</value>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- no current value found -->
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

So here our template match is for definition/value, and since all the configuration values start with a <definitions> node (note the plural), then a set of <definition> nodes underneath, this should select all the definition names. Though I wonder if this will work with Structured GCV's, since they exist inside a set of <instance> nodes, defined under the initial <definition> node. Well I guess since they are children of the <definition> node they ought be selected. However, when I went and made a quick test structured GCV, and looked at the XML, I see that in fact, it will work:

The GCV XML looks something like:
<configuration-values>
<definitions>
<definition display-name="xlfid(TEST.idmresource.TestGCVPrompt.gcv.Structure.GCV.Test)Test Structured GCV" instance-separator=":" name="Structure.GCV.Test" type="structured" value-separator=";">
<template>
<definition display-name="Class of the object" name="objectClass" type="string">
<description/>
</definition>
<definition display-name="List of attributes to filter" item-separator="," name="filterList" type="list">
<description/>
</definition>
<definition display-name="DN of workflow to call" dn-space="dirxml" dn-type="ldap" name="workflowDN" type="dn">
<description/>
</definition>
</template>
<value>
<instance>
<definition display-name="Class of the object" name="objectClass" type="string">
<description/>
<value>User</value>
</definition>
<definition display-name="List of attributes to filter" item-separator="," name="filterList" type="list">
<description/>
<value>
<item>Given Name</item>
<item>Surname</item>
</value>
</definition>
<definition display-name="DN of workflow to call" dn-space="dirxml" dn-type="ldap" name="workflowDN" type="dn">
<description/>
<value>cn=test,ou=AppConfig,ou=Driver,ou=DriverSet,o=Oname</value>
</definition>
</instance>
</value>
<description/>
</definition>
</definitions>
</configuration-values>

You can see a couple of things. First off, the name is now xlfid(somename) which allows for localization. These are the sorts of things that will get exported when you generate the localization files, and then import them back in.

Next there is the <template> section which defines what the constituent components of the structured GCV might be. My silly example is an object class (DN syntax), and then a list of attributes to filter (list syntax), and then another DN specifying the workflow this might trigger. We used this approach at a client with good success. I got the idea from the SAP HR CMP driver, where it is also used as a secondary filter structure.

But after the <template> node we have a <value> node, and then zero to many <instance> nodes, for each defined instance of the structured GCV.
You can read more about structured GCV's which were first introduced in IDM 3.6.1 and are really a lot of fun and a very useful construct in some cases in this article here: Structured Global Configuration Values in IDM

Once the definitions are selected, then we need to get the name of GCV into the variable name. With the XPATH of ../@name. That is one node up the definition/value path which is definition, and then the XML attribute of that called 'name', which would return the xlfid localized string.

The next part is hard to understand without knowing what the $curDoc variable is holding. We know from above it is holding the current prompt settings. The good news is the comments include some information about the structure:
<ds-attributes>
<ds-attribute ds-attr-name="shim-auth-id">
<ds-value>idmdriver</ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="shim-auth-server">
<ds-value>127.0.0.1</ds-value>
</ds-attribute>
<ds-attribute ds-attr-name="driver-start-option">
<ds-value>2</ds-value>
</ds-attribute>
</ds-attributes>

Thus the XPATH of $curDoc//ds-value[../@ds-attr-name=$name]/text() suggests that in the curDoc variable, find any child ds-value, (this way they do not need to specify the absolute paths, it searches, when using a //) whose parent node (the .. part) has an XML attribute ds-attr-name equal to our current GCV name. and use the text() function to make sure you get a string not a nodeset, which actually returns true if there is a value, and false if there is no value, therefore it is true if the value is set, and false if it is empty

Then if that is true, you set the local variable this test is nested inside to $curDoc//ds-value[../@ds-attr-name=$name]/text() which this same XPATH now gets the value, instead of returning true if there is a value. (Node test versus a select context).

Otherwise just use the value in the prompts XML. I.e. nothing to copy through.
<xsl:when test="$curDoc//ds-value[../@ds-attr-name=$name]/text()">
<xsl:value-of select="$curDoc//ds-value[../@ds-attr-name=$name]/text()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$curDoc//value[../@name=$name]/text()"/>
</xsl:otherwise>

Then it continues on further in a similar vein. You can see at least somewhat how the prompt system is configured from this example. It looks like there are some default variables provided, the curDoc, defsDoc, and npDoc, which you can work with and manipulate. I will keep looking for more interesting examples to work through if I can find them.
Comment List
  • The prompting for Structured GCVs at package installation with the default Target Transformation does not provide the expected result. In fact it destroys the validity of the GCV XML.
    But replacing the preset

    <!-- inject prompt values into target definitions -->
    <xsl:template match="definition/value">
    <xsl:variable name="name" select="../@name"/>
    <xsl:variable name="promptVal" select="$defsDoc//value[../@name=$name]"/>
    <xsl:choose>
    <!-- inject value from prompt -->
    <xsl:when test="$promptVal">
    <xsl:copy>
    <xsl:value-of select="$promptVal"/>
    </xsl:copy>
    </xsl:when>
    <!-- no current value found -->
    <xsl:otherwise>
    <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:template>

    with

    <!-- inject prompt instances of Structured GCVs into target definitions -->
    <xsl:template match="definition/value[../@type='structured']">
    <xsl:variable name="name" select="../@name"/>
    <xsl:variable name="promptVal" select="$defsDoc//value[../@name=$name]/instance"/>
    <xsl:choose>
    <!-- inject value from prompt -->
    <xsl:when test="$promptVal">
    <xsl:copy>
    <xsl:copy-of select="$promptVal"/>
    </xsl:copy>
    </xsl:when>
    <!-- no current value found -->
    <xsl:otherwise>
    <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:template>
    <!-- inject prompt values into target definitions -->
    <xsl:template match="definition/value[not(ancestor::definition/@type='structured')]">
    <xsl:variable name="name" select="../@name"/>
    <xsl:variable name="promptVal" select="$defsDoc//value[../@name=$name]"/>
    <xsl:choose>
    <!-- inject value from prompt -->
    <xsl:when test="$promptVal">
    <xsl:copy>
    <xsl:value-of select="$promptVal"/>
    </xsl:copy>
    </xsl:when>
    <!-- no current value found -->
    <xsl:otherwise>
    <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:template>

    does the trick.
Related
Recommended