001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.plugins.javascript; 019 020import com.moandjiezana.toml.Toml; 021import io.stallion.exceptions.UsageException; 022import io.stallion.plugins.PluginRegistry; 023import io.stallion.services.Log; 024import io.stallion.settings.Settings; 025import io.stallion.settings.SettingsLoader; 026import io.stallion.settings.childSections.CustomSettings; 027import io.stallion.utils.json.JSON; 028import jdk.nashorn.api.scripting.NashornScriptEngine; 029import jdk.nashorn.api.scripting.NashornScriptEngineFactory; 030import jdk.nashorn.internal.objects.Global; 031import jdk.nashorn.internal.runtime.Context; 032import jdk.nashorn.internal.runtime.ErrorManager; 033import jdk.nashorn.internal.runtime.ScriptFunction; 034import jdk.nashorn.internal.runtime.options.Options; 035import org.apache.commons.io.IOUtils; 036 037import javax.script.ScriptEngine; 038import javax.script.ScriptException; 039import java.io.File; 040import java.io.IOException; 041import java.util.HashMap; 042import java.util.HashSet; 043import java.util.List; 044import java.util.Map; 045 046import static io.stallion.utils.Literals.*; 047 048 049public class JsPluginEngine { 050 051 052 private ScriptEngine scriptEngine; 053 //private ScriptEngineManager scriptEngineManager; 054 private HashSet<String> watchedPaths = new HashSet<>(); 055 private Boolean watchFiles = true; 056 private String folder; 057 058 public JsPluginEngine() { 059 this(true); 060 } 061 062 public JsPluginEngine(Boolean watchFiles) { 063 this.watchFiles = watchFiles; 064 065 } 066 067 public void shutdown() { 068 069 } 070 071 public Object evaluate(String source) throws Exception { 072 Log.info("eval {0}", source); 073 return scriptEngine.eval(source); 074 } 075 076 public void loadJavascript(String plugin, String fullPath) throws Exception { 077 078 079 JsPluginSettings pluginSettings = new SettingsLoader().loadSettings(plugin, JsPluginSettings.class); 080 081 082 Sandbox box = Sandbox.forPlugin(plugin); 083 if (box == null) { 084 loadUnrestrictedJavascript(fullPath, pluginSettings); 085 } else { 086 loadSandboxedJavascript(box, fullPath, pluginSettings); 087 } 088 089 } 090 091 public void loadSandboxedJavascript(Sandbox box, String fullPath, JsPluginSettings pluginSettings) throws Exception { 092 SandboxClassFilter classFilter = new SandboxClassFilter(box); 093 094 this.scriptEngine = new NashornScriptEngineFactory().getScriptEngine(classFilter); 095 096 Log.info("Load sandboxed js file {0}", fullPath); 097 folder = new File(fullPath).getParent(); 098 scriptEngine.put("javaToJsHelpers", new JavaToJsHelpers(box)); 099 100 SandboxedContext ctx = new SandboxedContext(folder, box, pluginSettings); 101 scriptEngine.put("myContext", ctx); 102 String stallionSharedJs = IOUtils.toString(getClass().getResource("/jslib/stallion_shared.js"), UTF8); 103 classFilter.setDisabled(true); // Turn off white-listing while loading stallion_shared, since we need access to more classes 104 105 scriptEngine.eval("load(" + JSON.stringify(map(val("script", stallionSharedJs), val("name", "stallion_shared.js"))) + ");"); 106 classFilter.setDisabled(false); // Turn whitelisting back on 107 scriptEngine.put("stallionClassLoader", new SandboxedClassLoader(box)); 108 //scriptEngine.eval("Java = {extend: Java.extend, type: function(className) { return stallionClassLoader.loadClass(className).static; }}"); 109 //scriptEngine.eval("Packages = undefined;java = undefined;"); 110 scriptEngine.eval("load(\"" + fullPath + "\");"); 111 112 Log.info("Loaded js plugin {0}", fullPath); 113 } 114 115 public void runTestsInFileAndPrintResults(String testFile) { 116 List<TestResults> allResults = runTestsInFile(testFile); 117 118 boolean hasError = false; 119 120 for (TestResults results: allResults) { 121 if (results.hasAnyErrors()) { 122 hasError = true; 123 } 124 results.printResults(); 125 } 126 127 if (hasError) { 128 throw new AssertionError("Javascript tests failed with errors for file: " + testFile); 129 } 130 } 131 132 public List<TestResults> runTestsInFile(String testFile) { 133 134 if (testFile.contains("..") || testFile.startsWith("/")) { 135 throw new UsageException("Invalid file name: " + testFile); 136 } 137 String path = folder + "/" + testFile; 138 if (!new File(path).isFile()) { 139 throw new UsageException("Javascript test file not found: " + path); 140 } 141 Log.info("Running javascript tests for file {0}", path); 142 SuitesHolder holder = new SuitesHolder().setFile(testFile); 143 scriptEngine.put("jsSuitesHolder", holder); 144 try { 145 scriptEngine.eval("load(\"" + path + "\");"); 146 } catch (ScriptException e) { 147 throw new RuntimeException(e); 148 } 149 boolean hasError = false; 150 List<TestResults> results = list(); 151 152 for (JsTestSuite suite: holder.getSuites()) { 153 results.add(suite.getResults()); 154 } 155 return results; 156 } 157 158 159 private void loadUnrestrictedJavascript(String fullPath, JsPluginSettings pluginSettings) throws Exception { 160 folder = new File(fullPath).getParent(); 161 this.scriptEngine = (NashornScriptEngine)new NashornScriptEngineFactory().getScriptEngine(); 162 163 scriptEngine.put("javaToJsHelpers", new JavaToJsHelpers(null)); 164 165 String jvmNpm = IOUtils.toString(getClass().getResource("/jslib/jvm-npm.js"), UTF8); 166 scriptEngine.eval("load(" + JSON.stringify(map(val("script", jvmNpm), val("name", "jvm-npm.js"))) + ");"); 167 168 SandboxedContext ctx = new SandboxedContext(folder, Sandbox.allPermissions(), pluginSettings); 169 scriptEngine.put("myContext", ctx); 170 171 String stallionSharedJs = IOUtils.toString(getClass().getResource("/jslib/stallion_shared.js"), UTF8); 172 scriptEngine.eval("load(" + JSON.stringify(map(val("script", stallionSharedJs), val("name", "stallion_shared.js"))) + ");"); 173 174 175 scriptEngine.put("stallionClassLoader", new UnrestrictedJsClassLoader()); 176 //scriptEngine.eval("Java.type = function(className) { var cls = stallionClassLoader.loadClass(className); if (cls) {return cls.static} else { print('Could not find class ' + className); return null; } };"); 177 178 scriptEngine.put("pluginFolder", folder); 179 scriptEngine.put("jsEngine", this); 180 181 182 183 //Global global = Context.getGlobal(); 184 //global.put("astring", "foo", true); 185 186 //Log.info("astring: {0}", scriptEngine.get("astring")); 187 188 189 190 Log.info("Load js file {0}", fullPath); 191 String folder = new File(fullPath).getParent(); 192 String nodePath = folder + "/node_modules"; 193 scriptEngine.eval("require.NODE_PATH = \"" + nodePath + "\""); 194 195 scriptEngine.eval("load(\"" + fullPath + "\");"); 196 197 Log.info("Finish loading js {0}", fullPath); 198 199 } 200 201 /* 202 private void experimentalloadUnrestrictedJavascript(String fullPath, JsPluginSettings pluginSettings) throws Exception { 203 ErrorManager errors = new ErrorManager(); 204 205 Options options = new Options("nashorn"); 206 Context context = new Context(options, errors, Thread.currentThread().getContextClassLoader()); 207 208 209 Global global = context.createGlobal(); 210 Context.setGlobal(global); 211 212 global.put("javaToJsHelpers", new JavaToJsHelpers(null), true); 213 214 String jvmNpm = IOUtils.toString(getClass().getResource("/jslib/jvm-npm.js")); 215 context.eval(global, "load(" + JSON.stringify(map(val("script", jvmNpm), val("name", "jvm-npm.js"))) + ");", global, "<engineloading>"); 216 217 String stallionSharedJs = IOUtils.toString(getClass().getResource("/jslib/stallion_shared.js")); 218 context.eval(global, "load(" + JSON.stringify(map(val("script", stallionSharedJs), val("name", "stallion_shared.js"))) + ");", global, "<engineloading>"); 219 220 221 global.put("stallionClassLoader", new UnrestrictedJsClassLoader(), true); 222 //scriptEngine.eval("Java.type = function(className) { var cls = stallionClassLoader.loadClass(className); if (cls) {return cls.static} else { print('Could not find class ' + className); return null; } };"); 223 224 global.put("pluginFolder", folder, true); 225 global.put("jsEngine", this, true); 226 227 SandboxedContext ctx = new SandboxedContext(folder, Sandbox.allPermissions(), pluginSettings); 228 global.put("myContext", ctx, true); 229 230 231 global.put("astring", "foo", true); 232 233 //Log.info("astring: {0}", scriptEngine.get("astring")); 234 235 global.addShellBuiltins(); 236 237 //ScriptFunction value = ScriptFunction.createBuiltin("calc", EvalLoop.getCalcHandle2(global, context)); 238 global.addOwnProperty("calc", 2, value); 239 240 Log.info("Load js file {0}", fullPath); 241 String folder = new File(fullPath).getParent(); 242 String nodePath = folder + "/node_modules"; 243 context.eval(global, "require.NODE_PATH = \"" + nodePath + "\"", global, "<loading>"); 244 245 context.eval(global, "load(\"" + fullPath + "\");", global, "<evalscript>"); 246 247 Log.info("Finish loading js {0}", fullPath); 248 } 249 */ 250 251 252 public void fileChangeCallback(String jsPath) throws Exception { 253 //loadJavascript(jsPath); 254 Log.info("File change callback {0}", jsPath); 255 } 256 257 258 259 260 public ScriptEngine getScriptEngine() { 261 return scriptEngine; 262 } 263 264 public void setScriptEngine(ScriptEngine scriptEngine) { 265 this.scriptEngine = scriptEngine; 266 } 267 268 269}