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}