You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2020/03/16 08:39:05 UTC

[sling-org-apache-sling-scripting-jsp] branch issues/SLING-9206 created (now 3c73617)

This is an automated email from the ASF dual-hosted git repository.

radu pushed a change to branch issues/SLING-9206
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-jsp.git.


      at 3c73617  SLING-9206 - Add support for executing precompiled JSP scripts

This branch includes the following new commits:

     new 3c73617  SLING-9206 - Add support for executing precompiled JSP scripts

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-scripting-jsp] 01/01: SLING-9206 - Add support for executing precompiled JSP scripts

Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch issues/SLING-9206
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-jsp.git

commit 3c73617e546aec4358ac6953a4055010be62aaad
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Mon Mar 16 09:33:39 2020 +0100

    SLING-9206 - Add support for executing precompiled JSP scripts
    
    * added an optional import for org.apache.sling.scripting.bundle.tracker
    * added the PrecompiledJSPRunner which registers itself as a service if the
    previous API is available
    * if the PrecompiledJSPRunner is available, the JSPScriptEngine will try
    to execute a precompiled JSP, if one was available in the script context,
    otherwise it will try to compile the script matching the request
---
 bnd.bnd                                            |   4 +
 pom.xml                                            |  10 +-
 .../scripting/jsp/JspScriptEngineFactory.java      | 303 ++++++++++-----------
 .../sling/scripting/jsp/JspServletConfig.java      |   6 +-
 .../sling/scripting/jsp/PrecompiledJSPRunner.java  | 136 +++++++++
 .../scripting/jsp/jasper/runtime/package-info.java |  22 ++
 .../jsp/jasper/servlet/JspServletWrapper.java      |   8 +-
 7 files changed, 326 insertions(+), 163 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
index 7e39640..b48d373 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,3 +1,7 @@
 ScriptEngine-Name:${project.name}
 ScriptEngine-Version:${project.version}
+Import-Package:     org.apache.sling.commons.compiler.*;resolution:=optional, \\
+                    org.apache.sling.commons.classloader.*;resolution:=optional, \\
+                    org.apache.sling.scripting.bundle.tracker.*;resolution:=optional, \\
+                    *
 -conditionalpackage: org.apache.el.*
diff --git a/pom.xml b/pom.xml
index 3d2093a..cdb1eb2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.scripting.jsp</artifactId>
-    <version>2.4.3-SNAPSHOT</version>
+    <version>2.5.0-SNAPSHOT</version>
 
     <name>Apache Sling Scripting JSP</name>
     <description>Support for JSP scripting</description>
@@ -107,7 +107,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.scripting.api</artifactId>
-            <version>2.1.6</version>
+            <version>2.1.12</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -144,5 +144,11 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.bundle.tracker</artifactId>
+            <version>0.1.1-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/scripting/jsp/JspScriptEngineFactory.java b/src/main/java/org/apache/sling/scripting/jsp/JspScriptEngineFactory.java
index d1a09ec..69a8f44 100644
--- a/src/main/java/org/apache/sling/scripting/jsp/JspScriptEngineFactory.java
+++ b/src/main/java/org/apache/sling/scripting/jsp/JspScriptEngineFactory.java
@@ -16,15 +16,15 @@
  */
 package org.apache.sling.scripting.jsp;
 
-import static org.apache.sling.api.scripting.SlingBindings.SLING;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import javax.script.Bindings;
 import javax.script.ScriptContext;
@@ -33,9 +33,7 @@ import javax.script.ScriptException;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 
-import org.apache.sling.api.SlingException;
 import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.api.SlingIOException;
 import org.apache.sling.api.SlingServletException;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
@@ -43,8 +41,6 @@ import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.api.resource.observation.ResourceChangeListener;
 import org.apache.sling.api.scripting.SlingBindings;
-import org.apache.sling.api.scripting.SlingScript;
-import org.apache.sling.api.scripting.SlingScriptConstants;
 import org.apache.sling.api.scripting.SlingScriptHelper;
 import org.apache.sling.commons.classloader.ClassLoaderWriter;
 import org.apache.sling.commons.classloader.ClassLoaderWriterListener;
