blob: 17471cc9f7c615d23f74d814bd030b6d4a1016ec [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.java.impl;
import java.util.List;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.commons.classloader.DynamicClassLoader;
import org.apache.sling.commons.compiler.CompilationResult;
import org.apache.sling.commons.compiler.CompilerMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class wraps the java servlet and handles its compilation,
* instantiation, invocation und destruction.
*/
public class ServletWrapper {
/** The logger. */
private final Logger logger = LoggerFactory.getLogger(getClass());
/** The servlet config. */
private final ServletConfig config;
/** Sling IO Provider. */
private final SlingIOProvider ioProvider;
/** The name of the generated class. */
private final String className;
/** The path to the servlet. */
private final String sourcePath;
/** Flag for handling modifications. */
private volatile long lastModificationTest = 0L;
/** The compiled and instantiated servlet. */
private volatile Servlet theServlet;
/** Flag handling an unavailable exception. */
private volatile long available = 0L;
/** The exception thrown by the compilation. */
private volatile Exception compileException;
/**
* A wrapper for servlets.
*/
public ServletWrapper(final ServletConfig config,
final SlingIOProvider ioProvider,
final String servletPath) {
this.config = config;
this.ioProvider = ioProvider;
this.sourcePath = servletPath;
this.className = CompilerUtil.mapSourcePath(this.sourcePath).substring(1).replace('/', '.');
}
/**
* Call the servlet.
* @param request The current request.
* @param response The current response.
* @throws Exception
*/
public void service(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
try {
if ((available > 0L) && (available < Long.MAX_VALUE)) {
if (available > System.currentTimeMillis()) {
response.setDateHeader("Retry-After", available);
response.sendError
(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
"Servlet unavailable.");
logger.error("Java servlet {} is unavailable.", this.sourcePath);
return;
}
// Wait period has expired. Reset.
available = 0;
}
// check for compilation
if (this.lastModificationTest <= 0 ) {
synchronized (this) {
if (this.lastModificationTest <= 0 ) {
this.compile();
} else if (compileException != null) {
// Throw cached compilation exception
throw compileException;
}
}
} else if (compileException != null) {
// Throw cached compilation exception
throw compileException;
}
final Servlet servlet = getServlet();
// invoke the servlet
if (servlet instanceof SingleThreadModel) {
// sync on the wrapper so that the freshness
// of the page is determined right before servicing
synchronized (this) {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
} catch (UnavailableException ex) {
int unavailableSeconds = ex.getUnavailableSeconds();
if (unavailableSeconds <= 0) {
unavailableSeconds = 60; // Arbitrary default
}
available = System.currentTimeMillis() +
(unavailableSeconds * 1000L);
response.sendError
(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
ex.getMessage());
logger.error("Java servlet {} is unavailable.", this.sourcePath);
}
}
/**
* Destroy the servlet.
*/
public void destroy() {
if (theServlet != null) {
theServlet.destroy();
theServlet = null;
}
}
/**
* Handle the modification.
*/
public void handleModification() {
logger.debug("Received modification event for {}", this.sourcePath);
this.lastModificationTest = -1;
}
/**
* Check if the used classloader is still valid
*/
private boolean checkReload() {
final Servlet servlet = this.theServlet;
final Class<?> servletClass = servlet != null ? servlet.getClass() : null;
if ( servletClass != null && servletClass.getClassLoader() instanceof DynamicClassLoader ) {
return !((DynamicClassLoader)servletClass.getClassLoader()).isLive();
}
return false;
}
/**
* Get the servlet class - if the used classloader is not valid anymore
* the class is reloaded.
*/
public Servlet getServlet()
throws Exception {
// check if the used class loader is still alive
if (this.checkReload()) {
synchronized (this) {
if (this.checkReload()) {
this.compile();
}
}
}
return theServlet;
}
/**
* Compile the servlet java class
* and instantiate the servlet
*/
private void compile()
throws Exception {
logger.debug("Compiling {}", this.sourcePath);
// clear exception
this.compileException = null;
try {
final CompilerOptions opts = (this.lastModificationTest == -1 ? this.ioProvider.getForceCompileOptions() : this.ioProvider.getOptions());
final CompilationUnit unit = new CompilationUnit(this.sourcePath, className, ioProvider);
final CompilationResult result = this.ioProvider.getCompiler().compile(new org.apache.sling.commons.compiler.CompilationUnit[] {unit},
opts);
final List<CompilerMessage> errors = result.getErrors();
if ( errors != null && errors.size() > 0 ) {
throw CompilerException.create(errors, this.sourcePath);
}
if ( result.didCompile() || this.theServlet == null ) {
destroy();
final Class<?> servletClass = result.loadCompiledClass(this.className);
final Servlet servlet = (Servlet) servletClass.newInstance();
servlet.init(this.config);
this.theServlet = servlet;
}
} catch (final Exception ex) {
// store exception for futher access attempts
this.compileException = ex;
throw ex;
} finally {
this.lastModificationTest = System.currentTimeMillis();
}
}
/** Compiler exception .*/
protected final static class CompilerException extends ServletException {
private static final long serialVersionUID = 7353686069328527452L;
public static CompilerException create(final List<CompilerMessage> errors,
final String fileName) {
final StringBuilder buffer = new StringBuilder();
buffer.append("Compilation errors in ");
buffer.append(fileName);
buffer.append(":\n");
for(final CompilerMessage e : errors) {
buffer.append("Line ");
buffer.append(e.getLine());
buffer.append(", column ");
buffer.append(e.getColumn());
buffer.append(" : " );
buffer.append(e.getMessage());
buffer.append("\n");
}
return new CompilerException(buffer.toString());
}
public CompilerException(final String message) {
super(message);
}
}
}