Scripts
The script service compiles and evaluates JavaScript and Groovy scripts on behalf of other Wren:IDM components, and exposes ad-hoc script evaluation over REST.
- OSGi service class
-
org.forgerock.openidm.script.impl.ScriptRegistryService - OSGi persistent identifier
-
org.forgerock.openidm.script - Configuration file
-
script.json - Router mapping
-
/script
Scripts run throughout Wren:IDM: synchronization mappings, managed-object hooks, policy rules, authentication chains, and scheduled tasks all delegate to this service.
The service registers under four interfaces — ScriptRegistry (the in-memory registry of compiled scripts), RequestHandler (the /script endpoint), ScheduledService (script-triggered schedules), and ScriptExecutor (in-process invocation by other bundles).
Custom REST endpoints are provided by a cooperating component, the Custom Endpoints Service, documented in Custom endpoints.
Script engines
Wren:IDM ships with two script engines, configured under the ECMAScript and Groovy blocks of conf/script.json.
A script selects its engine through a type field:
-
text/javascript— JavaScript, evaluated by the bundled ECMAScript engine. -
groovy— Groovy; Groovy source files use the.groovyextension.
Configuration
The script service is configured by conf/script.json.
conf/script.json (structure){
"properties": { },
"ECMAScript": {
"javascript.recompile.minimumInterval": "60000"
},
"Groovy": {
"groovy.source.encoding": "UTF-8",
"groovy.target.directory": "&{launcher.install.location}/classes",
"groovy.classpath": "&{launcher.install.location}/lib",
"groovy.recompile": "true",
"groovy.recompile.minimumInterval": "60000"
},
"sources": {
"default": { "directory": "&{launcher.install.location}/bin/defaults/script" },
"install": { "directory": "&{launcher.install.location}" },
"project": { "directory": "&{launcher.project.location}" },
"project-script": { "directory": "&{launcher.project.location}/script" }
}
}
properties-
Global variables published to every script. Each entry becomes a binding named by its key, available to all scripts regardless of engine. Values may be strings, numbers, booleans, objects, or arrays; they are passed through without type coercion. In Groovy, JSON objects arrive as Java
Mapinstances and arrays as JavaListinstances. In JavaScript, the ECMAScript engine wraps these Java objects transparently, so they behave as ordinary JavaScript objects and arrays. Type: object. Default: empty. ECMAScript-
Settings passed to the JavaScript engine. Type: object. Required — the service fails to activate if this block is absent (an empty object
{}satisfies this requirement). Groovy-
Settings passed to the Groovy engine. Type: object. Optional — if absent, the Groovy engine uses its built-in defaults.
sources-
Named directories searched for script files referenced by name. Type: object. Default: the four entries shown above (
default,install,project,project-script).
The reserved binding names openidm, identityServer, and console are ignored if declared under properties; they always refer to the built-in bindings described in Script variables.
|
JavaScript engine settings
javascript.recompile.minimumInterval-
Minimum time in milliseconds that must elapse before a changed script is recompiled. Shipped value:
60000.
The service additionally injects javascript.exception.debug.info into the engine configuration, taken from the identity-server property of the same name and defaulting to false.
When true, JavaScript exceptions include additional debug information such as file name and line number.
The shipped conf/boot/boot.properties already contains this property set to false; to enable it, change the value to true:
javascript.exception.debug.info=true
Groovy engine settings
groovy.source.encoding-
Character encoding used to read Groovy source files. Shipped value:
UTF-8. groovy.target.directory-
Directory where compiled Groovy classes are written. Shipped value:
&{launcher.install.location}/classes. groovy.classpath-
Additional classpath made available to Groovy scripts. Shipped value:
&{launcher.install.location}/lib. groovy.recompile-
Whether changed Groovy scripts are recompiled at runtime. Shipped value:
true. groovy.recompile.minimumInterval-
Minimum time in milliseconds before a changed Groovy script is recompiled. Shipped value:
60000.
&{…} tokens are property substitutions resolved at startup; launcher.install.location is the installation directory and launcher.project.location is the project directory.
|
Script sources
A sources entry maps a name to a directory.
At activation each directory is registered as a script container, and scripts can then be referenced by a path relative to any source directory rather than by absolute path.
Sources are loaded in reverse declaration order.
A source declared later in the file therefore takes precedence: a script present under both project and default with the same relative path resolves to the project copy.
This is what lets a deployment override a shipped default script by placing a file of the same name under its project directory.
In the default configuration, project-script has the highest precedence, followed by project, install, and default (lowest).
To give a custom source directory the highest precedence, declare it last in the sources object.
The load order depends on the key order in the sources JSON object.
Wren:IDM’s JSON parser preserves insertion order (the underlying implementation uses LinkedHashMap), so the file order is the activation order.
Avoid JSON editors or prettifiers that sort or reorder object keys when editing script.json; if key order changes, the precedence hierarchy is silently altered and a deployment override may be shadowed by a shipped default without any error message.
|
The shipped sources are:
default-
&{launcher.install.location}/bin/defaults/script— scripts that ship with Wren:IDM. install-
&{launcher.install.location}— the installation root. project-
&{launcher.project.location}— the project root. project-script-
&{launcher.project.location}/script— project-specific scripts.
Script variables
Wren:IDM injects a fixed set of variables into the scope of every script.
All bindings described in this section — openidm, console, identityServer, and any configured properties — are available to both JavaScript and Groovy scripts.
Global bindings
These bindings are present in every script, regardless of how it is invoked.
openidm-
Resource and cryptography functions (see The openidm binding).
console-
Diagnostic output. Only
console.log(value)is provided; it prints a string argument to the server’s standard output. Non-string arguments are silently ignored —console.logdoes not coerce objects or numbers to strings. To log an object or array, pass it throughJSON.stringify(value)first. Other standard methods (console.error,console.warn, etc.) are not available. identityServer-
Accessors for server locations and boot properties (see The identityServer binding).
require(JavaScript only)-
Loads a named module from the configured script sources. The module name resolves to a
.jsfile relative to any configured source directory (using the same precedence order), sorequire('lib/lodash')loadslib/lodash.jsandrequire('crypto')loadscrypto.js. The loaded file must export its API via theexportsobject.
Groovy scripts do not have an equivalent to require().
To share code across Groovy scripts, package shared classes as JARs and place them on the groovy.classpath (the installation lib directory by default), then import them using standard Groovy or Java import statements.
|
In addition, every entry of the properties block in script.json is published as a binding of the same name.
Request bindings
A script that handles a router request — for example a custom endpoint — additionally receives:
request-
The CREST request being handled. Its type and payload let a single script serve multiple operations. Read
request.requestTypeto branch on the operation; its string representation is the uppercase operation name (READ,CREATE,UPDATE,DELETE,PATCH,QUERY, orACTION) for the seven standard CREST types routed to endpoint scripts. In Groovy,request.requestTypeaccesses the JavagetRequestType()method via Groovy property notation and returns aRequestTypeenum value; userequest.requestType.name()to obtain the uppercase string for comparison.The RequestTypeenum has an eighth constant,API, used internally for API Descriptor requests. Endpoint scripts are not invoked forAPIrequests; a defensivedefaultbranch in aswitchorif-chain covers this case automatically.request.contentcarries the JSON body of the request. It is present for Create, Update, and Action operations; for Read, Delete, and Query operations the content is absent or empty. In JavaScript,request.contentbehaves as a plain JavaScript object whose properties correspond to the JSON keys (for example,request.content.fieldName). In Groovy,request.contentreturns aJsonValue; call.asMap()to obtain a plainMap, or access individual keys viarequest.content.get("fieldName").asString(). When the key is absent,get("key")returns aJsonValuewrapping JSONnull; calling.asString()on it returns Javanullrather than throwing. Guard againstnullwhen the field is optional:String val = content.get("optKey").asString(); if (val != null) { … }.For Patch operations, the patch operations list is not exposed through request.content. Access it viarequest.patchOperations(Groovy property notation forgetPatchOperations()), which returns aList<PatchOperation>. Each entry exposes:getOperation()— the operation name string (replace,add,remove,increment,move, orcopy);getField()— the target field as aJsonPointer; andgetValue()— the operation value as aJsonValue. In JavaScript, the same list is accessible asrequest.patchOperations; each element behaves as a JavaPatchOperationobject. context-
The request context chain (security, HTTP, and routing context). Use
context.security.authenticationIdto read the caller’s authentication ID, andcontext.security.authorizationto access the authorization map (containingid,component,roles, and related keys). resourcePath-
The resource path of the request.
resourceName-
A deprecated alias of
resourcePath, retained for backward compatibility.
Scheduled bindings
A script run from a schedule receives the value of invokeContext.input from the schedule configuration under the object binding.
If invokeContext.input is absent from the schedule configuration, object is null.
The global bindings described in Script variables — openidm, console, identityServer, and configured properties — are also available to scheduled scripts.
See Scheduler for the schedule configuration format.
The openidm binding
The openidm binding exposes the router as a set of functions, so that scripts can perform resource operations against any Wren:IDM endpoint.
| Function | Description |
|---|---|
|
Creates a resource; returns the created resource content, including the server-assigned |
|
Reads a resource; returns its content, or |
|
Replaces a resource; returns the updated resource content.
Pass |
|
Applies a partial update; returns the updated resource content.
|
|
Deletes a resource; returns the deleted resource content. |
|
Runs a query; returns an object with |
|
Invokes an action; returns the action result. Three calling forms are available: * |
The same binding also provides the cryptography functions openidm.encrypt, openidm.decrypt, openidm.hash, openidm.isEncrypted, openidm.isHashed, and openidm.matches.
These are documented in Crypto Service — Script API.
The resource functions are wired to the IDM connection factory through an optional service reference.
If that reference is unavailable the resource functions are absent from the openidm binding, while the cryptography functions remain.
|
The identityServer binding
identityServer.getProperty(name, defaultValue, useCache)-
Returns a boot/system property by name. Property names are resolved in order: system properties first, then entries in
conf/boot/boot.properties, then explicit config properties. The common property names defined by the server are listed inconf/boot/boot.properties. Onlynameis required. When the property is not found,defaultValueis returned; passnullto receivenullon a missing property. WhenuseCacheistrue, the value is cached at the service level and reused across all subsequent script invocations; the cache is cleared when the script service is reconfigured or restarted. When omitted,useCachedefaults tofalseand the property is read on every call. identityServer.getWorkingLocation()-
Returns the absolute path of the working location.
identityServer.getProjectLocation()-
Returns the absolute path of the project location.
identityServer.getInstallLocation()-
Returns the absolute path of the installation location.
The /script endpoint
The /script endpoint evaluates a script supplied in the request.
Actions are invoked with POST /openidm/script?_action=eval or POST /openidm/script?_action=compile; the request body is a JSON object containing the script descriptor fields and any additional bindings.
It supports only the action operation; read, create, update, delete, patch, and query all return a Not Supported error.
The /script endpoint evaluates arbitrary code supplied by the caller.
HTTP access is governed by the router authorization filter (router-authz.js); by default it requires the openidm-admin role.
Verify that this endpoint is appropriately restricted in your deployment’s script/access.js before exposing it over a network.
|
Two action IDs are recognised:
compile-
Compiles the supplied script and returns
trueif compilation succeeds. Returns400 Bad Requestif compilation fails. All three addressing modes (source,file,name) are valid forcompile. eval-
Compiles and evaluates the script, returning its result serialized as JSON: a script returning an object produces a JSON object body, a string produces a JSON string, a number produces a JSON number, a boolean produces a JSON boolean, and returning
nullorundefinedproduces a JSONnullresponse. Returns400 Bad Requeston a compilation error, or500 Internal Server Errorif the script throws at runtime.
Any other action ID returns a Bad Request error.
Error responses from both actions use the standard CREST error body:
{
"code": 400,
"reason": "Bad Request",
"message": "<engine error text or exception message>"
}
The code and message fields are always present.
The reason field contains the HTTP reason phrase for the status code.
For a compilation failure, message contains the engine’s compile-error text (from ScriptCompilationException.getMessage()).
For a runtime script exception, message contains the thrown exception’s message.
No detail field is populated by the script endpoint.
In the request body, fields are split into script-descriptor fields and bindings: fields recognized as script-descriptor (name, file, source, type, revision, visibility, globals) configure the script, while all remaining body fields become bindings passed into the script.
URL query parameters (beyond the CREST-reserved _action) are also added to the bindings map.
This is intentional: sending POST /openidm/script?_action=eval&myVar=hello makes myVar available as a binding named myVar inside the script (as the string "hello").
Note that all additional parameter values arrive as strings; the script must parse them if a numeric or boolean type is needed.
The _action parameter is consumed by the CREST framework before the handler is called and does not appear as a binding.
Three mutually exclusive addressing modes identify which script to run:
source(inline)-
Supply
sourcewith the script body andtypeto select the engine. Valid values fortypearetext/javascriptandgroovy. file(source reference)-
Supply
filewith a path resolved against the configured script sources, andtypeto select the engine. Valid values fortypearetext/javascriptandgroovy. Path resolution uses the JavaFileAPI directly; on Linux (the standard deployment platform) it is case-sensitive. Internally, the file path string is stored as the registry entry name (takeScript()setsATTR_NAMEto the file path string when no explicit name is present), so the same caching and recompile rules apply as fornamemode: the compiled script is cached in the registry and only recompiled when the file changes and the configured minimum recompile interval (javascript.recompile.minimumIntervalorgroovy.recompile.minimumInterval) has elapsed. name(registry)-
Supply
nameto invoke a pre-registered script by its registered name.typeis inferred from the registration. The registered name is the same path string as thefilefield — for example,auth/customPolicy.js. A script is entered into the registry whentakeScript()is called for its name, which happens at component activation for any configured scripted service (custom endpoints, synchronization mappings, managed-object hooks, and similar). Config load therefore suffices to register a script — it does not require a prior HTTP request to the endpoint.namemode then reuses the compiled entry without re-parsing. If the named entry is not present and active in the script registry — for example because no component that references the script has yet activated, or because the service was reconfigured — the endpoint returns503 Service Unavailable.
auto-detect is recognized internally as a sentinel by the descriptor filter and is not a meaningful caller-supplied field.
|
The three additional descriptor fields — revision, visibility, and globals — control optional script-framework behavior:
revision-
An optional version label or hash. The script registry uses it to detect changes and force recompilation when the same source is re-submitted with a different revision value. Use
revisionwhen repeatedly submitting an evolving script source — changing the revision guarantees a fresh compile rather than a cached result. visibility-
An optional label that tags the script registry entry with an access scope (
publicby default). Within Wren:IDM, this label is stored in the registry but not enforced; the/scriptendpoint evaluates the script regardless of its visibility value. globals-
An optional JSON object whose key-value pairs are injected as additional script bindings before evaluation. Type: object. Values follow the same type mapping as the
propertiesblock: JSON objects arrive in Groovy as JavaMapinstances and JSON arrays as JavaListinstances; the ECMAScript engine wraps them transparently as ordinary JavaScript objects and arrays. Useglobalsto pass bindings that should not be confused with body data fields — particularly when a key name would collide with a descriptor field.
{
"type": "text/javascript",
"source": "a + b",
"a": 2,
"b": 3
}
Custom endpoints
Custom endpoints expose deployment-defined scripts as REST resources under /endpoint/.
- OSGi service class
-
org.forgerock.openidm.customendpoint.impl.EndpointsService - OSGi factory persistent identifier
-
org.forgerock.openidm.endpoint - Configuration file
-
endpoint-<name>.json(factory configuration) - Router mapping
-
endpoint/<name>(see below)
Each conf/endpoint-<name>.json file is one factory-configuration instance and registers one endpoint.
The endpoint’s router prefix is the value of the context field; if context is omitted, it defaults to endpoint/<factoryPid>*, where <factoryPid> is the portion of the file name after endpoint-.
For example, conf/endpoint-myService.json defaults to endpoint/myService*.
The trailing registers the route as a prefix match, so all paths under endpoint/myService/ are handled by the same endpoint script.
When context is provided without a trailing , the route matches that exact path only; requests to any subpath return a 404 Not Found because no route handles them.
An endpoint configuration declares the script the same way as any other script reference:
type-
Required. The script engine media type:
text/javascriptfor JavaScript orgroovyfor Groovy. source-
Required if
fileis absent. An inline script body. Mutually exclusive withfile. file-
Required if
sourceis absent. A path to a script file, resolved against the configured script sources. Mutually exclusive withsource. context-
Optional. The router prefix for the endpoint. If absent, defaults to
endpoint/<factoryPid>*.
The endpoint script receives the request bindings described in Request bindings, branches on the request type, and returns a value that becomes the HTTP response.
For resource operations (read, create, update, patch, delete), returning null produces 404 Not Found; any other return value produces 200 OK with the value as the response body.
For action operations, any return value including null produces 200 OK.
For query operations, the script may return:
+
-
A flat list — each element becomes a result entry; no pagination metadata.
-
A map with a
resultkey —resultis required and must be a list of result entries. Optional pagination metadata keys arepagedResultsCookie(string),totalPagedResultsPolicy(string), andtotalPagedResults(integer); omit them or set tonullwhen not applicable. A map that is not null and does not contain aresultkey causes a500 Internal Server Error. -
null— use callback-based result streaming by callingcallback.handleResource(…)directly from within the script.
+
To raise a typed HTTP status from a script, throw a plain object with code and message fields:
throw { "code": 403, "message": "Forbidden" };.
This is the pattern used by the shipped router-authz.js filter and is the recommended approach for both JavaScript and Groovy endpoint scripts.
Any other exception becomes 500 Internal Server Error.
The shipped conf/endpoint-linkedView.json uses an inline script:
conf/endpoint-linkedView.json{
"context": "endpoint/linkedView/*",
"type": "text/javascript",
"source": "require('linkedView').fetch(request.resourcePath);"
}
The shipped conf/endpoint-usernotifications.json references a script file instead:
conf/endpoint-usernotifications.json{
"type": "text/javascript",
"file": "ui/notification/userNotifications.js"
}