@@ -52,6 +48,7 @@ import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
 import org.apache.sling.commons.compiler.JavaCompiler;
 import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
 import org.apache.sling.scripting.api.AbstractSlingScriptEngine;
+import org.apache.sling.scripting.api.resource.ScriptingResourceResolverProvider;
 import org.apache.sling.scripting.jsp.jasper.compiler.JspRuntimeContext;
 import org.apache.sling.scripting.jsp.jasper.compiler.JspRuntimeContext.JspFactoryHandler;
 import org.apache.sling.scripting.jsp.jasper.runtime.AnnotationProcessor;
@@ -66,12 +63,15 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.sling.api.scripting.SlingBindings.SLING;
+
 /**
  * The JSP engine (a.k.a Jasper).
  *
@@ -171,29 +171,36 @@ public class JspScriptEngineFactory
     /** Default logger */
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
+    private static final Object BINDINGS_NOT_SWAPPED = new Object();
+
     private ServletContext slingServletContext;
 
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY)
+    private PrecompiledJSPRunner precompiledJSPRunner;
+
     @Reference
     private ClassLoaderWriter classLoaderWriter;
 
+    @Reference
+    private JavaCompiler javaCompiler;
+
+    @Reference
+    private ScriptingResourceResolverProvider scriptingResourceResolverProvider;
+
     private DynamicClassLoaderManager dynamicClassLoaderManager;
 
     private ClassLoader dynamicClassLoader;
 
-    @Reference
-    private JavaCompiler javaCompiler;
-
     /** The io provider for reading and writing. */
     private SlingIOProvider ioProvider;
 
     private SlingTldLocationsCache tldLocationsCache;
 
     private JspRuntimeContext jspRuntimeContext;
+    private ReentrantReadWriteLock jspRuntimeContextLock = new ReentrantReadWriteLock();
 
     private JspServletOptions options;
 
-    private JspServletContext jspServletContext;
-
     private JspServletConfig servletConfig;
 
     private boolean defaultIsSession;
@@ -246,105 +253,7 @@ public class JspScriptEngineFactory
         return super.getParameter(name);
     }
 
