All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.lang.js.JSVerticleFactory Maven / Gradle / Ivy

/*
 * Copyright 2014 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.vertx.lang.js;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Verticle;
import io.vertx.core.Vertx;
import io.vertx.core.spi.VerticleFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.net.URL;
import java.util.Scanner;

/**
 * @author Tim Fox
 */
public class JSVerticleFactory implements VerticleFactory {

  static {
    ClasspathFileResolver.init();
  }

  /**
   * By default we will add an empty `process` global with an `env` property which contains the environment
   * variables - this allows Vert.x to work well with libraries such as React which expect to run on Node.js
   * and expect to have this global set, and which fail when it is not set.
   * To disable this then provide a system property with this name and set to any value.
   */
  public static final String DISABLE_NODEJS_PROCESS_ENV_PROP_NAME = "vertx.disableNodeJSProcessENV";

  private static final boolean ADD_NODEJS_PROCESS_ENV = System.getProperty(DISABLE_NODEJS_PROCESS_ENV_PROP_NAME) == null;

  private static final String JVM_NPM = "vertx-js/util/jvm-npm.js";

  private Vertx vertx;
  private ScriptEngine engine;
  private ScriptObjectMirror futureJSClass;

  @Override
  public void init(Vertx vertx) {
    this.vertx = vertx;
  }

  @Override
  public String prefix() {
    return "js";
  }

  @Override
  public boolean blockingCreate() {
    return true;
  }

  @Override
  public Verticle createVerticle(String verticleName, ClassLoader classLoader) throws Exception {
    init();
    return new JSVerticle(VerticleFactory.removePrefix(verticleName));
  }

  public class JSVerticle extends AbstractVerticle {

    private static final String VERTX_START_FUNCTION = "vertxStart";
    private static final String VERTX_START_ASYNC_FUNCTION = "vertxStartAsync";
    private static final String VERTX_STOP_FUNCTION = "vertxStop";
    private static final String VERTX_STOP_ASYNC_FUNCTION = "vertxStopAsync";

    private final String verticleName;

    private JSVerticle(String verticleName) {
      this.verticleName = verticleName;
    }

    private ScriptObjectMirror exports;

    private boolean functionExists(String functionName) {
      Object som = exports.getMember(functionName);
      return som != null && !som.toString().equals("undefined");
    }

    @Override
    public void start(Future startFuture) throws Exception {

      /*
      NOTE:
      When we deploy a verticle we use require.noCache as each verticle instance must have the module evaluated.
      Also we run verticles in JS strict mode (with "use strict") -this means they cannot declare globals
      and other restrictions. We do this for isolation.
      However when doing a normal 'require' from inside a verticle we do not use strict mode as many JavaScript
      modules are written poorly and would fail to run otherwise.
       */
      exports = (ScriptObjectMirror) engine.eval("require.noCache('" + verticleName + "', null, true);");
      if (functionExists(VERTX_START_FUNCTION)) {
        exports.callMember(VERTX_START_FUNCTION);
        startFuture.complete();
      } else if (functionExists(VERTX_START_ASYNC_FUNCTION)) {
        Object wrappedFuture = futureJSClass.newObject(startFuture);
        exports.callMember(VERTX_START_ASYNC_FUNCTION, wrappedFuture);
      } else {
        startFuture.complete();
      }
    }

    @Override
    public void stop(Future stopFuture) throws Exception {
      if (functionExists(VERTX_STOP_FUNCTION)) {
        exports.callMember(VERTX_STOP_FUNCTION);
        stopFuture.complete();
      } else if (functionExists(VERTX_STOP_ASYNC_FUNCTION)) {
        Object wrappedFuture = futureJSClass.newObject(stopFuture);
        exports.callMember(VERTX_STOP_ASYNC_FUNCTION, wrappedFuture);
      } else {
        stopFuture.complete();
      }
    }
  }

  private synchronized void init() {
    if (engine == null) {
      ScriptEngineManager mgr = new ScriptEngineManager();
      engine = mgr.getEngineByName("nashorn");
      if (engine == null) {
        throw new IllegalStateException("Cannot find Nashorn JavaScript engine - maybe you are not running with Java 8 or later?");
      }

      URL url = getClass().getClassLoader().getResource(JVM_NPM);
      if (url == null) {
        throw new IllegalStateException("Cannot find " + JVM_NPM + " on classpath");
      }
      try (Scanner scanner = new Scanner(url.openStream(), "UTF-8").useDelimiter("\\A")) {
        String jvmNpm = scanner.next();
        String jvmNpmPath = ClasspathFileResolver.resolveFilename(JVM_NPM);
        jvmNpm += "\n//# sourceURL=" + jvmNpmPath;
        engine.eval(jvmNpm);
      } catch (Exception e) {
        throw new IllegalStateException(e);
      }

      try {
        futureJSClass = (ScriptObjectMirror) engine.eval("require('vertx-js/future');");
        // Put the globals in
        engine.put("__jvertx", vertx);
        String globs =
          "var Vertx = require('vertx-js/vertx'); var vertx = new Vertx(__jvertx);" +
          "var console = require('vertx-js/util/console');" +
          "var setTimeout = function(callback,delay) { return vertx.setTimer(delay, callback).doubleValue(); };" +
          "var clearTimeout = function(id) { vertx.cancelTimer(id); };" +
          "var setInterval = function(callback, delay) { return vertx.setPeriodic(delay, callback).doubleValue(); };" +
          "var clearInterval = clearTimeout;" +
          "var parent = this;" +
          "var global = this;";
        if (ADD_NODEJS_PROCESS_ENV) {
          globs += "var process = {}; process.env=java.lang.System.getenv();";
        }
        engine.eval(globs);
      } catch (ScriptException e) {
        throw new IllegalStateException("Failed to eval: " + e.getMessage(), e);
      }
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy