com.github.nlloyd.hornofmongo.MongoScope.java Source code

Java tutorial

Introduction

Here is the source code for com.github.nlloyd.hornofmongo.MongoScope.java

Source

/**
 *  Copyright (c) 2012 Nick Lloyd
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
package com.github.nlloyd.hornofmongo;

import static java.util.Collections.synchronizedSet;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.bson.BSON;
import org.bson.io.BasicOutputBuffer;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.tools.shell.Global;

import com.github.nlloyd.hornofmongo.action.MongoAction;
import com.github.nlloyd.hornofmongo.action.NewInstanceAction;
import com.github.nlloyd.hornofmongo.adaptor.BinData;
import com.github.nlloyd.hornofmongo.adaptor.DB;
import com.github.nlloyd.hornofmongo.adaptor.DBCollection;
import com.github.nlloyd.hornofmongo.adaptor.DBPointer;
import com.github.nlloyd.hornofmongo.adaptor.DBQuery;
import com.github.nlloyd.hornofmongo.adaptor.DBRef;
import com.github.nlloyd.hornofmongo.adaptor.InternalCursor;
import com.github.nlloyd.hornofmongo.adaptor.MaxKey;
import com.github.nlloyd.hornofmongo.adaptor.MinKey;
import com.github.nlloyd.hornofmongo.adaptor.Mongo;
import com.github.nlloyd.hornofmongo.adaptor.NumberInt;
import com.github.nlloyd.hornofmongo.adaptor.NumberLong;
import com.github.nlloyd.hornofmongo.adaptor.ObjectId;
import com.github.nlloyd.hornofmongo.adaptor.Timestamp;
import com.github.nlloyd.hornofmongo.exception.MongoRuntimeException;
import com.github.nlloyd.hornofmongo.exception.MongoScopeException;
import com.github.nlloyd.hornofmongo.exception.MongoScriptException;
import com.github.nlloyd.hornofmongo.util.BSONizer;
import com.github.nlloyd.hornofmongo.util.ClearHandler;
import com.github.nlloyd.hornofmongo.util.CurrentDirectoryHandler;
import com.github.nlloyd.hornofmongo.util.DefaultCurrentDirectoryHandler;
import com.github.nlloyd.hornofmongo.util.PrintHandler;
import com.github.nlloyd.hornofmongo.util.QuitHandler;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBEncoder;
import com.mongodb.DBObject;
import com.mongodb.DefaultDBEncoder;
import com.mongodb.MongoException;
import com.mongodb.util.Util;

/**
 * The MongoDB-specific {@link Scope} implementation. This extends
 * {@link Global} to add MongoDB shell JavaScript global functions objects, and
 * variables.
 *
 * Meant to emulate engine.cpp (and the more specific engine_*.cpp
 * implementations) in the official mongodb source.
 *
 * @author nlloyd
 *
 */
public class MongoScope extends Global {

    /**
    *
    */
    private static final long serialVersionUID = 4650743395507077775L;

    private static ThreadLocal<Random> threadLocalRandomGen = new ThreadLocal<Random>() {
        protected Random initialValue() {
            return new Random();
        }
    };

    private static String[] mongoApiFiles = { "mongodb/assert.js", "mongodb/types.js", "mongodb/utils.js",
            "mongodb/utils_sh.js", "mongodb/db.js", "mongodb/mongo.js", "mongodb/mr.js", "mongodb/query.js",
            "mongodb/collection.js", "mongodb/servers_misc.js", "mongodb/servers.js", "mongodb/shardingtest.js" };

    private CurrentDirectoryHandler currentDirHandler = new DefaultCurrentDirectoryHandler();

    private PrintHandler printHandler;

    private ClearHandler clearHandler;

    private QuitHandler quitHandler;

    private com.mongodb.DB lastCalledDB;

