/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.script.js.engine; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import org.mozilla.javascript.Context; import org.mozilla.javascript.commonjs.module.Require; import org.mozilla.javascript.commonjs.module.RequireBuilder; import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider; import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider; import org.mozilla.javascript.tools.shell.Global; public class CommonJSEngineFactory implements ScriptEngineFactory { private static List<String> names; private static List<String> mimeTypes; private static List<String> extensions; static { names = new ArrayList<String>(7); names.add("rhino-nonjdk"); names.add("js"); names.add("rhino"); names.add("JavaScript"); names.add("javascript"); names.add("ECMAScript"); names.add("ecmascript"); names = Collections.unmodifiableList(names); mimeTypes = new ArrayList<String>(4); mimeTypes.add("application/javascript"); mimeTypes.add("application/ecmascript"); mimeTypes.add("text/javascript"); mimeTypes.add("text/ecmascript"); mimeTypes = Collections.unmodifiableList(mimeTypes); extensions = new ArrayList<String>(1); extensions.add("js"); extensions = Collections.unmodifiableList(extensions); } private Global global; private RequireBuilder requireBuilder; private List<String> modulePaths; public CommonJSEngineFactory(List<String> modulePaths) { this.modulePaths = modulePaths; Context cx = CommonJSEngine.enterContext(); try { global = new Global(); global.initStandardObjects(cx, true); global.installRequire(cx, modulePaths, true); } finally { Context.exit(); } } /** * Create a new require function using the shared global. * */ @SuppressWarnings("unused") private Require createRequire() { Require require = null; RequireBuilder builder = getRequireBuilder(); Context cx = CommonJSEngine.enterContext(); try { require = builder.createRequire(cx, global); } finally { Context.exit(); } return require; } @Override public String getEngineName() { return (String) getParameter(ScriptEngine.ENGINE); } @Override public String getEngineVersion() { return (String)getParameter(ScriptEngine.ENGINE_VERSION); } @Override public List<String> getExtensions() { return extensions; } @Override public String getLanguageName() { return (String) getParameter(ScriptEngine.LANGUAGE); } @Override public String getLanguageVersion() { return (String) getParameter(ScriptEngine.LANGUAGE_VERSION); } @Override public String getMethodCallSyntax(String object, String method, String... args) { String syntax = object + "." + method + "("; int length = args.length; for (int i=0; i<length; ++i) { syntax += args[i]; if (i != length - 1) { syntax += ","; } } return syntax + ")"; } @Override public List<String> getMimeTypes() { return mimeTypes; } @Override public List<String> getNames() { return names; } @Override public String getOutputStatement(String arg) { return "print(" + arg + ")"; } @Override public Object getParameter(String key) { if (key.equals(ScriptEngine.NAME)) { return "javascript"; } else if (key.equals(ScriptEngine.ENGINE)) { return "Mozilla Rhino"; } else if (key.equals(ScriptEngine.ENGINE_VERSION)) { return "1.7R4"; } else if (key.equals(ScriptEngine.LANGUAGE)) { return "ECMAScript"; } else if (key.equals(ScriptEngine.LANGUAGE_VERSION)) { return "1.8"; } else if (key.equals("THREADING")) { return "MULTITHREADED"; } else { throw new IllegalArgumentException("Invalid key"); } } @Override public String getProgram(String... statements) { int length = statements.length; String program = ""; for (int i=0; i<length; ++i) { program += statements[i] + ";"; } return program; } /** * Creates and returns a shared require builder. This allows loaded * modules to be cached. The require builder is constructed with a module * provider that reloads modules only when they have changed on disk (with * a 60 second interval). This require builder will be configured with * the module paths returned by {@link #getModulePahts()}. * * @return a shared require builder */ private RequireBuilder getRequireBuilder() { if (requireBuilder == null) { synchronized (this) { if (requireBuilder == null) { requireBuilder = new RequireBuilder(); requireBuilder.setSandboxed(false); List<URI> uris = new ArrayList<URI>(); if (modulePaths != null) { for (String path : modulePaths) { try { URI uri = new URI(path); if (!uri.isAbsolute()) { // call resolve("") to canonify the path uri = new File(path).toURI().resolve(""); } if (!uri.toString().endsWith("/")) { // make sure URI always terminates with slash to // avoid loading from unintended locations uri = new URI(uri + "/"); } uris.add(uri); } catch (URISyntaxException usx) { throw new RuntimeException(usx); } } } requireBuilder.setModuleScriptProvider( new SoftCachingModuleScriptProvider( new UrlModuleSourceProvider(uris, null))); } } } return requireBuilder; } @Override public ScriptEngine getScriptEngine() { return new CommonJSEngine(this); } public Global getGlobal() { return global; } }