org.mule.tools.rhinodo.rhino.NodeRequire.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.tools.rhinodo.rhino.NodeRequire.java

Source

/**
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.tools.rhinodo.rhino;

import org.apache.commons.io.FilenameUtils;
import org.mozilla.javascript.*;
import org.mozilla.javascript.commonjs.module.ModuleScope;
import org.mozilla.javascript.commonjs.module.ModuleScriptProvider;
import org.mozilla.javascript.commonjs.module.Require;
import org.mule.tools.rhinodo.api.NativeModule;
import org.mule.tools.rhinodo.impl.ExitCallbackExecutor;
import org.mule.tools.rhinodo.impl.NodeModuleImplBuilder;
import org.mule.tools.rhinodo.node.child_process.ChildProcessNativeModule;
import org.mule.tools.rhinodo.node.fs.FsNativeModule;
import org.mule.tools.rhinodo.node.process.ProcessNativeModule;
import org.mule.tools.rhinodo.node.timer.ClearInterval;
import org.mule.tools.rhinodo.node.timer.ClearTimeout;
import org.mule.tools.rhinodo.node.timer.SetInterval;
import org.mule.tools.rhinodo.node.timer.SetTimeout;
import org.mule.tools.rhinodo.node.vm.VmNativeModule;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.io.File;
import java.io.FilenameFilter;
import java.net.URI;
import java.util.*;

public class NodeRequire extends Require {
    private HashMap<String, Scriptable> nativeModuleMap;

    private Script preExec;
    private Script postExec;
    private final ExitCallbackExecutor exitCallbackExecutor;

    private static void executeOptionalScript(Script script, Context cx, Scriptable executionScope) {
        if (script != null) {
            script.exec(cx, executionScope);
        }
    }

    private BaseFunction compile = new BaseFunction() {
        @Override
        public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {

            final ScriptableObject moduleObject = (ScriptableObject) cx.newObject(thisObj);

            defineReadOnlyProperty(moduleObject, "id", args[0]);

            String path = Context.toString(args[1]);
            File file = new File(path);
            URI uri = file.toURI();
            URI base;

            if (file.exists() && file.isFile()) {
                base = file.getParentFile().toURI();
            } else {
                base = file.toURI();
            }

            final Scriptable executionScope = new ModuleScope(thisObj, uri, base);

            defineReadOnlyProperty(moduleObject, "uri", uri.toString());

            Scriptable exports = cx.newObject(scope);
            executionScope.put("exports", executionScope, exports);
            executionScope.put("module", executionScope, moduleObject);
            moduleObject.put("exports", moduleObject, exports);
            install(executionScope);
            executeOptionalScript(preExec, cx, executionScope);
            cx.compileString(Context.toString(args[0]), path, 0, null).exec(cx, executionScope);
            executeOptionalScript(postExec, cx, executionScope);
            return ScriptRuntime.toObject(scope, ScriptableObject.getProperty(moduleObject, "exports"));
        }
    };

    private static void defineReadOnlyProperty(ScriptableObject obj, String name, Object value) {
        ScriptableObject.putProperty(obj, name, value);
        obj.setAttributes(name, ScriptableObject.READONLY | ScriptableObject.PERMANENT);
    }

    public NodeRequire(final Queue<Function> asyncCallbacksQueue, final Scriptable env, final Context cx,
            final Scriptable globalScope, final ModuleScriptProvider moduleScriptProvider, final Script preExec,
            final Script postExec, boolean sandboxed, final ExitCallbackExecutor exitCallbackExecutor) {
        super(cx, globalScope, moduleScriptProvider, preExec, postExec, sandboxed);
        this.exitCallbackExecutor = exitCallbackExecutor;
        loadNativeModules(env, cx, globalScope, asyncCallbacksQueue);

        ScriptableObject.putProperty(this, "cache", new NativeObject());

        //Adding extensions property
        NativeObject extensions = new NativeObject();
        BaseFunction notImplemented = new BaseFunction() {
            @Override
            public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
                if (ScriptableObject.getProperty(env, "RHINODO_IGNORE_NOT_IMPLEMENTED_EXTENSIONS") != null) {
                    return cx.newObject(scope);
                }
                throw new NotImplementedException();
            }
        };
        ScriptableObject.putProperty(extensions, ".js", new BaseFunction() {
            @Override
            public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
                return NodeRequire.this.callSuperWrapped(cx, scope, thisObj, new Object[] { args[1] });
            }
        });
        ScriptableObject.putProperty(extensions, ".json", notImplemented);
        ScriptableObject.putProperty(extensions, ".node", notImplemented);

        ScriptableObject.putProperty(this, "extensions", extensions);
        Scriptable process = nativeModuleMap.get("process");
        ScriptableObject.putProperty(globalScope, "process", process);

        ScriptableObject.putProperty(globalScope, "clearTimeout", new ClearTimeout());
        ScriptableObject.putProperty(globalScope, "setTimeout", new SetTimeout(asyncCallbacksQueue));
        ScriptableObject.putProperty(globalScope, "clearInterval", new ClearInterval());
        ScriptableObject.putProperty(globalScope, "setInterval", new SetInterval(asyncCallbacksQueue));

        this.preExec = preExec;
        this.postExec = postExec;
    }

    private void loadNativeModules(Scriptable env, Context cx, Scriptable globalScope,
            Queue<Function> asyncCallbacksQueue) {
        NativeModule[] nativeModules = { new FsNativeModule(asyncCallbacksQueue),
                new ProcessNativeModule(env, asyncCallbacksQueue, exitCallbackExecutor),
                new ChildProcessNativeModule(asyncCallbacksQueue), new VmNativeModule(asyncCallbacksQueue) };
        nativeModuleMap = new HashMap<String, Scriptable>();

        for (NativeModule nativeModule : nativeModules) {
            nativeModuleMap.put(nativeModule.getId(), nativeModule.getModule(cx, globalScope));
        }

    }

    public Object callSuperWrapped(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
        try {
            ScriptableObject.putProperty(thisObj, "_compile", compile);
            return super.call(cx, scope, thisObj, args);
        } catch (JavaScriptException e) {
            if (thisObj instanceof ModuleScope) {
                String id = (String) Context.jsToJava(args[0], String.class);
                ModuleScope moduleScope = (ModuleScope) thisObj;
                URI base = moduleScope.getBase();
                URI current = moduleScope.getUri();
                URI uri = current.resolve(id + ".js/");

                if (!id.startsWith("./") && !id.startsWith("../") && base != null
                        && new File(uri.getPath()).exists()) {
                    // try to convert to a relative URI rooted on base
                    return super.call(cx, scope, thisObj, new Object[] { uri.getPath() });
                }
            }
            throw e;
        }
    }

    public static class TryExtensionsResult {
        private final String extensionAsString;
        private final Function callback;
        private final File file;

        public TryExtensionsResult(String extensionAsString, Function value, File file) {
            this.extensionAsString = extensionAsString;
            this.callback = value;
            this.file = file;
        }

        public String getExtensionAsString() {
            return extensionAsString;
        }

        public Function getCallback() {
            return callback;
        }

        public File getFile() {
            return file;
        }
    }

    public TryExtensionsResult tryExtensions(String id, ModuleScope thisObj) {
        File cwdFile = getBasePathForModule(thisObj);
        NativeObject extensions = ScriptableObject.getTypedProperty(this, "extensions", NativeObject.class);
        Object[] propertyIds = ScriptableObject.getPropertyIds(extensions);

        if (!(id.startsWith("./") || id.startsWith("../") || id.startsWith("/"))) {
            return null;
        }

        for (Object extension : propertyIds) {
            String extensionAsString = (String) extension;
            File file;
            if (cwdFile != null) {
                file = new File(FilenameUtils.concat(cwdFile.getAbsolutePath(), id + extensionAsString));

                /* Case of ./ only where we need to add index.js */
                if (!file.exists()) {
                    file = new File(
                            FilenameUtils.concat(cwdFile.getAbsolutePath(), id + "index" + extensionAsString));
                }
            } else {
                /* Case of /a/b/c.js */
                file = new File(id + extensionAsString);
            }

            if (file.exists()) {
                Function value = ScriptableObject.getTypedProperty(extensions, extensionAsString, Function.class);
                return new TryExtensionsResult(extensionAsString, value, file);
            }
        }

        return null;
    }

    @Override
    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
        String id = Context.toString(args[0]);

        Scriptable moduleToLoad;
        if ((moduleToLoad = nativeModuleMap.get(id)) != null) {
            return moduleToLoad;
        }

        Scriptable extensions = ScriptableObject.getTypedProperty(this, "extensions", Scriptable.class);

        File file = new File(id);
        File packageJson = new File(id, "package.json");
        /* Case when a path like a/b/c is required and a file a/b/c.js exists */
        TryExtensionsResult extensionsResult;

        /* Absolute path */
        if (id.startsWith("/") && (extensionsResult = tryExtensions(id, null)) != null) {
            return extensionsResult.getCallback().call(cx, scope, thisObj,
                    new Object[] { thisObj, extensionsResult.getFile().getAbsolutePath() });
            /* Relative Path */
        } else if (thisObj instanceof ModuleScope
                && (extensionsResult = tryExtensions(id, (ModuleScope) thisObj)) != null) {
            return extensionsResult.getCallback().call(cx, scope, thisObj,
                    new Object[] { thisObj, extensionsResult.getFile().getAbsolutePath() });
        } else if (file.exists() && !file.isDirectory()
                && ScriptableObject.hasProperty(extensions, "." + FilenameUtils.getExtension(id))) {
            return ScriptableObject
                    .getTypedProperty(extensions, "." + FilenameUtils.getExtension(id), Function.class)
                    .call(cx, scope, thisObj, new Object[] { thisObj, id });
            /* Case when a path like a/b/c and a file named a/b/c/package.json exists */
        } else if (file.isDirectory() && packageJson.exists() && packageJson.isFile()) {
            Map<String, String> map = NodeModuleImplBuilder.getPackageJSONMap(packageJson);

            String main;
            if (map != null && (main = map.get("main")) != null) {
                /* Fetch entry point */
                File mainFile = new File(FilenameUtils.concat(file.getPath(), main));
                return callSuperWrapped(cx, scope, thisObj, new Object[] { mainFile.getAbsolutePath() });
            }
        } else if (thisObj instanceof ModuleScope) {
            File cwdFile = getBasePathForModule((ModuleScope) thisObj);
            cwdFile = getModuleRootDirectory(cwdFile);

            if (cwdFile == null) {
                return callSuperWrapped(cx, scope, thisObj, args);
            }

            File modulePath = new File(new File(cwdFile, "node_modules"), id);
            File modulePackageJSON = new File(modulePath, "package.json");
            if (modulePackageJSON.exists()) {
                Map<String, String> map = NodeModuleImplBuilder.getPackageJSONMap(modulePackageJSON);

                String main = map.get("main");
                if (main == null) {
                    main = "index.js";
                }
                /* Fetch entry point */
                File mainFile = new File(FilenameUtils.concat(modulePath.getPath(), main));
                if (!mainFile.exists()) {
                    mainFile = new File(FilenameUtils.concat(modulePath.getPath(), main + ".js"));
                }
                return callSuperWrapped(cx, scope, thisObj, new Object[] { mainFile.getAbsolutePath() });
            }

            cwdFile = getModuleRootDirectory(cwdFile.getParentFile());

            if (cwdFile == null) {
                return callSuperWrapped(cx, scope, thisObj, args);
            }

            modulePath = new File(new File(cwdFile, "node_modules"), id);
            modulePackageJSON = new File(modulePath, "package.json");
            if (modulePackageJSON.exists()) {
                Map<String, String> map = NodeModuleImplBuilder.getPackageJSONMap(modulePackageJSON);

                /* Fetch entry point */
                File mainFile = new File(FilenameUtils.concat(modulePath.getPath(), map.get("main")));
                if (!mainFile.exists()) {
                    mainFile = new File(FilenameUtils.concat(modulePath.getPath(), map.get("main") + ".js"));
                }
                return callSuperWrapped(cx, scope, thisObj, new Object[] { mainFile.getAbsolutePath() });
            }
        }

        return callSuperWrapped(cx, scope, thisObj, args);
    }

    private File getModuleRootDirectory(File cwdFile) {
        String[] list;

        FilenameFilter filenameFilter = new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.equals("package.json");
            }
        };
        list = cwdFile.list(filenameFilter);

        while (list.length != 1) {
            cwdFile = cwdFile.getParentFile();
            if (cwdFile == null) {
                return null;
            }
            list = cwdFile.list(filenameFilter);
        }

        return cwdFile;
    }

    private File getBasePathForModule(ModuleScope thisObj) {
        ModuleScope moduleScope = thisObj;
        if (moduleScope == null) {
            return null;
        }
        URI base = moduleScope.getUri();
        File cwdFile = new File(base.getPath());
        if (!cwdFile.isDirectory()) {
            cwdFile = cwdFile.getParentFile();
        }
        return cwdFile;
    }
}