    /**
     * If true then some {@link MongoException} will be caught and the messages
     * will be printed to stdout depending on behavior of the official mongodb
     * client shell. If false then the exceptions will be rethrown.
     *
     * Defaults to false.
     *
     * Meant to support the same behavior as the official mongodb shell client.
     */
    private boolean stdoutMongoErrorMessages = false;

    /**
     * {@link http://docs.mongodb.org/manual/release-notes/drivers-write-concern/}
     *
     * Default write concern has changed for all official mongo drivers, which
     * differs from the default mongo shell behavior. Set this flag to true
     * configure this MongoScope to behave like mongo shell as opposed to mongo
     * java driver (defaults to false).
     */
    private boolean useMongoShellWriteConcern = false;

    private Set<Mongo> mongoConnections = synchronizedSet(new HashSet<Mongo>());

    public MongoScope() {
        super();
    }

    public MongoScope(Context context)
            throws IllegalAccessException, InstantiationException, InvocationTargetException {
        super(context);
        initMongoJS(context);
        execCoreFiles(context);
    }

    /**
     * @return the stdoutMongoErrorMessages
     */
    public boolean isStdoutMongoErrorMessages() {
        return stdoutMongoErrorMessages;
    }

    /**
     * @param stdoutMongoErrorMessages
     *            the stdoutMongoErrorMessages to set
     */
    public void setStdoutMongoErrorMessages(boolean stdoutMongoErrorMessages) {
        this.stdoutMongoErrorMessages = stdoutMongoErrorMessages;
    }

    /**
     * @return the useMongoShellWriteConcern
     */
    public boolean useMongoShellWriteConcern() {
        return useMongoShellWriteConcern;
    }

    /**
     * @param useMongoShellWriteConcern
     *            the useMongoShellWriteConcern to set
     */
    public void setUseMongoShellWriteConcern(boolean useMongoShellWriteConcern) {
        this.useMongoShellWriteConcern = useMongoShellWriteConcern;
    }

    /**
     * @return the currentDirHandler
     */
    public CurrentDirectoryHandler getCurrentDirHandler() {
        return currentDirHandler;
    }

    /**
     * @param currentDirHandler
     *            the currentDirHandler to set
     */
    public void setCurrentDirHandler(CurrentDirectoryHandler currentDirHandler) {
        this.currentDirHandler = currentDirHandler;
    }

    /**
     * @return the printHandler
     */
    public PrintHandler getPrintHandler() {
        return printHandler;
    }

    /**
     * @param printHandler
     *            the printHandler to set
     */
    public void setPrintHandler(PrintHandler printHandler) {
        this.printHandler = printHandler;
    }

    /**
     * @return the clearHandler
     */
    public ClearHandler getClearHandler() {
        return clearHandler;
    }

    /**
     * @param clearHandler
     *            the clearHandler to set
     */
    public void setClearHandler(ClearHandler clearHandler) {
        this.clearHandler = clearHandler;
    }

    /**
     * @return the quitHandler
     */
    public QuitHandler getQuitHandler() {
        return quitHandler;
    }

    /**
     * @param quitHandler
     *            the quitHandler to set
     */
    public void setQuitHandler(QuitHandler quitHandler) {
        this.quitHandler = quitHandler;
    }

    /**
     *
     * @return the last called/queried/used {@link com.mongodb.DB}
     */
    public com.mongodb.DB getLastCalledDB() {
        return lastCalledDB;
    }

    /**
     * Set the last called/queries/used {@link com.mongodb.DB}
     * @param lastCalledDB the {@link com.mongodb.DB} to set
     */
    public void setLastCalledDB(com.mongodb.DB lastCalledDB) {
        this.lastCalledDB = lastCalledDB;
    }

    /**
     * @return the cwd
     */
    public File getCwd() {
        return getCurrentDirHandler().getCurrentDirectory();
    }

    /**
     * @param cwd
     *            the cwd to set
     */
    public void setCwd(File cwd) {
        getCurrentDirHandler().setCurrentDirectory(cwd);
    }

