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/20 10:35:54 UTC

[sling-org-apache-sling-scripting-jsp] branch master updated: SLING-9206 - Add support for executing precompiled JSP scripts

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8ef3313  SLING-9206 - Add support for executing precompiled JSP scripts
8ef3313 is described below

commit 8ef33131205f47cd34f2d5a206ac445102d631ca
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Fri Mar 20 11:35:46 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 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
    * documented that the o.a.s.scripting.jsp.jasper.runtime package should
    only be used by precompiled JSP scripts
---
 bnd.bnd                                            |   2 +
 pom.xml                                            |  10 +-
 .../scripting/jsp/JspScriptEngineFactory.java      | 303 ++++++++++-----------
 .../sling/scripting/jsp/JspServletConfig.java      |   6 +-
 .../sling/scripting/jsp/PrecompiledJSPRunner.java  | 114 ++++++++
 .../scripting/jsp/jasper/runtime/package-info.java |  25 ++
 .../jsp/jasper/servlet/JspServletWrapper.java      |   8 +-
 7 files changed, 305 insertions(+), 163 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
index 7e39640..568655a 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,3 +1,5 @@
 ScriptEngine-Name:${project.name}
 ScriptEngine-Version:${project.version}
+Import-Package:     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..e39cbc4
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/jsp/PrecompiledJSPRunner.java
@@ -0,0 +1,114 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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 java.util.concurrent.ConcurrentHashMap;
+
+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.service.component.annotations.Component;
+
+@Component(service = {PrecompiledJSPRunner.class})
+public class PrecompiledJSPRunner {
+
+    private final ConcurrentHashMap<HttpJspBase, Object> locks = new ConcurrentHashMap<>();
+
+    boolean callPrecompiledJSP(JspRuntimeContext.JspFactoryHandler jspFactoryHandler, JspServletConfig jspServletConfig,
+                               SlingBindings bindings) {
+        boolean found = false;
+        HttpJspBase jsp = null;
+        try {
+            jspFactoryHandler.incUsage();
+            BundledRenderUnit bundledRenderUnit = (BundledRenderUnit) bindings.get(BundledRenderUnit.VARIABLE);
+            if (bundledRenderUnit != null && bundledRenderUnit.getUnit() instanceof HttpJspBase) {
+                found = true;
+                jsp = (HttpJspBase) bundledRenderUnit.getUnit();
+                if (jsp.getServletConfig() == null) {
+                    Object lock = locks.computeIfAbsent(jsp, key -> new Object());
+                    synchronized (lock) {
+                        if (jsp.getServletConfig() == null) {
+                            PrecompiledServletConfig servletConfig = new PrecompiledServletConfig(jspServletConfig, bundledRenderUnit);
+                            AnnotationProcessor annotationProcessor =
+                                    (AnnotationProcessor) jspServletConfig.getServletContext()
+                                            .getAttribute(AnnotationProcessor.class.getName());
+                            if (annotationProcessor != null) {
+                                annotationProcessor.processAnnotations(jsp);
+                                annotationProcessor.postConstruct(jsp);
+                            }
+                            jsp.init(servletConfig);
+                        }
+                    }
+                }
+                jsp.service(bindings.getRequest(), bindings.getResponse());
+
+            }
+        } catch (IllegalAccessException | InvocationTargetException | NamingException e) {
+            throw new SlingException("Unable to process annotations for servlet " + jsp.getClass().getName() + ".", e);
+        } catch (NoClassDefFoundError ignored) {
+            // wave your hands like we don't care - we're missing support for precompiled JSPs
+        } catch (IOException e) {
+            throw new SlingIOException(e);
+        } catch (ServletException e) {
+            throw new SlingServletException(e);
+        } finally {
+            jspFactoryHandler.decUsage();
+            if (jsp != null) {
+                locks.remove(jsp);
+            }
+        }
+        return found;
+    }
+
+    private 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..62839ca
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/jsp/jasper/runtime/package-info.java
@@ -0,0 +1,25 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+/**
+ * This package should only be used by compiled JSP scripts when being executed on the platform.
+ */
+@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);
         }
     }