/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.bindings;

import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

import org.testng.annotations.Test;

import org.rhq.bindings.util.PackageFinder;

/**
 * 
 *
 * @author Lukas Krejci
 */
public class ScriptEngineTest extends ScriptedTestBase {

    private static StandardBindings EMPTY_BINDINGS = new StandardBindings(new PrintWriter(System.out), new FakeRhqFacade());
        
    @Test
    public void testFactory_javascript() throws ScriptException, IOException {
        ScriptEngine engine = getScriptEngine();
        assertNotNull(engine);
    }
    
    @Test
    public void testFactory_python() throws ScriptException, IOException {
        ScriptEngine engine = getScriptEngine();
        assertNotNull(engine);
    }
    
    @Test
    public void testSandbox_javascript() throws ScriptException, IOException {
        ScriptEngine sandbox = getSecuredScriptEngine();
        
        try {
            sandbox.eval("java.lang.System.exit(1);");
        } catch (Exception e) {
            assertSecurityExceptionPresent(e);
        }
        
        try {
            //try hard to get to the System.exit()
            sandbox.eval(
                "cls = java.lang.Class.forName('java.lang.System');" +
                "params = java.lang.reflect.Array.newInstance(java.lang.Object, 1);" +
                "params[0] = java.lang.Integer.valueOf('1');" +
                "st = new java.beans.Statement(cls, 'exit', params);" +
                "st.execute()");
            
        } catch (Exception e) {
            assertSecurityExceptionPresent(e);
        }
    }
    
    @Test
    public void testSandbox_python() throws Exception {
        ScriptEngine sandbox = getSecuredScriptEngine();
        
        try {
            sandbox.eval("import java.lang as foo\nfoo.System.exit(1)");
        } catch (Exception e) {
            assertSecurityExceptionPresent(e);
        }
        
        try {            
            //try hard to get to the System.exit()
            sandbox.eval(
                "import java.lang as l\n" +
                "import java.lang.reflect as r\n" +
                "import java.beans as b\n" + 
                "cls = l.Class.forName('java.lang.System')\n" +
                "params = r.Array.newInstance(l.Class.forName('java.lang.Object'), 1)\n" +
                "params[0] = l.Integer.valueOf('1')\n" +
                "st = b.Statement(cls, 'exit', params)\n" +
                "st.execute()");
            
        } catch (Exception e) {
            assertSecurityExceptionPresent(e);
        }
    }
    
    @Test
    public void testStandardBindings_javascript() throws ScriptException, IOException {
        ScriptEngine scriptEngine = getScriptEngine();
        
        for(String var : EMPTY_BINDINGS.keySet()) {
            boolean hasVar = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE).containsKey(var);
            assertTrue(hasVar, "The variable '" + var + "' is not present in the script context but should be.");
        }
    }
    
    @Test
    public void testStandardBindings_python() throws Exception {        
        ScriptEngine scriptEngine = getScriptEngine();
        
        for(String var : EMPTY_BINDINGS.keySet()) {
            boolean hasVar = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE).containsKey(var);
            assertTrue(hasVar, "The variable '" + var + "' is not present in the script context but should be.");
        }
    }
    
    private ScriptEngine getScriptEngine() throws ScriptException, IOException {
        return getScriptEngine(new PackageFinder(Collections.<File> emptyList()), EMPTY_BINDINGS);
    }
    
    private ScriptEngine getSecuredScriptEngine() throws ScriptException, IOException {
        return getSecuredScriptEngine(new PackageFinder(Collections.<File> emptyList()), EMPTY_BINDINGS, new StandardScriptPermissions());
    }
    
    private void assertSecurityExceptionPresent(Throwable t) {
        boolean ok = false;
        while (t != null) {
            if (t instanceof SecurityException) {
                ok = true;
                break;
            } else if ((t instanceof ScriptException) && 
                (t.getMessage().contains("java.security.AccessControlException")
                 || t.getMessage().contains("java.lang.SecurityException"))) {
                ok = true;
                break;
            }
            
            t = t.getCause();
        }
        
        assertTrue(ok, "Didn't find a SecurityException, which should have occured.");
    }
}