    public void addMongoConnection(Mongo mongoConnection) {
        mongoConnections.add(mongoConnection);
    }

    public int countMongoConnections() {
        return mongoConnections.size();
    }

    public void removeMongoConnection(Mongo mongoConnection) {
        mongoConnections.remove(mongoConnection);
    }

    public void cleanup() {
        for (Mongo connection : mongoConnections) {
            connection.close();
        }
        mongoConnections.clear();
    }

    protected void initMongoJS(Context context)
            throws IllegalAccessException, InstantiationException, InvocationTargetException {
        if (!isInitialized()) {
            super.init(context);
        }

        String[] names = { "quit", "sleep", "hex_md5", "_isWindows", "_srand", "_rand", "UUID", "MD5", "HexData",
                "print", "ls", "cd", "mkdir", "pwd", "listFiles", "hostname", "cat", "removeFile", "md5sumFile",
                "fuzzFile", "run", "runProgram", "getMemInfo", "load", "getHostName" };
        defineFunctionProperties(names, this.getClass(), ScriptableObject.DONTENUM);
        ScriptableObject objectPrototype = (ScriptableObject) ScriptableObject.getClassPrototype(this, "Object");
        objectPrototype.defineFunctionProperties(new String[] { "bsonsize" }, this.getClass(),
                ScriptableObject.DONTENUM);

        ScriptableObject.defineClass(this, Mongo.class, false, false);
        ScriptableObject.defineClass(this, ObjectId.class, false, false);
        ScriptableObject.defineClass(this, DB.class, false, false);
        ScriptableObject.defineClass(this, DBCollection.class, false, false);
        ScriptableObject.defineClass(this, InternalCursor.class, false, false);
        ScriptableObject.defineClass(this, DBQuery.class, false, false);
        ScriptableObject.defineClass(this, DBPointer.class, false, false);
        ScriptableObject.defineClass(this, BinData.class, false, false);

        ScriptableObject.defineClass(this, Timestamp.class, false, false);
        ScriptableObject.defineClass(this, NumberLong.class, false, false);
        ScriptableObject.defineClass(this, NumberInt.class, false, false);
        ScriptableObject.defineClass(this, MinKey.class, false, false);
        ScriptableObject.defineClass(this, MaxKey.class, false, false);

        ScriptableObject.defineClass(this, DBRef.class, false, false);
    }

    protected void execCoreFiles(Context context) {
        for (String jsSetupFile : mongoApiFiles) {
            try {
                context.evaluateReader(this, loadFromClasspath(jsSetupFile), jsSetupFile, 0, null);
            } catch (IOException e) {
                throw new MongoScopeException(
                        "Caught IOException attempting to load from classpath: " + jsSetupFile, e);
            } catch (JavaScriptException e) {
                throw new MongoScopeException(
                        "Caught JavaScriptException attempting to load from classpath: " + jsSetupFile, e);
            }
        }
    }

    protected Reader loadFromClasspath(String filePath) {
        Reader reader = null;
        ClassLoader loader = this.getClass().getClassLoader();
        reader = new BufferedReader(new InputStreamReader(loader.getResourceAsStream(filePath)));
        return reader;
    }

