blob: c45cc27710c852d406eee6c2a02fdd8feb1c9a60 [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.cocoon.servlet;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
/**
* This servlet builds a classloading sandbox and runs another servlet inside
* that sandbox. The purpose is to shield the libraries and classes shipped with
* the web application from any other classes with the same name that may exist
* in the system, such as Xerces and Xalan versions included in JDK 1.4.
* <p>
* This servlet propagates all initialisation parameters to the sandboxed
* servlet, and accepts the parameters <code>servlet-class</code> and
* <code>paranoid-classpath</code>.
* <ul>
* <li><code>servlet-class</code> defines the sandboxed servlet class, the
* default is {@link CocoonServlet}
* <li><code>paranoid-classpath</code> expects the name of a text file that
* can contain lines begining with
* <code>class-dir:<code> (directory containing classes),
* <code>lib-dir:<code> (directory containing JAR or ZIP libraries) and <code>#</code>
* (for comments). <br/>
* All other lines are considered as URLs.
* <br/>
* It is also possible to use a the pseudo protocol prefix<code>context:/<code> which
* is resolved to the basedir of the servlet context.
* </ul>
*
* @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
* @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
* @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a>
* @version CVS $Id$
*/
public class ParanoidCocoonServlet extends HttpServlet {
/**
* The name of the actual servlet class.
*/
public static final String DEFAULT_SERVLET_CLASS = "org.apache.cocoon.servlet.CocoonServlet";
protected static final String CONTEXT_PREFIX = "context:";
protected static final String FILE_PREFIX = "file:";
protected Servlet servlet;
protected ClassLoader classloader;
public void init(ServletConfig config) throws ServletException {
super.init(config);
// Create the classloader in which we will load the servlet
// this can either be specified by an external file configured
// as a parameter in web.xml or (the default) all jars and
// classes from WEB-INF/lib and WEB-INF/classes are used.
final String externalClasspath = config.getInitParameter("paranoid-classpath");
final URL[] classPath = (externalClasspath == null)
? getClassPath(getContextDir())
: getClassPath(externalClasspath, getContextDir());
final String classLoaderName = config.getInitParameter("classloader-class");
if (classLoaderName != null) {
log("Using classloader " + classLoaderName);
}
this.classloader = createClassLoader(classLoaderName, classPath);
String servletName = config.getInitParameter("servlet-class");
if (servletName == null) {
servletName = DEFAULT_SERVLET_CLASS;
}
log("Loading servlet class " + servletName);
// Create the servlet
try {
Class servletClass = this.classloader.loadClass(servletName);
this.servlet = (Servlet) servletClass.newInstance();
} catch (Exception e) {
throw new ServletException("Cannot load servlet " + servletName, e);
}
// Always set the context classloader. JAXP uses it to find a
// ParserFactory,
// and thus fails if it's not set to the webapp classloader.
final ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.classloader);
// Inlitialize the actual servlet
this.initServlet();
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
/**
* Initialize the wrapped servlet. Subclasses (see {@link BootstrapServlet}
* change the <code>ServletConfig</code> given to the servlet.
*
* @throws ServletException
*/
protected void initServlet() throws ServletException {
this.servlet.init(this.getServletConfig());
}
/**
* Get the web application context directory.
*
* @return the context dir
* @throws ServletException
*/
protected File getContextDir() throws ServletException {
String result = getServletContext().getRealPath("/");
if (result == null) {
throw new ServletException(this.getClass().getName() + " cannot run in an undeployed WAR file");
}
return new File(result);
}
protected URL[] getClassPath(final File contextDir) throws ServletException {
List urlList = new ArrayList();
try {
File classDir = new File(contextDir + "/WEB-INF/classes");
if (classDir.exists()) {
if (!classDir.isDirectory()) {
throw new ServletException(classDir + " exists but is not a directory");
}
URL classURL = classDir.toURL();
log("Adding class directory " + classURL);
urlList.add(classURL);
}
// List all .jar and .zip
File libDir = new File(contextDir + "/WEB-INF/lib");
File[] libraries = libDir.listFiles(new JarFileFilter());
for (int i = 0; i < libraries.length; i++) {
URL lib = libraries[i].toURL();
log("Adding class library " + lib);
urlList.add(lib);
}
} catch (MalformedURLException mue) {
throw new ServletException(mue);
}
URL[] urls = (URL[]) urlList.toArray(new URL[urlList.size()]);
return urls;
}
protected URL[] getClassPath(final String externalClasspath, final File contextDir) throws ServletException {
final List urlList = new ArrayList();
File file = new File(externalClasspath);
if (!file.isAbsolute()) {
file = new File(contextDir, externalClasspath);
}
log("Adding classpath from " + file);
try {
FileReader fileReader = new FileReader(file);
LineNumberReader lineReader = new LineNumberReader(fileReader);
String line;
do {
line = lineReader.readLine();
if (line != null) {
if (line.startsWith("class-dir:")) {
line = line.substring("class-dir:".length()).trim();
if (line.startsWith(CONTEXT_PREFIX)) {
line = contextDir + line.substring(CONTEXT_PREFIX.length());
}
URL url = new File(line).toURL();
log("Adding class directory " + url);
urlList.add(url);
} else if (line.startsWith("lib-dir:")) {
line = line.substring("lib-dir:".length()).trim();
if (line.startsWith(CONTEXT_PREFIX)) {
line = contextDir + line.substring(CONTEXT_PREFIX.length());
}
File dir = new File(line);
File[] libraries = dir.listFiles(new JarFileFilter());
log("Adding " + libraries.length + " libraries from " + dir.toURL());
for (int i = 0; i < libraries.length; i++) {
URL url = libraries[i].toURL();
urlList.add(url);
}
} else if (line.startsWith("#")) {
// skip it (consider it as comment)
} else {
// Consider it as a URL
final URL lib;
if (line.startsWith(CONTEXT_PREFIX)) {
line = FILE_PREFIX + "/" + contextDir + line.substring(CONTEXT_PREFIX.length()).trim();
}
if (line.indexOf(':') == -1) {
File entry = new File(line);
lib = entry.toURL();
} else {
lib = new URL(line);
}
log("Adding class URL " + lib);
urlList.add(lib);
}
}
} while (line != null);
lineReader.close();
} catch (IOException io) {
throw new ServletException(io);
}
URL[] urls = (URL[]) urlList.toArray(new URL[urlList.size()]);
return urls;
}
protected ClassLoader createClassLoader(final String className, final URL[] classPath) throws ServletException {
if (className != null) {
try {
final Class classLoaderClass = Class.forName(className);
final Class[] parameterClasses = new Class[] { ClassLoader.class };
final Constructor constructor = classLoaderClass.getConstructor(parameterClasses);
final Object[] parameters = new Object[] { this.getClass().getClassLoader() };
final ClassLoader classloader = (ClassLoader) constructor.newInstance(parameters);
return classloader;
} catch (InstantiationException e) {
throw new ServletException("", e);
} catch (IllegalAccessException e) {
throw new ServletException("", e);
} catch (ClassNotFoundException e) {
throw new ServletException("", e);
} catch (SecurityException e) {
throw new ServletException("", e);
} catch (NoSuchMethodException e) {
throw new ServletException("", e);
} catch (IllegalArgumentException e) {
throw new ServletException("", e);
} catch (InvocationTargetException e) {
throw new ServletException("", e);
}
} else {
return ParanoidClassLoader.newInstance(classPath, this.getClass().getClassLoader());
}
}
/**
* Get the classloader that will be used to create the actual servlet. Its
* classpath is defined by the WEB-INF/classes and WEB-INF/lib directories
* in the context dir.
* @deprecated
*/
protected ClassLoader getClassLoader(File contextDir) throws ServletException {
return createClassLoader(null, getClassPath(contextDir));
}
/**
* Get the classloader that will be used to create the actual servlet. Its
* classpath is defined by an external file.
* @deprecated
*/
protected ClassLoader getClassLoader(final String externalClasspath, final File contextDir) throws ServletException {
return createClassLoader(null, getClassPath(externalClasspath, contextDir));
}
/**
* Service the request by delegating the call to the real servlet
*/
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
final ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.classloader);
this.servlet.service(request, response);
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
/**
* Destroy the actual servlet
*/
public void destroy() {
if (this.servlet != null) {
final ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.classloader);
this.servlet.destroy();
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
super.destroy();
}
private static class JarFileFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
return name.endsWith(".zip") || name.endsWith(".jar");
}
}
}