SLING-915 - [Rhino] Implement Compilable

* implemented Compilable support
* added support for the ScripCache implemented in SLING-913 for scripts that are not executed as part of the normal Sling script resolution

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1690905 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 93b3450..d58d60d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -103,16 +103,25 @@
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
             <version>2.1.0</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.scripting.api</artifactId>
-            <version>2.1.0</version>
+            <version>2.1.7-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.core</artifactId>
+            <version>2.0.29-SNAPSHOT</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.resource</artifactId>
             <version>2.3.8</version>
+            <scope>provided</scope>
             <exclusions>
                 <exclusion>
                     <groupId>org.apache.jackrabbit</groupId>
@@ -128,11 +137,13 @@
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.classloader</artifactId>
             <version>1.3.0</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.json</artifactId>
             <version>2.0.6</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -143,10 +154,12 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>javax.jcr</groupId>
@@ -157,10 +170,12 @@
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.mozilla</groupId>
@@ -170,6 +185,7 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>commons-collections</groupId>
@@ -177,6 +193,12 @@
             <version>3.2.1</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.4</version>
+            <scope>provided</scope>
+        </dependency>
         <!-- Testing -->
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -184,5 +206,17 @@
             <version>2.0.16</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <version>1.6.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito</artifactId>
+            <version>1.6.2</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngine.java b/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngine.java
index b762ece..d04d3d4 100644
--- a/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngine.java
+++ b/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngine.java
@@ -16,25 +16,36 @@
  */
 package org.apache.sling.scripting.javascript.internal;
 
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.Reader;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
 import javax.script.Bindings;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
 import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptException;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.sling.api.scripting.SlingBindings;
 import org.apache.sling.api.scripting.SlingScriptHelper;
 import org.apache.sling.commons.classloader.DynamicClassLoader;
 import org.apache.sling.scripting.api.AbstractSlingScriptEngine;
+import org.apache.sling.scripting.api.CachedScript;
+import org.apache.sling.scripting.api.ScriptCache;
+import org.apache.sling.scripting.api.ScriptNameAware;
+import org.apache.sling.scripting.core.ScriptNameAwareReader;
 import org.apache.sling.scripting.javascript.io.EspReader;
 import org.mozilla.javascript.ClassCache;
 import org.mozilla.javascript.Context;
 import org.mozilla.javascript.ImporterTopLevel;
 import org.mozilla.javascript.JavaScriptException;
+import org.mozilla.javascript.Script;
 import org.mozilla.javascript.ScriptRuntime;
 import org.mozilla.javascript.Scriptable;
 import org.mozilla.javascript.ScriptableObject;
@@ -48,152 +59,102 @@
  * A ScriptEngine that uses the Rhino interpreter to process Sling requests with
  * server-side javascript.
  */
