diff --git a/build.gradle b/build.gradle index a6a3af81..88370243 100644 --- a/build.gradle +++ b/build.gradle @@ -46,11 +46,12 @@ configurations { } dependencies { + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.sun.activation:javax.activation:1.2.0' implementation 'commons-codec:commons-codec:1.14' implementation 'commons-fileupload:commons-fileupload:1.4' implementation 'commons-logging:commons-logging:1.2' implementation 'commons-net:commons-net:3.6' - implementation 'com.sun.activation:javax.activation:1.2.0' implementation 'javax.mail:javax.mail-api:1.6.2' implementation 'javax.servlet:javax.servlet-api:4.0.1' implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' diff --git a/src/main/java/helma/main/ApplicationManager.java b/src/main/java/helma/main/ApplicationManager.java index 1911a760..b55348c4 100644 --- a/src/main/java/helma/main/ApplicationManager.java +++ b/src/main/java/helma/main/ApplicationManager.java @@ -7,31 +7,30 @@ * http://adele.helma.org/download/helma/license.txt * * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ */ package helma.main; -import helma.framework.core.*; -import helma.framework.repository.Repository; -import helma.framework.repository.FileRepository; -import helma.util.StringUtils; -import org.apache.xmlrpc.XmlRpcHandler; +import java.io.File; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + import org.apache.commons.logging.Log; +import org.apache.xmlrpc.XmlRpcHandler; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import java.io.*; -import java.util.*; -import helma.util.ResourceProperties; +import helma.framework.core.Application; +import helma.framework.repository.FileRepository; +import helma.framework.repository.Repository; import helma.servlet.EmbeddedServletClient; +import helma.util.ResourceProperties; +import helma.util.StringUtils; /** * This class is responsible for starting and stopping Helma applications. @@ -55,11 +54,11 @@ public class ApplicationManager implements XmlRpcHandler { public ApplicationManager(ResourceProperties props, Server server) { this.props = props; this.server = server; - descriptors = new Hashtable(); - applications = new Hashtable(); - xmlrpcHandlers = new Hashtable(); - lastModified = 0; - jetty = server.jetty; + this.descriptors = new Hashtable(); + this.applications = new Hashtable(); + this.xmlrpcHandlers = new Hashtable(); + this.lastModified = 0; + this.jetty = server.jetty; } /** @@ -67,13 +66,13 @@ public class ApplicationManager implements XmlRpcHandler { * to create and start new applications. */ protected void checkForChanges() { - if (props.lastModified() > lastModified && server.getApplicationsOption() == null) { + if (this.props.lastModified() > this.lastModified && this.server.getApplicationsOption() == null) { try { - for (Enumeration e = props.keys(); e.hasMoreElements();) { + for (Enumeration e = this.props.keys(); e.hasMoreElements();) { String appName = (String) e.nextElement(); - if ((appName.indexOf(".") == -1) && - (applications.get(appName) == null)) { + if ((appName.indexOf(".") == -1) && //$NON-NLS-1$ + (this.applications.get(appName) == null)) { AppDescriptor appDesc = new AppDescriptor(appName); appDesc.start(); appDesc.bind(); @@ -81,27 +80,27 @@ public class ApplicationManager implements XmlRpcHandler { } // then stop deleted ones - for (Enumeration e = descriptors.elements(); e.hasMoreElements();) { + for (Enumeration e = this.descriptors.elements(); e.hasMoreElements();) { AppDescriptor appDesc = (AppDescriptor) e.nextElement(); // check if application has been removed and should be stopped - if (!props.containsKey(appDesc.appName)) { + if (!this.props.containsKey(appDesc.appName)) { appDesc.stop(); - } else if (server.jetty != null) { + } else if (this.server.jetty != null) { // If application continues to run, remount // as the mounting options may have changed. AppDescriptor ndesc = new AppDescriptor(appDesc.appName); ndesc.app = appDesc.app; appDesc.unbind(); ndesc.bind(); - descriptors.put(ndesc.appName, ndesc); + this.descriptors.put(ndesc.appName, ndesc); } } } catch (Exception mx) { getLogger().error("Error checking applications", mx); } - lastModified = System.currentTimeMillis(); + this.lastModified = System.currentTimeMillis(); } } @@ -118,7 +117,7 @@ public class ApplicationManager implements XmlRpcHandler { * Bind an application by name */ public void register(String appName) { - AppDescriptor desc = (AppDescriptor) descriptors.get(appName); + AppDescriptor desc = (AppDescriptor) this.descriptors.get(appName); if (desc != null) { desc.bind(); } @@ -128,7 +127,7 @@ public class ApplicationManager implements XmlRpcHandler { * Stop an application by name */ public void stop(String appName) { - AppDescriptor desc = (AppDescriptor) descriptors.get(appName); + AppDescriptor desc = (AppDescriptor) this.descriptors.get(appName); if (desc != null) { desc.stop(); } @@ -140,18 +139,18 @@ public class ApplicationManager implements XmlRpcHandler { */ public void startAll() { try { - String[] apps = server.getApplicationsOption(); + String[] apps = this.server.getApplicationsOption(); if (apps != null) { for (int i = 0; i < apps.length; i++) { AppDescriptor desc = new AppDescriptor(apps[i]); desc.start(); } } else { - for (Enumeration e = props.keys(); e.hasMoreElements();) { + for (Enumeration e = this.props.keys(); e.hasMoreElements();) { String appName = (String) e.nextElement(); - if (appName.indexOf(".") == -1) { - String appValue = props.getProperty(appName); + if (appName.indexOf(".") == -1) { //$NON-NLS-1$ + String appValue = this.props.getProperty(appName); if (appValue != null && appValue.length() > 0) { appName = appValue; @@ -163,12 +162,12 @@ public class ApplicationManager implements XmlRpcHandler { } } - for (Enumeration e = descriptors.elements(); e.hasMoreElements();) { + for (Enumeration e = this.descriptors.elements(); e.hasMoreElements();) { AppDescriptor appDesc = (AppDescriptor) e.nextElement(); appDesc.bind(); } - lastModified = System.currentTimeMillis(); + this.lastModified = System.currentTimeMillis(); } catch (Exception mx) { getLogger().error("Error starting applications", mx); mx.printStackTrace(); @@ -179,7 +178,7 @@ public class ApplicationManager implements XmlRpcHandler { * Stop all running applications. */ public void stopAll() { - for (Enumeration en = descriptors.elements(); en.hasMoreElements();) { + for (Enumeration en = this.descriptors.elements(); en.hasMoreElements();) { try { AppDescriptor appDesc = (AppDescriptor) en.nextElement(); @@ -194,14 +193,14 @@ public class ApplicationManager implements XmlRpcHandler { * Get an array containing all currently running applications. */ public Object[] getApplications() { - return applications.values().toArray(); + return this.applications.values().toArray(); } /** * Get an application by name. */ public Application getApplication(String name) { - return (Application) applications.get(name); + return (Application) this.applications.get(name); } /** @@ -209,7 +208,7 @@ public class ApplicationManager implements XmlRpcHandler { */ public Object execute(String method, Vector params) throws Exception { - int dot = method.indexOf("."); + int dot = method.indexOf("."); //$NON-NLS-1$ if (dot == -1) { throw new Exception("Method name \"" + method + @@ -222,10 +221,10 @@ public class ApplicationManager implements XmlRpcHandler { String handler = method.substring(0, dot); String method2 = method.substring(dot + 1); - Application app = (Application) xmlrpcHandlers.get(handler); + Application app = (Application) this.xmlrpcHandlers.get(handler); if (app == null) { - app = (Application) xmlrpcHandlers.get("*"); + app = (Application) this.xmlrpcHandlers.get("*"); //$NON-NLS-1$ // use the original method name, the handler is resolved within the app. method2 = method; } @@ -240,33 +239,32 @@ public class ApplicationManager implements XmlRpcHandler { private String getMountpoint(String mountpoint) { mountpoint = mountpoint.trim(); - if ("".equals(mountpoint)) { - return "/"; - } else if (!mountpoint.startsWith("/")) { - return "/" + mountpoint; + if ("".equals(mountpoint)) { //$NON-NLS-1$ + return "/"; //$NON-NLS-1$ + } else if (!mountpoint.startsWith("/")) { //$NON-NLS-1$ + return "/" + mountpoint; //$NON-NLS-1$ } return mountpoint; } private String joinMountpoint(String prefix, String suffix) { - if (prefix.endsWith("/") || suffix.startsWith("/")) { + if (prefix.endsWith("/") || suffix.startsWith("/")) { //$NON-NLS-1$//$NON-NLS-2$ return prefix+suffix; - } else { - return prefix+"/"+suffix; } + return prefix+"/"+suffix; //$NON-NLS-1$ } private String getPathPattern(String mountpoint) { - if (!mountpoint.startsWith("/")) { - mountpoint = "/"+mountpoint; + if (!mountpoint.startsWith("/")) { //$NON-NLS-1$ + mountpoint = "/"+mountpoint; //$NON-NLS-1$ } - if ("/".equals(mountpoint)) { - return "/"; + if ("/".equals(mountpoint)) { //$NON-NLS-1$ + return "/"; //$NON-NLS-1$ } - if (mountpoint.endsWith("/")) { + if (mountpoint.endsWith("/")) { //$NON-NLS-1$ return mountpoint.substring(0, mountpoint.length()-1); } @@ -279,19 +277,18 @@ public class ApplicationManager implements XmlRpcHandler { File file = new File(path); if (file.isAbsolute()) { return file; - } else { - return file.getAbsoluteFile(); } + return file.getAbsoluteFile(); } private Log getLogger() { - return server.getLogger(); + return this.server.getLogger(); } private String findResource(String path) { File file = new File(path); if (!file.isAbsolute() && !file.exists()) { - file = new File(server.getHopHome(), path); + file = new File(this.server.getHopHome(), path); } return file.getAbsolutePath(); } @@ -336,58 +333,58 @@ public class ApplicationManager implements XmlRpcHandler { * @param name the application name */ AppDescriptor(String name) { - ResourceProperties conf = props.getSubProperties(name + '.'); - appName = name; - mountpoint = getMountpoint(conf.getProperty("mountpoint", appName)); - pathPattern = getPathPattern(mountpoint); - staticDir = conf.getProperty("static"); - staticMountpoint = getPathPattern(conf.getProperty("staticMountpoint", - joinMountpoint(mountpoint, "static"))); - staticIndex = "true".equalsIgnoreCase(conf.getProperty("staticIndex")); - String home = conf.getProperty("staticHome"); + ResourceProperties conf = ApplicationManager.this.props.getSubProperties(name + '.'); + this.appName = name; + this.mountpoint = getMountpoint(conf.getProperty("mountpoint", this.appName)); //$NON-NLS-1$ + this.pathPattern = getPathPattern(this.mountpoint); + this.staticDir = conf.getProperty("static"); //$NON-NLS-1$ + this.staticMountpoint = getPathPattern(conf.getProperty("staticMountpoint", //$NON-NLS-1$ + joinMountpoint(this.mountpoint, "static"))); //$NON-NLS-1$ + this.staticIndex = "true".equalsIgnoreCase(conf.getProperty("staticIndex")); //$NON-NLS-1$//$NON-NLS-2$ + String home = conf.getProperty("staticHome"); //$NON-NLS-1$ if (home == null) { - staticHome = new String[] {"index.html", "index.htm"}; + this.staticHome = new String[] {"index.html", "index.htm"}; //$NON-NLS-1$ //$NON-NLS-2$ } else { - staticHome = StringUtils.split(home, ","); + this.staticHome = StringUtils.split(home, ","); //$NON-NLS-1$ } - protectedStaticDir = conf.getProperty("protectedStatic"); + this.protectedStaticDir = conf.getProperty("protectedStatic"); //$NON-NLS-1$ - cookieDomain = conf.getProperty("cookieDomain"); - sessionCookieName = conf.getProperty("sessionCookieName"); - protectedSessionCookie = conf.getProperty("protectedSessionCookie"); - uploadLimit = conf.getProperty("uploadLimit"); - uploadSoftfail = conf.getProperty("uploadSoftfail"); - debug = conf.getProperty("debug"); - String appDirName = conf.getProperty("appdir"); - appDir = (appDirName == null) ? null : getAbsoluteFile(appDirName); - String dbDirName = conf.getProperty("dbdir"); - dbDir = (dbDirName == null) ? null : getAbsoluteFile(dbDirName); - servletClassName = conf.getProperty("servletClass"); + this.cookieDomain = conf.getProperty("cookieDomain"); //$NON-NLS-1$ + this.sessionCookieName = conf.getProperty("sessionCookieName"); //$NON-NLS-1$ + this.protectedSessionCookie = conf.getProperty("protectedSessionCookie"); //$NON-NLS-1$ + this.uploadLimit = conf.getProperty("uploadLimit"); //$NON-NLS-1$ + this.uploadSoftfail = conf.getProperty("uploadSoftfail"); //$NON-NLS-1$ + this.debug = conf.getProperty("debug"); //$NON-NLS-1$ + String appDirName = conf.getProperty("appdir"); //$NON-NLS-1$ + this.appDir = (appDirName == null) ? null : getAbsoluteFile(appDirName); + String dbDirName = conf.getProperty("dbdir"); //$NON-NLS-1$ + this.dbDir = (dbDirName == null) ? null : getAbsoluteFile(dbDirName); + this.servletClassName = conf.getProperty("servletClass"); //$NON-NLS-1$ // got ignore dirs - ignoreDirs = conf.getProperty("ignore"); + this.ignoreDirs = conf.getProperty("ignore"); //$NON-NLS-1$ // read and configure app repositories ArrayList repositoryList = new ArrayList(); Class[] parameters = { String.class }; for (int i = 0; true; i++) { - String repositoryArgs = conf.getProperty("repository." + i); + String repositoryArgs = conf.getProperty("repository." + i); //$NON-NLS-1$ if (repositoryArgs != null) { // lookup repository implementation - String repositoryImpl = conf.getProperty("repository." + i + - ".implementation"); + String repositoryImpl = conf.getProperty("repository." + i + //$NON-NLS-1$ + ".implementation"); //$NON-NLS-1$ if (repositoryImpl == null) { // implementation not set manually, have to guess it - if (repositoryArgs.endsWith(".zip")) { + if (repositoryArgs.endsWith(".zip")) { //$NON-NLS-1$ repositoryArgs = findResource(repositoryArgs); - repositoryImpl = "helma.framework.repository.ZipRepository"; - } else if (repositoryArgs.endsWith(".js")) { + repositoryImpl = "helma.framework.repository.ZipRepository"; //$NON-NLS-1$ + } else if (repositoryArgs.endsWith(".js")) { //$NON-NLS-1$ repositoryArgs = findResource(repositoryArgs); - repositoryImpl = "helma.framework.repository.SingleFileRepository"; + repositoryImpl = "helma.framework.repository.SingleFileRepository"; //$NON-NLS-1$ } else { repositoryArgs = findResource(repositoryArgs); - repositoryImpl = "helma.framework.repository.FileRepository"; + repositoryImpl = "helma.framework.repository.FileRepository"; //$NON-NLS-1$ } } @@ -408,17 +405,17 @@ public class ApplicationManager implements XmlRpcHandler { } } - if (appDir != null) { - FileRepository appRep = new FileRepository(appDir); + if (this.appDir != null) { + FileRepository appRep = new FileRepository(this.appDir); if (!repositoryList.contains(appRep)) { repositoryList.add(appRep); } } else if (repositoryList.isEmpty()) { repositoryList.add(new FileRepository( - new File(server.getAppsHome(), appName))); + new File(ApplicationManager.this.server.getAppsHome(), this.appName))); } - this.repositories = (RepositoryInterface[]) repositoryList.toArray(new RepositoryInterface[repositoryList.size()]); + this.repositories = (Repository[]) repositoryList.toArray(new Repository[repositoryList.size()]); } @@ -427,21 +424,21 @@ public class ApplicationManager implements XmlRpcHandler { try { // create the application instance - app = new Application(appName, server, repositories, appDir, dbDir); + this.app = new Application(this.appName, ApplicationManager.this.server, this.repositories, this.appDir, this.dbDir); // register ourselves - descriptors.put(appName, this); - applications.put(appName, app); + ApplicationManager.this.descriptors.put(this.appName, this); + ApplicationManager.this.applications.put(this.appName, this.app); // the application is started later in the register method, when it's bound - app.init(ignoreDirs); + this.app.init(this.ignoreDirs); // set application URL prefix if it isn't set in app.properties - if (!app.hasExplicitBaseURI()) { - app.setBaseURI(mountpoint); + if (!this.app.hasExplicitBaseURI()) { + this.app.setBaseURI(this.mountpoint); } - app.start(); + this.app.start(); } catch (Exception x) { getLogger().error("Error creating application " + appName, x); x.printStackTrace(); @@ -462,8 +459,8 @@ public class ApplicationManager implements XmlRpcHandler { getLogger().error("Couldn't stop app", x); } - descriptors.remove(appName); - applications.remove(appName); + ApplicationManager.this.descriptors.remove(this.appName); + ApplicationManager.this.applications.remove(this.appName); } void bind() { @@ -471,21 +468,21 @@ public class ApplicationManager implements XmlRpcHandler { getLogger().info("Binding application " + appName + " :: " + app.hashCode() + " :: " + this.hashCode()); // set application URL prefix if it isn't set in app.properties - if (!app.hasExplicitBaseURI()) { - app.setBaseURI(mountpoint); + if (!this.app.hasExplicitBaseURI()) { + this.app.setBaseURI(this.mountpoint); } // bind to Jetty HTTP server - if (jetty != null) { - if (context == null) { - context = new ContextHandlerCollection(); - jetty.getHttpServer().setHandler(context); + if (ApplicationManager.this.jetty != null) { + if(ApplicationManager.this.context == null) { + ApplicationManager.this.context = new ContextHandlerCollection(); + ApplicationManager.this.jetty.getHttpServer().setHandler(ApplicationManager.this.context); } // if there is a static direcory specified, mount it - if (staticDir != null) { + if (this.staticDir != null) { - File staticContent = getAbsoluteFile(staticDir); + File staticContent = getAbsoluteFile(this.staticDir); getLogger().info("Serving static from " + staticContent.getPath()); getLogger().info("Mounting static at " + staticMountpoint); @@ -494,58 +491,60 @@ public class ApplicationManager implements XmlRpcHandler { rhandler.setResourceBase(staticContent.getPath()); rhandler.setWelcomeFiles(staticHome); - staticContext = context.addContext(staticMountpoint, ""); + staticContext = ApplicationManager.this.context.addContext(staticMountpoint, ""); //$NON-NLS-1$ staticContext.setHandler(rhandler); staticContext.start(); } - appContext = new ServletContextHandler(context, pathPattern, true, true); + appContext = new ServletContextHandler(context, pathPattern); Class servletClass = servletClassName == null ? EmbeddedServletClient.class : Class.forName(servletClassName); + ServletHolder holder = new ServletHolder(servletClass); - holder.setInitParameter("application", appName); - appContext.addServlet(holder, "/*"); + appContext.addServlet(holder, "/*"); //$NON-NLS-1$ - if (cookieDomain != null) { - holder.setInitParameter("cookieDomain", cookieDomain); + holder.setInitParameter("application", appName); //$NON-NLS-1$ + + if (this.cookieDomain != null) { + holder.setInitParameter("cookieDomain", this.cookieDomain); //$NON-NLS-1$ } - if (sessionCookieName != null) { - holder.setInitParameter("sessionCookieName", sessionCookieName); + if (this.sessionCookieName != null) { + holder.setInitParameter("sessionCookieName", this.sessionCookieName); //$NON-NLS-1$ } - if (protectedSessionCookie != null) { - holder.setInitParameter("protectedSessionCookie", protectedSessionCookie); + if (this.protectedSessionCookie != null) { + holder.setInitParameter("protectedSessionCookie", this.protectedSessionCookie); //$NON-NLS-1$ } - if (uploadLimit != null) { - holder.setInitParameter("uploadLimit", uploadLimit); + if (this.uploadLimit != null) { + holder.setInitParameter("uploadLimit", this.uploadLimit); //$NON-NLS-1$ } - if (uploadSoftfail != null) { - holder.setInitParameter("uploadSoftfail", uploadSoftfail); + if (this.uploadSoftfail != null) { + holder.setInitParameter("uploadSoftfail", this.uploadSoftfail); //$NON-NLS-1$ } - if (debug != null) { - holder.setInitParameter("debug", debug); + if (this.debug != null) { + holder.setInitParameter("debug", this.debug); //$NON-NLS-1$ } - if (protectedStaticDir != null) { - File protectedContent = getAbsoluteFile(protectedStaticDir); - appContext.setResourceBase(protectedContent.getPath()); + if (this.protectedStaticDir != null) { + File protectedContent = getAbsoluteFile(this.protectedStaticDir); + this.appContext.setResourceBase(protectedContent.getPath()); getLogger().info("Serving protected static from " + protectedContent.getPath()); } // Remap the context paths and start - context.mapContexts(); - appContext.start(); + ApplicationManager.this.context.mapContexts(); + this.appContext.start(); } // register as XML-RPC handler - xmlrpcHandlerName = app.getXmlRpcHandlerName(); - xmlrpcHandlers.put(xmlrpcHandlerName, app); + this.xmlrpcHandlerName = this.app.getXmlRpcHandlerName(); + ApplicationManager.this.xmlrpcHandlers.put(this.xmlrpcHandlerName, this.app); } catch (Exception x) { getLogger().error("Couldn't bind app", x); x.printStackTrace(); @@ -557,26 +556,26 @@ public class ApplicationManager implements XmlRpcHandler { try { // unbind from Jetty HTTP server - if (jetty != null) { - if (appContext != null) { - context.removeHandler(appContext); - appContext.stop(); - appContext.destroy(); - appContext = null; + if (ApplicationManager.this.jetty != null) { + if (this.appContext != null) { + ApplicationManager.this.context.removeHandler(this.appContext); + this.appContext.stop(); + this.appContext.destroy(); + this.appContext = null; } - if (staticContext != null) { - context.removeHandler(staticContext); - staticContext.stop(); - staticContext.destroy(); - staticContext = null; + if (this.staticContext != null) { + ApplicationManager.this.context.removeHandler(this.staticContext); + this.staticContext.stop(); + this.staticContext.destroy(); + this.staticContext = null; } - context.mapContexts(); + ApplicationManager.this.context.mapContexts(); } // unregister as XML-RPC handler - if (xmlrpcHandlerName != null) { - xmlrpcHandlers.remove(xmlrpcHandlerName); + if (this.xmlrpcHandlerName != null) { + ApplicationManager.this.xmlrpcHandlers.remove(this.xmlrpcHandlerName); } } catch (Exception x) { getLogger().error("Couldn't unbind app", x); @@ -584,8 +583,9 @@ public class ApplicationManager implements XmlRpcHandler { } + @Override public String toString() { - return "[AppDescriptor "+app+"]"; + return "[AppDescriptor "+this.app+"]"; //$NON-NLS-1$ //$NON-NLS-2$ } } } diff --git a/src/main/java/helma/scripting/rhino/CompiledOrInterpretedModuleScriptProvider.java b/src/main/java/helma/scripting/rhino/CompiledOrInterpretedModuleScriptProvider.java new file mode 100644 index 00000000..cec1e359 --- /dev/null +++ b/src/main/java/helma/scripting/rhino/CompiledOrInterpretedModuleScriptProvider.java @@ -0,0 +1,73 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 2017 Daniel Ruthardt. All rights reserved. + */ + +package helma.scripting.rhino; + +import java.net.URI; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.commonjs.module.ModuleScript; +import org.mozilla.javascript.commonjs.module.provider.ModuleSourceProvider; +import org.mozilla.javascript.commonjs.module.provider.StrongCachingModuleScriptProvider; + +/** + * Provides module scripts without compiling, should compiling not be possible for whatever reason. + * The main reason being targeted though, is the "generated bytecode for method exceeds 64K limit" issue. + */ +public class CompiledOrInterpretedModuleScriptProvider extends StrongCachingModuleScriptProvider { + + /** + * Define the serialization UID. + */ + private static final long serialVersionUID = 1170789670529274963L; + + /** + * Delegates to the super constructor. + */ + public CompiledOrInterpretedModuleScriptProvider(ModuleSourceProvider moduleSourceProvider) { + // do what would have been done anyways + super(moduleSourceProvider); + } + + @Override + public ModuleScript getModuleScript(Context cx, String moduleId, URI moduleUri, URI baseUri, Scriptable paths) throws Exception { + try { + // try to load the module script with whatever optimization level is set for the application + return super.getModuleScript(cx, moduleId, moduleUri, baseUri, paths); + } catch (EvaluatorException ignore) { + // unlikely, but possible exception during loading the module script without compilation + Exception exception; + // get the application's optimization level + int optimizationLevel = cx.getOptimizationLevel(); + + try { + // set the optimization level to not compile, but interpret + cx.setOptimizationLevel(-1); + // load the module script with the newly set optimization level + ModuleScript moduleScript = super.getModuleScript(cx, moduleId, moduleUri, baseUri, paths); + // return the module script + return moduleScript; + } catch (Exception e) { + // remember the exception + exception = e; + } finally { + // re-set the optimization + cx.setOptimizationLevel(optimizationLevel); + } + + // re-throw the exception catched when trying to load the module script without compilation + throw exception; + } + } + +} \ No newline at end of file diff --git a/src/main/java/helma/scripting/rhino/JSONModuleSource.java b/src/main/java/helma/scripting/rhino/JSONModuleSource.java new file mode 100644 index 00000000..89a3c480 --- /dev/null +++ b/src/main/java/helma/scripting/rhino/JSONModuleSource.java @@ -0,0 +1,35 @@ +package helma.scripting.rhino; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.net.URI; + +import org.apache.commons.io.IOUtils; +import org.mozilla.javascript.commonjs.module.provider.ModuleSource; + +public class JSONModuleSource extends ModuleSource { + + private static final long serialVersionUID = 4446798833357540398L; + + public JSONModuleSource(Object securityDomain, URI uri, URI base, Object validator) { + super(null, securityDomain, uri, base, validator); + } + + @Override + public Reader getReader() { + StringBuffer content = new StringBuffer(); + content.append("module.exports = "); //$NON-NLS-1$ + + try { + content.append(IOUtils.toString(this.getUri().toURL().openStream())); + } catch (IOException e) { + content.append("null"); //$NON-NLS-1$ + } + + content.append(";"); //$NON-NLS-1$ + + return new StringReader(content.toString()); + } + +} diff --git a/src/main/java/helma/scripting/rhino/NodeModulesProvider.java b/src/main/java/helma/scripting/rhino/NodeModulesProvider.java new file mode 100644 index 00000000..4ee114de --- /dev/null +++ b/src/main/java/helma/scripting/rhino/NodeModulesProvider.java @@ -0,0 +1,212 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 2017 Daniel Ruthardt. All rights reserved. + */ + +package helma.scripting.rhino; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; + +import org.apache.commons.io.FilenameUtils; +import org.mozilla.javascript.commonjs.module.provider.ModuleSource; +import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +/** + * Bridges the gap between CommonJS-style module loading and Node.js-style module loading. + */ +public class NodeModulesProvider extends UrlModuleSourceProvider { + + /** + * Define the serialization UID. + */ + private static final long serialVersionUID = 6858072487233136717L; + + /** + * Delegates to the super constructor. + */ + public NodeModulesProvider(Iterable privilegedUris, Iterable fallbackUris) { + // do what would have been done anyways + super(privilegedUris, fallbackUris); + } + + /** + * Do what Node.js's LOAD_AS_FILE(X) would do. + * Case 4 ("If X.node is a file, load X.node as binary addon. STOP") is currently not supported (for + * quite obvious reasons). We might want to load JAR files in the future. + * + * @see https://nodejs.org/dist/latest-v9.x/docs/api/modules.html#modules_file_modules + * + * @param uri + * The file to load. FILE, FILE.js and FILE.json will be tried in this order. + * @param base + * Not used, only forwarded to + * {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + * @param validator + * Not used, only forwarded to + * {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + * @return + * The module source or null, if the module was not found. + * + * @throws IOException + * See {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + * @throws URISyntaxException + * See {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + */ + private ModuleSource loadAsFile(URI uri, URI base, Object validator) + throws IOException, URISyntaxException { + // lets assume the module is a file + File file = new File(uri); + // check if the file exists and is a file + if (file.exists() && file.isFile()) { + // check if the file is a JSON file + if (file.getAbsolutePath().toLowerCase().endsWith(".json")) { //$NON-NLS-1$ + // return a JSON module source + return new JSONModuleSource(null, file.toURI(), base, validator); + } else { + // do what would have been done anyways + return super.loadFromUri(uri, base, validator); + } + } + + // lets assume the module is a JS file + file = new File(new File(uri).getPath() + ".js"); //$NON-NLS-1$ + // check if a file.js exists and is a file + if (file.exists() && file.isFile()) { + // do what would have been done anyways + return super.loadFromUri(uri, base, validator); + } + + // lets assume the module is a JSON file + file = new File(new File(uri).getPath() + ".json"); //$NON-NLS-1$ + // check if a file.json exists and is a file + if (file.exists() && file.isFile()) { + // return a JSON module source + return new JSONModuleSource(null, file.toURI(), base, validator); + } + + // module not found + return null; + } + + /** + * Do what Node.js's LOAD_AS_DIRECTORY(X) would do. + * + * @see https://nodejs.org/dist/latest-v9.x/docs/api/modules.html#modules_file_modules + * + * @param uri + * The directory to load. + * @param base + * Not used, only forwarded to + * {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + * @param validator + * Not used, only forwarded to + * {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + * @return + * The module source or null, if the module was not found. + * + * @throws JsonSyntaxException + * Thrown for problems accessing the module's "package.json" file. + * @throws IOException + * See {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + * @throws URISyntaxException + * See {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}. + */ + private ModuleSource loadAsDirectory(URI uri, URI base, Object validator) + throws JsonSyntaxException, IOException, URISyntaxException { + // lets assume the module is a directory + File directory = new File(uri); + // check if the directory exists and is a directory + if (directory.exists() && directory.isDirectory()) { + // the module source + ModuleSource moduleSource; + + // lets assume that there is a "package.json" file in the directory + File packageFile = new File(directory, "package.json"); //$NON-NLS-1$ + + // check if the there is a "package.json" file in the directory + if (packageFile.exists() && packageFile.isFile()) { + // parse the JSON file + JsonObject json = new JsonParser() + .parse(new String(Files.readAllBytes(packageFile.toPath()))).getAsJsonObject(); + // check if the JSON file defines a main JS file + if (json.has("main")) { //$NON-NLS-1$ + // get the main JS file, removing the filename extension + String main = FilenameUtils.removeExtension(json.get("main").getAsString()); //$NON-NLS-1$ + + // load as file + moduleSource = this.loadAsFile(new File(directory, main).toURI(), base, validator); + // check if something was loaded + if (moduleSource != null) { + // return the loaded module source + return moduleSource; + } + } + } + + // load as index + moduleSource = this.loadAsFile(new File(directory, "index").toURI(), base, validator); //$NON-NLS-1$ + // check if something was loaded + if (moduleSource != null) { + // return the loaded module source + return moduleSource; + } + } + + // module not found + return null; + } + + /** + * Do what Node.js's require(X) would do. + * + * Case 1 is not supported, you will have to use modules from npmjs.org, re-implementing the core + * core module's functionality. We might want to use Nodeschnaps in the future. + * Case 2 is not supported, paths are always treated as relative paths within the application's + * "commonjs" directory. + * Case 5 additionally tries {@link UrlModuleSourceProvider#loadSource(URI, URI, Object)}, even if it is + * very unlikely that something, which hasn't been tried yet, will be done. One could say we are just + * delegating throwing the error. + * + * @see https://nodejs.org/dist/latest-v9.x/docs/api/modules.html#modules_file_modules + * @see https://github.com/killmag10/nodeschnaps + */ + protected ModuleSource loadFromUri(URI uri, URI base, Object validator) + throws IOException, URISyntaxException { + // the module source + ModuleSource moduleSource; + + // load as file + moduleSource = this.loadAsFile(uri, base, validator); + // check if something was loaded + if (moduleSource != null) { + // return the loaded module source + return moduleSource; + } + + // load as directory + moduleSource = this.loadAsDirectory(uri, base, validator); + // check if something was loaded + if (moduleSource != null) { + // return the loaded module source + return moduleSource; + } + + // do what would have been done anyways + return super.loadFromUri(uri, base, validator); + } + +} diff --git a/src/main/java/helma/scripting/rhino/RhinoCore.java b/src/main/java/helma/scripting/rhino/RhinoCore.java index 8e7814ed..36c69ad6 100644 --- a/src/main/java/helma/scripting/rhino/RhinoCore.java +++ b/src/main/java/helma/scripting/rhino/RhinoCore.java @@ -44,7 +44,7 @@ import org.mozilla.javascript.Undefined; import org.mozilla.javascript.WrapFactory; import org.mozilla.javascript.Wrapper; import org.mozilla.javascript.commonjs.module.RequireBuilder; -import org.mozilla.javascript.commonjs.module.provider.*; +import org.mozilla.javascript.commonjs.module.provider.StrongCachingModuleScriptProvider; import org.mozilla.javascript.tools.debugger.ScopeProvider; import java.io.*; @@ -175,9 +175,11 @@ public final class RhinoCore implements ScopeProvider { } } + // install the global require() function using our custom modules provider, so that + // CommonJS-style as well as NodeJS-style modules can be required new RequireBuilder() - .setModuleScriptProvider(new StrongCachingModuleScriptProvider( - new UrlModuleSourceProvider(commonJsPaths, null))) + .setModuleScriptProvider(new CompiledOrInterpretedModuleScriptProvider( + new NodeModulesProvider(commonJsPaths, null))) .setSandboxed(true) .createRequire(context, global) .install(global);