| /* |
| * 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"); |
| } |
| } |
| |
| } |
| |