-public class RhinoJavaScriptEngine extends AbstractSlingScriptEngine {
+public class RhinoJavaScriptEngine extends AbstractSlingScriptEngine implements Compilable {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(RhinoJavaScriptEngine.class);
+    private static final String NO_SCRIPT_NAME = "NO_SCRIPT_NAME";
 
     private Scriptable rootScope;
+    private ScriptCache scriptCache;
 
-    public RhinoJavaScriptEngine(ScriptEngineFactory factory,
-            Scriptable rootScope) {
+    public RhinoJavaScriptEngine(ScriptEngineFactory factory, Scriptable rootScope, ScriptCache scriptCache) {
         super(factory);
         this.rootScope = rootScope;
+        this.scriptCache = scriptCache;
     }
 
-    public Object eval(Reader scriptReader, ScriptContext scriptContext)
-            throws ScriptException {
-        Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
-        String scriptName = "NO_SCRIPT_NAME";
-        {
-            SlingScriptHelper helper = (SlingScriptHelper) bindings.get(SlingBindings.SLING);
-            if (helper != null) {
-                scriptName = helper.getScript().getScriptResource().getPath();
-            }
-        }
-
-        // wrap the reader in an EspReader for ESP scripts
-        if (scriptName.endsWith(RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION)) {
-            scriptReader = new EspReader(scriptReader);
-        }
-
-        // container for replaced properties
-        Map<String, Object> replacedProperties = null;
-        Scriptable scope = null;
-        boolean isTopLevelCall = false;
-
-        // create a rhino Context and execute the script
+    public CompiledScript compile(String script) throws ScriptException {
+        Reader reader = null;
         try {
-
-            final Context rhinoContext = Context.enter();
-            rhinoContext.setOptimizationLevel(optimizationLevel());
-
-            if (ScriptRuntime.hasTopCall(rhinoContext)) {
-                // reuse the top scope if we are included
-                scope = ScriptRuntime.getTopCallScope(rhinoContext);
-
-            } else {
-                // create the request top scope, use the ImporterToplevel here
-                // to support the importPackage and importClasses functions
-                scope = new ImporterTopLevel();
-
-                // Set the global scope to be our prototype
-                scope.setPrototype(rootScope);
-
-                // We want "scope" to be a new top-level scope, so set its
-                // parent scope to null. This means that any variables created
-                // by assignments will be properties of "scope".
-                scope.setParentScope(null);
-
-                // setup the context for use
-                WrapFactory wrapFactory = ((RhinoJavaScriptEngineFactory) getFactory()).getWrapFactory();
-                rhinoContext.setWrapFactory(wrapFactory);
-
-                // this is the top level call
-                isTopLevelCall = true;
-            }
-
-            // add initial properties to the scope
-            replacedProperties = setBoundProperties(scope, bindings);
-
-            final int lineNumber = 1;
-            final Object securityDomain = null;
-
-            Object result = rhinoContext.evaluateReader(scope, scriptReader, scriptName,
-                    lineNumber, securityDomain);
-
-            if (result instanceof Wrapper) {
-                result = ((Wrapper) result).unwrap();
-            }
-
-            return (result instanceof Undefined) ? null : result;
-
-        } catch (JavaScriptException t) {
-
-            // prevent variables to be pushed back in case of errors
-            isTopLevelCall = false;
-
-            final ScriptException se = new ScriptException(t.details(),
-                t.sourceName(), t.lineNumber());
-
-            // log the script stack trace
-            ((Logger) bindings.get(SlingBindings.LOG)).error(t.getScriptStackTrace());
-
-            // set the exception cause
-            Object value = t.getValue();
-            if (value != null) {
-                if (value instanceof Wrapper) {
-                    value = ((Wrapper) value).unwrap();
-                }
-                if (value instanceof Throwable) {
-                    se.initCause((Throwable) value);
-                }
-            }
-
-            // if the cause could not be set, overwrite the stack trace
-            if (se.getCause() == null) {
-                se.setStackTrace(t.getStackTrace());
-            }
-
-            throw se;
-
-        } catch (Throwable t) {
-
-            // prevent variables to be pushed back in case of errors
-            isTopLevelCall = false;
-
-            final ScriptException se = new ScriptException(
-                "Failure running script " + scriptName + ": " + t.getMessage());
-            se.initCause(t);
-            throw se;
-
+            reader = new InputStreamReader(IOUtils.toInputStream(script));
+            return compile(reader);
         } finally {
+            IOUtils.closeQuietly(reader);
+        }
+    }
 
-            // if we are the top call (the Context is now null) we have to
-            // play back any properties from the scope back to the bindings
-            if (isTopLevelCall) {
-                getBoundProperties(scope, bindings);
+    public CompiledScript compile(Reader scriptReader) throws ScriptException {
+        final String scriptName = getScriptName(scriptReader);
+        CachedScript cachedScript = scriptCache.getScript(scriptName);
+        if (cachedScript != null) {
+            LOGGER.debug("Detected cached script for {}.", scriptName);
+            return cachedScript.getCompiledScript();
+        } else {
+            scriptReader = wrapReaderIfEspScript(scriptReader, scriptName);
+            try {
+                final Context rhinoContext = Context.enter();
+                rhinoContext.setOptimizationLevel(optimizationLevel());
+
+                if (!ScriptRuntime.hasTopCall(rhinoContext)) {
+                    // setup the context for use
+                    WrapFactory wrapFactory = ((RhinoJavaScriptEngineFactory) getFactory()).getWrapFactory();
+                    rhinoContext.setWrapFactory(wrapFactory);
+                }
+
+                final int lineNumber = 1;
+                final Object securityDomain = null;
+
+                final Script script = rhinoContext.compileReader(scriptReader, scriptName, lineNumber, securityDomain);
+                final SlingCompiledScript slingCompiledScript = new SlingCompiledScript(script, this);
+                cachedScript = new CachedScript() {
+                    @Override
+                    public String getScriptPath() {
+                        return scriptName;
+                    }
+
+                    @Override
+                    public CompiledScript getCompiledScript() {
+                        return slingCompiledScript;
+                    }
+                };
+                scriptCache.putScript(cachedScript);
+                LOGGER.debug("Added {} script to Script Cache.", scriptName);
+                return slingCompiledScript;
+            } catch (IOException e) {
+                final ScriptException se = new ScriptException("Failure running script " + scriptName + ": " + e.getMessage());
+                se.initCause(e);
+                throw se;
+            } finally {
+                Context.exit();
             }
+        }
+    }
 
-            // if properties have been replaced, reset them
-            resetBoundProperties(scope, replacedProperties);
-            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-            if (classLoader instanceof DynamicClassLoader) {
-                DynamicClassLoader dynamicClassLoader = (DynamicClassLoader) classLoader;
-                if (!dynamicClassLoader.isLive()) {
-                    /**
-                     * if the classloader on this thread is a dynamic class loader and it's dirty we should clear Rhino's class cache
-                     * to avoid classloader leaks
-                     */
-                    if (scope != null) {
-                        ClassCache classCache = ClassCache.get(scope);
-                        classCache.clearCaches();
-                        LOGGER.info("Detected dirty classloader on thread {}. Emptying Rhino's class cache.", Thread.currentThread()
-                                .getName());
+    public Object eval(Reader scriptReader, ScriptContext scriptContext) throws ScriptException {
+        String scriptName = getScriptName(scriptReader);
+        Reader reader = wrapReaderIfEspScript(scriptReader, scriptName);
+        if (!(scriptReader instanceof ScriptNameAware)) {
+            if (NO_SCRIPT_NAME.equals(scriptName)) {
+                String script = (String) scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).get(ScriptEngine.FILENAME);
+                if (script != null) {
+                    for (String extension : getFactory().getExtensions()) {
+                        if (script.endsWith(extension)) {
+                            scriptName = script;
+                            reader = new ScriptNameAwareReader(reader, scriptName);
+                            break;
+                        }
                     }
                 }
             }
-            Context.exit();
         }
+        return compile(reader).eval(scriptContext);
+    }
+
+    private Reader wrapReaderIfEspScript(Reader scriptReader, String scriptName) {
+        if (scriptName.endsWith(RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION)) {
+            scriptReader = new EspReader(scriptReader);
+        }
+        return scriptReader;
     }
 
     private Map<String, Object> setBoundProperties(Scriptable scope,
@@ -251,4 +212,154 @@
     private int optimizationLevel() {
         return ((RhinoJavaScriptEngineFactory)getFactory()).getOptimizationLevel();
     }
+
+    private class SlingCompiledScript extends CompiledScript {
+
+        private final Script script;
+        private final ScriptEngine engine;
+
+        SlingCompiledScript(Script script, ScriptEngine engine) {
+            this.script = script;
+            this.engine = engine;
+        }
+
+        @Override
+        public Object eval(ScriptContext scriptContext) throws ScriptException {
+            Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+            String scriptName = "NO_SCRIPT_NAME";
+            {
+                SlingScriptHelper helper = (SlingScriptHelper) bindings.get(SlingBindings.SLING);
+                if (helper != null) {
+                    scriptName = helper.getScript().getScriptResource().getPath();
+                }
+            }
+
+            // container for replaced properties
+            Map<String, Object> replacedProperties = null;
+            Scriptable scope = null;
+            boolean isTopLevelCall = false;
+
+            // create a rhino Context and execute the script
+            try {
+
+                final Context rhinoContext = Context.enter();
+                rhinoContext.setOptimizationLevel(optimizationLevel());
+
+                if (ScriptRuntime.hasTopCall(rhinoContext)) {
+                    // reuse the top scope if we are included
+                    scope = ScriptRuntime.getTopCallScope(rhinoContext);
+
+                } else {
+                    // create the request top scope, use the ImporterToplevel here
+                    // to support the importPackage and importClasses functions
+                    scope = new ImporterTopLevel();
+
+                    // Set the global scope to be our prototype
+                    scope.setPrototype(rootScope);
+
+                    // We want "scope" to be a new top-level scope, so set its
+                    // parent scope to null. This means that any variables created
+                    // by assignments will be properties of "scope".
+                    scope.setParentScope(null);
+
+                    // setup the context for use
+                    WrapFactory wrapFactory = ((RhinoJavaScriptEngineFactory) getFactory()).getWrapFactory();
+                    rhinoContext.setWrapFactory(wrapFactory);
+
+                    // this is the top level call
+                    isTopLevelCall = true;
+                }
+
+                // add initial properties to the scope
+                replacedProperties = setBoundProperties(scope, bindings);
+
+                Object result = script.exec(rhinoContext, scope);
+
+                if (result instanceof Wrapper) {
+                    result = ((Wrapper) result).unwrap();
+                }
+
+                return (result instanceof Undefined) ? null : result;
+
+            } catch (JavaScriptException t) {
+
+                // prevent variables to be pushed back in case of errors
+                isTopLevelCall = false;
+
+                final ScriptException se = new ScriptException(t.details(),
+                        t.sourceName(), t.lineNumber());
+
+                // log the script stack trace
+                ((Logger) bindings.get(SlingBindings.LOG)).error(t.getScriptStackTrace());
+
+                // set the exception cause
+                Object value = t.getValue();
+                if (value != null) {
+                    if (value instanceof Wrapper) {
+                        value = ((Wrapper) value).unwrap();
+                    }
+                    if (value instanceof Throwable) {
+                        se.initCause((Throwable) value);
+                    }
+                }
+
+                // if the cause could not be set, overwrite the stack trace
+                if (se.getCause() == null) {
+                    se.setStackTrace(t.getStackTrace());
+                }
+
+                throw se;
+
+            } catch (Throwable t) {
+
+                // prevent variables to be pushed back in case of errors
+                isTopLevelCall = false;
+
+                final ScriptException se = new ScriptException(
+                        "Failure running script " + scriptName + ": " + t.getMessage());
+                se.initCause(t);
+                throw se;
+
+            } finally {
+
+                // if we are the top call (the Context is now null) we have to
+                // play back any properties from the scope back to the bindings
+                if (isTopLevelCall) {
+                    getBoundProperties(scope, bindings);
+                }
+
+                // if properties have been replaced, reset them
+                resetBoundProperties(scope, replacedProperties);
+                ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+                if (classLoader instanceof DynamicClassLoader) {
+                    DynamicClassLoader dynamicClassLoader = (DynamicClassLoader) classLoader;
+                    if (!dynamicClassLoader.isLive()) {
+                        /**
+                         * if the class loader on this thread is a dynamic class loader and it's dirty we should clear Rhino's class cache
+                         * to avoid class loader leaks
+                         */
+                        if (scope != null) {
+                            ClassCache classCache = ClassCache.get(scope);
+                            classCache.clearCaches();
+                            LOGGER.info("Detected dirty class loader on thread {}. Emptying Rhino's class cache.", Thread.currentThread()
+                                    .getName());
+                        }
+                    }
+                }
+                Context.exit();
+            }
+        }
+
+        @Override
+        public ScriptEngine getEngine() {
+            return engine;
+        }
+    }
+
+    private String getScriptName(Reader scriptReader) {
+        if(scriptReader instanceof ScriptNameAware){
+            return ((ScriptNameAware) scriptReader).getScriptName();
+        }
+        return NO_SCRIPT_NAME;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java b/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java
index 53225b4..d2924b0 100644
--- a/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java
+++ b/src/main/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineFactory.java
@@ -22,6 +22,7 @@
 import java.util.HashSet;
 import java.util.Set;
 import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -34,6 +35,7 @@
 import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
 import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
+import org.apache.sling.scripting.api.ScriptCache;
 import org.apache.sling.scripting.javascript.RhinoHostObjectProvider;
 import org.apache.sling.scripting.javascript.SlingWrapper;
 import org.apache.sling.scripting.javascript.helper.SlingContextFactory;
@@ -61,39 +63,48 @@
 
 /**
  * The <code>RhinoJavaScriptEngineFactory</code> TODO
- *
  */
 @Component(
         metatype = true,
-        label="Apache Sling Rhino Javascript Engine Factory",
-        description="Javascript engine based on Rino")
-@Service(value=javax.script.ScriptEngineFactory.class)
-@Reference(name="HostObjectProvider", referenceInterface=RhinoHostObjectProvider.class,
-           cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE, policy=ReferencePolicy.DYNAMIC,
-           bind="addHostObjectProvider", unbind="removeHostObjectProvider")
+        label = "Apache Sling Rhino Javascript Engine Factory",
+        description = "Javascript engine based on Rhino"
+)
+@Service(ScriptEngineFactory.class)
+@Reference(
+        name = "HostObjectProvider",
+        referenceInterface = RhinoHostObjectProvider.class,
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+        policy = ReferencePolicy.DYNAMIC,
+        bind = "addHostObjectProvider",
+        unbind = "removeHostObjectProvider"
+)
 @Property(
         name = RhinoJavaScriptEngineFactory.OPTIMIZATION_CONFIG,
         label = "Rhino optimization level",
         intValue = RhinoJavaScriptEngineFactory.DEFAULT_OPTIMIZATION_LEVEL,
-        description = "The level of optimization for the bytecode generated by Rhino. Provide values between 0-9, 9 being the most aggressive level of optimization. A value of -1 will run scripts in interpreted mode")
-public class RhinoJavaScriptEngineFactory extends AbstractScriptEngineFactory
-        implements ScopeProvider {
+        description = "The level of optimization for the bytecode generated by Rhino. Provide values between 0-9, 9 being the most " +
+                "aggressive level of optimization. A value of -1 will run scripts in interpreted mode."
+)
+public class RhinoJavaScriptEngineFactory extends AbstractScriptEngineFactory implements ScopeProvider {
 
     public final static String OPTIMIZATION_CONFIG = "org.apache.sling.scripting.javascript.rhino.optLevel";
 
-    public final static int DEFAULT_OPTIMIZATION_LEVEL = - 1;
+    public final static int DEFAULT_OPTIMIZATION_LEVEL = 9;
 
     public final static String ECMA_SCRIPT_EXTENSION = "ecma";
 
     public final static String ESP_SCRIPT_EXTENSION = "esp";
 
     private static final Class<?>[] HOSTOBJECT_CLASSES = {
-        ScriptableResource.class, ScriptableNode.class,
-        ScriptableProperty.class, ScriptableItemMap.class,
-        ScriptablePrintWriter.class, ScriptableVersionHistory.class,
-        ScriptableVersion.class, ScriptableCalendar.class };
+            ScriptableResource.class, ScriptableNode.class,
+            ScriptableProperty.class, ScriptableItemMap.class,
+            ScriptablePrintWriter.class, ScriptableVersionHistory.class,
+            ScriptableVersion.class, ScriptableCalendar.class
+    };
 
-    /** default log */
+    /**
+     * default log
+     */
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     private int optimizationLevel;
@@ -107,10 +118,13 @@
     private final Set<RhinoHostObjectProvider> hostObjectProvider = new HashSet<RhinoHostObjectProvider>();
 
     @Reference
-    private DynamicClassLoaderManager dynamicClassLoaderManager;
+    private DynamicClassLoaderManager dynamicClassLoaderManager = null;
+
+    @Reference
+    private ScriptCache scriptCache = null;
 
     public ScriptEngine getScriptEngine() {
-        return new RhinoJavaScriptEngine(this, getRootScope());
+        return new RhinoJavaScriptEngine(this, getRootScope(), scriptCache);
     }
 
     public String getLanguageName() {
@@ -124,6 +138,7 @@
     /**
      * Get the optimization level that should be used when running JS scripts
      * with Rhino
+     *
      * @return an integer from 0-9 with 9 being the most aggressive optimization, or
      * -1 if interpreted mode is to be used
      */
@@ -154,20 +169,16 @@
             final Context rhinoContext = Context.enter();
             try {
 
-                Scriptable tmpScope = rhinoContext.initStandardObjects(
-                    new ImporterTopLevel(), false);
+                Scriptable tmpScope = rhinoContext.initStandardObjects(new ImporterTopLevel(), false);
 
                 // default classes
-                addHostObjects(tmpScope,
-                    (Class<? extends ScriptableObject>[]) HOSTOBJECT_CLASSES);
+                addHostObjects(tmpScope, (Class<? extends ScriptableObject>[]) HOSTOBJECT_CLASSES);
 
                 // provided classes
                 for (RhinoHostObjectProvider provider : hostObjectProvider) {
                     addHostObjects(tmpScope, provider.getHostObjectClasses());
-                    addImportedClasses(rhinoContext, tmpScope,
-                        provider.getImportedClasses());
-                    addImportedPackages(rhinoContext, tmpScope,
-                        provider.getImportedPackages());
+                    addImportedClasses(rhinoContext, tmpScope, provider.getImportedClasses());
+                    addImportedPackages(rhinoContext, tmpScope, provider.getImportedPackages());
                 }
 
                 // only assign the root scope when complete set up
@@ -201,9 +212,7 @@
     @Activate
     protected void activate(ComponentContext context) {
         Dictionary<?, ?> props = context.getProperties();
-        boolean debugging = getProperty(
-            "org.apache.sling.scripting.javascript.debug", props,
-            context.getBundleContext(), false);
+        boolean debugging = getProperty("org.apache.sling.scripting.javascript.debug", props, context.getBundleContext(), false);
 
         optimizationLevel = readOptimizationLevel(props);
 
@@ -214,14 +223,12 @@
         SlingContextFactory.setup(this);
 
         Context cx = Context.enter();
-        setEngineName(getEngineName() + " (" + cx.getImplementationVersion()
-            + ")");
+        setEngineName(getEngineName() + " (" + cx.getImplementationVersion() + ")");
         languageVersion = String.valueOf(cx.getLanguageVersion());
         Context.exit();
 
         setExtensions(ECMA_SCRIPT_EXTENSION, ESP_SCRIPT_EXTENSION);
-        setMimeTypes("text/javascript", "application/ecmascript",
-            "application/javascript");
+        setMimeTypes("text/javascript", "application/ecmascript", "application/javascript");
         setNames("javascript", ECMA_SCRIPT_EXTENSION, ESP_SCRIPT_EXTENSION);
 
         final ContextFactory contextFactory = ContextFactory.getGlobal();
@@ -230,14 +237,15 @@
         }
         // set the dynamic class loader as the application class loader
         final DynamicClassLoaderManager dclm = this.dynamicClassLoaderManager;
-        if ( dclm != null ) {
+        if (dclm != null) {
             contextFactory.initApplicationClassLoader(dynamicClassLoaderManager.getDynamicClassLoader());
         }
-        
+
         log.info("Activated with optimization level {}", optimizationLevel);
     }
 
     @Deactivate
+    @SuppressWarnings("unused")
     protected void deactivate(ComponentContext context) {
 
         // remove the root scope
@@ -251,6 +259,7 @@
         hostObjectProvider.clear();
     }
 
+    @SuppressWarnings("unused")
     protected void addHostObjectProvider(RhinoHostObjectProvider provider) {
         hostObjectProvider.add(provider);
 
@@ -259,6 +268,7 @@
         }
     }
 
+    @SuppressWarnings("unused")
     protected void removeHostObjectProvider(RhinoHostObjectProvider provider) {
         // remove the current root scope and have it recreated using the
         // new host object classes
@@ -269,8 +279,7 @@
 
     // ---------- internal
 
-    private void addHostObjects(Scriptable scope,
-            Class<? extends Scriptable>[] classes) {
+    private void addHostObjects(Scriptable scope, Class<? extends Scriptable>[] classes) {
         if (classes != null) {
             for (Class<? extends Scriptable> clazz : classes) {
                 try {
@@ -283,30 +292,23 @@
                         // SlingWrappers can map to several classes if needed
                         final SlingWrapper hostWrapper = (SlingWrapper) clazz.newInstance();
                         for (Class<?> c : hostWrapper.getWrappedClasses()) {
-                            getWrapFactory().registerWrapper(c,
-                                hostWrapper.getClassName());
+                            getWrapFactory().registerWrapper(c, hostWrapper.getClassName());
                         }
-
                     } else {
-
                         // but other Scriptable host objects need to be
                         // registered as well
                         final Scriptable host = clazz.newInstance();
-                        getWrapFactory().registerWrapper(
-                            host.getClass(), host.getClassName());
-
+                        getWrapFactory().registerWrapper(host.getClass(), host.getClassName());
                     }
-
                 } catch (Throwable t) {
-                    log.warn("addHostObjects: Cannot prepare host object "
-                        + clazz, t);
+                    log.warn("addHostObjects: Cannot prepare host object " + clazz, t);
                 }
             }
         }
     }
 
     private void addImportedClasses(Context cx, Scriptable scope,
-            Class<?>[] classes) {
+                                    Class<?>[] classes) {
         if (classes != null && classes.length > 0) {
             NativeJavaClass[] np = new NativeJavaClass[classes.length];
             for (int i = 0; i < classes.length; i++) {
@@ -317,7 +319,7 @@
     }
 
     private void addImportedPackages(Context cx, Scriptable scope,
-            String[] packages) {
+                                     String[] packages) {
         if (packages != null && packages.length > 0) {
             NativeJavaPackage[] np = new NativeJavaPackage[packages.length];
             for (int i = 0; i < packages.length; i++) {
@@ -328,7 +330,7 @@
     }
 
     private boolean getProperty(String name, Dictionary<?, ?> props,
-            BundleContext bundleContext, boolean defaultValue) {
+                                BundleContext bundleContext, boolean defaultValue) {
         Object value = props.get(name);
         if (value == null) {
             value = bundleContext.getProperty(name);
diff --git a/src/test/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineTest.java b/src/test/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineTest.java
index c51ddb0..7c68c5b 100644
--- a/src/test/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineTest.java
+++ b/src/test/java/org/apache/sling/scripting/javascript/internal/RhinoJavaScriptEngineTest.java
@@ -23,15 +23,19 @@
 import javax.script.ScriptException;
 import javax.script.SimpleBindings;
 
-import junit.framework.TestCase;
-
+import org.apache.sling.scripting.api.ScriptCache;
 import org.apache.sling.scripting.javascript.helper.SlingWrapFactory;
+import org.mockito.Mockito;
 import org.mozilla.javascript.Context;
 import org.mozilla.javascript.ImporterTopLevel;
 import org.mozilla.javascript.Scriptable;
 
+import junit.framework.TestCase;
+
 public class RhinoJavaScriptEngineTest extends TestCase {
 
+    private static ScriptCache scriptCache = Mockito.mock(ScriptCache.class);
+
     public void testPreserveScopeBetweenEvals() throws ScriptException {
         MockRhinoJavaScriptEngineFactory factory = new MockRhinoJavaScriptEngineFactory();
         ScriptEngine engine = factory.getScriptEngine();
@@ -55,7 +59,7 @@
         public ScriptEngine getScriptEngine() {
             final Context rhinoContext = Context.enter();
             Scriptable scope = rhinoContext.initStandardObjects(new ImporterTopLevel(), false);
-            return new RhinoJavaScriptEngine(this, scope);
+            return new RhinoJavaScriptEngine(this, scope, scriptCache);
         }
 
         @Override
diff --git a/src/test/java/org/apache/sling/scripting/javascript/internal/ScriptEngineHelper.java b/src/test/java/org/apache/sling/scripting/javascript/internal/ScriptEngineHelper.java
index b4c66cb..135112d 100644
--- a/src/test/java/org/apache/sling/scripting/javascript/internal/ScriptEngineHelper.java
+++ b/src/test/java/org/apache/sling/scripting/javascript/internal/ScriptEngineHelper.java
@@ -18,15 +18,11 @@
  */
 package org.apache.sling.scripting.javascript.internal;
 
-import java.io.File;
-import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Map;
-
 import javax.script.Bindings;
 import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
@@ -36,24 +32,19 @@
 
 import org.apache.sling.commons.testing.osgi.MockBundle;
 import org.apache.sling.commons.testing.osgi.MockComponentContext;
+import org.apache.sling.scripting.api.ScriptCache;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.Whitebox;
 import org.mozilla.javascript.Context;
 import org.mozilla.javascript.ScriptableObject;
 import org.mozilla.javascript.Wrapper;
-import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.BundleListener;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkListener;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceListener;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
 
 
 /** Helpers to run javascript code fragments in tests */
 public class ScriptEngineHelper {
     private static ScriptEngine engine;
+    private static ScriptCache scriptCache = Mockito.mock(ScriptCache.class);
 
     public static class Data extends HashMap<String, Object> {
     }
@@ -62,6 +53,7 @@
         if (engine == null) {
             synchronized (ScriptEngineHelper.class) {
                 RhinoJavaScriptEngineFactory f = new RhinoJavaScriptEngineFactory();
+                Whitebox.setInternalState(f, "scriptCache", scriptCache);
                 f.activate(new RhinoMockComponentContext());
                 engine = f.getScriptEngine();
             }
@@ -121,139 +113,15 @@
 
     private static class RhinoMockComponentContext extends MockComponentContext {
 
+        private BundleContext bundleContext = Mockito.mock(BundleContext.class);
+
         private RhinoMockComponentContext() {
             super(new MockBundle(0));
         }
 
         @Override
         public BundleContext getBundleContext() {
-            return new BundleContext() {
-
-                public void addBundleListener(BundleListener arg0) {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public void addFrameworkListener(
-                        FrameworkListener arg0) {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public void addServiceListener(ServiceListener arg0) {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public void addServiceListener(
-                        ServiceListener arg0, String arg1)
-                        throws InvalidSyntaxException {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public Filter createFilter(String arg0)
-                        throws InvalidSyntaxException {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public ServiceReference[] getAllServiceReferences(
-                        String arg0, String arg1)
-                        throws InvalidSyntaxException {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public Bundle getBundle() {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public Bundle getBundle(long arg0) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public Bundle[] getBundles() {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public File getDataFile(String arg0) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public String getProperty(String arg0) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public Object getService(ServiceReference arg0) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public ServiceReference getServiceReference(
-                        String arg0) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public ServiceReference[] getServiceReferences(
-                        String arg0, String arg1)
-                        throws InvalidSyntaxException {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public Bundle installBundle(String arg0)
-                        throws BundleException {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public Bundle installBundle(String arg0,
-                        InputStream arg1) throws BundleException {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public ServiceRegistration registerService(
-                        String[] arg0, Object arg1, Dictionary arg2) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public ServiceRegistration registerService(
-                        String arg0, Object arg1, Dictionary arg2) {
-                    // TODO Auto-generated method stub
-                    return null;
-                }
-
-                public void removeBundleListener(BundleListener arg0) {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public void removeFrameworkListener(
-                        FrameworkListener arg0) {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public void removeServiceListener(
-                        ServiceListener arg0) {
-                    // TODO Auto-generated method stub
-
-                }
-
-                public boolean ungetService(ServiceReference arg0) {
-                    // TODO Auto-generated method stub
-                    return false;
-                }
-            };
+            return bundleContext;
         }
     }
 }