    public void handleMongoException(MongoException me) {
        if (this.isStdoutMongoErrorMessages()) {
            // check error codes that do NOT result in an exception
            switch (me.getCode()) {
            case 10088: // cannot index parallel arrays [b] [d]
            case 10096: // invalid ns to index
            case 10098: // bad index key pattern
            case 10148: // Mod on _id not allowed
            case 10149: // Invalid mod field name, may not end in a period
            case 10159: // multi update only works with $ operators
            case 11000: // E11000 duplicate key error index:
            case 15896: // Modified field name may not start with $
            case 16650: // Cannot apply the positional operator without a
                        // corresponding query field containing an array.
            case 10141: // Cannot apply $push/$pushAll modifier to non-array
            case 16734: // Unknown index plugin '*' in index { *: * }
            case 10089: // can't remove from a capped collection
            case 13023: // 2d has to be first in index
            case 13028: // bits in geo index must be between 1 and 32
            case 13027: // point not in interval of [ -0.99995, 0.99995 ]
            case 16572: // Can't extract geo keys from object, malformed
                        // geometry?
            case 16687: // coarsestIndexedLevel must be >= 0
            case 16688: // finestIndexedLevel must be <= 30
            case 16241: // Currently only single field hashed index supported.
            case 16242: // Currently hashed indexes cannot guarantee uniqueness.
                        // Use a regular index.
            case 15855: // Ambiguous field name found in array (do not use
                        // numeric field names in embedded elements in
                        // an array)
            case 12505: // add index fails, too many indexes
                MongoScope.print(Context.getCurrentContext(), this, new Object[] { me.getMessage() }, null);
                return;
            default:
                throw me;
            }
        } else
            throw me;
    }

    /* --- global and globalish utility functions --- */

    // public static Object eval(Context cx, Scriptable thisObj, Object[] args,
    // Function funObj) {
    // final String evalScript = Context.toString(args[0]);
    // Object result = MongoRuntime.call(new MongoScriptAction(
    // (MongoScope) thisObj, "(eval)", evalScript));
    // return result;
    // }

    // public static Object version(Context cx, Scriptable thisObj, Object[]
    // args,
    // Function funObj) {
    // return MongoScope.print(cx, thisObj, new Object[]{}, funObj);
    // }

    public static String hex_md5(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        // just like mongo native_hex_md5 call, only expects a single string
        final String str = Context.toString(args[0]);
        return Util.hexMD5(str.getBytes());
    }

    public static Boolean _isWindows(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        return System.getProperty("os.name").startsWith("Windows");
    }

    public static void _srand(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        Random randomGen = threadLocalRandomGen.get();
        if (args[0] instanceof Long)
            randomGen.setSeed((Long) args[0]);
        else
            randomGen.setSeed(Double.valueOf(args[0].toString()).longValue());
    }

    public static Double _rand(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        return threadLocalRandomGen.get().nextDouble();
    }

