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/scriptendpoint. -
ScheduledService, script-triggered schedules. -
ScriptExecutor, in-process invocation by other bundles.
A cooperating component, the Custom Endpoints Service, provides custom REST endpoints, 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. The bundled ECMAScript engine evaluates it. -
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 pass 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 also injects javascript.exception.debug.info into the engine configuration.
This value comes from the identity-server property of the same name and defaults 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.
For example, 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 the service registers each directory as a script container.
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 as declared in script.json.
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. The
consolebinding provides onlyconsole.log(value), which prints a string argument to the server’s standard output. It silently ignores non-string arguments. It does 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-
Loads a named module. Shipped JavaScript scripts use it to load library modules from the configured script sources, for example
require('lib/lodash')inrouter-authz.jsandlinkedView.js. No shipped Groovy script callsrequire(). To share code across Groovy scripts, package shared classes as JARs and place them on thegroovy.classpath(the installation lib directory by default). Then import them using standard Groovy or Java import statements.
The service also publishes every entry of the properties block in script.json as a binding of the same name.
Request bindings
A script that handles a router request (for example a custom endpoint) also receives:
request-
The CREST request being handled. Its type and payload let a single script serve multiple operations. See below for the individual properties.
request.requestType-
Read
request.requestTypeto branch on the operation. Its string representation is the uppercase operation name:READ,CREATE,UPDATE,DELETE,PATCH,QUERY, orACTION. These are 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 get 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.content-
Carries 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 get 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) { … }. request.patchOperations-
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 three accessors:-
getOperation()– returns the operation name string (replace,add,remove,increment,move,copy, ortransform) -
getField()– returns the target field as aJsonPointer -
getValue()– returns the operation value as aJsonValue
-
In JavaScript, the same list is accessible as request.patchOperations.
Each element behaves as a Java PatchOperation object.
+
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.
The third parameter, |
|
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 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. Only
nameis required. When the property is not found, the method returnsdefaultValue. Passnullto receivenullon a missing property.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.+ NOTE: When
useCacheistrue, the service caches the value and reuses it across all subsequent script invocations. It clears the cache 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.
A caller invokes actions 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.
The router authorization filter (router-authz.js) governs HTTP access.
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. The response body depends on the script’s return value:
-
An object produces a JSON object body.
-
A string produces a JSON string body.
-
A number produces a JSON number body.
-
A boolean produces a JSON boolean body.
-
nullorundefinedproduces a JSONnullresponse.
Returns
400 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, the endpoint splits fields into script-descriptor fields and bindings.
Fields recognized as script-descriptor (name, file, source, type, revision, visibility, globals) configure the script.
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 registry stores the file path string as the entry name. When no explicit name is present,takeScript()setsATTR_NAMEto the file path string. The same caching and recompile rules therefore apply as fornamemode. The registry caches the compiled script and recompiles it only 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. Itstypeis inferred from the registration. The registered name is the same path string as thefilefield, for exampleauth/customPolicy.js. CallingtakeScript()for the script’s name enters it into the registry. This call 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. This mode then reuses the compiled entry without re-parsing. If the named entry is not present and active in the script registry, the endpoint returns503 Service Unavailable. This can happen, for example, because no component that references the script has yet activated, or because the service was reconfigured.
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, the registry stores this label but does not enforce it. 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 arrive 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, with no pagination metadata.
-
A map with a
resultkey. This key is required and must be a list of result entries. Optional pagination metadata keys arepagedResultsCookie(string),totalPagedResultsPolicy(string), andtotalPagedResults(integer). Omit them or set them tonullwhen not applicable. A map that is not null and does not contain aresultkey causes a500 Internal Server Error. -
null. Produces an empty result: no entries are reported to the caller, and no pagination metadata is set.
+
To raise a typed HTTP status from a script, throw a plain object with code and message fields:
throw { "code": 403, "message": "Access denied" };.
The shipped router-authz.js filter uses this pattern, and it 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"
}