diff --git a/api/src/org/labkey/api/action/ApiResponseWriter.java b/api/src/org/labkey/api/action/ApiResponseWriter.java index fcb9d70691d..1656cb0c636 100644 --- a/api/src/org/labkey/api/action/ApiResponseWriter.java +++ b/api/src/org/labkey/api/action/ApiResponseWriter.java @@ -23,6 +23,7 @@ import org.json.JSONObject; import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.PropertyValidationError; +import org.labkey.api.query.QueryService; import org.labkey.api.query.SimpleValidationError; import org.labkey.api.query.ValidationError; import org.labkey.api.query.ValidationException; @@ -445,7 +446,7 @@ public JSONObject toJSON(Throwable e) json.put("errors", arr); json.put("errorCount", arr.length()); json.put("exception", message); - json.put("extraContext", bve.getExtraContext()); + json.put(QueryService.EXTRA_CONTEXT, bve.getExtraContext()); return json; } diff --git a/api/src/org/labkey/api/data/triggers/ScriptTrigger.java b/api/src/org/labkey/api/data/triggers/ScriptTrigger.java index 99c18ca8f31..d34306e5045 100644 --- a/api/src/org/labkey/api/data/triggers/ScriptTrigger.java +++ b/api/src/org/labkey/api/data/triggers/ScriptTrigger.java @@ -23,6 +23,7 @@ import org.labkey.api.data.PHI; import org.labkey.api.data.TableInfo; import org.labkey.api.query.BatchValidationException; +import org.labkey.api.query.QueryService; import org.labkey.api.query.ValidationException; import org.labkey.api.script.ScriptReference; import org.labkey.api.security.User; @@ -258,7 +259,7 @@ private T _try(Container c, User user, Map extraContext, Scr Map bindings = new HashMap<>(); if (extraContext == null) extraContext = new HashMap<>(); - bindings.put("extraContext", extraContext); + bindings.put(QueryService.EXTRA_CONTEXT, extraContext); bindings.put("schemaName", _table.getPublicSchemaName()); bindings.put("tableName", _table.getPublicName()); diff --git a/api/src/org/labkey/api/query/QueryService.java b/api/src/org/labkey/api/query/QueryService.java index bb6d87b7614..82b56a71f08 100644 --- a/api/src/org/labkey/api/query/QueryService.java +++ b/api/src/org/labkey/api/query/QueryService.java @@ -69,6 +69,8 @@ public interface QueryService String SCHEMA_TEMPLATE_EXTENSION = ".template.xml"; + String EXTRA_CONTEXT = "extraContext"; + static QueryService get() { return ServiceRegistry.get().getService(QueryService.class); diff --git a/core/src/org/labkey/core/script/RhinoService.java b/core/src/org/labkey/core/script/RhinoService.java index 2def34d20ba..258a902ac73 100644 --- a/core/src/org/labkey/core/script/RhinoService.java +++ b/core/src/org/labkey/core/script/RhinoService.java @@ -16,11 +16,12 @@ package org.labkey.core.script; import com.sun.phobos.script.javascript.RhinoScriptEngineFactory; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; -import org.json.JSONObject; import org.json.JSONArray; +import org.json.JSONObject; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -35,6 +36,7 @@ import org.labkey.api.module.ModuleResourceCaches; import org.labkey.api.module.ResourceRootProvider; import org.labkey.api.query.BatchValidationException; +import org.labkey.api.query.QueryService; import org.labkey.api.query.ValidationException; import org.labkey.api.reader.Readers; import org.labkey.api.resource.Resource; @@ -58,6 +60,7 @@ import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeJavaObject; import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.WrapFactory; @@ -237,8 +240,8 @@ public void testModuleResourceCache() if (null != simpleTest) { - assertEquals("Scripts from the simpletest module", 15, ScriptReferenceImpl.SCRIPT_CACHE.getResourceMap(simpleTest).size()); - assertEquals("Top-level script timestamps from the simpletest module", 15, LabKeyModuleSourceProvider.TOP_LEVEL_SCRIPT_CACHE.getResourceMap(simpleTest).size()); + assertEquals("Scripts from the simpletest module", 16, ScriptReferenceImpl.SCRIPT_CACHE.getResourceMap(simpleTest).size()); + assertEquals("Top-level script timestamps from the simpletest module", 16, LabKeyModuleSourceProvider.TOP_LEVEL_SCRIPT_CACHE.getResourceMap(simpleTest).size()); } } } @@ -1006,7 +1009,7 @@ protected void observeInstructionCount(Context cx, int instructionCount) { SandboxContext ctx = (SandboxContext)cx; long currentTime = HeartBeat.currentTimeMillis(); - final int timeout = 60; + final long timeout = ctx.getTimeout(); if (currentTime - ctx.startTime > timeout*1000) Context.reportError("Script execution exceeded " + timeout + " seconds."); } @@ -1043,6 +1046,10 @@ public boolean visibleToScripts(String fullClassName) private static class SandboxContext extends Context { + private static final String SCRIPT_TIMEOUT_PROPERTY = "scriptTimeout"; + private static final int MAX_ALLOWABLE_TIMEOUT = 300; + private final long DEFAULT_TIMEOUT = 60; + private final long startTime; private SandboxContext(SandboxContextFactory factory) @@ -1051,6 +1058,39 @@ private SandboxContext(SandboxContextFactory factory) setLanguageVersion(Context.VERSION_1_8); startTime = HeartBeat.currentTimeMillis(); } + + public long getTimeout() + { + if (ScriptRuntime.hasTopCall(this)) + { + Scriptable script = ScriptRuntime.getTopCallScope(this); + Object o = ScriptRuntime.getObjectProp(script, QueryService.EXTRA_CONTEXT, this); + if (o instanceof ScriptableMap mp) + { + if (mp.getMap().get(SCRIPT_TIMEOUT_PROPERTY) != null) + { + Object rawTimeout = mp.getMap().get(SCRIPT_TIMEOUT_PROPERTY); + try + { + int scriptTimeout = Integer.parseInt(String.valueOf(rawTimeout)); + if (scriptTimeout > MAX_ALLOWABLE_TIMEOUT) + { + scriptTimeout = MAX_ALLOWABLE_TIMEOUT; + LOG.error("Script timeout is greater than max allowable, using: {}", MAX_ALLOWABLE_TIMEOUT); + } + + return scriptTimeout; + } + catch (Exception e) + { + LOG.error("Non-integer value provided to extractContext.scriptTimeout for script: {}", rawTimeout); + } + } + } + } + + return DEFAULT_TIMEOUT; + } } private static class SandboxWrapFactory extends WrapFactory diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index 35ba9a5d411..e67c7b9e6fd 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -4643,7 +4643,7 @@ protected JSONObject executeJson(JSONObject json, CommandType commandType, boole } } - Map extraContext = json.has("extraContext") ? json.getJSONObject("extraContext").toMap() : new CaseInsensitiveHashMap<>(); + Map extraContext = json.has(QueryService.EXTRA_CONTEXT) ? json.getJSONObject(QueryService.EXTRA_CONTEXT).toMap() : new CaseInsensitiveHashMap<>(); Map auditDetails = json.has("auditDetails") ? json.getJSONObject("auditDetails").toMap() : new CaseInsensitiveHashMap<>(); @@ -5138,7 +5138,7 @@ else if (scope != tableInfo.getSchema().getScope()) } JSONArray resultArray = new JSONArray(); - JSONObject extraContext = json.optJSONObject("extraContext"); + JSONObject extraContext = json.optJSONObject(QueryService.EXTRA_CONTEXT); JSONObject auditDetails = json.optJSONObject("auditDetails"); int startingErrorIndex = 0; @@ -5162,11 +5162,11 @@ else if (scope != tableInfo.getSchema().getScope()) Map commandExtraContext = new HashMap<>(); if (extraContext != null) commandExtraContext.putAll(extraContext.toMap()); - if (commandObject.has("extraContext")) + if (commandObject.has(QueryService.EXTRA_CONTEXT)) { - commandExtraContext.putAll(commandObject.getJSONObject("extraContext").toMap()); + commandExtraContext.putAll(commandObject.getJSONObject(QueryService.EXTRA_CONTEXT).toMap()); } - commandObject.put("extraContext", commandExtraContext); + commandObject.put(QueryService.EXTRA_CONTEXT, commandExtraContext); Map commandAuditDetails = new HashMap<>(); if (auditDetails != null) commandAuditDetails.putAll(auditDetails.toMap());