| /* |
| * 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 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.PrintWriter; |
| import java.io.Reader; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.script.Bindings; |
| import javax.script.ScriptContext; |
| import javax.script.ScriptEngine; |
| import javax.script.ScriptException; |
| import javax.servlet.Servlet; |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| 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; |
| 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.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.jsp.jasper.compiler.JspRuntimeContext; |
| import org.apache.sling.scripting.jsp.jasper.compiler.JspRuntimeContext.JspFactoryHandler; |
| import org.apache.sling.scripting.jsp.jasper.runtime.AnnotationProcessor; |
| import org.apache.sling.scripting.jsp.jasper.runtime.JspApplicationContextImpl; |
| import org.apache.sling.scripting.jsp.jasper.servlet.JspServletWrapper; |
| import org.apache.sling.scripting.jsp.util.TagUtil; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Deactivate; |
| import org.osgi.service.component.annotations.Reference; |
| 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; |
| |
| /** |
| * The JSP engine (a.k.a Jasper). |
| * |
| */ |
| @Component(service = {javax.script.ScriptEngineFactory.class,ResourceChangeListener.class,Servlet.class}, |
| property = { |
| Constants.SERVICE_VENDOR + "=The Apache Software Foundation", |
| Constants.SERVICE_DESCRIPTION + "=JSP Script Handler", |
| ResourceChangeListener.PATHS + "=glob:/**/*.jsp", |
| ResourceChangeListener.PATHS + "=glob:/**/*.jspf", |
| ResourceChangeListener.PATHS + "=glob:/**/*.jspx", |
| ResourceChangeListener.PATHS + "=glob:/**/*.tld", |
| ResourceChangeListener.PATHS + "=glob:/**/*.tag", |
| "felix.webconsole.label=slingjsp", |
| "felix.webconsole.title=JSP", |
| "felix.webconsole.category=Sling" |
| }) |
| @Designate(ocd = JspScriptEngineFactory.Config.class) |
| public class JspScriptEngineFactory |
| extends AbstractScriptEngineFactory |
| implements Servlet,ResourceChangeListener,ExternalResourceChangeListener { |
| |
| @ObjectClassDefinition(name = "Apache Sling JSP Script Handler", |
| description = "The JSP Script Handler supports development of JSP " + |
| "scripts to render response content on behalf of ScriptComponents. Internally " + |
| "Jasper 6.0.14 JSP Engine is used together with the Eclipse Java Compiler to " + |
| "compile generated Java code into Java class files. Some settings of Jasper " + |
| "may be configured as shown below. Note that JSP scripts are expected in the " + |
| "JCR repository and generated Java source and class files will be written to " + |
| "the JCR repository below the configured Compilation Location.") |
| public @interface Config { |
| |
| @AttributeDefinition(name = "Target Version", |
| description = "The taret JVM version for the compiled classes. If " + |
| "left empty, the default version, 1.6., is used. If the value \"auto\" is used, the " + |
| "current vm version will be used.") |
| String jasper_compilerTargetVM() default JspServletOptions.AUTOMATIC_VERSION; |
| |
| @AttributeDefinition(name = "Source Version", |
| description = "The JVM version for the java/JSP source. If " + |
| "left empty, the default version, 1.6., is used. If the value \"auto\" is used, the " + |
| "current vm version will be used.") |
| String jasper_compilerSourceVM() default JspServletOptions.AUTOMATIC_VERSION; |
| |
| @AttributeDefinition(name = "Generate Debug Info", |
| description = "Should the class file be compiled with " + |
| "debugging information? true or false, default true.") |
| boolean jasper_classdebuginfo() default true; |
| |
| @AttributeDefinition(name = "Tag Pooling", |
| description = "Determines whether tag handler pooling is " + |
| "enabled. true or false, default true.") |
| boolean jasper_enablePooling() default true; |
| |
| @AttributeDefinition(name = "Plugin Class-ID", |
| description = "The class-id value to be sent to Internet " + |
| "Explorer when using <jsp:plugin> tags. Default " + |
| "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93.") |
| String jasper_ieClassId() default "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"; |
| |
| @AttributeDefinition(name = "Char Array Strings", |
| description = "Should text strings be generated as " + |
| "char arrays, to improve performance in some cases? Default false.") |
| boolean jasper_genStringAsCharArray() default false; |
| |
| @AttributeDefinition(name = "Keep Generated Java", |
| description = "Should we keep the generated Java source " + |
| "code for each page instead of deleting it? true or false, default true.") |
| boolean jasper_keepgenerated() default true; |
| |
| @AttributeDefinition(name = "Mapped Content", |
| description = "Should we generate static content with one " + |
| "print statement per input line, to ease debugging? true or false, default true.") |
| boolean jasper_mappedfile() default true; |
| |
| @AttributeDefinition(name = "Trim Spaces", |
| description = "Should white spaces in template text between " + |
| "actions or directives be trimmed ?, default false.") |
| boolean jasper_trimSpaces() default false; |
| |
| @AttributeDefinition(name = "Display Source Fragments", |
| description = "Should we include a source fragment " + |
| "in exception messages, which could be displayed to the developer") |
| boolean jasper_displaySourceFragments() default false; |
| |
| @AttributeDefinition(name = "Default Session Value", |
| description = "Should a session be created by default for every " + |
| "JSP page? Warning - this behavior may produce unintended results and changing " + |
| "it will not impact previously-compiled pages.") |
| boolean default_is_session() default true; |
| } |
| /** |
| default.is.session.name = Default Session Value |
| default.is.session.description = Should a session be created by default for every \ |
| JSP page? Warning - this behavior may produce unintended results and changing \ |
| it will not impact previously-compiled pages. */ |
| /** Default logger */ |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| private ServletContext slingServletContext; |
| |
| @Reference |
| private ClassLoaderWriter classLoaderWriter; |
| |
| @Reference |
| 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 JspServletOptions options; |
| |
| private JspServletContext jspServletContext; |
| |
| private JspServletConfig servletConfig; |
| |
| private boolean defaultIsSession; |
| |
| /** The handler for the jsp factories. */ |
| private JspFactoryHandler jspFactoryHandler; |
| |
| public static final String[] SCRIPT_TYPE = { "jsp", "jspf", "jspx" }; |
| |
| public static final String[] NAMES = { "jsp", "JSP" }; |
| |
| public JspScriptEngineFactory() { |
| setExtensions(SCRIPT_TYPE); |
| setNames(NAMES); |
| } |
| |
| /** |
| * @see javax.script.ScriptEngineFactory#getScriptEngine() |
| */ |
| @Override |
| public ScriptEngine getScriptEngine() { |
| return new JspScriptEngine(); |
| } |
| |
| /** |
| * @see javax.script.ScriptEngineFactory#getLanguageName() |
| */ |
| @Override |
| public String getLanguageName() { |
| return "Java Server Pages"; |
| } |
| |
| /** |
| * @see javax.script.ScriptEngineFactory#getLanguageVersion() |
| */ |
| @Override |
| public String getLanguageVersion() { |
| return "2.1"; |
| } |
| |
| /** |
| * @see javax.script.ScriptEngineFactory#getParameter(String) |
| */ |
| @Override |
| public Object getParameter(final String name) { |
| if ("THREADING".equals(name)) { |
| return "STATELESS"; |
| } |
| |
| 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) { |
| 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) { |
| logger.warn("callJsp: JSP Script Engine seems to be shut down concurrently; not calling {}", |
| scriptHelper.getScript().getScriptResource().getPath()); |
| return; |
| } |
| |
| 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) { |
| |
| 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) { |
| logger.warn("callJsp: JSP Script Engine seems to be shut down concurrently; not calling {}", |
| scriptHelper.getScript().getScriptResource().getPath()); |
| return; |
| } |
| |
| 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 { |
| JspRuntimeContext rctxt = this.getJspRuntimeContext(); |
| |
| JspServletWrapper wrapper = rctxt.getWrapper(scriptName); |
| if (wrapper != null) { |
| if ( wrapper.isValid() ) { |
| return wrapper; |
| } |
| synchronized ( this ) { |
| rctxt = this.getJspRuntimeContext(); |
| wrapper = rctxt.getWrapper(scriptName); |
| if ( wrapper != null ) { |
| if ( wrapper.isValid() ) { |
| return wrapper; |
| } |
| this.renewJspRuntimeContext(); |
| rctxt = this.getJspRuntimeContext(); |
| } |
| } |
| } |
| |
| wrapper = new JspServletWrapper(servletConfig, options, |
| scriptName, false, rctxt, defaultIsSession); |
| wrapper = rctxt.addWrapper(scriptName, wrapper); |
| |
| 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 ---------------------------------------------- |
| |
| /** |
| * Activate this component |
| */ |
| @Activate |
| protected void activate(final BundleContext bundleContext, |
| final Config config, |
| final Map<String, Object> properties) { |
| this.defaultIsSession = config.default_is_session(); |
| |
| // set the current class loader as the thread context loader for |
| // the setup of the JspRuntimeContext |
| final ClassLoader old = Thread.currentThread().getContextClassLoader(); |
| Thread.currentThread().setContextClassLoader(this.dynamicClassLoader); |
| |
| try { |
| this.jspFactoryHandler = JspRuntimeContext.initFactoryHandler(); |
| |
| this.tldLocationsCache = new SlingTldLocationsCache(bundleContext); |
| |
| // prepare some classes |
| ioProvider = new SlingIOProvider(this.classLoaderWriter, this.javaCompiler); |
| |
| // return options which use the jspClassLoader |
| options = new JspServletOptions(slingServletContext, ioProvider, |
| properties, tldLocationsCache); |
| |
| jspServletContext = new JspServletContext(ioProvider, |
| slingServletContext, tldLocationsCache); |
| |
| servletConfig = new JspServletConfig(jspServletContext, options.getProperties()); |
| |
| } finally { |
| // make sure the context loader is reset after setting up the |
| // JSP runtime context |
| Thread.currentThread().setContextClassLoader(old); |
| } |
| |
| // check for changes in jasper config |
| this.checkJasperConfig(); |
| |
| logger.info("Activating Apache Sling Script Engine for JSP with options {}", options.getProperties()); |
| logger.debug("IMPORTANT: Do not modify the generated servlet classes directly"); |
| } |
| |
| /** |
| * Activate this component |
| */ |
| @Deactivate |
| protected void deactivate(final BundleContext bundleContext) { |
| logger.info("Deactivating Apache Sling Script Engine for JSP"); |
| |
| if ( this.tldLocationsCache != null ) { |
| this.tldLocationsCache.deactivate(bundleContext); |
| this.tldLocationsCache = null; |
| } |
| if (jspRuntimeContext != null) { |
| this.destroyJspRuntimeContext(this.jspRuntimeContext); |
| jspRuntimeContext = null; |
| } |
| |
| ioProvider = null; |
| this.jspFactoryHandler.destroy(); |
| this.jspFactoryHandler = null; |
| } |
| |
| private static final String CONFIG_PATH = "/jsp.config"; |
| |
| /** |
| * Check if the jasper configuration changed. |
| */ |
| private void checkJasperConfig() { |
| boolean changed = false; |
| InputStream is = null; |
| try { |
| 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"); |
| changed = !oldKey.equals(this.servletConfig.getConfigKey()); |
| if ( changed ) { |
| logger.info("Removing all class files due to jsp configuration change"); |
| } |
| } catch ( final IOException notFound ) { |
| changed = true; |
| } finally { |
| if ( is != null ) { |
| try { |
| is.close(); |
| } catch ( final IOException ignore) { |
| // ignore |
| } |
| } |
| } |
| if ( changed ) { |
| OutputStream os = null; |
| try { |
| os = this.classLoaderWriter.getOutputStream(CONFIG_PATH); |
| os.write(this.servletConfig.getConfigKey().getBytes("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"); |
| } |
| } |
| |
| @Reference(target="(name=org.apache.sling)") |
| protected void bindSlingServletContext(final ServletContext context) { |
| this.slingServletContext = context; |
| } |
| |
| /** |
| * Unbinds the Sling ServletContext and removes any known servlet context |
| * attributes preventing the bundles's class loader from being collected. |
| * |
| * @param slingServletContext The <code>ServletContext</code> to be unbound |
| */ |
| protected void unbindSlingServletContext( |
| final ServletContext slingServletContext) { |
| |
| // remove JspApplicationContextImpl from the servlet context, |
| // otherwise a ClassCastException may be caused after this component |
| // is recreated because the class loader of the |
| // JspApplicationContextImpl class object is different from the one |
| // stored in the servlet context same for the AnnotationProcessor |
| // (which generally does not exist here) |
| try { |
| if (slingServletContext != null) { |
| slingServletContext.removeAttribute(JspApplicationContextImpl.class.getName()); |
| slingServletContext.removeAttribute(AnnotationProcessor.class.getName()); |
| } |
| } catch (NullPointerException npe) { |
| // SLING-530, might be thrown on system shutdown in a servlet |
| // container when using the Equinox servlet container bridge |
| logger.debug( |
| "unbindSlingServletContext: ServletContext might already be unavailable", |
| npe); |
| } |
| |
| if (this.slingServletContext == slingServletContext) { |
| this.slingServletContext = null; |
| } |
| } |
| |
| /** |
| * Bind the class load provider. |
| * |
| * @param repositoryClassLoaderProvider the new provider |
| */ |
| protected void bindDynamicClassLoaderManager(final DynamicClassLoaderManager rclp) { |
| if ( this.dynamicClassLoader != null ) { |
| this.ungetClassLoader(); |
| } |
| this.getClassLoader(rclp); |
| } |
| |
| /** |
| * Unbind the class loader provider. |
| * @param repositoryClassLoaderProvider the old provider |
| */ |
| protected void unbindDynamicClassLoaderManager(final DynamicClassLoaderManager rclp) { |
| if ( this.dynamicClassLoaderManager == rclp ) { |
| this.ungetClassLoader(); |
| } |
| } |
| |
| /** |
| * Get the class loader |
| */ |
| private void getClassLoader(final DynamicClassLoaderManager rclp) { |
| this.dynamicClassLoaderManager = rclp; |
| this.dynamicClassLoader = rclp.getDynamicClassLoader(); |
| } |
| |
| /** |
| * Unget the class loader |
| */ |
| private void ungetClassLoader() { |
| this.dynamicClassLoader = null; |
| this.dynamicClassLoaderManager = null; |
| } |
| |
| // ---------- Internal ----------------------------------------------------- |
| |
| private class JspScriptEngine extends AbstractSlingScriptEngine { |
| |
| JspScriptEngine() { |
| super(JspScriptEngineFactory.this); |
| } |
| |
| @Override |
| public Object eval(final Reader script, final ScriptContext context) |
| throws ScriptException { |
| Bindings props = context.getBindings(ScriptContext.ENGINE_SCOPE); |
| SlingScriptHelper scriptHelper = (SlingScriptHelper) props.get(SLING); |
| if (scriptHelper != null) { |
| |
| // set the current class loader as the thread context loader for |
| // the compilation and execution of the JSP script |
| ClassLoader old = Thread.currentThread().getContextClassLoader(); |
| Thread.currentThread().setContextClassLoader(dynamicClassLoader); |
| |
| try { |
| callJsp(props, scriptHelper, context); |
| } catch (final SlingServletException e) { |
| // ServletExceptions use getRootCause() instead of getCause(), |
| // so we have to extract the actual root cause and pass it as |
| // cause in our new ScriptException |
| if (e.getCause() != null) { |
| // SlingServletException always wraps ServletExceptions |
| Throwable rootCause = TagUtil.getRootCause((ServletException) e.getCause()); |
| // the ScriptException unfortunately does not accept a Throwable as cause, |
| // but only a Exception, so we have to wrap it with a dummy Exception in Throwable cases |
| if (rootCause instanceof Exception) { |
| throw new BetterScriptException(rootCause.toString(), (Exception) rootCause); |
| } |
| throw new BetterScriptException(rootCause.toString(), |
| new Exception("Wrapping Throwable: " + rootCause.toString(), rootCause)); |
| } |
| |
| // fallback to standard behaviour |
| throw new BetterScriptException(e.getMessage(), e); |
| } catch (final SlingPageException sje) { |
| callErrorPageJsp(props, scriptHelper, context, sje.getErrorPage()); |
| |
| } catch (final Exception e) { |
| |
| throw new BetterScriptException(e.getMessage(), e); |
| |
| } finally { |
| |
| // make sure the context loader is reset after setting up the |
| // JSP runtime context |
| Thread.currentThread().setContextClassLoader(old); |
| |
| } |
| } |
| return null; |
| } |
| } |
| |
| private void destroyJspRuntimeContext(final JspRuntimeContext jrc) { |
| if (jrc != null) { |
| try { |
| jrc.destroy(); |
| } catch (final NullPointerException npe) { |
| // SLING-530, might be thrown on system shutdown in a servlet |
| // container when using the Equinox servlet container bridge |
| logger.debug("deactivate: ServletContext might already be unavailable", npe); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| return this.jspRuntimeContext; |
| } |
| |
| /** |
| * Fixes {@link ScriptException} that overwrites the |
| * {@link ScriptException#getMessage()} method to display its own |
| * <code>message</code> instead of the <code>detailMessage</code> |
| * defined in {@link Throwable}. Unfortunately using the constructor |
| * {@link ScriptException#ScriptException(Exception)} does not set the |
| * <code>message</code> member of {@link ScriptException}, which leads to |
| * a message of <code>"null"</code>, effectively supressing the detailed |
| * information of the cause. This class provides a way to do that explicitly |
| * with a new constructor accepting both a message and a causing exception. |
| * |
| */ |
| private static class BetterScriptException extends ScriptException { |
| |
| private static final long serialVersionUID = -6490165487977283019L; |
| |
| public BetterScriptException(final String message, final Exception cause) { |
| super(message); |
| this.initCause(cause); |
| } |
| |
| } |
| |
| @Override |
| public void onChange(final List<ResourceChange> changes) { |
| for(final ResourceChange change : changes){ |
| final JspRuntimeContext rctxt = this.jspRuntimeContext; |
| if ( rctxt != null && rctxt.handleModification(change.getPath(), change.getType() == ChangeType.REMOVED) ) { |
| renewJspRuntimeContext(); |
| } |
| } |
| } |
| |
| /** |
| * Renew the jsp runtime context. |
| * A new context is created, the old context is destroyed in the background |
| */ |
| private void renewJspRuntimeContext() { |
| final JspRuntimeContext jrc; |
| synchronized ( this ) { |
| jrc = this.jspRuntimeContext; |
| this.jspRuntimeContext = null; |
| } |
| final Thread t = new Thread() { |
| @Override |
| public void run() { |
| destroyJspRuntimeContext(jrc); |
| } |
| }; |
| t.start(); |
| } |
| |
| // |
| // Web Console Plugin |
| // |
| private ServletConfig config; |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.Servlet#destroy() |
| */ |
| @Override |
| public void destroy() { |
| this.config = null; |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.Servlet#getServletConfig() |
| */ |
| @Override |
| public ServletConfig getServletConfig() { |
| return this.config; |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.Servlet#getServletInfo() |
| */ |
| @Override |
| public String getServletInfo() { |
| return ""; |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) |
| */ |
| @Override |
| public void init(final ServletConfig config) throws ServletException { |
| this.config = config; |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) |
| */ |
| @Override |
| public void service(final ServletRequest request, final ServletResponse response) |
| throws ServletException, IOException { |
| if ( request instanceof HttpServletRequest ) { |
| final HttpServletRequest req = (HttpServletRequest) request; |
| final HttpServletResponse res = (HttpServletResponse) response; |
| |
| final String path = req.getContextPath() + req.getServletPath() + req.getPathInfo(); |
| |
| if ( req.getMethod().equals("POST") ) { |
| final JspRuntimeContext rctxt = this.jspRuntimeContext; |
| this.classLoaderWriter.delete("/org/apache/jsp"); |
| if ( rctxt != null ) { |
| renewJspRuntimeContext(); |
| } |
| |
| res.sendRedirect(path + "?reset"); |
| return; |
| } else if ( req.getMethod().equals("GET") ) { |
| final PrintWriter pw = res.getWriter(); |
| pw.println("<h1>Apache Sling JSP Scripting</h1>"); |
| pw.println("<br/>"); |
| if ( req.getParameter("reset") != null ) { |
| pw.println("<p>All compiled jsp files removed."); |
| pw.println("<br/>"); |
| } |
| pw.print("<form action='"); |
| pw.print(path); |
| pw.println("' method='POST'>"); |
| pw.println("<input type='submit' value='Recompile all JSPs'>"); |
| pw.println("</form>"); |
| return; |
| } |
| } |
| throw new ServletException("Request not supported."); |
| } |
| } |