From 6a410dd85157a5931784c3e7f8912a2cdc497632 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 13 May 2026 12:06:52 +0300 Subject: [PATCH 1/8] ci: fail "Print openidm logs" step on errors/exceptions in OpenIDM logs --- .github/workflows/build.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12b40e165..565a24d19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -170,7 +170,23 @@ jobs: done else echo "openidm/logs directory not found" + exit 0 fi + echo "----- Checking logs for errors/exceptions -----" + status=0 + while IFS= read -r f; do + if grep -E -n "ERROR|SEVERE|Exception|Throwable" "$f" > /tmp/log_errors.$$ 2>/dev/null; then + echo "Found errors/exceptions in $f:" + cat /tmp/log_errors.$$ + status=1 + fi + rm -f /tmp/log_errors.$$ + done < <(find openidm/logs -type f) + if [ "$status" -ne 0 ]; then + echo "Errors or exceptions detected in openidm logs" + exit 1 + fi + echo "No errors or exceptions detected in openidm logs" build-docker: runs-on: 'ubuntu-latest' services: From 9945e76bc6c42d08ef5601a724e14d264595fa0c Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 13 May 2026 12:28:34 +0300 Subject: [PATCH 2/8] ci: fail "Print openidm logs" step on errors/exceptions in OpenIDM logs --- .github/workflows/build.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 565a24d19..8bebbfe3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,8 +55,7 @@ jobs: openidm/startup.sh & timeout 3m bash -c 'until grep -q "OpenIDM ready" openidm/logs/openidm0.log.0 ; do sleep 5; done' || cat openidm/logs/openidm0.log.0 grep -q "OpenIDM ready" openidm/logs/openidm0.log.0 - ! grep "ERROR" openidm/logs/openidm0.log.0 - ! grep "SEVERE" openidm/logs/openidm0.log.0 + ! grep -E "ERROR|SEVERE|Exception|Throwable" openidm/logs/openidm0.log.0 - name: Test on Windows if: runner.os == 'Windows' run: | @@ -66,8 +65,11 @@ jobs: Start-Sleep -s 180 type logs\openidm0.log.0 findstr "OpenIDM ready" logs\openidm0.log.0 - type logs\openidm0.log.0 | find /c '"ERROR"' | findstr "0" - type logs\openidm0.log.0 | find /c '"SEVERE"' | findstr "0" + if (Select-String -Path logs\openidm0.log.0 -Pattern 'ERROR|SEVERE|Exception|Throwable' -Quiet) { + Write-Host "Errors or exceptions detected in openidm0.log.0" + Select-String -Path logs\openidm0.log.0 -Pattern 'ERROR|SEVERE|Exception|Throwable' + exit 1 + } - name: Upload failure artifacts uses: actions/upload-artifact@v7 if: ${{ failure() }} @@ -136,8 +138,7 @@ jobs: OPENIDM_OPTS="$OPTS" openidm/startup.sh $ARGS & timeout 3m bash -c 'until grep -q "OpenIDM ready" openidm/logs/openidm0.log.0 ; do sleep 5; done' || cat openidm/logs/openidm0.log.0 grep -q "OpenIDM ready" openidm/logs/openidm0.log.0 - ! grep "ERROR" openidm/logs/openidm0.log.0 - ! grep "SEVERE" openidm/logs/openidm0.log.0 + ! grep -E "ERROR|SEVERE|Exception|Throwable" openidm/logs/openidm0.log.0 - name: UI Smoke Tests (Playwright) run: | cd e2e From 35b6c450f36782c98209f17ffc126aafeb833f7a Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 13 May 2026 13:24:35 +0300 Subject: [PATCH 3/8] Fix Felix Web Console PreferencesConfigurationPrinter not enabled Add org.apache.felix.prefs bundle to provide org.osgi.service.prefs package (including BackingStoreException class) at runtime, fixing: INFO: org.apache.felix.webconsole.internal.compendium.PreferencesConfigurationPrinter not enabled. Reason: Class org/osgi/service/prefs/BackingStoreException missing --- openidm-zip/pom.xml | 4 ++++ pom.xml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/openidm-zip/pom.xml b/openidm-zip/pom.xml index b5b889f96..2152dca0a 100644 --- a/openidm-zip/pom.xml +++ b/openidm-zip/pom.xml @@ -272,6 +272,10 @@ org.apache.felix org.apache.felix.webconsole.plugins.packageadmin + + org.apache.felix + org.apache.felix.prefs + org.apache.geronimo.bundles json diff --git a/pom.xml b/pom.xml index 8d4ca3b33..029722aa5 100644 --- a/pom.xml +++ b/pom.xml @@ -518,6 +518,11 @@ org.apache.felix.webconsole.plugins.packageadmin ${felix.webconsole.packageadmin.version} + + org.apache.felix + org.apache.felix.prefs + 1.1.0 + org.apache.felix From 7957f5f148b29da4e9f74225d5e3a7aa5756df0b Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 13 May 2026 17:02:58 +0300 Subject: [PATCH 4/8] Remove dead `logback.configurationFile` reference and silence noisy pax-web INFO logs --- .github/workflows/build.yml | 2 +- Dockerfile | 2 +- Dockerfile-alpine | 2 +- openidm-zip/pom.xml | 3 +-- openidm-zip/src/main/resources/bin/install-service.bat | 2 +- openidm-zip/src/main/resources/conf/logging.properties | 3 +++ openidm-zip/src/main/resources/startup.sh | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8bebbfe3e..b7b9ca52d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -129,7 +129,7 @@ jobs: run: | OPTS="" if [ -n "${{ matrix.context_path }}" ]; then - OPTS="-Dlogback.configurationFile=conf/logging-config.groovy -Dopenidm.context.path=${{ matrix.context_path }}" + OPTS="-Dopenidm.context.path=${{ matrix.context_path }}" fi ARGS="" if [ -n "${{ matrix.samples }}" ]; then diff --git a/Dockerfile b/Dockerfile index 561d723b6..9ea6cdbd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ FROM eclipse-temurin:25-jre-jammy LABEL org.opencontainers.image.authors="Open Identity Platform Community" ENV USER="openidm" -ENV OPENIDM_OPTS="-server -XX:+UseContainerSupport --add-exports java.base/com.sun.jndi.ldap=ALL-UNNAMED -Dlogback.configurationFile=conf/logging-config.groovy" +ENV OPENIDM_OPTS="-server -XX:+UseContainerSupport --add-exports java.base/com.sun.jndi.ldap=ALL-UNNAMED" ARG VERSION diff --git a/Dockerfile-alpine b/Dockerfile-alpine index e7f32f1ab..ef3731ff5 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -16,7 +16,7 @@ FROM alpine:latest LABEL org.opencontainers.image.authors="Open Identity Platform Community" ENV USER="openidm" -ENV OPENIDM_OPTS="-server -XX:+UseContainerSupport -Dlogback.configurationFile=conf/logging-config.groovy" +ENV OPENIDM_OPTS="-server -XX:+UseContainerSupport" ARG VERSION diff --git a/openidm-zip/pom.xml b/openidm-zip/pom.xml index 2152dca0a..2b84098d5 100644 --- a/openidm-zip/pom.xml +++ b/openidm-zip/pom.xml @@ -22,7 +22,7 @@ ~ your own identifying information: ~ "Portions Copyrighted [year] [name of copyright owner]" ~ - ~ Portions Copyrighted 2019-2025 3A Systems LLC. + ~ Portions Copyrighted 2019-2026 3A Systems LLC. --> 4.0.0 @@ -901,7 +901,6 @@ - -Dlogback.configurationFile=conf/logging-config.groovy diff --git a/openidm-zip/src/main/resources/bin/install-service.bat b/openidm-zip/src/main/resources/bin/install-service.bat index 24b09a299..049246a0d 100644 --- a/openidm-zip/src/main/resources/bin/install-service.bat +++ b/openidm-zip/src/main/resources/bin/install-service.bat @@ -28,7 +28,7 @@ set OPENIDM_OPTS_SERVICE=%OPENIDM_OPTS: =;% rem set SERVER_START_PARAMS="-c;bin/launcher.json" set CP=bin/launcher.jar;bin/felix.jar rem JAVA_OPTS_SERVICE will be fed to the prunmgr.exe which requires all semi-colon delimiters -set JAVA_OPTS_SERVICE=%OPENIDM_OPTS_SERVICE%;-Djava.util.logging.config.file=conf\logging.properties;-Dlogback.configurationFile=conf\logging-config.xml; +set JAVA_OPTS_SERVICE=%OPENIDM_OPTS_SERVICE%;-Djava.util.logging.config.file=conf\logging.properties; rem Enable debugging uncomment the line below rem set JAVA_OPTS_SERVICE=%JAVA_OPTS_SERVICE%;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005; diff --git a/openidm-zip/src/main/resources/conf/logging.properties b/openidm-zip/src/main/resources/conf/logging.properties index c96e84681..413daa50c 100644 --- a/openidm-zip/src/main/resources/conf/logging.properties +++ b/openidm-zip/src/main/resources/conf/logging.properties @@ -73,6 +73,9 @@ org.identityconnectors.framework.impl.api.local.LocalConnectorInfoManagerImpl.le # Suppress warnings of failed error page model validation org.ops4j.pax.web.service.spi.model.elements.ErrorPageModel.level=SEVERE +# Suppress noisy INFO records from pax-web bundle (servlet/error-page registration) +org.ops4j.pax.web.level=WARNING + # OrientDB 3.x: suppress harmless WARNINGs that we cannot act on # - OScriptManager logs "ECMAScript engine not found" when no JSR-223 javascript # engine is on the classpath (we don't ship one and don't use OrientDB JS). diff --git a/openidm-zip/src/main/resources/startup.sh b/openidm-zip/src/main/resources/startup.sh index 1ef748319..0da8279ff 100755 --- a/openidm-zip/src/main/resources/startup.sh +++ b/openidm-zip/src/main/resources/startup.sh @@ -95,7 +95,7 @@ PRGDIR=`dirname "$PRG"` [ -z "$OPENIDM_PID_FILE" ] && OPENIDM_PID_FILE="$OPENIDM_HOME"/.openidm.pid # Only set OPENIDM_OPTS if not already set -[ -z "$OPENIDM_OPTS" ] && OPENIDM_OPTS="-Dlogback.configurationFile=conf/logging-config.groovy" +[ -z "$OPENIDM_OPTS" ] && OPENIDM_OPTS="" # Set JDK Logger config file if it is present and an override has not been issued PROJECT_HOME=$OPENIDM_HOME From 9b4ea26e597fb3249642de81e00e20c7d4f8f3c6 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 13 May 2026 17:32:08 +0300 Subject: [PATCH 5/8] ci(windows): make log error scan case-sensitive to match Unix grep Select-String in PowerShell is case-insensitive by default, so the Windows smoke-test step was matching benign INFO records such as "ErrorServletComponent activate" / "Registered servlet at /error" against the ERROR|SEVERE|Exception|Throwable pattern and failing the build. The equivalent Unix step uses `grep -E`, which is case-sensitive, so the same lines pass on Linux/macOS. Add the -CaseSensitive flag to both Select-String invocations in the "Test on Windows" step of .github/workflows/build.yml so the check behaves identically across OSes. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7b9ca52d..6a488b2d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,9 +65,9 @@ jobs: Start-Sleep -s 180 type logs\openidm0.log.0 findstr "OpenIDM ready" logs\openidm0.log.0 - if (Select-String -Path logs\openidm0.log.0 -Pattern 'ERROR|SEVERE|Exception|Throwable' -Quiet) { + if (Select-String -Path logs\openidm0.log.0 -Pattern 'ERROR|SEVERE|Exception|Throwable' -CaseSensitive -Quiet) { Write-Host "Errors or exceptions detected in openidm0.log.0" - Select-String -Path logs\openidm0.log.0 -Pattern 'ERROR|SEVERE|Exception|Throwable' + Select-String -Path logs\openidm0.log.0 -Pattern 'ERROR|SEVERE|Exception|Throwable' -CaseSensitive exit 1 } - name: Upload failure artifacts From 926f800cbf8b68ae23f5d47133ad27473c7c6330 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 13 May 2026 19:16:56 +0300 Subject: [PATCH 6/8] fix(samples/workflow): honor `openidm.context.path` in Accept Notice script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Problem The `samples/workflow` end-to-end smoke job `ui-smoke-tests (… , /myidm, samples/workflow)` consistently fails on both Java 17 and Java 26 in [Build run #25805813364](https://github.com/OpenIdentityPlatform/OpenIDM/actions/runs/25805813364) while every other matrix combination is green: | context_path | sample | result | |--------------|---------------------|----------| | (default) | samples/workflow | ✅ pass | | /myidm | samples/getting-started | ✅ pass | | /myidm | (no sample) | ✅ pass | | **/myidm** | **samples/workflow**| ❌ fail | The job's final step (`Print openidm logs`) scans `openidm/logs/*` for `ERROR|SEVERE|Exception|Throwable` and exits 1 if anything matches. With `-Dopenidm.context.path=/myidm` the workflow sample's `Accept Notice` Groovy script task in `contractorOnboarding.bpmn20.xml` was calling a hard-coded REST URL: ```groovy "url": "https://localhost:" + identityServer.getProperty('openidm.port.https') + "/openidm/selfservice/reset?_action=submitRequirements" --- .../workflow/workflow/contractorOnboarding.bpmn20.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openidm-zip/src/main/resources/samples/workflow/workflow/contractorOnboarding.bpmn20.xml b/openidm-zip/src/main/resources/samples/workflow/workflow/contractorOnboarding.bpmn20.xml index ea5ddfd9c..1189345ca 100644 --- a/openidm-zip/src/main/resources/samples/workflow/workflow/contractorOnboarding.bpmn20.xml +++ b/openidm-zip/src/main/resources/samples/workflow/workflow/contractorOnboarding.bpmn20.xml @@ -13,6 +13,7 @@ information: "Portions Copyrighted [year] [name of copyright owner]". Copyright (c) 2011-2015 ForgeRock AS. All rights reserved. + Portions Copyright 2026 3A Systems, LLC. --> @@ -98,8 +99,12 @@ // Automatically send the user a password reset email // Current limitation with supplying locale via http headers requires the call to be made via http + def openidmContextPath = identityServer.getProperty('openidm.context.path', '/openidm') + if (!openidmContextPath.startsWith('/')) { + openidmContextPath = '/' + openidmContextPath + } openidm.action("external/rest", "call", [ - "url": "https://localhost:"+identityServer.getProperty('openidm.port.https')+"/openidm/selfservice/reset?_action=submitRequirements", + "url": "https://localhost:"+identityServer.getProperty('openidm.port.https')+openidmContextPath+"/selfservice/reset?_action=submitRequirements", "method": "POST", "headers": [ "Content-Type": "application/json", From 65c82c011d3caf25532ffa774f52d18b3587c38f Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Thu, 14 May 2026 11:37:15 +0300 Subject: [PATCH 7/8] Replace activiti-osgi `OsgiScriptingEngines` with stock Activiti `ScriptingEngines` ## Summary Removes the use of `org.activiti.osgi.OsgiScriptingEngines` from the OpenIDM workflow bundle and wires the stock `org.activiti.engine.impl.scripting.ScriptingEngines` with an explicitly registered Groovy `ScriptEngineFactory`. This eliminates noisy `ClassNotFoundException` warnings emitted during every script-task execution and makes the script-engine resolution deterministic under OSGi without touching the rest of `activiti-osgi` (BPMN auto-deployment via `ProcessEngineFactory` is preserved). ## Motivation `activiti-osgi` 5.15 ships `Extender$BundleScriptEngineResolver`, which is invoked by `OsgiScriptingEngines` for every script execution. Its implementation parses `META-INF/services/javax.script.ScriptEngineFactory` line by line and calls `Class.forName(...)` on each line **including `#`-prefixed comments**, producing a `ClassNotFoundException` warning per script-task invocation: --- .../activiti/impl/ActivitiServiceImpl.java | 80 ++++++++++++------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/openidm-workflow-activiti/src/main/java/org/forgerock/openidm/workflow/activiti/impl/ActivitiServiceImpl.java b/openidm-workflow-activiti/src/main/java/org/forgerock/openidm/workflow/activiti/impl/ActivitiServiceImpl.java index 49adf3cb9..0b2022dc7 100644 --- a/openidm-workflow-activiti/src/main/java/org/forgerock/openidm/workflow/activiti/impl/ActivitiServiceImpl.java +++ b/openidm-workflow-activiti/src/main/java/org/forgerock/openidm/workflow/activiti/impl/ActivitiServiceImpl.java @@ -12,7 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2012-2016 ForgeRock AS. - * Portions Copyrighted 2024 3A Systems LLC. + * Portions Copyrighted 2024-2026 3A Systems LLC. */ package org.forgerock.openidm.workflow.activiti.impl; @@ -34,7 +34,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; -import javax.script.ScriptEngine; import javax.sql.DataSource; import javax.transaction.TransactionManager; import org.activiti.engine.ProcessEngine; @@ -46,8 +45,6 @@ import org.activiti.engine.impl.scripting.ResolverFactory; import org.activiti.engine.impl.scripting.ScriptBindingsFactory; import org.activiti.engine.impl.scripting.ScriptingEngines; -import org.activiti.osgi.Extender; -import org.activiti.osgi.OsgiScriptingEngines; import org.activiti.osgi.blueprint.ProcessEngineFactory; import org.forgerock.openidm.datasource.DataSourceService; import org.forgerock.openidm.router.IDMConnectionFactory; @@ -77,10 +74,7 @@ import org.forgerock.openidm.workflow.activiti.impl.session.OpenIDMSessionFactory; import org.forgerock.util.promise.Promise; import org.h2.jdbcx.JdbcDataSource; -import org.osgi.framework.Bundle; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceFactory; -import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -320,12 +314,54 @@ void activate(ComponentContext compContext) { processEngineFactory.setBundle(compContext.getBundleContext().getBundle()); processEngineFactory.init(); + // Post-init wiring: variableTypes / resolverFactories / scriptingEngines + // are populated by buildProcessEngine() inside init(), so they must be + // mutated AFTER init(). ScriptTaskActivityBehavior reads + // configuration.getScriptingEngines() on every script execution, so + // replacing it here is effective. //ScriptResolverFactory List resolverFactories = configuration.getResolverFactories(); + if (resolverFactories == null) { + resolverFactories = new ArrayList(); + } resolverFactories.add(new OpenIDMResolverFactory()); configuration.setResolverFactories(resolverFactories); configuration.getVariableTypes().addType(new JsonValueType()); - configuration.setScriptingEngines(new OsgiScriptingEngines(new ScriptBindingsFactory(resolverFactories))); + // Use the stock Activiti ScriptingEngines (which delegates to javax.script + // ScriptEngineManager) instead of OsgiScriptingEngines. The latter routes + // resolution through org.activiti.osgi.Extender, whose + // BundleScriptEngineResolver naively parses + // META-INF/services/javax.script.ScriptEngineFactory line by line and + // attempts to Class.forName each '#'-comment line, producing a noisy + // ClassNotFoundException WARNING for every script-task execution + // (see activiti-osgi 5.15 Extender.java). The Groovy ScriptEngineFactory + // is registered explicitly below because OSGi class loaders prevent the + // JDK ServiceLoader inside ScriptEngineManager from discovering it in + // the groovy-all bundle. + ScriptingEngines scriptingEngines = + new ScriptingEngines(new ScriptBindingsFactory(resolverFactories)); + // GroovyScriptEngineFactory.getEngineName() returns "Groovy" but + // BPMN scriptFormat="groovy" looks up by lowercase language name. + // ScriptingEngines.addScriptEngineFactory only registers under + // getEngineName(); we additionally register all language/short names + // (and mime types/extensions) directly on a pre-built ScriptEngineManager. + javax.script.ScriptEngineFactory groovyFactory = + new org.codehaus.groovy.jsr223.GroovyScriptEngineFactory(); + javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager(); + mgr.registerEngineName(groovyFactory.getEngineName(), groovyFactory); + for (String name : groovyFactory.getNames()) { + mgr.registerEngineName(name, groovyFactory); + } + for (String mime : groovyFactory.getMimeTypes()) { + mgr.registerEngineMimeType(mime, groovyFactory); + } + for (String ext : groovyFactory.getExtensions()) { + mgr.registerEngineExtension(ext, groovyFactory); + } + scriptingEngines = new ScriptingEngines(mgr); + scriptingEngines.setScriptBindingsFactory(new ScriptBindingsFactory(resolverFactories)); + configuration.setScriptingEngines(scriptingEngines); + //We are done!! processEngine = processEngineFactory.getObject(); @@ -477,29 +513,11 @@ protected void unbindProcessEngine(ProcessEngine processEngine) { target = "(service.pid=org.forgerock.openidm.script)") protected void bindScriptRegistry(ScriptRegistry scriptRegistry) { this.idmSessionFactory.setScriptRegistry(scriptRegistry); - if (Extender.getBundleContext()!=null) { - Extender.getBundleContext().registerService(Extender.ScriptEngineResolver.class, new ServiceFactory() { - @Override - public Extender.ScriptEngineResolver getService(Bundle bundle, ServiceRegistration serviceRegistration) { - return new Extender.ScriptEngineResolver() { - @Override - public ScriptEngine resolveScriptEngine(String s) { - if (!"groovy".equalsIgnoreCase(s)) { - throw new RuntimeException("unknown resolveScriptEngine " + s); - } - return new org.codehaus.groovy.jsr223.GroovyScriptEngineImpl(); - } - }; - } - - ; - - @Override - public void ungetService(Bundle bundle, ServiceRegistration serviceRegistration, Extender.ScriptEngineResolver scriptEngineResolver) { - - } - }, null); - } + // The previous registration of an Extender.ScriptEngineResolver OSGi service was + // tied to OsgiScriptingEngines (now replaced by stock Activiti ScriptingEngines). + // Without OsgiScriptingEngines nothing invokes Extender.resolveScriptEngine, so the + // resolver service is no longer needed and the noisy + // BundleScriptEngineResolver code path is no longer reached at script execution time. } protected void unbindScriptRegistry(ScriptRegistry scriptRegistry) { From a5bc7531dfe8310dda3c89bd2bccc601e31aefda Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Thu, 14 May 2026 14:17:19 +0300 Subject: [PATCH 8/8] samples/workflow: extract role-mapping transforms into `script/*.js` files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Replaces the two inline JavaScript transforms in `openidm-zip/src/main/resources/samples/workflow/conf/sync.json` (mapping `systemXmlfileAccounts_managedUser`, properties `roles → authzRoles` and `→ roles`) with file-based scripts under `samples/workflow/script/`. ## Motivation The CI job `ui-smoke-tests (17, samples/workflow)` failed even though every Playwright test passed (one was retried as flaky). The "Print openidm logs" step found this in `openidm/logs/openidm0.log.0` and exited 1: ``` WARNING: Property mapping /authzRoles transformation script encountered exception javax.script.ScriptException: Script status is 8 at org.forgerock.script.registry.ScriptRegistryImpl$LibraryRecord.invoke(ScriptRegistryImpl.java:507) ... org.forgerock.openidm.sync.SynchronizationException: Transformation script error : Script status is 8 for attribute '/authzRoles' ``` `8` is `CompilationHandler.STARTING` (`UNINSTALLED=1, INSTALLED=2, RESOLVED=4, STARTING=8, ACTIVE=32`). `ScriptRegistryImpl#invoke`: ```java if (null != target) return method.invoke(target, arguments); else throw new ScriptException("Script status is " + status); ``` Inline scripts embedded directly in `sync.json` are compiled and registered asynchronously when the mapping config is loaded. The first reconciliation that runs immediately after boot can hit the script while it is still transitioning `STARTING → ACTIVE` (`target == null`) and throw. The same recon retried a few seconds later succeeds — exactly what we see in the failed run logs. File-based scripts are registered through the same path used by every other OpenIDM sample (e.g. existing `correlationQuery.js`, `usecase/.../*.js`, `sample5/getEffectiveAssignments.js`) and are guaranteed to be `ACTIVE` by the time mappings run, so the race window does not exist. ## Changes ### `openidm-zip/src/main/resources/samples/workflow/script/rolesToAuthzRoles.js` (new) Replaces the inline transform that turns the comma-separated XML `roles` attribute into the array form expected by `managed/user.authzRoles`: ```js source.split(',').map(function (r) { return {'_ref' : (r.indexOf('openidm-') === 0 ? 'repo/internal/role/' : 'managed/role/') + r }; }) ``` The file version adds `null/empty` guards, so a missing `` no longer throws but yields `[]`. ### `openidm-zip/src/main/resources/samples/workflow/script/authzRolesToRoles.js` (new) Replaces the inline transform that flattens `authzRoles` back into the comma-separated XML `roles` value: ```js openidm.query('managed/user/' + source._id + '/authzRoles', {'_queryFilter': 'true'}) .result.map(function (r) { return r._ref.split('/').pop(); }) .join(',') ``` Adds defensive checks for `source` / `source._id` and an empty query result. ### `openidm-zip/src/main/resources/samples/workflow/conf/sync.json` Two `"transform"` blocks updated: ```diff - "transform" : { - "type" : "text/javascript", - "source" : "source.split(',').map(function (r) { ... })" - }, + "transform" : { + "type" : "text/javascript", + "file" : "script/rolesToAuthzRoles.js" + }, ``` ```diff - "transform" : { - "type" : "text/javascript", - "source" : "openidm.query('managed/user/' + source._id + ...).result.map(...).join(',')" - }, + "transform" : { + "type" : "text/javascript", + "file" : "script/authzRolesToRoles.js" + }, ``` No other changes to mappings, policies, properties or test data. ## Compatibility - Same observable behaviour: same input → same output (plus graceful handling of empty/null sources, which previously threw). - Same mapping shape, same property names, no schema or config-format changes. - Sample assembly already packages `samples/workflow/script/correlationQuery.js`; the two new files end up in `samples/workflow/script/` of the distribution zip with no assembly changes. ## Verification - `python3 -m json.tool sync.json` — config still valid JSON. - `mvn -pl openidm-zip package -DskipTests` — green. - Built `openidm-*.zip` contains: - `openidm/samples/workflow/script/rolesToAuthzRoles.js` - `openidm/samples/workflow/script/authzRolesToRoles.js` - updated `openidm/samples/workflow/conf/sync.json` referencing both files. - Local run of `samples/workflow` reconciliations (Step 2 a-d of the README) no longer logs `Script status is 8 for attribute '/authzRoles'`; the strict CI log scan (`grep -E "ERROR|SEVERE|Exception|Throwable"` over `openidm/logs/*`) is clean. --- .../resources/samples/workflow/conf/sync.json | 4 +- .../workflow/script/authzRolesToRoles.js | 37 ++++++++++++++++++ .../workflow/script/rolesToAuthzRoles.js | 39 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 openidm-zip/src/main/resources/samples/workflow/script/authzRolesToRoles.js create mode 100644 openidm-zip/src/main/resources/samples/workflow/script/rolesToAuthzRoles.js diff --git a/openidm-zip/src/main/resources/samples/workflow/conf/sync.json b/openidm-zip/src/main/resources/samples/workflow/conf/sync.json index 5a4e80de8..8c1375148 100644 --- a/openidm-zip/src/main/resources/samples/workflow/conf/sync.json +++ b/openidm-zip/src/main/resources/samples/workflow/conf/sync.json @@ -89,7 +89,7 @@ "source" : "roles", "transform" : { "type" : "text/javascript", - "source" : "source.split(',').map(function (r) { return {'_ref' : (r.indexOf('openidm-') === 0 ? 'repo/internal/role/' : 'managed/role/') + r }; })" + "file" : "script/rolesToAuthzRoles.js" }, "target" : "authzRoles" }, @@ -216,7 +216,7 @@ "source" : "", "transform" : { "type" : "text/javascript", - "source" : "openidm.query('managed/user/' + source._id + '/authzRoles', {'_queryFilter': 'true'}).result.map(function (r) { return r._ref.split('/').pop(); } ).join(',')" + "file" : "script/authzRolesToRoles.js" }, "target" : "roles" }, diff --git a/openidm-zip/src/main/resources/samples/workflow/script/authzRolesToRoles.js b/openidm-zip/src/main/resources/samples/workflow/script/authzRolesToRoles.js new file mode 100644 index 000000000..fcc81da7e --- /dev/null +++ b/openidm-zip/src/main/resources/samples/workflow/script/authzRolesToRoles.js @@ -0,0 +1,37 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems, LLC. + */ +/*global source, openidm */ +// Flatten the managed/user authzRoles relationship into the comma-separated +// "roles" attribute consumed by the XML connector. +// +// File-based (vs inline in sync.json) for the same race-condition reason +// documented in rolesToAuthzRoles.js: avoids ScriptRegistry STARTING-state +// ("Script status is 8") errors during the first reconciliation after boot. +(function () { + if (source === null || source === undefined || source._id === null || source._id === undefined) { + return ""; + } + var result = openidm.query( + 'managed/user/' + source._id + '/authzRoles', + {'_queryFilter': 'true'} + ); + if (!result || !result.result) { + return ""; + } + return result.result.map(function (r) { + return r._ref.split('/').pop(); + }).join(','); +}()); diff --git a/openidm-zip/src/main/resources/samples/workflow/script/rolesToAuthzRoles.js b/openidm-zip/src/main/resources/samples/workflow/script/rolesToAuthzRoles.js new file mode 100644 index 000000000..22e1acc41 --- /dev/null +++ b/openidm-zip/src/main/resources/samples/workflow/script/rolesToAuthzRoles.js @@ -0,0 +1,39 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems, LLC. + */ + +/*global source */ + +// Transform a comma-separated XML "roles" attribute into the array form +// expected by the managed/user "authzRoles" relationship. +// +// Lives in a separate file (rather than as an inline transform inside +// sync.json) so the script is registered through the file-based code path of +// ScriptRegistry. Inline scripts embedded in mapping configs can be invoked +// during the very first reconciliation while the registry is still moving the +// freshly compiled script from STARTING (status=8) to ACTIVE (status=32), +// producing a transient "Script status is 8" ScriptException that gets +// flagged by the strict CI log scan. +(function () { + if (source === null || source === undefined || String(source).length === 0) { + return []; + } + return String(source).split(',').map(function (r) { + return { + "_ref": (r.indexOf('openidm-') === 0 ? 'repo/internal/role/' : 'managed/role/') + r + }; + }); +}()); +