Ejemplo n.º 1
0
  private Map<String, Object> mergeBindings(
      final Context context, final Bindings request, final Bindings... scopes) {
    Set<String> safeAttributes = null != request ? request.keySet() : Collections.EMPTY_SET;
    Map<String, Object> scope = new HashMap<String, Object>();
    for (Map<String, Object> next : scopes) {
      if (null == next) {
        continue;
      }
      for (Map.Entry<String, Object> entry : next.entrySet()) {
        if (scope.containsKey(entry.getKey()) || safeAttributes.contains(entry.getKey())) {
          continue;
        }
        scope.put(entry.getKey(), entry.getValue());
      }
    }
    // Make lazy deep copy
    if (!scope.isEmpty()) {
      scope =
          new LazyMap<String, Object>(
              new InnerMapFactory(
                  scope,
                  new OperationParameter(context, "DEFAULT", engine.getPersistenceConfig())));
    }

    if (null == request || request.isEmpty()) {
      return scope;
    } else if (scope.isEmpty()) {
      return request;
    } else {
      request.putAll(scope);
      return request;
    }
  }
Ejemplo n.º 2
0
  /**
   * Выполняет скрипт
   *
   * @param script Тело скрипта / код
   * @param language Язык скрипта (JavaScript)
   * @param bindings Переменные для привязки/передачи данных
   * @return Результат выполнения
   * @throws ScriptException Ошибка исполнения скрипта
   */
  public static Object eval(
      String script, String language, Map<? extends String, ? extends Object> bindings)
      throws ScriptException {
    if (script == null) throw new IllegalArgumentException("script==null");
    if (language == null) throw new IllegalArgumentException("language==null");
    //        if( bindings==null )throw new IllegalArgumentException("bindings==null");

    ScriptEngine se = null;
    Map<String, ScriptEngine> map = getMapScriptEngines();
    if (map.containsKey(language)) {
      se = map.get(language);
    } else {
      se = getScriptEngineManager().getEngineByName(language);
      if (se == null)
        throw new IllegalArgumentException(Text.template("language ({0}) not supported", language));
      map.put(language, se);
    }

    Bindings seBindings = se.getBindings(javax.script.ScriptContext.ENGINE_SCOPE);
    if (bindings != null) seBindings.putAll(bindings);

    return se.eval(script);
  }
  private ScriptEngines createScriptEngines() {
    // plugins already on the path - ones static to the classpath
    final List<GremlinPlugin> globalPlugins = new ArrayList<>();
    ServiceLoader.load(GremlinPlugin.class).forEach(globalPlugins::add);

    return new ScriptEngines(
        se -> {
          // this first part initializes the scriptengines Map
          for (Map.Entry<String, EngineSettings> config : settings.entrySet()) {
            final String language = config.getKey();
            se.reload(
                language,
                new HashSet<>(config.getValue().getImports()),
                new HashSet<>(config.getValue().getStaticImports()),
                config.getValue().getConfig());
          }

          // use grabs dependencies and returns plugins to load
          final List<GremlinPlugin> pluginsToLoad = new ArrayList<>(globalPlugins);
          use.forEach(
              u -> {
                if (u.size() != 3)
                  logger.warn(
                      "Could not resolve dependencies for [{}].  Each entry for the 'use' configuration must include [groupId, artifactId, version]",
                      u);
                else {
                  logger.info("Getting dependencies for [{}]", u);
                  pluginsToLoad.addAll(se.use(u.get(0), u.get(1), u.get(2)));
                }
              });

          // now that all dependencies are in place, the imports can't get messed up if a plugin
          // tries to execute
          // a script (as the script engine appends the import list to the top of all scripts passed
          // to the engine).
          // only enable those plugins that are configured to be enabled.
          se.loadPlugins(
              pluginsToLoad
                  .stream()
                  .filter(plugin -> enabledPlugins.contains(plugin.getName()))
                  .collect(Collectors.toList()));

          // initialization script eval can now be performed now that dependencies are present with
          // "use"
          for (Map.Entry<String, EngineSettings> config : settings.entrySet()) {
            final String language = config.getKey();

            // script engine initialization files that fail will only log warnings - not fail server
            // initialization
            final AtomicBoolean hasErrors = new AtomicBoolean(false);
            config
                .getValue()
                .getScripts()
                .stream()
                .map(File::new)
                .filter(
                    f -> {
                      if (!f.exists()) {
                        logger.warn(
                            "Could not initialize {} ScriptEngine with {} as file does not exist",
                            language,
                            f);
                        hasErrors.set(true);
                      }

                      return f.exists();
                    })
                .map(
                    f -> {
                      try {
                        return Pair.with(f, Optional.of(new FileReader(f)));
                      } catch (IOException ioe) {
                        logger.warn(
                            "Could not initialize {} ScriptEngine with {} as file could not be read - {}",
                            language,
                            f,
                            ioe.getMessage());
                        hasErrors.set(true);
                        return Pair.with(f, Optional.<FileReader>empty());
                      }
                    })
                .filter(p -> p.getValue1().isPresent())
                .map(p -> Pair.with(p.getValue0(), p.getValue1().get()))
                .forEachOrdered(
                    p -> {
                      try {
                        final Bindings bindings = new SimpleBindings();
                        bindings.putAll(this.globalBindings);

                        // evaluate init scripts with hard reference so as to ensure it doesn't get
                        // garbage collected
                        bindings.put(
                            GremlinGroovyScriptEngine.KEY_REFERENCE_TYPE,
                            GremlinGroovyScriptEngine.REFERENCE_TYPE_HARD);

                        se.eval(p.getValue1(), bindings, language);

                        // re-assign graph bindings back to global bindings and grab TraversalSource
                        // creations.
                        // prevent assignment of non-graph implementations just in case someone
                        // tries to overwrite
                        // them in the init
                        bindings
                            .entrySet()
                            .stream()
                            .filter(promoteBinding)
                            .forEach(kv -> this.globalBindings.put(kv.getKey(), kv.getValue()));

                        logger.info("Initialized {} ScriptEngine with {}", language, p.getValue0());
                      } catch (ScriptException sx) {
                        hasErrors.set(true);
                        logger.warn(
                            "Could not initialize {} ScriptEngine with {} as script could not be evaluated - {}",
                            language,
                            p.getValue0(),
                            sx.getMessage());
                      }
                    });
          }
        });
  }
  /**
   * Evaluate a script and allow for the submission of both a transform {@link Function} and {@link
   * Consumer}. The {@link Function} will transform the result after script evaluates but before
   * transaction commit and before the returned {@link CompletableFuture} is completed. The {@link
   * Consumer} will take the result for additional processing after the script evaluates and after
   * the {@link CompletableFuture} is completed, but before the transaction is committed.
   *
   * @param script the script to evaluate
   * @param language the language to evaluate it in
   * @param boundVars the bindings to evaluate in the context of the script
   * @param transformResult a {@link Function} that transforms the result - can be {@code null}
   * @param withResult a {@link Consumer} that accepts the result - can be {@code null}
   */
  public CompletableFuture<Object> eval(
      final String script,
      final String language,
      final Bindings boundVars,
      final Function<Object, Object> transformResult,
      final Consumer<Object> withResult) {
    final String lang = Optional.ofNullable(language).orElse("gremlin-groovy");

    logger.debug(
        "Preparing to evaluate script - {} - in thread [{}]",
        script,
        Thread.currentThread().getName());

    final Bindings bindings = new SimpleBindings();
    bindings.putAll(this.globalBindings);
    bindings.putAll(boundVars);
    beforeEval.accept(bindings);

    final CompletableFuture<Object> evaluationFuture = new CompletableFuture<>();
    final FutureTask<Void> f =
        new FutureTask<>(
            () -> {
              try {
                logger.debug(
                    "Evaluating script - {} - in thread [{}]",
                    script,
                    Thread.currentThread().getName());

                final Object o = scriptEngines.eval(script, bindings, lang);

                // apply a transformation before sending back the result - useful when trying to
                // force serialization
                // in the same thread that the eval took place given ThreadLocal nature of graphs as
                // well as some
                // transactional constraints
                final Object result = null == transformResult ? o : transformResult.apply(o);
                evaluationFuture.complete(result);

                // a mechanism for taking the final result and doing something with it in the same
                // thread, but
                // AFTER the eval and transform are done and that future completed.  this provides a
                // final means
                // for working with the result in the same thread as it was eval'd
                if (withResult != null) withResult.accept(result);

                afterSuccess.accept(bindings);
              } catch (Exception ex) {
                final Throwable root = null == ex.getCause() ? ex : ExceptionUtils.getRootCause(ex);

                // thread interruptions will typically come as the result of a timeout, so in those
                // cases,
                // check for that situation and convert to TimeoutException
                if (root instanceof InterruptedException)
                  evaluationFuture.completeExceptionally(
                      new TimeoutException(
                          String.format(
                              "Script evaluation exceeded the configured threshold of %s ms for request [%s]: %s",
                              scriptEvaluationTimeout, script, root.getMessage())));
                else {
                  afterFailure.accept(bindings, root);
                  evaluationFuture.completeExceptionally(root);
                }
              }

              return null;
            });

    executorService.execute(f);

    if (scriptEvaluationTimeout > 0) {
      // Schedule a timeout in the thread pool for future execution
      final ScheduledFuture<?> sf =
          scheduledExecutorService.schedule(
              () -> {
                logger.info(
                    "Timing out script - {} - in thread [{}]",
                    script,
                    Thread.currentThread().getName());
                if (!f.isDone()) {
                  afterTimeout.accept(bindings);
                  f.cancel(true);
                }
              },
              scriptEvaluationTimeout,
              TimeUnit.MILLISECONDS);

      // Cancel the scheduled timeout if the eval future is complete or the script evaluation failed
      // with exception
      evaluationFuture.handleAsync(
          (v, t) -> {
            logger.debug(
                "Killing scheduled timeout on script evaluation as the eval completed (possibly with exception).");
            return sf.cancel(true);
          });
    }

    return evaluationFuture;
  }