    public static Long bsonsize(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException {
        DBObject bsonObj = (DBObject) BSONizer.convertJStoBSON(args[0], true);
        Long size = new Long(0);
        if (bsonObj != null) {
            BasicOutputBuffer byteBuffer = new BasicOutputBuffer();
            DefaultDBEncoder.FACTORY.create().writeObject(byteBuffer, bsonObj);
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            byteBuffer.pipe(byteStream);
            size = new Long(byteStream.size());
        }
        return size;
    }

    public static BinData UUID(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (args.length != 1)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("UUID needs 1 argument"));
        String uuidHex = Context.toString(args[0]);
        if (uuidHex.length() != 32)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("UUID string must have 32 characters"));
        String str = hexToBase64(uuidHex);
        BinData uuid = (BinData) MongoRuntime
                .call(new NewInstanceAction((MongoScope) thisObj, "BinData", new Object[] { BSON.B_UUID, str }));
        return uuid;
    }

    public static BinData MD5(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (args.length != 1)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("MD5 needs 1 argument"));
        String md5Hex = Context.toString(args[0]);
        if (md5Hex.length() != 32)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("MD5 string must have 32 characters"));
        String str = hexToBase64(md5Hex);
        // MD5Type = 5 in bsontypes.h
        BinData md5 = (BinData) MongoRuntime
                .call(new NewInstanceAction((MongoScope) thisObj, "BinData", new Object[] { 5, str }));
        return md5;
    }

    public static BinData HexData(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (args.length != 2)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("HexData needs 2 arguments"));
        int type = Double.valueOf(Context.toNumber(args[0])).intValue();
        String str = hexToBase64(Context.toString(args[1]));
        BinData md5 = (BinData) MongoRuntime
                .call(new NewInstanceAction((MongoScope) thisObj, "BinData", new Object[] { type, str }));
        return md5;
    }

    private static final String hexToBase64(final String hex) {
        String base64 = null;
        try {
            base64 = Base64.encodeBase64String(Hex.decodeHex(hex.toCharArray()));
        } catch (DecoderException e) {
            Context.throwAsScriptRuntimeEx(e);
        }
        return base64;
    }

    public static Object print(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (thisObj instanceof MongoScope) {
            MongoScope mongoScope = (MongoScope) thisObj;
            if (mongoScope.getPrintHandler() != null)
                mongoScope.getPrintHandler().doPrint(cx, thisObj, args);
            else
                Global.print(cx, thisObj, args, funObj);
        } else {
            Global.print(cx, thisObj, args, funObj);
        }

        return Context.getUndefinedValue();
    }

    /**
     * Call the {@link ClearHandler} or noop.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     */
    public static void clear(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (thisObj instanceof MongoScope) {
            MongoScope mongoScope = (MongoScope) thisObj;
            if (mongoScope.getClearHandler() != null)
                mongoScope.getClearHandler().doClear(cx, thisObj, args);
        }
    }

    /**
     * Call the {@link QuitHandler} or call the default
     * {@link Global#quit(Context, Scriptable, Object[], Function)}.
     */
    public static void quit(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (thisObj instanceof MongoScope) {
            MongoScope mongoScope = (MongoScope) thisObj;
            if (mongoScope.getQuitHandler() != null)
                mongoScope.getQuitHandler().doQuit(cx, thisObj, args);
            else
                Global.quit(cx, thisObj, args, funObj);
        } else
            Global.quit(cx, thisObj, args, funObj);
    }

    // *** extended shell functions ***

    public static Object ls(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        File path = null;
        MongoScope mongoScope = (MongoScope) thisObj;
        if (args.length == 0)
            path = mongoScope.getCwd();
        else if (args.length > 1)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("need to specify 1 argument to listFiles"));
        else {
            try {
                path = resolveFilePath(mongoScope, Context.toString(args[0]));
            } catch (IOException e) {
                Context.throwAsScriptRuntimeEx(new MongoScriptException("listFiles: " + e.getMessage()));
            }
        }

        // mongo only checks if the path exists, so we will do the same here
        // official mongo has ls() call listFiles()... im not doing that here
        // but i am honoring the error messages as they appear in the official
        // shell
        if (!path.exists())
            Context.throwAsScriptRuntimeEx(
                    new MongoScriptException("listFiles: no such directory: " + path.getAbsolutePath()));
        if (!path.isDirectory())
            Context.throwAsScriptRuntimeEx(
                    new MongoScriptException("listFiles: not a directory: " + path.getAbsolutePath()));

        List<String> files = new ArrayList<String>();
        for (File file : path.listFiles()) {
            String name = file.getPath();
            if (file.isDirectory())
                name += "/";
            files.add(name);
        }

        Object jsResult = BSONizer.convertBSONtoJS((MongoScope) thisObj, files);

        return jsResult;
    }

    public static Object cd(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        assertSingleArgument(args);
        MongoScope mongoScope = (MongoScope) thisObj;
        String newDirPath = Context.toString(args[0]);
        String result = null;
        try {
            File newCwd = resolveFilePath(mongoScope, newDirPath);
            if (newCwd.isDirectory()) {
                mongoScope.setCwd(newCwd.getCanonicalFile());
            } else
                result = "change directory failed";
        } catch (IOException e) {
            result = "change directory failed: " + e.getMessage();
        }
        return result;
    }

    public static Object mkdir(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        assertSingleArgument(args);
        boolean success = false;
        File newDir;
        try {
            newDir = resolveFilePath((MongoScope) thisObj, Context.toString(args[0])).getCanonicalFile();
            success = newDir.mkdirs();
        } catch (IOException e) {
        }
        // despite what the official shell does, i want to return if this fails
        return success;
    }

    public static Object pwd(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        return ((MongoScope) thisObj).getCwd().getAbsolutePath();
    }

    public static Object listFiles(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        File path = null;
        if (args.length == 0)
            path = ((MongoScope) thisObj).getCwd();
        else if (args.length > 1)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("need to specify 1 argument to listFiles"));
        else {
            try {
                path = resolveFilePath((MongoScope) thisObj, Context.toString(args[0]));
            } catch (IOException e) {
                Context.throwAsScriptRuntimeEx(new MongoScriptException("listFiles: " + e.getMessage()));
            }
        }

        // mongo only checks if the path exists, so we will do the same here
        if (!path.exists())
            Context.throwAsScriptRuntimeEx(
                    new MongoScriptException("listFiles: no such directory: " + path.getAbsolutePath()));
        if (!path.isDirectory())
            Context.throwAsScriptRuntimeEx(
                    new MongoScriptException("listFiles: not a directory: " + path.getAbsolutePath()));

        List<DBObject> files = new ArrayList<DBObject>();
        for (File file : path.listFiles()) {
            BasicDBObjectBuilder fileObj = new BasicDBObjectBuilder();
            fileObj.append("name", file.getPath()).append("isDirectory", file.isDirectory());
            if (!file.isDirectory())
                fileObj.append("size", file.length());
            files.add(fileObj.get());
        }

        Object jsResult = BSONizer.convertBSONtoJS((MongoScope) thisObj, files);

        return jsResult;
    }

    public static Object hostname(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            Context.throwAsScriptRuntimeEx(e);
            return Undefined.instance;
        }
    }

    public static Object getHostName(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        return hostname(cx, thisObj, args, funObj);
    }

    public static Object cat(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        assertSingleArgument(args);
        try {
            File toRead = resolveFilePath((MongoScope) thisObj, Context.toString(args[0])).getAbsoluteFile();
            return FileUtils.readFileToString(toRead);
        } catch (IOException e) {
            Context.throwAsScriptRuntimeEx(e);
            return Undefined.instance;
        }
    }

    public static Object removeFile(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        assertSingleArgument(args);
        boolean success = false;
        File toRemove;
        try {
            toRemove = resolveFilePath((MongoScope) thisObj, Context.toString(args[0])).getCanonicalFile();
            success = FileUtils.deleteQuietly(toRemove);
        } catch (IOException e) {
        }
        return success;
    }

    public static Object md5sumFile(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        assertSingleArgument(args);
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            Context.throwAsScriptRuntimeEx(e);
        }
        File inFile;
        try {
            inFile = resolveFilePath((MongoScope) thisObj, Context.toString(args[0])).getCanonicalFile();
        } catch (IOException e) {
            Context.throwAsScriptRuntimeEx(e);
            return null;
        }
        InputStream in = null;
        DigestInputStream dis = null;
        try {
            in = new BufferedInputStream(new FileInputStream(inFile));
            dis = new DigestInputStream(in, md);
            while (dis.available() > 0)
                dis.read();
            byte[] digest = md.digest();
            String hexStr = Hex.encodeHexString(digest);
            return hexStr;
        } catch (FileNotFoundException e) {
            Context.throwAsScriptRuntimeEx(e);
        } catch (IOException e) {
            Context.throwAsScriptRuntimeEx(e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
            if (dis != null) {
                try {
                    dis.close();
                } catch (IOException e) {
                }
            }
        }
        return Undefined.instance;
    }

    public static Object fuzzFile(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        if (args.length != 2)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("fuzzFile takes 2 arguments"));
        File fileToFuzz;
        try {
            fileToFuzz = resolveFilePath((MongoScope) thisObj, Context.toString(args[0])).getCanonicalFile();
        } catch (IOException e) {
            Context.throwAsScriptRuntimeEx(new MongoScriptException(e));
            return null;
        }
        RandomAccessFile fuzzFile = null;
        try {
            fuzzFile = new RandomAccessFile(fileToFuzz, "rw");
            long fuzzPosition = Double.valueOf(Context.toNumber(args[1])).longValue();
            fuzzFile.seek(fuzzPosition);
            int byteToFuzz = fuzzFile.readByte();
            byteToFuzz = ~byteToFuzz;
            fuzzFile.seek(fuzzPosition);
            fuzzFile.write(byteToFuzz);
            fuzzFile.close();
        } catch (FileNotFoundException e) {
            Context.throwAsScriptRuntimeEx(e);
        } catch (IOException e) {
            Context.throwAsScriptRuntimeEx(e);
        } finally {
            if (fuzzFile != null) {
                try {
                    fuzzFile.close();
                } catch (IOException e) {
                }
            }
        }

        return Undefined.instance;
    }

    public static Object run(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        return "run(...) not supported";
    }

    public static Object runProgram(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        return "runProgram(...) not supported";
    }

    public static void sleep(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws NumberFormatException, InterruptedException {
        Thread.sleep(Double.valueOf(args[0].toString()).longValue());
    }

    public static Object getMemInfo(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        Scriptable memInfo = (Scriptable) MongoRuntime.call(new NewInstanceAction((MongoScope) thisObj, "Object"));

        Runtime runtime = Runtime.getRuntime();
        long freeMemory = runtime.freeMemory();
        long totalMemory = runtime.totalMemory();
        long maxMemory = runtime.maxMemory();

        ScriptableObject.putProperty(memInfo, "used", ((totalMemory - freeMemory) / 1024 / 1024));
        ScriptableObject.putProperty(memInfo, "total", (totalMemory / 1024 / 1024));
        ScriptableObject.putProperty(memInfo, "max", (maxMemory / 1024 / 1024));

        return memInfo;
    }

    // *** ******************** ***

    public static void assertSingleArgument(final Object[] args) {
        if (args.length != 1)
            Context.throwAsScriptRuntimeEx(new MongoScriptException("need to specify 1 argument"));
    }

    public static final class InitMongoScopeAction extends MongoAction {

        public InitMongoScopeAction() {
            super(null);
        }

        @Override
        public Object doRun(Context cx) {
            try {
                return new MongoScope(cx);
            } catch (IllegalAccessException e) {
                throw new MongoScopeException("caught when attempting to create a new MongoScope", e);
            } catch (InstantiationException e) {
                throw new MongoScopeException("caught when attempting to create a new MongoScope", e);
            } catch (InvocationTargetException e) {
                throw new MongoScopeException("caught when attempting to create a new MongoScope", e);
            }
        }

    }

    private static Reader loadFile(MongoScope scope, String filePath) throws IOException {
        Reader reader = null;
        File file = resolveFilePath(scope, filePath);
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        return reader;
    }

    /**
     * Load and execute a set of JavaScript source files.
     *
     * Overrides Global.load()
     *
     */
    public static void load(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
        for (int i = 0; i < args.length; i++) {
            String filename = Context.toString(args[i]);
            try {
                cx.evaluateReader(thisObj, loadFile((MongoScope) thisObj, filename), filename, 0, null);
            } catch (Exception e) {
                throw new MongoScopeException("error loading js file: " + filename, e);
            }
        }
    }

    private static File resolveFilePath(MongoScope scope, String filePath) throws IOException {
        File resolvedFile = null;
        if (scope != null)
            resolvedFile = scope.getCurrentDirHandler().resolveFilePath(filePath);
        else
            throw new MongoRuntimeException("resolveFilePath() called with a null MongoScope!");
        return resolvedFile;
    }
}