Class DefaultScriptEngineResolver

java.lang.Object
org.camunda.bpm.engine.impl.scripting.engine.DefaultScriptEngineResolver
All Implemented Interfaces:
ScriptEngineResolver

public class DefaultScriptEngineResolver extends Object implements ScriptEngineResolver
  • Field Details

  • Constructor Details

    • DefaultScriptEngineResolver

      public DefaultScriptEngineResolver(ScriptEngineManager scriptEngineManager)
  • Method Details

    • addScriptEngineFactory

      public void addScriptEngineFactory(ScriptEngineFactory scriptEngineFactory)
      Specified by:
      addScriptEngineFactory in interface ScriptEngineResolver
    • getScriptEngineManager

      public ScriptEngineManager getScriptEngineManager()
      Specified by:
      getScriptEngineManager in interface ScriptEngineResolver
    • getScriptEngine

      public ScriptEngine getScriptEngine(String language, boolean resolveFromCache)
      Returns a cached script engine or creates a new script engine if no such engine is currently cached.

      GraalJS is deliberately never cached here - see issue #3174. It reports THREADING = null via the JSR-223 factory parameter (see isCachable(javax.script.ScriptEngine)), so a fresh engine is created for every call, and the caller is responsible for explicitly releasing it once done (see ScriptingEngines.disposeScriptEngine(javax.script.ScriptEngine)).

      An engine-level cache was tried twice for GraalJS (issues #3101, #3148) and both attempts had to be reverted: GraalJS never releases the native/heap-resident Truffle state behind a discarded engine via garbage collection alone, so any cache needs to explicitly close evicted entries - but GraalJS also enforces thread affinity (a Context is bound to its creating thread) and, separately, BPMN/DMN each keep their own long-lived cache of a CompiledScript tied to whichever engine compiled it (see SourceExecutableScript/CompiledExecutableScript and DMN's CachedCompiledScriptSupport). Any engine-level cache's own eviction timing (LRU capacity, or a GraalJS config change - see issue #3174) can end up closing an engine that one of those long-lived compiled-script caches still depends on, since the two caches have no way to coordinate their lifetimes. Not caching at all avoids this entirely: the decision of whether an engine may be closed is made once, locally, by whichever call site just used it - see ScriptingEngines.disposeScriptEngine(javax.script.ScriptEngine).

      Specified by:
      getScriptEngine in interface ScriptEngineResolver
      Parameters:
      language - the language (such as 'groovy' for the script engine)
      Returns:
      the cached engine or null if no script engine can be created for the given language
    • isJsEngineNameStale

      protected boolean isJsEngineNameStale(ScriptEngine cached, ProcessEngineConfigurationImpl config)
      Returns true if the cached engine's factory no longer matches the effective JavaScript engine name for the current configuration.

      The effective name is determined as follows:

      1. If scriptEngineNameJavaScript is explicitly configured, that name is used.
      2. Otherwise, if the default JavaScript engine (ScriptingEngines.DEFAULT_JS_SCRIPTING_LANGUAGE, i.e. GraalJS) is available on the classpath, that is the effective name — a cached engine from a previous explicit configuration that has since been reset to null is therefore correctly detected as stale.
      3. Otherwise (default engine unavailable — fallback path in getJavaScriptScriptEngine(java.lang.String) is in effect), the cached fallback engine is treated as not stale to prevent unnecessary recreation on every lookup.

      If config is null (no process engine context available), the entry is conservatively treated as not stale, mirroring the pre-existing behavior for this edge case.

    • isDefaultJsEngineAvailable

      protected boolean isDefaultJsEngineAvailable()
      Returns true if the default JavaScript engine (ScriptingEngines.DEFAULT_JS_SCRIPTING_LANGUAGE, i.e. GraalJS) appears in the JDK ServiceLoader/SPI metadata already visible to the ScriptEngineManager, without instantiating a new engine. Used by isJsEngineNameStale(javax.script.ScriptEngine, org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl) to correctly distinguish "no explicit engine configured, use the default" from "default unavailable, fallback is in effect".

      Treats a null ScriptEngineManager.getEngineFactories() result (never returned by the JDK's own implementation, but possible for a custom/mocked ScriptEngineManager) the same as an empty one - i.e. the default engine is unavailable.

    • isJavaScriptAlias

      protected boolean isJavaScriptAlias(String language)
    • getScriptEngine

      protected ScriptEngine getScriptEngine(String language)
    • getJavaScriptScriptEngine

      protected ScriptEngine getJavaScriptScriptEngine(String language)
    • getEngineByName

      protected ScriptEngine getEngineByName(String name)
      Resolves a script engine by name, giving GraalJS its own private, single-use org.graalvm.polyglot.Engine instead of going through scriptEngineManager.getEngineByName(...) directly - see issue #3174.

      GraalJSScriptEngine.create() (which is what ScriptEngineManager calls internally when no explicit Engine is supplied) implicitly participates in GraalVM's JVM-wide shared/pooled default Engine. Confirmed by experiment: two separately obtained GraalJSScriptEngine instances from ScriptEngineManager#getEngineByName("Graal.js") return getPolyglotEngine() == the exact same object. Also confirmed by experiment: closing only the JSR-223 Context (what GraalJSScriptEngine#close() does) never actually releases the native/heap-resident state GraalJS retains per execution - only closing the underlying Engine does (see ScriptingEngines.disposeScriptEngine(javax.script.ScriptEngine)) - so closing that shared default Engine would break every other GraalJS engine in the entire JVM, permanently, while never closing it leaks unboundedly. Giving each execution its own private Engine (used for exactly one ScriptEngine, then closed alongside it) sidesteps this entirely: nothing else could ever be sharing it.

      name is matched against every alias GraalJS's own factory registers (e.g. "js", "graal.js", "ecmascript" - not just the canonical ScriptingEngines.GRAAL_JS_SCRIPT_ENGINE_NAME), via isGraalJsAlias(String), so any of them - whether from a script task/DMN literal expression's language attribute or an explicitly configured scriptEngineNameJavaScript - reach the substitution below; see issue #3174.

      That match is deliberately metadata-only (ScriptEngineFactory.getNames()), never by first asking scriptEngineManager for an actual engine: doing the latter would itself construct a real GraalJSScriptEngine bound to GraalVM's shared default Engine just to inspect it - and per the same experiments referenced above, closing only that probe's Context (the most a shared engine ever allows) does not release its native/heap-resident state, so it would leak on every single lookup.

      Falls back to the plain ScriptEngineManager lookup for every other language, and for GraalJS itself if no matching factory is registered with scriptEngineManager (e.g. a classloader-isolated deployment where the caller-supplied manager has no GraalJS factory registered, even though GraalJS classes are reflectively loadable from this module's own classloader - see ScriptEngineNameJavaScriptTest#shouldFallbackToAnyEngineForJavaScriptIfDefaultUnavailable), or if the reflective construction itself fails (e.g. an incompatible GraalJS version without the expected API).

    • isGraalJsAlias

      protected boolean isGraalJsAlias(String name)
      Returns true if scriptEngineManager has a GraalJS factory registered whose ScriptEngineFactory.getNames() contains name (case-insensitively) - i.e. name is any alias GraalJS itself registers, not just ScriptingEngines.GRAAL_JS_SCRIPT_ENGINE_NAME. Purely metadata-based - never instantiates a ScriptEngine - see getEngineByName(String).
    • createGraalJsScriptEngineWithPrivateEngine

      protected ScriptEngine createGraalJsScriptEngineWithPrivateEngine()
      Constructs a GraalJS ScriptEngine bound to a freshly created, private org.graalvm.polyglot.Engine - never the implicit JVM-wide shared default. Passing null for the Context.Builder argument lets GraalJSScriptEngine build its own default set of Context options (the same ones a plain ScriptEngineManager#getEngineByName("Graal.js") lookup would use), so behavior is unaffected except for which Engine backs it.

      Accessed via reflection because org.graalvm.polyglot/GraalJS classes are only a test-scope dependency of this module - see getScriptEngine(String, boolean) Javadoc.

      Returns:
      the constructed engine, or null if this GraalJS version doesn't expose the expected Engine.Builder/GraalJSScriptEngine.create(Engine, Context.Builder) API, in which case the caller falls back to the plain ScriptEngineManager lookup.
    • isCachable

      protected boolean isCachable(ScriptEngine scriptEngine)
      Allows checking whether the script engine can be cached. Cache if the engine declares thread-safety via the THREADING factory parameter.

      GraalJS reports THREADING = null, so it is - deliberately - never cached here. See the getScriptEngine(String, boolean) Javadoc for why.

      Parameters:
      scriptEngine - the script engine to check.
      Returns:
      true if the script engine may be cached.
    • configureScriptEngines

      protected void configureScriptEngines(String language, ScriptEngine scriptEngine)
    • isGraalJs

      protected boolean isGraalJs(ScriptEngine scriptEngine)
      Returns:
      true if scriptEngine's factory reports the canonical GraalJS engine name. Unlike isGraalJsAlias(String), this checks an already-resolved engine's own (fixed, alias-independent) ScriptEngineFactory.getEngineName(), so a plain equals is sufficient here.
    • configureGroovyScriptEngine

      protected void configureGroovyScriptEngine(ScriptEngine scriptEngine)
      Allows providing custom configuration for the groovy script engine.
      Parameters:
      scriptEngine - the groovy script engine to configure.
    • configureGraalJsScriptEngine

      protected void configureGraalJsScriptEngine(ScriptEngine scriptEngine)
      Allows providing custom configuration for the Graal JS script engine.
      Parameters:
      scriptEngine - the Graal JS script engine to configure.