diff --git a/pom.xml b/pom.xml index 0ec107d..3a01371 100644 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,9 @@ - + 4.0.0 pw.yumc MiaoScript - 0.11.2 + 0.13.0 502647092 @@ -53,15 +54,17 @@ DEV + §621-06-19 §afeat: 兼容JDK16 反射异常; + §621-05-15 §afeat: 兼容JDK15+ 自动下载Nashorn类库; §621-03-25 §afeat: 异步加载 polyfill 并且同步加载 @ccms/core; - §621-03-25 §cfix: 修改 ployfill 为 polyfill; + §621-03-25 §cfix: 修改 ployfill 为 polyfill + + §620-12-22 §cfix: 增加 require 效验; §620-12-17 §afeat: JavaScriptTask 新增任务ID 并通过 ID 比较优先级; §620-12-16 §afeat: 新增 require 缓存 优化路径寻找速度; §620-12-15 §cfix: 修复 非主线程重载时发生的异常; - §620-12-07 §cfix: 修复 Windows 环境 重载异常 - - + §620-12-07 §cfix: 修复 Windows 环境 重载异常; §620-11-19 §afeat: 新增 JavaScriptTask 类; §620-11-11 §afeat: 新增 package 版本锁定逻辑; §620-09-21 §afeat: 完善 upgrade 逻辑; @@ -184,7 +187,7 @@ org.projectlombok lombok - 1.18.10 + 1.18.18 org.kamranzafar diff --git a/src/main/java/pw/yumc/MiaoScript/MiaoScriptEngine.java b/src/main/java/pw/yumc/MiaoScript/MiaoScriptEngine.java index a0fc88d..0a4613f 100644 --- a/src/main/java/pw/yumc/MiaoScript/MiaoScriptEngine.java +++ b/src/main/java/pw/yumc/MiaoScript/MiaoScriptEngine.java @@ -1,25 +1,21 @@ package pw.yumc.MiaoScript; +import lombok.SneakyThrows; +import lombok.val; + +import javax.script.ScriptEngine; +import javax.script.*; import java.io.File; import java.io.Reader; -import java.lang.reflect.InvocationTargetException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.HashMap; -import javax.script.Bindings; -import javax.script.Invocable; -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import javax.script.SimpleBindings; - -import lombok.val; - /** * 喵式脚本引擎 * @@ -27,8 +23,12 @@ import lombok.val; * @since 2016年8月29日 上午7:51:43 */ public class MiaoScriptEngine implements ScriptEngine, Invocable { + private static String MavenRepo = "https://maven.aliyun.com/repository/public"; private static MiaoScriptEngine DEFAULT; - private static ScriptEngineManager manager; + private static final ScriptEngineManager manager; + + private Object ucp; + private MethodHandle addURLMethodHandle; private ScriptEngine engine; static { @@ -48,39 +48,108 @@ public class MiaoScriptEngine implements ScriptEngine, Invocable { } public MiaoScriptEngine(final String engineType) { - this(manager, engineType); + this(manager, engineType, null); } public MiaoScriptEngine(ScriptEngineManager engineManager) { - this(engineManager, "js"); + this(engineManager, "js", null); } - public MiaoScriptEngine(ScriptEngineManager engineManager, final String engineType) { + public MiaoScriptEngine(final String engineType, String engineRoot) { + this(manager, engineType, engineRoot); + } + + public MiaoScriptEngine(ScriptEngineManager engineManager, final String engineType, String engineRoot) { try { engine = engineManager.getEngineByName(engineType); } catch (final NullPointerException ignored) { } if (engine == null) { - val dirs = System.getProperty("java.ext.dirs").split(File.pathSeparator); - for (String dir : dirs) { - File nashorn = new File(dir, "nashorn.jar"); - if (nashorn.exists()) { - try { - Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - // 设置方法的访问权限 - method.setAccessible(true); - // 获取系统类加载器 - URL url = nashorn.toURI().toURL(); - method.invoke(Thread.currentThread().getContextClassLoader(), url); - engineManager = new ScriptEngineManager(); - engine = engineManager.getEngineByName(engineType); - } catch (NoSuchMethodException | MalformedURLException | IllegalAccessException | InvocationTargetException | NullPointerException ignored) { + val extDirs = System.getProperty("java.ext.dirs"); + if (extDirs != null) { + val dirs = extDirs.split(File.pathSeparator); + for (String dir : dirs) { + File nashorn = new File(dir, "nashorn.jar"); + if (nashorn.exists()) { + this.loadJar(nashorn); + this.createEngineByName(engineType); } - return; } + } else if (engineRoot != null) { + this.loadLocalNashorn(engineRoot); } - throw new UnsupportedOperationException("当前环境不支持 " + engineType + " 脚本类型!"); } + if (engine == null) + throw new UnsupportedOperationException("当前环境不支持 " + engineType + " 脚本类型!"); + } + + private void initReflect() { + try { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null); + Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field)); + Field ucpField; + try { + ucpField = loader.getClass().getDeclaredField("ucp"); + } catch (NoSuchFieldException e) { + ucpField = loader.getClass().getSuperclass().getDeclaredField("ucp"); + } + long offset = unsafe.objectFieldOffset(ucpField); + ucp = unsafe.getObject(loader, offset); + Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class); + addURLMethodHandle = lookup.unreflect(method); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SneakyThrows + private void loadLocalNashorn(String engineRoot) { + initReflect(); + File libRootFile = new File(engineRoot, "lib"); + libRootFile.mkdirs(); + String libRoot = libRootFile.getCanonicalPath(); + downloadJar(libRoot, "org.openjdk.nashorn", "nashorn-core", "15.2"); + downloadJar(libRoot, "org.ow2.asm", "asm", "9.1"); + downloadJar(libRoot, "org.ow2.asm", "asm-commons", "9.1"); + downloadJar(libRoot, "org.ow2.asm", "asm-tree", "9.1"); + downloadJar(libRoot, "org.ow2.asm", "asm-util", "9.1"); + this.createEngineByName("nashorn"); + } + + @SneakyThrows + private void loadJar(File file) { + addURLMethodHandle.invoke(ucp, file.toURI().toURL()); + } + + @SneakyThrows + private void downloadJar(String engineRoot, String groupId, String artifactId, String version) { + File lib = new File(engineRoot, artifactId + ".jar"); + if (!lib.exists()) { + Files.copy(new URL(MavenRepo + + String.format("/%1$s/%2$s/%3$s/%2$s-%3$s.jar", + groupId.replace(".", "/"), + artifactId, + version) + ).openStream(), + lib.toPath(), + StandardCopyOption.REPLACE_EXISTING); + } + this.loadJar(lib); + } + + private void createEngineByName(String engineType) { + try { + engine = new ScriptEngineManager(Thread.currentThread().getContextClassLoader()).getEngineByName(engineType); + } catch (NullPointerException ignored) { + } + } + + public ScriptEngine getEngine() { + return this.engine; } public static MiaoScriptEngine getDefault() { diff --git a/src/main/java/pw/yumc/MiaoScript/ScriptEngine.java b/src/main/java/pw/yumc/MiaoScript/ScriptEngine.java index 90d2e6d..eec0989 100644 --- a/src/main/java/pw/yumc/MiaoScript/ScriptEngine.java +++ b/src/main/java/pw/yumc/MiaoScript/ScriptEngine.java @@ -2,14 +2,9 @@ package pw.yumc.MiaoScript; import lombok.SneakyThrows; -import javax.script.ScriptEngineManager; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; /** * Created with IntelliJ IDEA @@ -31,14 +26,13 @@ public class ScriptEngine { this.base = new Base(instance); } - public MiaoScriptEngine createEngine() { + public void createEngine() { synchronized (logger) { if (this.engine == null) { - this.engine = new MiaoScriptEngine(new ScriptEngineManager(), "nashorn"); + this.engine = new MiaoScriptEngine("nashorn", root); this.engine.put("base", this.base); this.engine.put("ScriptEngineContextHolder", this); } - return this.engine; } } @@ -60,14 +54,16 @@ public class ScriptEngine { @SneakyThrows public void enableEngine() { - engine.invokeFunction("start", future); + if (this.engine != null) { + engine.invokeFunction("enable", future); + } } @SneakyThrows public void disableEngine() { synchronized (logger) { if (this.engine != null) { - this.engine.invokeFunction("engineDisable"); + this.engine.invokeFunction("disable"); this.engine = null; } } diff --git a/src/main/resources/bios.js b/src/main/resources/bios.js index 6beed3a..90c2c40 100644 --- a/src/main/resources/bios.js +++ b/src/main/resources/bios.js @@ -3,77 +3,88 @@ var global = this; /** * Init MiaoScriptEngine Runtime */ +/*global base */ (function () { - global.engineDisable = function () { - global.engineDisableImpl && global.engineDisableImpl() - if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "old_node_modules"))) { - logger.info('Found old_node_modules folder delete...') - base.delete(java.nio.file.Paths.get(root, "old_node_modules")) - } - if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "upgrade"))) { - logger.info('Found upgrade file delete node_modules...') - base.delete(java.nio.file.Paths.get(root, "node_modules")) - base.delete(java.nio.file.Paths.get(root, "upgrade")) - } - } + var Files = Java.type('java.nio.file.Files') + var Paths = Java.type('java.nio.file.Paths') + var System = Java.type('java.lang.System') + var Thread = Java.type('java.lang.Thread') + var FutureTask = Java.type('java.util.concurrent.FutureTask') + global.boot = function (root, logger) { - global.scope = java.lang.System.getenv("MS_NODE_CORE_SCOPE") || "@ccms" - global.log = logger + global.scope = System.getenv("MS_NODE_CORE_SCOPE") || "@ccms" + global.logger = logger // Development Env Detect global.root = root || "src/main/resources" + checkDebug() + checkUpgrade() + return bootEngineThread(checkClassLoader()) + } + + function bootEngineThread(loader) { + logger.info("ScriptEngine: " + ScriptEngineContextHolder.getEngine().getEngine().class.name) + var future = new FutureTask(function () { + Thread.currentThread().contextClassLoader = loader + load(System.getenv("MS_NODE_CORE_POLYFILL") || 'classpath:core/polyfill.js')(root, logger) + }) + // Async Loading MiaoScript Engine + new Thread(future, "MiaoScript thread").start() + return future + } + + global.enable = function (future) { + if (!future.isDone()) { + logger.info("Waiting MiaoScript booted...") + future.get() + } + logger.info("MiaoScript booted starting...") + global.engineDisableImpl = require(System.getenv("MS_NODE_CORE_MODULE") || (global.scope + '/core')).default || function () { + logger.info('Error: abnormal Initialization MiaoScript Engine. Skip disable step...') + } + } + + global.disable = function () { + global.engineDisableImpl && global.engineDisableImpl() + } + + function checkDebug() { if (__FILE__.indexOf('!') === -1) { logger.info('Loading custom BIOS file ' + __FILE__) global.debug = true } - if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "debug"))) { + if (Files.exists(Paths.get(root, "debug"))) { logger.info('Running in debug mode...') global.debug = true } - if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "level"))) { - global.level = base.read(java.nio.file.Paths.get(root, "level")) + if (Files.exists(Paths.get(root, "level"))) { + global.level = base.read(Paths.get(root, "level")) logger.info('Set system level to [' + global.level + ']...') } - if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "upgrade"))) { + } + + function checkUpgrade() { + if (Files.exists(Paths.get(root, "upgrade"))) { logger.info('Found upgrade file starting upgrade...') - base.move(java.nio.file.Paths.get(root, "node_modules"), java.nio.file.Paths.get(root, "old_node_modules")) - base.delete(java.nio.file.Paths.get(root, "upgrade")) + base.move(Paths.get(root, "node_modules"), Paths.get(root, "old_node_modules")) + base.delete(Paths.get(root, "upgrade")) } - new java.lang.Thread(function () { + new Thread(function () { try { - base.delete(java.nio.file.Paths.get(root, "old_node_modules")) + base.delete(Paths.get(root, "old_node_modules")) } catch (ex) { } }, "MiaoScript node_modules clean thread").start() - // Check Class Loader, Sometimes Server will can't found plugin.yml file - var loader = checkClassLoader() - var future = new java.util.concurrent.FutureTask(function () { - java.lang.Thread.currentThread().contextClassLoader = loader - load(java.lang.System.getenv("MS_NODE_CORE_POLYFILL") || 'classpath:core/polyfill.js')(root, logger) - }) - // Async Loading MiaoScript Engine - new java.lang.Thread(future, "MiaoScript thread").start() - return future - } - - global.start = function (future) { - if (!future.isDone()) { - log.info("Waiting MiaoScript booted...") - future.get() - } - log.info("MiaoScript booted starting...") - global.engineDisableImpl = require(java.lang.System.getenv("MS_NODE_CORE_MODULE") || (global.scope + '/core')).default || function () { - log.info('Error: abnormal Initialization MiaoScript Engine. Skip disable step...') - } } function checkClassLoader() { - var classLoader = java.lang.Thread.currentThread().contextClassLoader + // Check Class Loader, Sometimes Server will can't found plugin.yml file + var classLoader = Thread.currentThread().contextClassLoader if (classLoader.getResource("bios.js") === null) { throw Error("Error class loader: " + classLoader.class.name + " Please contact the author MiaoWoo!") } else { - log.info("Class loader compatible: " + classLoader.class.name) + logger.info("Class loader compatible: " + classLoader.class.name) if (classLoader.parent) { - log.info("Parent class loader: " + classLoader.parent.class.name) + logger.info("Parent class loader: " + classLoader.parent.class.name) } } return classLoader diff --git a/src/main/resources/core/require.js b/src/main/resources/core/require.js index 46382ba..e9c2db8 100644 --- a/src/main/resources/core/require.js +++ b/src/main/resources/core/require.js @@ -39,8 +39,6 @@ var Files = Java.type('java.nio.file.Files') // @ts-ignore var StandardCopyOption = Java.type('java.nio.file.StandardCopyOption') - // @ts-ignore - var FileNotFoundException = Java.type('java.io.FileNotFoundException') // @ts-ignore var TarInputStream = Java.type('org.kamranzafar.jtar.TarInputStream') @@ -403,7 +401,7 @@ return _require(download(name), path, optional) } catch (ex) { notFoundModules[name] = true - throw new FileNotFoundException("Can't found module " + name + ' in directory ' + path + ' ERROR: ' + ex, ex) + throw new Error("Can't found module " + name + ' in directory ' + path + ' ERROR: ' + ex) } } setCacheModule(file, optional)