Property Value Substitution

In an environment with multiple IG instances, you can require similar but not identical configurations across the different instances.

Property value substitution enables you to do the following:

  • Define a configuration that is specific to a single instance, for example, setting the location of the keystore on a particular host.

  • Define a configuration whose parameters vary between different environments, for example, the URLs and passwords for test, development, and production environments.

  • Disable certain capabilities on specific nodes.

Property value substitution uses configuration tokens to introduce variables into the server configuration. For information, see Configuration Tokens.

The substitution follows a process of token resolution, JSON evaluation, and data transformation, as described in the following sections:

Configuration Tokens

A configuration token is a simple reference to a value. When configuration tokens are resolved, the result is always a string. Transformation described in Transformations can be used to coerce the output type.

Configuration Tokens for File System

IG provides ig.instance.dir and ig.instance.url to define the file system directory and URL for configuration files.

Their values are computed at startup, and evaluate to a directory such as $HOME/.openig (appdata\OpenIG). You can use these tokens in your configuration without explicitly setting their values.

For information about how to change the default values, see Change the Default Location of the Configuration Folders.

Syntax

Configuration tokens follow the syntax &{token[|default]}, as follows:

  • Are preceded by an ampersand, &

  • Are enclosed in braces, {}

  • Define default values with a vertical bar (|) after the configuration token

  • Are in lowercase

  • Use the period as a separator, .

When a configuration token is supplied in a configuration parameter, it is always inside a string enclosed in quotation marks, as shown in the following example:

"&{listen.port|8080}"

To escape a string with the syntax of a configuration token, use a backslash (\). The following string is treated as normal text:

"\&{listen.port|8080}"

A configuration property can include a mix of static values and expressions, as shown in the following example:

"&{hostname}.example.com"

Configuration tokens can be nested inside other configuration tokens as shown in the following example:

"&{&{protocol.scheme}.port}"

Default values or values in the property resolver chain can be nested, as shown in the following example:

"&{&{protocol.scheme|http}.port|8080}"

JSON Evaluation

JSON evaluation is the process of substituting configuration tokens and transforming JSON nodes for an entire JSON configuration. After JSON evaluation, all configuration tokens and transformations in the configuration are replaced by values.

At startup, IG evaluates the configuration tokens in config.json and admin.json. When routes are deployed, IG evaluates the configuration tokens in the route.

Configuration tokens are matched with tokens available in the chain of resolvers, and the configuration token is substituted with the value available in the resolver. For information about each of the resolvers mentioned in the following section, see Token Resolution.

IG searches for matching tokens in the chain of resolvers, using the following order of precedence:

  1. Local resolver:

    The route resolver for the route being deployed

  2. Intermediate resolver:

    All intermediate route resolvers (for example, for parent routes to the route being deployed) up to the bootstrap resolver

  3. Bootstrap resolver:

    1. Environment variables resolver

    2. System properties resolver

    3. Token source file resolvers

    4. Hardcoded default values

The first resolver that matches the token returns the value of the token.

If the token can’t be resolved, IG uses the default value defined with the configuration token. If there is no default value, the token can’t be resolved and an error occurs:

  • If the configuration token is in config.json or admin.json, IG fails to start up.

  • If the configuration token is in a route, the route fails to load.

When configuration tokens are nested inside other configuration tokens, the tokens are evaluated bottom-up, or leaf-first. For example, if the following configuration token takes only the default values, it is resolved as follows:

  1. "&{&{protocol.scheme|http}.port|8080}"

  2. "&{http.port|8080}"

    When &{protocol.scheme|http} takes the default value http.

  3. "8080"

    When &{http.port|8080} takes the default value 8080.

If the configuration includes a transformation, IG applies the transformation after the token is substituted. When transformations are nested inside other transformations, the transformations are applied bottom-up, or leaf-first. For more information, see Transformations.

Token Resolution

At startup, the bootstrap resolver builds a chain of resolvers to resolve configuration tokens included in config.json and admin.json. When a route is deployed, route resolvers build on the chain to add resolvers for the route.

Route Token Resolvers

When a route is deployed in IG a route resolver is created to resolve the configuration tokens for the route. The resolvers uses token values defined in the properties section of the route.

If the token can’t be resolved locally, the route resolver accesses token values recursively in a parent route.

For more information, about route properties, see Route Properties.

Environment Variables Resolver

When the bootstrap resolver resolves a configuration token to an environment variable, it replaces the lowercase and periods (.) in the token to match the convention for environment variables.

Environment variable keys are transformed as follows:

  • Periods (.) are converted to underscores

  • All characters are transformed to uppercase

The following example sets the value of an environment variable for the port number:

$ export LISTEN_PORT=8080

In the following IG configuration, the value of port is 8080:

{
  "port": "&{listen.port}"
}

System Properties Resolver

The system property name must match a configuration token exactly. The following example sets a system property for a port number:

$ java -Dlisten.port=8080 -jar start.jar

In the following IG configuration, the value of port is 8080:

{
  "port": "&{listen.port}"
}

Token Source File Resolvers

Token source files have the .json or .properties extension. The bootstrap resolver uses the files to add file resolvers to the chain of resolvers:

  • JSON file resolvers

    Token source files with the .json extension take a JSON format. The token name is mapped either to the JSON attribute name or to the JSON path.

    Each of the following .json files set the value for the configuration token product.listen.port:

    {
      "product.listen.port": 8080
    }
{
  "product.listen": {
     "port": 8080
  }
}
{
  "product": {
    "listen": {
      "port": 8080
    }
  }
}
  • Properties file resolvers

    Token source files with the .properties extension are Java properties files. They contain a flat list of key/value pairs, and keys must match tokens exactly.

    The following .properties file also sets the value for the tokens listen.port and listen.address:

    listen.port=8080
    listen.address=192.168.0.10

Token source files are stored in one or more directories defined by the environment variable IG_ENVCONFIG_DIRS or the system property ig.envconfig.dirs.

If token source files are in multiple directories, each directory must be specified in a comma-separated list. IG doesn’t scan subdirectories. The following example sets an environment variable to define two directories that hold token source files:

$ export IG_ENVCONFIG_DIRS="/myconfig/directory1,/myconfig/directory2"

At startup, the bootstrap resolver scans the directories in the specified order,

and adds a resolver to the chain of resolvers for each token source file in the directories.

Although the bootstrap resolver scans the directories in the specified order, within a directory it scans the files in a nondeterministic order.

Note the following constraints for using the same configuration token more than once:

  • Do not define the same configuration token more than once in a single file. There is no error, but you won’t know which token is used.

  • Do not define the same configuration token in more than one file in a single directory. An error occurs.

    This constraint implies that you can’t have backup .properties and .json files in a single directory if they define the same tokens.
  • You can define the same configuration token once in several files that are located in different directories, but the first value that IG reads during JSON evaluation is used.

When logging is enabled at the DEBUG level for token resolvers, the origin of the token value is logged.

If you are using the default logback implementation, add the following line to your logback.xml to enable logging:

<logger name="org.forgerock.config.resolvers" level="DEBUG" />

Transformations

A set of built-in transformations are available to coerce strings to other data types. The transformations can be applied to any string, including strings resulting from the resolution of configurations tokens.

After transformation, the JSON node representing the transformation is replaced by the result value.

The following sections describe how to use transformations, and describe the transformations available:

Usage

{
   "$transformation": string or transformation
}

A transformation is a JSON object with a required main attribute, starting with a $. The following example transforms a string to an integer:

{"$int": string}

The value of a transformation value can be a JSON string or another transformation that results in a string. The following example shows a nested transformation:

{
  "$array": {
    "$base64:decode": string
  }
}

The input string must match the format expected by the transformation. In the previous example, because the final transformation is to an array, the input string must be a string that represents an array, such as "[ \"one\", \"two\" ]".

In the first transformation, the encoded string is transformed to a base64-decoded string. In the second, the string is transformed into a JSON array, for example, [ "one", "two" ].

array

{"$array": string}

Returns a JSON array of the argument.

Argument
string

String representing a JSON array.

Returns
array

JSON array.

The following example transformation results in the JSON array [ "one", "two" ]:

{"$array": "[ \"one\", \"two\" ]"}

bool

{"$bool": string}

Returns a Boolean with a value represented by the argument.

Returns true if the input value equals "true" (ignoring case). Otherwise, returns false.

Argument
string

String containing the boolean representation.

Returns
boolean

Boolean value represented by the argument.

If the configuration token &{capture.entity}" resolves to "true", the following example transformation results in the value true:

{"$bool": "&{capture.entity}"}

decodeBase64

{
  "$base64:decode": string,
  "$charset": "charset"
}

Transforms a base64-encoded string into a decoded string.

If $charset is specified, the decoded value is interpreted with the character set.

Argument
string

Base64-encoded string.

Parameters
$charset

The name of a Java character set, as described in Class Charset.

Returns
string

Base64-decoded string in the given character set.

The following example transformation returns the Hello string:

{
 "$base64:decode": "SGVsbG8=",
 "$charset": "UTF-8"
}

encodeBase64

{
  "$base64:encode": string,
  "$charset": "charset"
}

Transforms a string into a base64-encoded string. Transforms to null if the string is null.

If $charset is specified, the string is encoded with the character set.

Argument
string

String to encode with the given character set.

Parameters
$charset

The name of a Java character set, as described in Class Charset.

Returns
string

Base64-encoded string.

int

{"$int": string}

Transforms a string into an integer.

If the parameter is not a valid number in radix 10, returns null.

Argument
string

String containing the integer representation.

Returns
int

Integer value represented by the argument.

The following example transformation results in the integer 1234:

{"$int": "1234"}

list

{"$list": string}

Transforms a comma-separated list of strings into a JSON array of strings

Argument
string

A string representing a comma-separated list of strings.

Returns
array

The JSON array of the provided argument. Values are not trimmed of leading spaces.

The following example transformation results in the array of strings ["Apple","Banana","Orange","Strawberry"]:

{"$list": "Apple,Banana,Orange,Strawberry"}

The following example transformation results in the array of strings ["Apple"," Banana"," Orange"," Strawberry"], including the untrimmed spaces:

{"$list": "Apple, Banana, Orange, Strawberry"}

The following example transformation results in the array of strings ["1","2","3","4"], and not an array of JSON numbers [1,2,3,4]:

{"$list": "1,2,3,4"}

number

{"$number": string}

Transform a string into a Java number, as defined in Class Number.

Argument
strings

A string containing the number representation.

Returns
number

The number value represented by the argument.

The following example transformation results in the number 0.999:

{"$number": ".999"}

object

{"$object": string}

Transforms a string representation of a JSON object into a JSON object.

Argument
string

String representation of a JSON object.

Returns
object

JSON object of the argument.

The following example transformation

{"$object": "{\"ParamOne\":{\"InnerParamOne\":\"InnerParamOneValue\",\"InnerParamTwo\": false}}"}

results in the following JSON object:

{
  "ParamOne": {
    "InnerParamOne": "myValue",
    "InnerParamTwo": false
  }
}