-    /**
-     * Call the error page
-     * @param bindings The bindings
-     * @param scriptHelper Script helper service
-     * @param context The script context
-     * @param scriptName The name of the script
-     */
-    private void callErrorPageJsp(final Bindings bindings,
-                                  final SlingScriptHelper scriptHelper,
-                                  final ScriptContext context,
-                                  final String scriptName) throws RuntimeException {
-    	final SlingBindings slingBindings = new SlingBindings();
-        slingBindings.putAll(bindings);
-
-        ResourceResolver resolver = (ResourceResolver) context.getAttribute(SlingScriptConstants.ATTR_SCRIPT_RESOURCE_RESOLVER,
-                SlingScriptConstants.SLING_SCOPE);
-        if ( resolver == null ) {
-            resolver = scriptHelper.getScript().getScriptResource().getResourceResolver();
-        }
-        final SlingIOProvider io = this.ioProvider;
-        final JspFactoryHandler jspfh = this.jspFactoryHandler;
-
-        // abort if JSP Support is shut down concurrently (SLING-2704)
-        if (io == null || jspfh == null) {
-            throw new RuntimeException("callJsp: JSP Script Engine seems to be shut down concurrently; not calling "+
-                    scriptHelper.getScript().getScriptResource().getPath());
-        }
-
-        final ResourceResolver oldResolver = io.setRequestResourceResolver(resolver);
-        jspfh.incUsage();
-		try {
-			final JspServletWrapper errorJsp = getJspWrapper(scriptName, slingBindings);
-			errorJsp.service(slingBindings);
-
-            // The error page could be inside an include.
-	        final SlingHttpServletRequest request = slingBindings.getRequest();
-            final Throwable t = (Throwable)request.getAttribute("javax.servlet.jsp.jspException");
-
-	        final Object newException = request
-                    .getAttribute("javax.servlet.error.exception");
-
-            // t==null means the attribute was not set.
-            if ((newException != null) && (newException == t)) {
-                request.removeAttribute("javax.servlet.error.exception");
-            }
-
-            // now clear the error code - to prevent double handling.
-            request.removeAttribute("javax.servlet.error.status_code");
-            request.removeAttribute("javax.servlet.error.request_uri");
-            request.removeAttribute("javax.servlet.error.status_code");
-            request.removeAttribute("javax.servlet.jsp.jspException");
-		} finally {
-            jspfh.decUsage();
-			io.resetRequestResourceResolver(oldResolver);
-		}
-     }
-
-    /**
-     * Call a JSP script
-     * @param bindings The bindings
-     * @param scriptHelper Script helper service
-     * @param context The script context
-     * @throws SlingServletException
-     * @throws SlingIOException
-     */
-    private void callJsp(final Bindings bindings,
-                         final SlingScriptHelper scriptHelper,
-                         final ScriptContext context) throws RuntimeException {
-
-        ResourceResolver resolver = (ResourceResolver) context.getAttribute(SlingScriptConstants.ATTR_SCRIPT_RESOURCE_RESOLVER,
-                SlingScriptConstants.SLING_SCOPE);
-        if ( resolver == null ) {
-            resolver = scriptHelper.getScript().getScriptResource().getResourceResolver();
-        }
-        final SlingIOProvider io = this.ioProvider;
-        final JspFactoryHandler jspfh = this.jspFactoryHandler;
-        // abort if JSP Support is shut down concurrently (SLING-2704)
-        if (io == null || jspfh == null) {
-            throw new RuntimeException("callJsp: JSP Script Engine seems to be shut down concurrently; not calling "+
-                    scriptHelper.getScript().getScriptResource().getPath());
-        }
-
-        final ResourceResolver oldResolver = io.setRequestResourceResolver(resolver);
-        jspfh.incUsage();
-        try {
-            final SlingBindings slingBindings = new SlingBindings();
-            slingBindings.putAll(bindings);
-
-            final JspServletWrapper jsp = getJspWrapper(scriptHelper, slingBindings);
-            // create a SlingBindings object
-            jsp.service(slingBindings);
-        } finally {
-            jspfh.decUsage();
-            io.resetRequestResourceResolver(oldResolver);
-        }
-    }
-
-    private JspServletWrapper getJspWrapper(final String scriptName, final SlingBindings bindings)
-    throws SlingException {
+    private JspServletWrapper getJspWrapper(final String scriptName) {
         JspRuntimeContext rctxt = this.getJspRuntimeContext();
 
     	JspServletWrapper wrapper = rctxt.getWrapper(scriptName);
@@ -372,13 +281,6 @@ public class JspScriptEngineFactory
         return wrapper;
     }
 
-    private JspServletWrapper getJspWrapper(final SlingScriptHelper scriptHelper, final SlingBindings bindings)
-    throws SlingException {
-        final SlingScript script = scriptHelper.getScript();
-        final String scriptName = script.getScriptResource().getPath();
-        return getJspWrapper(scriptName, bindings);
-    }
-
     // ---------- SCR integration ----------------------------------------------
 
     /**
@@ -407,7 +309,7 @@ public class JspScriptEngineFactory
             options = new JspServletOptions(slingServletContext, ioProvider,
                     properties, tldLocationsCache);
 
-            jspServletContext = new JspServletContext(ioProvider,
+            JspServletContext jspServletContext = new JspServletContext(ioProvider,
                 slingServletContext, tldLocationsCache);
 
             servletConfig = new JspServletConfig(jspServletContext, options.getProperties());
@@ -454,16 +356,14 @@ public class JspScriptEngineFactory
     private void checkJasperConfig() {
         boolean changed = false;
         InputStream is = null;
-        try {
+        try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
             is = this.classLoaderWriter.getInputStream(CONFIG_PATH);
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
             byte[] buffer = new byte[1024];
             int length = 0;
             while ( ( length = is.read(buffer)) != -1 ) {
                 baos.write(buffer, 0, length);
             }
-            baos.close();
-            final String oldKey = new String(baos.toByteArray(), "UTF-8");
+            final String oldKey = new String(baos.toByteArray(), StandardCharsets.UTF_8);
             changed = !oldKey.equals(this.servletConfig.getConfigKey());
             if ( changed ) {
                 logger.info("Removing all class files due to jsp configuration change");
@@ -480,20 +380,10 @@ public class JspScriptEngineFactory
             }
         }
         if ( changed ) {
-            OutputStream os = null;
-            try {
-                os = this.classLoaderWriter.getOutputStream(CONFIG_PATH);
-                os.write(this.servletConfig.getConfigKey().getBytes("UTF-8"));
-            } catch ( final IOException ignore ) {
+            try (OutputStream os = this.classLoaderWriter.getOutputStream(CONFIG_PATH)) {
+                os.write(this.servletConfig.getConfigKey().getBytes(StandardCharsets.UTF_8));
+            } catch (final IOException ignore) {
                 // ignore
-            } finally {
-                if ( os != null ) {
-                    try {
-                        os.close();
-                    } catch ( final IOException ignore ) {
-                        // ignore
-                    }
-                }
             }
             this.classLoaderWriter.delete("/org/apache/jsp");
         }
@@ -540,7 +430,7 @@ public class JspScriptEngineFactory
     /**
      * Bind the class load provider.
      *
-     * @param repositoryClassLoaderProvider the new provider
+     * @param rclp the new provider
      */
     @Reference(cardinality=ReferenceCardinality.MANDATORY, policy=ReferencePolicy.STATIC)
     protected void bindDynamicClassLoaderManager(final DynamicClassLoaderManager rclp) {
@@ -552,7 +442,7 @@ public class JspScriptEngineFactory
 
     /**
      * Unbind the class loader provider.
-     * @param repositoryClassLoaderProvider the old provider
+     * @param rclp the old provider
      */
     protected void unbindDynamicClassLoaderManager(final DynamicClassLoaderManager rclp) {
         if ( this.dynamicClassLoaderManager == rclp ) {
@@ -584,10 +474,97 @@ public class JspScriptEngineFactory
             super(JspScriptEngineFactory.this);
         }
 
+        /**
+         * Call a JSP script
+         * @param slingBindings The bindings
+         */
+        private void callJsp(final SlingBindings slingBindings) {
+            SlingScriptHelper scriptHelper = slingBindings.getSling();
+            if (scriptHelper == null) {
+                throw new IllegalStateException(String.format("The %s variable is missing from the bindings.", SLING));
+            }
+            ResourceResolver resolver = scriptingResourceResolverProvider.getRequestScopedResourceResolver();
+            if ( resolver == null ) {
+                resolver = scriptHelper.getScript().getScriptResource().getResourceResolver();
+            }
+            final SlingIOProvider io = ioProvider;
+            final JspFactoryHandler jspfh = jspFactoryHandler;
+            // abort if JSP Support is shut down concurrently (SLING-2704)
+            if (io == null || jspfh == null) {
+                throw new RuntimeException("callJsp: JSP Script Engine seems to be shut down concurrently; not calling "+
+                        scriptHelper.getScript().getScriptResource().getPath());
+            }
+
+            final ResourceResolver oldResolver = io.setRequestResourceResolver(resolver);
+            jspfh.incUsage();
+            try {
+                final JspServletWrapper jsp = getJspWrapper(scriptHelper.getScript().getScriptResource().getPath());
+                jsp.service(slingBindings);
+            } finally {
+                jspfh.decUsage();
+                io.resetRequestResourceResolver(oldResolver);
+            }
+        }
+
+        /**
+         * Call the error page
+         * @param slingBindings The bindings
+         * @param scriptName The name of the script
+         */
+        private void callErrorPageJsp(final SlingBindings slingBindings, final String scriptName) {
+            SlingScriptHelper scriptHelper = slingBindings.getSling();
+            if (scriptHelper == null) {
+                throw new IllegalStateException(String.format("The %s variable is missing from the bindings.", SLING));
+            }
+            ResourceResolver resolver = scriptingResourceResolverProvider.getRequestScopedResourceResolver();
+            if ( resolver == null ) {
+                resolver = scriptHelper.getScript().getScriptResource().getResourceResolver();
+            }
+            final SlingIOProvider io = ioProvider;
+            final JspFactoryHandler jspfh = jspFactoryHandler;
+
+            // abort if JSP Support is shut down concurrently (SLING-2704)
+            if (io == null || jspfh == null) {
+                throw new RuntimeException("callJsp: JSP Script Engine seems to be shut down concurrently; not calling "+
+                        scriptHelper.getScript().getScriptResource().getPath());
+            }
+
+            final ResourceResolver oldResolver = io.setRequestResourceResolver(resolver);
+            jspfh.incUsage();
+            try {
+                final JspServletWrapper errorJsp = getJspWrapper(scriptName);
+                errorJsp.service(slingBindings);
+
+                // The error page could be inside an include.
+                final SlingHttpServletRequest request = slingBindings.getRequest();
+                if (request != null) {
+                    final Throwable t = (Throwable) request.getAttribute("javax.servlet.jsp.jspException");
+
+                    final Object newException = request
+                            .getAttribute("javax.servlet.error.exception");
+
+                    // t==null means the attribute was not set.
+                    if ((newException != null) && (newException == t)) {
+                        request.removeAttribute("javax.servlet.error.exception");
+                    }
+
+                    // now clear the error code - to prevent double handling.
+                    request.removeAttribute("javax.servlet.error.status_code");
+                    request.removeAttribute("javax.servlet.error.request_uri");
+                    request.removeAttribute("javax.servlet.error.status_code");
+                    request.removeAttribute("javax.servlet.jsp.jspException");
+                }
+            } finally {
+                jspfh.decUsage();
+                io.resetRequestResourceResolver(oldResolver);
+            }
+        }
+
         @Override
-        public Object eval(final Reader script, final ScriptContext context)
-                throws ScriptException {
+        public Object eval(final Reader script, final ScriptContext context) throws ScriptException {
             Bindings props = context.getBindings(ScriptContext.ENGINE_SCOPE);
+            SlingBindings slingBindings = new SlingBindings();
+            slingBindings.putAll(props);
             SlingScriptHelper scriptHelper = (SlingScriptHelper) props.get(SLING);
             if (scriptHelper != null) {
 
@@ -596,8 +573,20 @@ public class JspScriptEngineFactory
                 ClassLoader old = Thread.currentThread().getContextClassLoader();
                 Thread.currentThread().setContextClassLoader(dynamicClassLoader);
 
+                SlingHttpServletRequest request = slingBindings.getRequest();
+                Object oldSlingBindings = BINDINGS_NOT_SWAPPED;
+                if (request != null) {
+                    oldSlingBindings = request.getAttribute(SlingBindings.class.getName());
+                    request.setAttribute(SlingBindings.class.getName(), slingBindings);
+                }
                 try {
-                    callJsp(props, scriptHelper, context);
+                    boolean contextHasPrecompiledJsp = false;
+                    if (precompiledJSPRunner != null) {
+                        contextHasPrecompiledJsp = precompiledJSPRunner.callPrecompiledJSP(jspFactoryHandler, servletConfig, slingBindings);
+                    }
+                    if (!contextHasPrecompiledJsp) {
+                        callJsp(slingBindings);
+                    }
                 } catch (final SlingServletException e) {
                     // ServletExceptions use getRootCause() instead of getCause(),
                     // so we have to extract the actual root cause and pass it as
@@ -618,9 +607,8 @@ public class JspScriptEngineFactory
                     throw new BetterScriptException(e.getMessage(), e);
                 } catch (final SlingPageException sje) {
                     try {
-                        callErrorPageJsp(props, scriptHelper, context, sje.getErrorPage());
-                    }
-                    catch (final Exception e) {
+                        callErrorPageJsp(slingBindings, sje.getErrorPage());
+                    } catch (final Exception e) {
 
                         throw new BetterScriptException(e.getMessage(), e);
                     }
@@ -634,7 +622,9 @@ public class JspScriptEngineFactory
                     // make sure the context loader is reset after setting up the
                     // JSP runtime context
                     Thread.currentThread().setContextClassLoader(old);
-
+                    if (request != null && oldSlingBindings != BINDINGS_NOT_SWAPPED) {
+                        request.setAttribute(SlingBindings.class.getName(), oldSlingBindings);
+                    }
                 }
             }
             return null;
@@ -654,16 +644,23 @@ public class JspScriptEngineFactory
     }
 
     private JspRuntimeContext getJspRuntimeContext() {
-        if ( this.jspRuntimeContext == null ) {
-            synchronized ( this ) {
-                if ( this.jspRuntimeContext == null ) {
-                    // Initialize the JSP Runtime Context
-                    this.jspRuntimeContext = new JspRuntimeContext(slingServletContext,
-                            options, ioProvider);
-                }
+        jspRuntimeContextLock.readLock().lock();
+        if (jspRuntimeContext == null) {
+            jspRuntimeContextLock.readLock().unlock();
+            jspRuntimeContextLock.writeLock().lock();
+            try {
+                jspRuntimeContext = new JspRuntimeContext(slingServletContext,
+                        options, ioProvider);
+                jspRuntimeContextLock.readLock().lock();
+            } finally {
+                jspRuntimeContextLock.writeLock().unlock();
             }
         }
-        return this.jspRuntimeContext;
+        try {
+            return jspRuntimeContext;
+        } finally {
+            jspRuntimeContextLock.readLock().unlock();
+        }
     }
 
     /**
@@ -717,7 +714,7 @@ public class JspScriptEngineFactory
         };
         t.start();
     }
-    
+
 	@Override
 	public void onClassLoaderClear(String context) {
         final JspRuntimeContext rctxt = this.jspRuntimeContext;
diff --git a/src/main/java/org/apache/sling/scripting/jsp/JspServletConfig.java b/src/main/java/org/apache/sling/scripting/jsp/JspServletConfig.java
index 70d5cf8..ab616b7 100644
--- a/src/main/java/org/apache/sling/scripting/jsp/JspServletConfig.java
+++ b/src/main/java/org/apache/sling/scripting/jsp/JspServletConfig.java
@@ -76,4 +76,8 @@ class JspServletConfig implements ServletConfig {
         }
         return sb.toString();
     }
-}
\ No newline at end of file
+
+    public Map<String, String> getProperties() {
+        return Collections.unmodifiableMap(properties);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/jsp/PrecompiledJSPRunner.java b/src/main/java/org/apache/sling/scripting/jsp/PrecompiledJSPRunner.java
new file mode 100644
index 0000000..5f42177
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/jsp/PrecompiledJSPRunner.java
@@ -0,0 +1,136 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.scripting.jsp;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+
+import javax.naming.NamingException;
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingException;
+import org.apache.sling.api.SlingIOException;
+import org.apache.sling.api.SlingServletException;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.commons.compiler.source.JavaEscapeHelper;
+import org.apache.sling.scripting.bundle.tracker.BundledRenderUnit;
+import org.apache.sling.scripting.jsp.jasper.compiler.JspRuntimeContext;
+import org.apache.sling.scripting.jsp.jasper.runtime.AnnotationProcessor;
+import org.apache.sling.scripting.jsp.jasper.runtime.HttpJspBase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        immediate = true,
+        service = {}
+        /*
+         * this component will register itself as a service only if the org.apache.sling.scripting.bundle.tracker API is present
+         */
+        )
+public class PrecompiledJSPRunner {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PrecompiledJSPRunner.class);
+
+    private final ServiceRegistration<?> serviceRegistration;
+
+    @Activate
+    public PrecompiledJSPRunner(BundleContext bundleContext) {
+        serviceRegistration = register(bundleContext);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        if (serviceRegistration != null) {
+            serviceRegistration.unregister();
+        }
+    }
+
+    boolean callPrecompiledJSP(JspRuntimeContext.JspFactoryHandler jspFactoryHandler, JspServletConfig jspServletConfig,
+                               SlingBindings bindings) {
+        boolean found = false;
+        BundledRenderUnit bundledRenderUnit = (BundledRenderUnit) bindings.get(BundledRenderUnit.VARIABLE);
+        if (bundledRenderUnit != null && bundledRenderUnit.getUnit() instanceof HttpJspBase) {
+            found = true;
+            HttpJspBase jsp = (HttpJspBase) bundledRenderUnit.getUnit();
+            PrecompiledServletConfig servletConfig = new PrecompiledServletConfig(jspServletConfig, bundledRenderUnit);
+            try {
+                jspFactoryHandler.incUsage();
+                AnnotationProcessor annotationProcessor =
+                        (AnnotationProcessor) jspServletConfig.getServletContext().getAttribute(AnnotationProcessor.class.getName());
+                if (annotationProcessor != null) {
+                    annotationProcessor.processAnnotations(jsp);
+                    annotationProcessor.postConstruct(jsp);
+                }
+                if (jsp.getServletConfig() == null) {
+                    jsp.init(servletConfig);
+                }
+                jsp.service(bindings.getRequest(), bindings.getResponse());
+            } catch (IOException e) {
+                throw new SlingIOException(e);
+            } catch (ServletException e) {
+                throw new SlingServletException(e);
+            } catch (IllegalAccessException | InvocationTargetException | NamingException e) {
+                throw new SlingException("Unable to process annotations for servlet " + servletConfig.getServletName() + ".", e);
+            } finally {
+                jspFactoryHandler.decUsage();
+            }
+        }
+        return found;
+    }
+
+    private ServiceRegistration<?> register(BundleContext bundleContext) {
+        try {
+            PrecompiledJSPRunner.class.getClassLoader().loadClass("org.apache.sling.scripting.bundle.tracker.BundledRenderUnit");
+            return bundleContext.registerService(PrecompiledJSPRunner.class, this, null);
+        } catch (Exception e) {
+            LOGGER.info("No support for precompiled scripts.");
+        }
+        return null;
+    }
+
+    public static class PrecompiledServletConfig extends JspServletConfig {
+
+        private final BundledRenderUnit bundledRenderUnit;
+        private String servletName;
+
+        PrecompiledServletConfig(JspServletConfig jspServletConfig, BundledRenderUnit bundledRenderUnit) {
+            super(jspServletConfig.getServletContext(), new HashMap<>(jspServletConfig.getProperties()));
+            this.bundledRenderUnit = bundledRenderUnit;
+        }
+
+        @Override
+        public String getServletName() {
+            if (servletName == null && bundledRenderUnit.getUnit() != null) {
+                Bundle bundle = bundledRenderUnit.getBundle();
+                Object jsp = bundledRenderUnit.getUnit();
+                String originalName =
+                        JavaEscapeHelper.unescapeAll(jsp.getClass().getPackage().getName()) + "/" + JavaEscapeHelper.unescapeAll(jsp.getClass().getSimpleName());
+                servletName = bundle.getSymbolicName() + ": " + originalName;
+            }
+            return servletName;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/jsp/jasper/runtime/package-info.java b/src/main/java/org/apache/sling/scripting/jsp/jasper/runtime/package-info.java
new file mode 100644
index 0000000..b263791
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/jsp/jasper/runtime/package-info.java
@@ -0,0 +1,22 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+@Version("2.5.0")
+package org.apache.sling.scripting.jsp.jasper.runtime;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/jsp/jasper/servlet/JspServletWrapper.java b/src/main/java/org/apache/sling/scripting/jsp/jasper/servlet/JspServletWrapper.java
index d4eb1a2..6845d8b 100644
--- a/src/main/java/org/apache/sling/scripting/jsp/jasper/servlet/JspServletWrapper.java
+++ b/src/main/java/org/apache/sling/scripting/jsp/jasper/servlet/JspServletWrapper.java
@@ -41,7 +41,6 @@ import javax.servlet.jsp.tagext.TagInfo;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.sling.api.SlingException;
-import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingIOException;
 import org.apache.sling.api.SlingServletException;
 import org.apache.sling.api.scripting.ScriptEvaluationException;
@@ -442,11 +441,8 @@ public class JspServletWrapper {
      *             request parameter has an illegal value.
      */
     public void service(final SlingBindings bindings) {
-        final SlingHttpServletRequest request = bindings.getRequest();
-        final Object oldValue = request.getAttribute(SlingBindings.class.getName());
         try {
-            request.setAttribute(SlingBindings.class.getName(), bindings);
-            service(request, bindings.getResponse());
+            service(bindings.getRequest(), bindings.getResponse());
         } catch (SlingException se) {
             // rethrow as is
             throw se;
@@ -454,8 +450,6 @@ public class JspServletWrapper {
             throw new SlingIOException(ioe);
         } catch (ServletException se) {
             throw new SlingServletException(se);
-        } finally {
-            request.setAttribute(SlingBindings.class.getName(), oldValue);
         }
     }