blob: 94962c29b5ff97da03244b9f21be5ded4389f1ee [file] [log] [blame]
/*
* 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.");
}
}