diff --git a/polyglot/src/main/java/org/restheart/polyglot/AbstractJSPlugin.java b/polyglot/src/main/java/org/restheart/polyglot/AbstractJSPlugin.java
deleted file mode 100644
index 48cd499cec..0000000000
--- a/polyglot/src/main/java/org/restheart/polyglot/AbstractJSPlugin.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*-
- * ========================LICENSE_START=================================
- * restheart-polyglot
- * %%
- * Copyright (C) 2020 - 2024 SoftInstigate
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- * =========================LICENSE_END==================================
- */
-package org.restheart.polyglot;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-import com.mongodb.client.MongoClient;
-
-import org.graalvm.polyglot.Context;
-import org.graalvm.polyglot.Engine;
-import org.graalvm.polyglot.HostAccess;
-import org.graalvm.polyglot.Source;
-import org.graalvm.polyglot.io.IOAccess;
-import org.restheart.configuration.Configuration;
-import org.restheart.plugins.InterceptPoint;
-import org.restheart.plugins.RegisterPlugin.MATCH_POLICY;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public abstract class AbstractJSPlugin {
- private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJSPlugin.class);
-
- protected Map contextOptions = new HashMap<>();
-
- protected Engine engine = Engine.create();
-
- protected String modulesReplacements;
- protected Source handleSource;
-
- protected String name;
- protected String pluginClass;
- protected String description;
- protected String uri;
- protected boolean secured;
- protected MATCH_POLICY matchPolicy;
- protected InterceptPoint interceptPoint;
-
- protected boolean isService;
- protected boolean isInterceptor;
-
- protected Optional mclient;
- protected Configuration conf;
-
- protected AbstractJSPlugin() {
- this.name = null;
- this.pluginClass = null;
- this.description = null;
- this.uri = null;
- this.secured = false;
- this.matchPolicy = null;
- this.interceptPoint = null;
- this.conf = null;
- this.isService = true;
- this.isInterceptor = false;
- }
-
- protected AbstractJSPlugin(String name,
- String pluginClass,
- String description,
- String uri,
- boolean secured,
- MATCH_POLICY matchPolicy,
- InterceptPoint interceptPoint,
- Configuration conf,
- boolean isService,
- boolean isInterceptor) {
- this.name = name;
- this.pluginClass = pluginClass;
- this.description = description;
- this.uri = uri;
- this.secured = secured;
- this.matchPolicy = matchPolicy;
- this.interceptPoint = interceptPoint;
- this.conf = conf;
- this.isService = isService;
- this.isInterceptor = isInterceptor;
- }
-
- public static Context context(Engine engine, Map OPTS) {
- return Context.newBuilder().engine(engine)
- .allowAllAccess(true)
- .allowHostAccess(HostAccess.ALL)
- .allowHostClassLookup(className -> true)
- .allowIO(IOAccess.ALL)
- .allowExperimentalOptions(true)
- .options(OPTS)
- .build();
- }
-
- public String getName() {
- return name;
- }
-
- public String getPluginClass() {
- return pluginClass;
- }
-
- public String getUri() {
- return uri;
- }
-
- public String getDescription() {
- return description;
- }
-
- public boolean isSecured() {
- return secured;
- }
-
- public MATCH_POLICY getMatchPolicy() {
- return matchPolicy;
- }
-
- public InterceptPoint getInterceptPoint() {
- return interceptPoint;
- }
-
- public static void addBindings(Context ctx,
- String pluginName,
- Configuration conf,
- Logger LOGGER,
- Optional mclient) {
- ctx.getBindings("js").putMember("LOGGER", LOGGER);
-
- if (mclient.isPresent()) {
- ctx.getBindings("js").putMember("mclient", mclient.get());
- }
-
- var args = conf != null
- ? conf.getOrDefault(pluginName, new HashMap())
- : new HashMap();
-
- ctx.getBindings("js").putMember("pluginArgs", args);
- }
-
- /**
- *
- * @return the Context
- */
- protected Context ctx() {
- var ret = context(engine, contextOptions);
- addBindings(ret, pluginClass, conf, LOGGER, mclient);
- return ret;
- }
-}
diff --git a/polyglot/src/main/java/org/restheart/polyglot/ContextQueue.java b/polyglot/src/main/java/org/restheart/polyglot/ContextQueue.java
new file mode 100644
index 0000000000..4fd4e1a37a
--- /dev/null
+++ b/polyglot/src/main/java/org/restheart/polyglot/ContextQueue.java
@@ -0,0 +1,103 @@
+/*-
+ * ========================LICENSE_START=================================
+ * restheart-polyglot
+ * %%
+ * Copyright (C) 2020 - 2024 SoftInstigate
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * =========================LICENSE_END==================================
+ */
+package org.restheart.polyglot;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Engine;
+import org.graalvm.polyglot.HostAccess;
+import org.graalvm.polyglot.io.IOAccess;
+import org.restheart.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.client.MongoClient;
+
+public class ContextQueue {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ContextQueue.class);
+ private final int QUEUE_SIZE = 4 * Runtime.getRuntime().availableProcessors();
+ private final ArrayBlockingQueue QUEUE = new ArrayBlockingQueue<>(QUEUE_SIZE);
+
+ public ContextQueue(Engine engine, String name, Configuration conf, Logger logger, Optional mclient, String modulesReplacements, Map OPTS) {
+ for (var c = 0; c < QUEUE_SIZE; c++) {
+ QUEUE.add(newContext(engine, name, conf, logger, mclient, modulesReplacements, OPTS));
+ }
+ }
+
+ public Context take() throws InterruptedException {
+ return QUEUE.take();
+ }
+
+ public void release(Context ctx) {
+ try {
+ QUEUE.add(ctx);
+ } catch(IllegalStateException ise) {
+ try (ctx) {
+ // queue is full
+ LOGGER.warn("Error releasing Context in {}", Thread.currentThread().getName());
+ }
+ }
+ }
+
+ public static Context newContext(Engine engine, String name, Configuration conf, Logger LOGGER, Optional mclient, String modulesReplacements, Map OPTS) {
+ if (modulesReplacements!= null) {
+ LOGGER.debug("modules-replacements: {} ", modulesReplacements);
+ OPTS.put("js.commonjs-core-modules-replacements", modulesReplacements);
+ } else {
+ OPTS.remove("js.commonjs-core-modules-replacements");
+ }
+
+ var ctx = Context.newBuilder().engine(engine)
+ .allowAllAccess(true)
+ .allowHostAccess(HostAccess.ALL)
+ .allowHostClassLookup(className -> true)
+ .allowIO(IOAccess.ALL)
+ .allowExperimentalOptions(true)
+ .options(OPTS)
+ .build();
+
+ addBindings(ctx, name, conf, LOGGER, mclient);
+
+ return ctx;
+ }
+
+ private static void addBindings(Context ctx,
+ String pluginName,
+ Configuration conf,
+ Logger logger,
+ Optional mclient) {
+ ctx.getBindings("js").putMember("LOGGER", logger);
+
+ if (mclient.isPresent()) {
+ ctx.getBindings("js").putMember("mclient", mclient.get());
+ }
+
+ var args = conf != null
+ ? conf.getOrDefault(pluginName, new HashMap<>())
+ : new HashMap();
+
+ ctx.getBindings("js").putMember("pluginArgs", args);
+ }
+}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/JSPlugin.java b/polyglot/src/main/java/org/restheart/polyglot/JSPlugin.java
new file mode 100644
index 0000000000..adc87f1337
--- /dev/null
+++ b/polyglot/src/main/java/org/restheart/polyglot/JSPlugin.java
@@ -0,0 +1,131 @@
+/*-
+ * ========================LICENSE_START=================================
+ * restheart-polyglot
+ * %%
+ * Copyright (C) 2020 - 2024 SoftInstigate
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * =========================LICENSE_END==================================
+ */
+package org.restheart.polyglot;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Engine;
+import org.graalvm.polyglot.Source;
+import org.restheart.configuration.Configuration;
+import org.restheart.polyglot.services.JSServiceArgs;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.client.MongoClient;
+
+public abstract class JSPlugin {
+ protected static final Logger LOGGER = LoggerFactory.getLogger(JSPlugin.class);
+
+ private static final Engine engine = Engine.create();
+
+ private final String modulesReplacements;
+ private final Source handleSource;
+
+ private final String name;
+ private final String description;
+ private final Optional mclient;
+ private final Configuration configuration;
+
+ protected ContextQueue contextQueue;
+
+ /**
+ *
+ * @param name
+ * @param configuration
+ * @param description
+ * @param modulesReplacements
+ * @param handleSource
+ * @param mclient
+ * @param opts
+ */
+ public JSPlugin(String name,
+ String description,
+ Source handleSource,
+ String modulesReplacements,
+ Configuration configuration,
+ Optional mclient,
+ Map opts) {
+ this.name = name;
+ this.description = description;
+ this.handleSource = handleSource;
+ this.mclient = mclient;
+ this.configuration = configuration;
+ this.modulesReplacements = modulesReplacements;
+ this.contextQueue = new ContextQueue(engine, name, configuration, LOGGER, mclient, modulesReplacements, opts);
+ }
+
+ /**
+ *
+ * @param args
+ */
+ public JSPlugin(JSServiceArgs args) {
+ this.name = args.name();
+ this.description = args.description();
+ this.handleSource = args.handleSource();
+ this.mclient = args.mclient();
+ this.configuration = args.configuration();
+ this.modulesReplacements = args.modulesReplacements();
+ this.contextQueue = new ContextQueue(engine, name, configuration, LOGGER, mclient, modulesReplacements, args.contextOptions());
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ *
+ * @return the Context
+ * @throws java.lang.InterruptedException
+ */
+ protected Context takeCtx() throws InterruptedException {
+ return this.contextQueue.take();
+ }
+
+ /**
+ *
+ * @param ctx
+ */
+ protected void releaseCtx(Context ctx) {
+ this.contextQueue.release(ctx);
+ }
+
+ public Optional mclient() {
+ return mclient;
+ }
+
+ public Source handleSource() {
+ return handleSource;
+ }
+
+ public Configuration configuration() {
+ return configuration;
+ }
+
+ public static Engine engine() {
+ return engine;
+ }
+}
diff --git a/polyglot/src/main/java/org/restheart/polyglot/PolyglotDeployer.java b/polyglot/src/main/java/org/restheart/polyglot/PolyglotDeployer.java
index c89a88d152..bfdf2dc9ee 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/PolyglotDeployer.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/PolyglotDeployer.java
@@ -63,6 +63,11 @@
import org.restheart.exchange.ServiceRequest;
import org.restheart.exchange.ServiceResponse;
import org.restheart.graal.ImageInfo;
+import org.restheart.polyglot.interceptors.JSInterceptor;
+import org.restheart.polyglot.interceptors.JSInterceptorFactory;
+import org.restheart.polyglot.services.JSService;
+import org.restheart.polyglot.services.JSStringService;
+import org.restheart.polyglot.services.NodeService;
import org.restheart.utils.ThreadsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,7 +85,7 @@ public class PolyglotDeployer implements Initializer {
private Path pluginsDirectory = null;
- private static final Map DEPLOYEES = new HashMap<>();
+ private static final Map DEPLOYEES = new HashMap<>();
private JSInterceptorFactory jsInterceptorFactory;
@@ -344,7 +349,7 @@ private List findDeclaredPlugins(Path path, String prop, boolean checkPlug
}
}
- private void deploy(List services, List nodeServices, List interceptors) throws IOException {
+ private void deploy(List services, List nodeServices, List interceptors) throws IOException, InterruptedException {
for (Path service: services) {
deployService(service);
}
@@ -358,28 +363,28 @@ private void deploy(List services, List nodeServices, List int
}
}
- private void deployService(Path pluginPath) throws IOException {
+ private void deployService(Path pluginPath) throws IOException, InterruptedException {
if (isRunningOnNode()) {
throw new IllegalStateException("Cannot deploy a CommonJs service, RESTHeart is running on Node");
}
try {
- var srv = new JavaScriptService(pluginPath, this.mclient, this.config);
+ var srv = new JSStringService(pluginPath, this.mclient, this.config);
- var record = new PluginRecord, ? extends ServiceResponse>>>(srv.getName(),
+ var record = new PluginRecord, ? extends ServiceResponse>>>(srv.name(),
srv.getDescription(),
- srv.isSecured(),
+ srv.secured(),
true,
srv.getClass().getName(),
srv,
new HashMap<>());
- registry.plugService(record, srv.getUri(), srv.getMatchPolicy(), srv.isSecured());
+ registry.plugService(record, srv.uri(), srv.matchPolicy(), srv.secured());
DEPLOYEES.put(pluginPath.toAbsolutePath(), srv);
LOGGER.info(ansi().fg(GREEN).a("URI {} bound to service {}, description: {}, secured: {}, uri match {}").reset().toString(),
- srv.getUri(), srv.getName(), srv.getDescription(), srv.isSecured(), srv.getMatchPolicy());
+ srv.uri(), srv.name(), srv.getDescription(), srv.secured(), srv.matchPolicy());
} catch(IllegalArgumentException | IllegalStateException e) {
LOGGER.error("Error deploying plugin {}", pluginPath, e);
}
@@ -402,15 +407,15 @@ private void deployNodeService(Path pluginPath) throws IOException {
var srv = srvf.get();
- var record = new PluginRecord, ? extends ServiceResponse>>>(srv.getName(), "description", srv.isSecured(), true,
+ var record = new PluginRecord, ? extends ServiceResponse>>>(srv.name(), "description", srv.secured(), true,
srv.getClass().getName(), srv, new HashMap<>());
- registry.plugService(record, srv.getUri(), srv.getMatchPolicy(), srv.isSecured());
+ registry.plugService(record, srv.uri(), srv.matchPolicy(), srv.secured());
DEPLOYEES.put(pluginPath.toAbsolutePath(), srv);
LOGGER.info(ansi().fg(GREEN).a("URI {} bound to service {}, description: {}, secured: {}, uri match {}").reset().toString(),
- srv.getUri(), srv.getName(), srv.getDescription(), srv.isSecured(), srv.getMatchPolicy());
+ srv.uri(), srv.name(), srv.getDescription(), srv.secured(), srv.matchPolicy());
} catch (IOException | InterruptedException | ExecutionException ex) {
LOGGER.error("Error deploying node service {}", pluginPath, ex);
Thread.currentThread().interrupt();
@@ -421,7 +426,7 @@ var record = new PluginRecord, ? extends Ser
}
- private void deployInterceptor(Path pluginPath) throws IOException {
+ private void deployInterceptor(Path pluginPath) throws IOException, InterruptedException {
if (isRunningOnNode()) {
throw new IllegalStateException("Cannot deploy a CommonJs interceptor, RESTHeart is running on Node");
}
@@ -430,7 +435,7 @@ private void deployInterceptor(Path pluginPath) throws IOException {
registry.addInterceptor(interceptorRecord);
- DEPLOYEES.put(pluginPath.toAbsolutePath(), (AbstractJSPlugin) interceptorRecord.getInstance());
+ DEPLOYEES.put(pluginPath.toAbsolutePath(), (JSPlugin) interceptorRecord.getInstance());
LOGGER.info(ansi().fg(GREEN).a("Added interceptor {}, description: {}").reset().toString(),
interceptorRecord.getName(),
@@ -444,36 +449,35 @@ private void undeploy(Path pluginPath) {
private void undeployServices(Path pluginPath) {
var pathsToUndeploy = DEPLOYEES.keySet().stream()
- .filter(path -> DEPLOYEES.get(path).isService)
+ .filter(path -> (DEPLOYEES.get(path) instanceof JSService))
.filter(path -> path.startsWith(pluginPath))
.collect(Collectors.toList());
for (var pathToUndeploy: pathsToUndeploy) {
- var toUndeploy = DEPLOYEES.remove(pathToUndeploy);
+ var _toUndeploy = DEPLOYEES.remove(pathToUndeploy);
- if (toUndeploy != null) {
- registry.unplug(toUndeploy.getUri(), toUndeploy.getMatchPolicy());
+ if (_toUndeploy != null && _toUndeploy instanceof JSService toUndeploy) {
+ registry.unplug(toUndeploy.uri(), toUndeploy.matchPolicy());
- LOGGER.info(ansi().fg(GREEN).a("removed service {} bound to URI {}").reset().toString(),
- toUndeploy.getName(), toUndeploy.getUri());
+ LOGGER.info(ansi().fg(GREEN).a("removed service {} bound to URI {}").reset().toString(), toUndeploy.name(), toUndeploy.uri());
}
}
}
private void undeployInterceptors(Path pluginPath) {
var pathsToUndeploy = DEPLOYEES.keySet().stream()
- .filter(path -> DEPLOYEES.get(path).isInterceptor)
+ .filter(path -> DEPLOYEES.get(path) instanceof JSInterceptor)
.filter(path -> path.startsWith(pluginPath))
.collect(Collectors.toList());
for (var pathToUndeploy: pathsToUndeploy) {
var toUndeploy = DEPLOYEES.remove(pathToUndeploy);
- var removed = registry.removeInterceptorIf(interceptor -> Objects.equal(interceptor.getName(), toUndeploy.getName()));
+ var removed = registry.removeInterceptorIf(interceptor -> Objects.equal(interceptor.getName(), toUndeploy.name()));
if (removed) {
- LOGGER.info(ansi().fg(GREEN).a("removed interceptor {}").reset().toString(), toUndeploy.getName());
+ LOGGER.info(ansi().fg(GREEN).a("removed interceptor {}").reset().toString(), toUndeploy.name());
} else {
- LOGGER.warn("interceptor {} was not removed", toUndeploy.getName());
+ LOGGER.warn("interceptor {} was not removed", toUndeploy.name());
}
}
}
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/BsonJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/BsonJSInterceptor.java
index d164db752c..b8c7ca11da 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/BsonJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/BsonJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.BsonRequest;
import org.restheart.exchange.BsonResponse;
import org.restheart.plugins.InterceptPoint;
-public class BsonJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class BsonJSInterceptor extends JSInterceptor {
public BsonJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayJSInterceptor.java
index 758791da14..548e348751 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.ByteArrayRequest;
import org.restheart.exchange.ByteArrayResponse;
import org.restheart.plugins.InterceptPoint;
-public class ByteArrayJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class ByteArrayJSInterceptor extends JSInterceptor {
public ByteArrayJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayProxyJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayProxyJSInterceptor.java
index 2bc8223ae3..3b265f6644 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayProxyJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/ByteArrayProxyJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.ByteArrayProxyRequest;
import org.restheart.exchange.ByteArrayProxyResponse;
import org.restheart.plugins.InterceptPoint;
-public class ByteArrayProxyJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class ByteArrayProxyJSInterceptor extends JSInterceptor {
public ByteArrayProxyJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/CsvJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/CsvJSInterceptor.java
index b1d4d7d624..5768414886 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/CsvJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/CsvJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.BsonFromCsvRequest;
import org.restheart.exchange.BsonResponse;
import org.restheart.plugins.InterceptPoint;
-public class CsvJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class CsvJSInterceptor extends JSInterceptor {
public CsvJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/AbstractJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JSInterceptor.java
similarity index 55%
rename from polyglot/src/main/java/org/restheart/polyglot/interceptors/AbstractJSInterceptor.java
rename to polyglot/src/main/java/org/restheart/polyglot/interceptors/JSInterceptor.java
index 43593dfa79..9f4b7f1a3c 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/AbstractJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JSInterceptor.java
@@ -23,6 +23,7 @@
import java.util.Map;
import java.util.Optional;
+import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.restheart.configuration.Configuration;
@@ -30,16 +31,13 @@
import org.restheart.exchange.Response;
import org.restheart.plugins.InterceptPoint;
import org.restheart.plugins.Interceptor;
-import org.restheart.polyglot.AbstractJSPlugin;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.restheart.polyglot.JSPlugin;
-import com.google.common.collect.Maps;
import com.mongodb.client.MongoClient;
-public class AbstractJSInterceptor, S extends Response>> extends AbstractJSPlugin implements Interceptor {
- private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJSInterceptor.class);
-
+public class JSInterceptor, S extends Response>> extends JSPlugin implements Interceptor {
+ private final String pluginClass;
+ private final InterceptPoint interceptPoint;
private final Source resolveSource;
/**
@@ -47,69 +45,80 @@ public class AbstractJSInterceptor, S extends Response>>
* @param pluginClass
* @param description
* @param interceptPoint
+ * @param modulesReplacements
* @param handleSource
* @param mclient
* @param resolveSource
* @param contextOptions
* @param config
*/
- public AbstractJSInterceptor(String name,
+ public JSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, null, false, null, interceptPoint, config, false, true);
- this.contextOptions = contextOptions;
- this.mclient = mclient;
- this.conf = config;
- this.handleSource = handleSource;
+ super(name, description, handleSource, modulesReplacements, config, mclient, contextOptions);
+ this.pluginClass = pluginClass;
+ this.interceptPoint = interceptPoint;
this.resolveSource = resolveSource;
}
+ public InterceptPoint getInterceptPoint() {
+ return interceptPoint;
+ }
+
/**
*
* @param request
- * @param response */
+ * @param response
+ * @throws java.lang.InterruptedException */
@Override
- public void handle(R request, S response) {
- try (final var ctx = ctx()) {
- ctx.eval(this.handleSource).executeVoid(request, response);
+ public void handle(R request, S response) throws InterruptedException {
+ Context ctx = null;
+
+ try {
+ ctx = takeCtx();
+ ctx.eval(handleSource()).executeVoid(request, response);
+ } finally {
+ if (ctx != null) {
+ releaseCtx(ctx);
+ }
}
}
@Override
public boolean resolve(R request, S response) {
- var ret = resolve().execute(request);
+ Context ctx = null;
+ Value ret = null;
+
+ try {
+ ctx = takeCtx();
+ ret = ctx.eval(this.resolveSource).execute(request);
+ } catch(InterruptedException ie) {
+ LOGGER.error("error on interceptor {} resolve()", name(), ie);
+ request.setInError(true);
+ return false;
+ } finally {
+ if (ctx != null) {
+ releaseCtx(ctx);
+ }
+ }
- if (ret.isBoolean()) {
+ if (ret != null && ret.isBoolean()) {
return ret.asBoolean();
} else {
- LOGGER.error("resolve() of interceptor did not returned a boolean", name);
+ LOGGER.error("resolve() of interceptor {} did not returned a boolean", name());
+ request.setInError(true);
return false;
}
}
- // each working thread is associates with one resolve
- // because js Context does not allow multithreaded access
- protected Map resolves = Maps.newHashMap();
-
- /**
- *
- * @return the resolve Value associated with this thread. If not existing, it instanitates it.
- */
- private Value resolve() {
- var workingThreadName = Thread.currentThread().getName();
-
- if (this.resolves.containsKey(workingThreadName)) {
- return this.resolves.get(workingThreadName);
- } else {
- var resolve = ctx().eval(this.resolveSource);
- this.resolves.put(workingThreadName, resolve);
- return resolve;
- }
+ public String getPluginClass() {
+ return pluginClass;
}
}
diff --git a/polyglot/src/main/java/org/restheart/polyglot/JSInterceptorFactory.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JSInterceptorFactory.java
similarity index 87%
rename from polyglot/src/main/java/org/restheart/polyglot/JSInterceptorFactory.java
rename to polyglot/src/main/java/org/restheart/polyglot/interceptors/JSInterceptorFactory.java
index 17edee03e0..2d43bf0206 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/JSInterceptorFactory.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JSInterceptorFactory.java
@@ -18,7 +18,7 @@
* along with this program. If not, see .
* =========================LICENSE_END==================================
*/
-package org.restheart.polyglot;
+package org.restheart.polyglot.interceptors;
import java.io.IOException;
import java.nio.file.Files;
@@ -27,6 +27,7 @@
import java.util.Map;
import java.util.Optional;
+import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
@@ -36,14 +37,8 @@
import org.restheart.plugins.InterceptPoint;
import org.restheart.plugins.Interceptor;
import org.restheart.plugins.PluginRecord;
-import static org.restheart.polyglot.AbstractJSPlugin.addBindings;
-import org.restheart.polyglot.interceptors.AbstractJSInterceptor;
-import org.restheart.polyglot.interceptors.ByteArrayJSInterceptor;
-import org.restheart.polyglot.interceptors.ByteArrayProxyJSInterceptor;
-import org.restheart.polyglot.interceptors.CsvJSInterceptor;
-import org.restheart.polyglot.interceptors.JsonJSInterceptor;
-import org.restheart.polyglot.interceptors.MongoJSInterceptor;
-import org.restheart.polyglot.interceptors.StringJSInterceptor;
+import org.restheart.polyglot.ContextQueue;
+import org.restheart.polyglot.JSPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,7 +50,7 @@
* @author Andrea Di Cesare {@literal }
*/
public class JSInterceptorFactory {
- private static final Logger LOGGER = LoggerFactory.getLogger(JSInterceptorFactory.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(JSPlugin.class);
Map contextOptions = new HashMap<>();
@@ -70,7 +65,7 @@ public JSInterceptorFactory(Optional mclient, Configuration config)
this.config = config;
}
- public PluginRecord> create(Path pluginPath) throws IOException {
+ public PluginRecord> create(Path pluginPath) throws IOException, InterruptedException {
// find plugin root, i.e the parent dir that contains package.json
var pluginRoot = pluginPath.getParent();
while(true) {
@@ -100,9 +95,8 @@ public JSInterceptorFactory(Optional mclient, Configuration config)
// check plugin definition
var sindexPath = pluginPath.toAbsolutePath().toString();
- try (var ctx = AbstractJSPlugin.context(engine, contextOptions)) {
- // add bindings to contenxt
- addBindings(ctx, "foo", null, LOGGER, this.mclient);
+
+ try (Context ctx = ContextQueue.newContext(engine, "foo", config, LOGGER, mclient, "", contextOptions)) {
// ******** evaluate and check options
@@ -150,9 +144,9 @@ public JSInterceptorFactory(Optional mclient, Configuration config)
var sb = new StringBuilder();
options.getMember("modulesReplacements").getMemberKeys().stream()
- .forEach(k -> sb.append(k).append(":")
- .append(options.getMember("modulesReplacements").getMember(k))
- .append(","));
+ .forEach(k -> sb.append(k).append(":")
+ .append(options.getMember("modulesReplacements").getMember(k))
+ .append(","));
modulesReplacements = sb.toString();
}
@@ -181,8 +175,7 @@ public JSInterceptorFactory(Optional mclient, Configuration config)
if (!options.getMemberKeys().contains("pluginClass")) {
pluginClass = "StringInterceptor";
} else if (!options.getMember("pluginClass").isString()) {
- throw new IllegalArgumentException(
- "wrong js interceptor " + pluginPath.toAbsolutePath() + ", wrong member 'options.pluginClass', " + HANDLE_RESOLVE_HINT);
+ throw new IllegalArgumentException("wrong js interceptor " + pluginPath.toAbsolutePath() + ", wrong member 'options.pluginClass', " + HANDLE_RESOLVE_HINT);
} else {
pluginClass = options.getMember("pluginClass").asString();
}
@@ -221,18 +214,16 @@ public JSInterceptorFactory(Optional mclient, Configuration config)
throw new IllegalArgumentException("wrong js interceptor " + pluginPath.toAbsolutePath() + ", " + HANDLE_RESOLVE_HINT);
}
- AbstractJSInterceptor extends Request>, ? extends Response>> interceptor;
-
-
+ JSInterceptor extends Request>, ? extends Response>> interceptor;
- Map opts = Maps.newHashMap();
- opts.putAll(contextOptions);
+ Map contextOpts = Maps.newHashMap();
+ contextOpts.putAll(contextOptions);
if (modulesReplacements != null) {
LOGGER.debug("modules-replacements: {} ", modulesReplacements);
- opts.put("js.commonjs-core-modules-replacements", modulesReplacements);
+ contextOpts.put("js.commonjs-core-modules-replacements", modulesReplacements);
} else {
- opts.remove("js.commonjs-core-modules-replacements");
+ contextOpts.remove("js.commonjs-core-modules-replacements");
}
switch (pluginClass) {
@@ -240,69 +231,76 @@ public JSInterceptorFactory(Optional mclient, Configuration config)
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
case "BsonInterceptor", "org.restheart.plugins.BsonInterceptor" -> interceptor = new StringJSInterceptor(name,
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
case "ByteArrayInterceptor", "org.restheart.plugins.ByteArrayInterceptor" -> interceptor = new ByteArrayJSInterceptor(name,
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
case "ByteArrayProxyInterceptor", "org.restheart.plugins.ByteArrayProxyInterceptor" -> interceptor = new ByteArrayProxyJSInterceptor(name,
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
case "CsvInterceptor", "org.restheart.plugins.CsvInterceptor" -> interceptor = new CsvJSInterceptor(name,
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
case "JsonInterceptor", "org.restheart.plugins.JsonInterceptor" -> interceptor = new JsonJSInterceptor(name,
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
case "MongoInterceptor", "org.restheart.plugins.MongoInterceptor" -> interceptor = new MongoJSInterceptor(name,
pluginClass,
description,
interceptPoint,
+ modulesReplacements,
handleSource,
resolveSource,
mclient,
config,
- opts);
+ contextOpts);
default -> throw new IllegalArgumentException("wrong js interceptor, wrong member 'options.pluginClass', " + PACKAGE_HINT);
}
- return new PluginRecord<>(interceptor.getName(),
+ return new PluginRecord<>(interceptor.name(),
interceptor.getDescription(),
false,
true,
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonJSInterceptor.java
index a52f70ee22..517f7d9322 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.JsonRequest;
import org.restheart.exchange.JsonResponse;
import org.restheart.plugins.InterceptPoint;
-public class JsonJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class JsonJSInterceptor extends JSInterceptor {
public JsonJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonProxyJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonProxyJSInterceptor.java
index 1af9495764..9975db9dc7 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonProxyJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/JsonProxyJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.JsonProxyRequest;
import org.restheart.exchange.JsonProxyResponse;
import org.restheart.plugins.InterceptPoint;
-public class JsonProxyJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class JsonProxyJSInterceptor extends JSInterceptor {
public JsonProxyJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/MongoJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/MongoJSInterceptor.java
index b9783c9175..25d843ae51 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/MongoJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/MongoJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.MongoRequest;
import org.restheart.exchange.MongoResponse;
import org.restheart.plugins.InterceptPoint;
-public class MongoJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class MongoJSInterceptor extends JSInterceptor {
public MongoJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/interceptors/StringJSInterceptor.java b/polyglot/src/main/java/org/restheart/polyglot/interceptors/StringJSInterceptor.java
index e31df9d9f3..b748ea1aca 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/interceptors/StringJSInterceptor.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/interceptors/StringJSInterceptor.java
@@ -23,24 +23,25 @@
import java.util.Map;
import java.util.Optional;
-import com.mongodb.client.MongoClient;
-
import org.graalvm.polyglot.Source;
import org.restheart.configuration.Configuration;
import org.restheart.exchange.StringRequest;
import org.restheart.exchange.StringResponse;
import org.restheart.plugins.InterceptPoint;
-public class StringJSInterceptor extends AbstractJSInterceptor {
+import com.mongodb.client.MongoClient;
+
+public class StringJSInterceptor extends JSInterceptor {
public StringJSInterceptor(String name,
String pluginClass,
String description,
InterceptPoint interceptPoint,
+ String modulesReplacements,
Source handleSource,
Source resolveSource,
Optional mclient,
Configuration config,
Map contextOptions) {
- super(name, pluginClass, description, interceptPoint, handleSource, resolveSource, mclient, config, contextOptions);
+ super(name, pluginClass, description, interceptPoint, modulesReplacements, handleSource, resolveSource, mclient, config, contextOptions);
}
}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/services/JSService.java b/polyglot/src/main/java/org/restheart/polyglot/services/JSService.java
new file mode 100644
index 0000000000..f6835e63e7
--- /dev/null
+++ b/polyglot/src/main/java/org/restheart/polyglot/services/JSService.java
@@ -0,0 +1,81 @@
+/*-
+ * ========================LICENSE_START=================================
+ * restheart-security
+ * %%
+ * Copyright (C) 2018 - 2024 SoftInstigate
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * =========================LICENSE_END==================================
+ */
+
+package org.restheart.polyglot.services;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.graalvm.polyglot.Source;
+import org.restheart.configuration.Configuration;
+import org.restheart.plugins.RegisterPlugin.MATCH_POLICY;
+import org.restheart.polyglot.JSPlugin;
+
+import com.mongodb.client.MongoClient;
+
+
+/**
+ *
+ * @author Andrea Di Cesare {@literal }
+ */
+public abstract class JSService extends JSPlugin {
+ private final String uri;
+ private final boolean secured;
+ private final MATCH_POLICY matchPolicy;
+
+ public JSService(String name,
+ String pluginClass,
+ String description,
+ String uri,
+ boolean secured,
+ MATCH_POLICY matchPolicy,
+ Source handleSource,
+ String modulesReplacements,
+ Configuration config,
+ Optional mclient,
+ Map contextOptions) {
+ super(name, description, handleSource, modulesReplacements, config, mclient, contextOptions);
+ this.uri = uri;
+ this.secured = secured;
+ this.matchPolicy = matchPolicy;
+ }
+
+
+ public JSService(JSServiceArgs args) {
+ super(args.name(), args.description(), args.handleSource(), args.modulesReplacements(), args.configuration(), args.mclient(), args.contextOptions());
+ this.uri = args.uri();
+ this.secured = args.secured();
+ this.matchPolicy = args.matchPolicy();
+ }
+
+ public String uri() {
+ return uri;
+ }
+
+
+ public boolean secured() {
+ return secured;
+ }
+
+ public MATCH_POLICY matchPolicy() {
+ return matchPolicy;
+ }
+}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/services/JSServiceArgs.java b/polyglot/src/main/java/org/restheart/polyglot/services/JSServiceArgs.java
new file mode 100644
index 0000000000..e5ca090e3d
--- /dev/null
+++ b/polyglot/src/main/java/org/restheart/polyglot/services/JSServiceArgs.java
@@ -0,0 +1,47 @@
+/*-
+ * ========================LICENSE_START=================================
+ * restheart-security
+ * %%
+ * Copyright (C) 2018 - 2024 SoftInstigate
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * =========================LICENSE_END==================================
+ */
+
+package org.restheart.polyglot.services;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.graalvm.polyglot.Source;
+import org.restheart.configuration.Configuration;
+import org.restheart.plugins.RegisterPlugin.MATCH_POLICY;
+
+import com.mongodb.client.MongoClient;
+
+/**
+ *
+ * @author Andrea Di Cesare {@literal }
+ */
+public record JSServiceArgs(String name,
+ String description,
+ String uri,
+ boolean secured,
+ String modulesReplacements,
+ MATCH_POLICY matchPolicy,
+ Source handleSource,
+ Configuration configuration,
+ Optional mclient,
+ Map contextOptions) {
+}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/JavaScriptService.java b/polyglot/src/main/java/org/restheart/polyglot/services/JSStringService.java
similarity index 73%
rename from polyglot/src/main/java/org/restheart/polyglot/JavaScriptService.java
rename to polyglot/src/main/java/org/restheart/polyglot/services/JSStringService.java
index 3d661503c3..456db677f4 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/JavaScriptService.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/services/JSStringService.java
@@ -18,11 +18,12 @@
* along with this program. If not, see .
* =========================LICENSE_END==================================
*/
-package org.restheart.polyglot;
+package org.restheart.polyglot.services;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashMap;
import java.util.Optional;
import org.graalvm.polyglot.Context;
@@ -33,8 +34,7 @@
import org.restheart.exchange.StringResponse;
import org.restheart.plugins.RegisterPlugin.MATCH_POLICY;
import org.restheart.plugins.StringService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.restheart.polyglot.ContextQueue;
import com.mongodb.client.MongoClient;
@@ -42,9 +42,7 @@
*
* @author Andrea Di Cesare {@literal }
*/
-public class JavaScriptService extends AbstractJSPlugin implements StringService {
- private static final Logger LOGGER = LoggerFactory.getLogger(JavaScriptService.class);
-
+public class JSStringService extends JSService implements StringService {
private static final String HANDLE_HINT = """
the plugin module must export the function 'handle', example:
export function handle(request, response) {
@@ -71,13 +69,13 @@ export function handle(request, response) {
}
""";
- JavaScriptService(Path pluginPath, Optional mclient, Configuration conf) throws IOException {
- this.mclient = mclient;
- this.conf = conf;
- this.isService = true;
- this.isInterceptor = false;
+ public JSStringService(Path pluginPath, Optional mclient, Configuration config) throws IOException, InterruptedException {
+ super(args(pluginPath, mclient, config));
+ }
+ private static JSServiceArgs args(Path pluginPath, Optional mclient, Configuration config) throws IOException {
// find plugin root, i.e the parent dir that contains package.json
+ var contextOptions = new HashMap();
var pluginRoot = pluginPath.getParent();
while(true) {
var p = pluginRoot.resolve("package.json");
@@ -96,18 +94,15 @@ export function handle(request, response) {
LOGGER.trace("Enabling require for service {} with require-cwd {} ", pluginPath, requireCwdPath);
}
- // check that the plugin script is js
- var language = Source.findLanguage(pluginPath.toFile());
-
- if (!"js".equals(language)) {
- throw new IllegalArgumentException("wrong js plugin, not javascript");
- }
+ try (Context ctx = ContextQueue.newContext(engine(), "foo", config, LOGGER, mclient, "", contextOptions)) {
+ // check that the plugin script is js
+ var language = Source.findLanguage(pluginPath.toFile());
- var sindexPath = pluginPath.toAbsolutePath().toString();
- try (var ctx = context(engine, contextOptions)) {
+ if (!"js".equals(language)) {
+ throw new IllegalArgumentException("wrong js plugin, not javascript");
+ }
- // add bindings to contenxt
- addBindings(ctx, this.name, conf, LOGGER, this.mclient);
+ var sindexPath = pluginPath.toAbsolutePath().toString();
var optionsScript = "import { options } from '" + sindexPath + "'; options;";
var optionsSource = Source.newBuilder(language, optionsScript, "optionsScript").mimeType("application/javascript+module").build();
@@ -128,84 +123,59 @@ export function handle(request, response) {
checkOptions(options, pluginPath);
- this.name = options.getMember("name").asString();
- this.description = options.getMember("description").asString();
- this.uri = options.getMember("uri").asString();
-
- if (!options.getMemberKeys().contains("secured")) {
- this.secured = false;
- } else {
- this.secured = options.getMember("secured").asBoolean();
- }
-
- if (!options.getMemberKeys().contains("matchPolicy")) {
- this.matchPolicy = MATCH_POLICY.PREFIX;
- } else {
- var _matchPolicy = options.getMember("matchPolicy").asString();
- this.matchPolicy = MATCH_POLICY.valueOf(_matchPolicy);
- }
+ var name = options.getMember("name").asString();
+ var description = options.getMember("description").asString();
+ var uri = options.getMember("uri").asString();
+ var secured = !options.getMemberKeys().contains("secured") ? false : options.getMember("secured").asBoolean();
+ var matchPolicy = !options.getMemberKeys().contains("matchPolicy") ? MATCH_POLICY.PREFIX : MATCH_POLICY.valueOf(options.getMember("matchPolicy").asString());
+ String modulesReplacements = null;
- if (!options.getMemberKeys().contains("modulesReplacements")) {
- this.modulesReplacements = null;
- } else {
+ if (options.getMemberKeys().contains("modulesReplacements")) {
var sb = new StringBuilder();
options.getMember("modulesReplacements").getMemberKeys().stream()
- .forEach(k -> sb.append(k).append(":")
- .append(options.getMember("modulesReplacements").getMember(k))
- .append(","));
+ .forEach(k -> sb.append(k).append(":")
+ .append(options.getMember("modulesReplacements").getMember(k))
+ .append(","));
- this.modulesReplacements = sb.toString();
+ modulesReplacements = sb.toString();
}
// ******** evaluate and check handle
- var handleScript = "import { handle } from '" + sindexPath + "'; handle;";
- this.handleSource = Source.newBuilder(language, handleScript, "handleScript").mimeType("application/javascript+module").build();
+ var _handleScript = "import { handle } from '" + sindexPath + "'; handle;";
+ var handleSource = Source.newBuilder(language, _handleScript, "handleScript").mimeType("application/javascript+module").build();
Value handle;
try {
- handle = ctx.eval(this.handleSource);
+ handle = ctx.eval(handleSource);
} catch (Throwable t) {
throw new IllegalArgumentException("wrong js service " + pluginPath.toAbsolutePath() + ", " + t.getMessage());
}
checkHandle(handle, pluginPath);
- }
- }
- /**
- *
- */
- @Override
- public void handle(StringRequest request, StringResponse response) {
- try (final var ctx = ctx()) {
- ctx.eval(this.handleSource).executeVoid(request, response);
+ return new JSServiceArgs(name, description, uri, secured, modulesReplacements, matchPolicy, handleSource, config, mclient, contextOptions);
}
}
+
+
/**
*
- * @return the Context
- */
+ * @throws java.lang.InterruptedException */
@Override
- protected Context ctx() {
- if (getModulesReplacements() != null) {
- LOGGER.debug("modules-replacements: {} ", getModulesReplacements());
- contextOptions.put("js.commonjs-core-modules-replacements", getModulesReplacements());
- } else {
- contextOptions.remove("js.commonjs-core-modules-replacements");
+ public void handle(StringRequest request, StringResponse response) throws InterruptedException {
+ Context ctx = null;
+ try {
+ ctx = takeCtx();
+ ctx.eval(handleSource()).executeVoid(request, response);
+ } finally {
+ if (ctx != null) {
+ releaseCtx(ctx);
+ }
}
-
- var ctx = context(engine, contextOptions);
- addBindings(ctx, this.name, conf, LOGGER, this.mclient);
-
- return ctx;
- }
-
- public String getModulesReplacements() {
- return this.modulesReplacements;
}
static void checkOptions(Value options, Path pluginPath) {
@@ -272,4 +242,4 @@ static void checkHandle(Value handle, Path pluginPath) {
throw new IllegalArgumentException("wrong js service " + pluginPath.toAbsolutePath() + ", " + HANDLE_HINT);
}
}
-}
+}
\ No newline at end of file
diff --git a/polyglot/src/main/java/org/restheart/polyglot/NodeService.java b/polyglot/src/main/java/org/restheart/polyglot/services/NodeService.java
similarity index 75%
rename from polyglot/src/main/java/org/restheart/polyglot/NodeService.java
rename to polyglot/src/main/java/org/restheart/polyglot/services/NodeService.java
index c34b78a0a4..ec3032f3a6 100644
--- a/polyglot/src/main/java/org/restheart/polyglot/NodeService.java
+++ b/polyglot/src/main/java/org/restheart/polyglot/services/NodeService.java
@@ -18,11 +18,12 @@
* along with this program. If not, see .
* =========================LICENSE_END==================================
*/
-package org.restheart.polyglot;
+package org.restheart.polyglot.services;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.Optional;
import java.util.concurrent.Executors;
@@ -37,6 +38,7 @@
import org.restheart.exchange.StringResponse;
import org.restheart.plugins.StringService;
import org.restheart.plugins.RegisterPlugin.MATCH_POLICY;
+import org.restheart.polyglot.NodeQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,14 +46,11 @@
*
* @author Andrea Di Cesare {@literal }
*/
-public class NodeService extends AbstractJSPlugin implements StringService {
- private static final Logger LOGGER = LoggerFactory.getLogger(NodeService.class);
-
+public class NodeService extends JSService implements StringService {
private String source;
-
private int codeHash = 0;
- private static final String errorHint = """
+ private static final String ERROR_HINT = """
hint: the last statement in the script something like:
({
options: {
@@ -81,18 +80,18 @@ public static Future get(Path scriptPath, Optional mcl
return executor.submit(() -> new NodeService(scriptPath, mclient, conf));
}
- private NodeService(Path scriptPath, Optional mclient, Configuration conf) throws IOException {
- this.mclient = mclient;
- this.conf = conf;
-
+ private NodeService(Path scriptPath, Optional mclient, Configuration config) throws IOException {
+ super(args(scriptPath, mclient, config));
this.source = Files.readString(scriptPath);
this.codeHash = this.source.hashCode();
+ }
+ private static JSServiceArgs args(Path scriptPath, Optional mclient, Configuration config) throws IOException {
// check plugin definition
-
var out = new LinkedBlockingDeque();
- Object[] message = { "parse", this.source, out };
+ Object[] message = { "parse", Files.readString(scriptPath), out };
NodeQueue.instance().queue().offer(message);
+
try {
var result = out.take();
@@ -101,105 +100,113 @@ private NodeService(Path scriptPath, Optional mclient, Configuratio
var parsed = JsonParser.parseString(result);
if (!parsed.isJsonObject()) {
- throw new IllegalArgumentException("wrong node plugin, " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, " + ERROR_HINT);
}
var parsedObj = parsed.getAsJsonObject();
if (!parsedObj.has("options")) {
- throw new IllegalArgumentException("wrong node plugin, missing member 'options', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, missing member 'options', " + ERROR_HINT);
}
if (!parsedObj.get("options").isJsonObject()) {
- throw new IllegalArgumentException("wrong node plugin, wrong member 'options', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, wrong member 'options', " + ERROR_HINT);
}
var optionsObj = parsedObj.getAsJsonObject("options");
if (!optionsObj.has("name")) {
- throw new IllegalArgumentException("wrong node plugin, missing member 'options.name', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, missing member 'options.name', " + ERROR_HINT);
}
if (!optionsObj.get("name").isJsonPrimitive() || !optionsObj.get("name").getAsJsonPrimitive().isString()) {
- throw new IllegalArgumentException("wrong node plugin, wrong member 'options.name', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, wrong member 'options.name', " + ERROR_HINT);
}
- this.name = optionsObj.get("name").getAsString();
+ var name = optionsObj.get("name").getAsString();
if (!optionsObj.has("description")) {
throw new IllegalArgumentException(
- "wrong node plugin, missing member 'options.description', " + errorHint);
+ "wrong node plugin, missing member 'options.description', " + ERROR_HINT);
}
if (!optionsObj.get("description").isJsonPrimitive()
|| !optionsObj.get("description").getAsJsonPrimitive().isString()) {
throw new IllegalArgumentException(
- "wrong node plugin, wrong member 'options.description', " + errorHint);
+ "wrong node plugin, wrong member 'options.description', " + ERROR_HINT);
}
- this.description = optionsObj.get("description").getAsString();
+ var description = optionsObj.get("description").getAsString();
if (!optionsObj.has("uri")) {
- throw new IllegalArgumentException("wrong node plugin, missing member 'options.uri', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, missing member 'options.uri', " + ERROR_HINT);
}
if (!optionsObj.get("uri").isJsonPrimitive() || !optionsObj.get("uri").getAsJsonPrimitive().isString()) {
- throw new IllegalArgumentException("wrong node plugin, wrong member 'options.uri', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, wrong member 'options.uri', " + ERROR_HINT);
}
if (!optionsObj.get("uri").getAsString().startsWith("/")) {
- throw new IllegalArgumentException("wrong node plugin, wrong member 'options.uri', " + errorHint);
+ throw new IllegalArgumentException("wrong node plugin, wrong member 'options.uri', " + ERROR_HINT);
}
- this.uri = optionsObj.get("uri").getAsString();
+ var uri = optionsObj.get("uri").getAsString();
+
+ boolean secured;
if (!optionsObj.has("secured")) {
- this.secured = false;
+ secured = false;
} else {
if (!optionsObj.get("secured").isJsonPrimitive()
|| !optionsObj.get("secured").getAsJsonPrimitive().isBoolean()) {
throw new IllegalArgumentException(
- "wrong node plugin, wrong member 'options.secured', " + errorHint);
+ "wrong node plugin, wrong member 'options.secured', " + ERROR_HINT);
} else {
- this.secured = optionsObj.get("secured").getAsBoolean();
+ secured = optionsObj.get("secured").getAsBoolean();
}
}
+ MATCH_POLICY matchPolicy;
+
if (!optionsObj.has("matchPolicy")) {
- this.matchPolicy = MATCH_POLICY.PREFIX;
+ matchPolicy = MATCH_POLICY.PREFIX;
} else {
if (!optionsObj.get("matchPolicy").isJsonPrimitive()
|| !optionsObj.get("matchPolicy").getAsJsonPrimitive().isString()) {
throw new IllegalArgumentException(
- "wrong node plugin, wrong member 'options.secured', " + errorHint);
+ "wrong node plugin, wrong member 'options.secured', " + ERROR_HINT);
} else {
var _matchPolicy = optionsObj.get("matchPolicy").getAsString();
try {
- this.matchPolicy = MATCH_POLICY.valueOf(_matchPolicy);
+ matchPolicy = MATCH_POLICY.valueOf(_matchPolicy);
} catch (Throwable t) {
throw new IllegalArgumentException(
- "wrong node plugin, wrong member 'options.matchPolicy', " + errorHint);
+ "wrong node plugin, wrong member 'options.matchPolicy', " + ERROR_HINT);
}
}
}
if (!parsedObj.has("handle")) {
- throw new IllegalArgumentException("wrong js plugin, missing member 'handle', " + errorHint);
+ throw new IllegalArgumentException("wrong js plugin, missing member 'handle', " + ERROR_HINT);
}
- if (!parsedObj.get("handle").isJsonPrimitive() || !parsedObj.get("handle").getAsJsonPrimitive().isString()
- || !"function".equals(parsedObj.get("handle").getAsString())) {
- throw new IllegalArgumentException("wrong js plugin, member 'handle' is not a function, " + errorHint);
+ if (!parsedObj.get("handle").isJsonPrimitive() || !parsedObj.get("handle").getAsJsonPrimitive().isString() || !"function".equals(parsedObj.get("handle").getAsString())) {
+ throw new IllegalArgumentException("wrong js plugin, member 'handle' is not a function, " + ERROR_HINT);
}
+
+ return new JSServiceArgs(name, description, uri, secured, null, matchPolicy, null, config, mclient, new HashMap());
} catch (InterruptedException ie) {
LOGGER.debug("Error initializing node plugin", ie);
Thread.currentThread().interrupt();
}
+
+ return null;
}
/**
*
*/
+ @Override
public void handle(StringRequest request, StringResponse response) {
var out = new LinkedBlockingDeque