sylvain 2003/06/05 09:33:11
Modified: src/java/org/apache/cocoon/servlet BootstrapServlet.java ParanoidClassLoader.java ParanoidCocoonServlet.java Log: Backported the ParanoidCocoon servlet, which is now really paranoid Revision Changes Path 1.2 +52 -138 cocoon-2.0/src/java/org/apache/cocoon/servlet/BootstrapServlet.java Index: BootstrapServlet.java =================================================================== RCS file: /home/cvs/cocoon-2.0/src/java/org/apache/cocoon/servlet/BootstrapServlet.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- BootstrapServlet.java 9 Mar 2003 00:03:16 -0000 1.1 +++ BootstrapServlet.java 5 Jun 2003 16:33:10 -0000 1.2 @@ -51,17 +51,17 @@ package org.apache.cocoon.servlet; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; import java.util.Enumeration; -import java.util.List; import java.util.Set; -import javax.servlet.*; -import javax.servlet.http.HttpServlet; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; /** * A bootstrap servlet to allow Cocoon to run in servlet engines that aren't fully @@ -81,141 +81,55 @@ * @version CVS $Id$ */ -public class BootstrapServlet extends HttpServlet { +public class BootstrapServlet extends ParanoidCocoonServlet { - /** - * The name of the actual servlet class. - */ - public static final String SERVLET_CLASS = "org.apache.cocoon.servlet.CocoonServlet"; - - protected Servlet servlet; - - protected ClassLoader classloader; - - protected ServletContext context; + private File contextDir; - public void init(ServletConfig config) throws ServletException { - this.context = config.getServletContext(); - - this.context.log("getRealPath(\"/\") = " + context.getRealPath("/")); + protected File getContextDir() throws ServletException { + + ServletContext context = getServletContext(); + ServletConfig config = getServletConfig(); + + log("getRealPath(\"/\") = " + context.getRealPath("/")); + + String contextDirParam = config.getInitParameter("context-directory"); + + if (contextDirParam == null) { + throw new ServletException("The 'context-directory' parameter must be set to the root of the servlet context"); + } + + // Ensure context dir doesn't end with a "/" (servlet spec says that paths for + // getResource() should start by a "/") + if (contextDirParam.endsWith("/")) { + contextDirParam = contextDirParam.substring(0, contextDirParam.length() - 1); + } + + // Ensure context dir exists and is a directory + this.contextDir = new File(contextDirParam); + if (!this.contextDir.exists()) { + String msg = "Context dir '" + this.contextDir + "' doesn't exist"; + log(msg); + throw new ServletException(msg); + } + + if (!this.contextDir.isDirectory()) { + String msg = "Context dir '" + this.contextDir + "' should be a directory"; + log(msg); + throw new ServletException(msg); + } + + context.log("Context dir set to " + this.contextDir); + + return this.contextDir; + } - String contextDirParam = config.getInitParameter("context-directory"); - if (contextDirParam == null) { - // Check old form, not consistent with other parameter names - contextDirParam = config.getInitParameter("context-dir"); - if (contextDirParam == null) { - String msg = "The 'context-directory' parameter must be set to the root of the servlet context"; - this.context.log(msg); - throw new ServletException(msg); - } else { - this.context.log("Parameter 'context-dir' is deprecated - use 'context-directory'"); - } - } - - // Ensure context dir doesn't end with a "/" (servlet spec says that paths for - // getResource() should start by a "/") - if (contextDirParam.endsWith("/")) { - contextDirParam = contextDirParam.substring(0, contextDirParam.length() - 1); - } - - // Ensure context dir exists and is a directory - File contextDir = new File(contextDirParam); - if (!contextDir.exists()) { - String msg = "Context dir '" + contextDir + "' doesn't exist"; - this.context.log(msg); - throw new ServletException(msg); - } - if (!contextDir.isDirectory()) { - String msg = "Context dir '" + contextDir + "' should be a directory"; - this.context.log(msg); - throw new ServletException(msg); - } - - context.log("Context dir set to " + contextDir); - - this.classloader = getClassLoader(contextDirParam); - - try { - Class servletClass = this.classloader.loadClass(SERVLET_CLASS); - - this.servlet = (Servlet)servletClass.newInstance(); - } catch(Exception e) { - context.log("Cannot load servlet", e); - throw new ServletException(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. - Thread.currentThread().setContextClassLoader(this.classloader); - - ServletContext newContext = new ContextWrapper(context, contextDirParam); - ServletConfig newConfig = new ConfigWrapper(config, newContext); - - super.init(newConfig); + protected void initServlet(Servlet servlet) throws ServletException { - // Inlitialize the actual servlet - this.servlet.init(newConfig); + ServletContext newContext = new ContextWrapper(getServletContext(), this.contextDir); + ServletConfig newConfig = new ConfigWrapper(getServletConfig(), newContext); - } - - /** - * Get the classloader that will be used to create the actual servlet. - */ - protected ClassLoader getClassLoader(String contextDirParam) throws ServletException { - List urlList = new ArrayList(); - - try { - File classDir = new File(contextDirParam + "/WEB-INF/classes"); - if (classDir.exists()) { - if (!classDir.isDirectory()) { - String msg = classDir + " exists but is not a directory"; - this.context.log(msg); - throw new ServletException(msg); - } - - URL classURL = classDir.toURL(); - context.log("Adding class directory " + classURL); - urlList.add(classURL); - - } - - File libDir = new File(contextDirParam + "/WEB-INF/lib"); - File[] libraries = libDir.listFiles(); - - for (int i = 0; i < libraries.length; i++) { - URL lib = libraries[i].toURL(); - context.log("Adding class library " + lib); - urlList.add(lib); - } - } catch (MalformedURLException mue) { - context.log("Malformed url", mue); - throw new ServletException(mue); - } - - URL[] urls = (URL[])urlList.toArray(new URL[urlList.size()]); - - return ParanoidClassLoader.newInstance(urls, this.getClass().getClassLoader()); - } - - /** - * Service the request by delegating the call to the real servlet - */ - public void service(ServletRequest request, ServletResponse response) - throws ServletException, IOException { - - Thread.currentThread().setContextClassLoader(this.classloader); - this.servlet.service(request, response); - } - - /** - * Destroy the actual servlet - */ - public void destroy() { - - super.destroy(); - Thread.currentThread().setContextClassLoader(this.classloader); - this.servlet.destroy(); + servlet.init(newConfig); } //------------------------------------------------------------------------- @@ -260,13 +174,13 @@ */ public static class ContextWrapper implements ServletContext { ServletContext context; - String contextRoot; + File contextRoot; /** * Builds a wrapper around an existing context, and handle all * resource resolution relatively to <code>contextRoot</code> */ - public ContextWrapper(ServletContext context, String contextRoot) { + public ContextWrapper(ServletContext context, File contextRoot) { this.context = context; this.contextRoot = contextRoot; } @@ -293,7 +207,7 @@ * returned. */ public URL getResource(String path) throws MalformedURLException { - File file = new File(this.contextRoot + path); + File file = new File(this.contextRoot, path); if (file.exists()) { URL result = file.toURL(); //this.context.log("getResource(" + path + ") = " + result); 1.2 +15 -4 cocoon-2.0/src/java/org/apache/cocoon/servlet/ParanoidClassLoader.java Index: ParanoidClassLoader.java =================================================================== RCS file: /home/cvs/cocoon-2.0/src/java/org/apache/cocoon/servlet/ParanoidClassLoader.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ParanoidClassLoader.java 9 Mar 2003 00:03:16 -0000 1.1 +++ ParanoidClassLoader.java 5 Jun 2003 16:33:10 -0000 1.2 @@ -50,6 +50,8 @@ */ package org.apache.cocoon.servlet; +import org.apache.cocoon.CascadingIOException; + import java.io.File; import java.io.IOException; import java.net.MalformedURLException; @@ -62,7 +64,7 @@ * classes. It checks this classloader before it checks its parent. * * @author <a href="mailto:[EMAIL PROTECTED]">Berin Loritsch</a> - * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> + * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a> * @version CVS $Id$ */ @@ -83,7 +85,7 @@ * Alternate constructor to define a parent. */ public ParanoidClassLoader(final ClassLoader parent) { - this(null, parent, null); + this(new URL[0], parent, null); } /** @@ -149,6 +151,7 @@ try { clazz = findClass(name); + //System.err.println("Paranoid load : " + name); } catch (ClassNotFoundException cnfe) { if (this.parent != null) { // Ask to parent ClassLoader (can also throw a CNFE). @@ -197,7 +200,15 @@ try { this.addURL(file.getCanonicalFile().toURL()); } catch (MalformedURLException mue) { - throw new IOException("Could not add repository"); + throw new CascadingIOException("Could not add repository", mue); } + } + + /** + * Adds a new URL + */ + + public void addURL(URL url) { + super.addURL(url); } } 1.2 +148 -103 cocoon-2.0/src/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java Index: ParanoidCocoonServlet.java =================================================================== RCS file: /home/cvs/cocoon-2.0/src/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ParanoidCocoonServlet.java 9 Mar 2003 00:03:16 -0000 1.1 +++ ParanoidCocoonServlet.java 5 Jun 2003 16:33:10 -0000 1.2 @@ -50,121 +50,166 @@ */ package org.apache.cocoon.servlet; -import org.apache.cocoon.components.classloader.RepositoryClassLoader; -import org.apache.cocoon.util.IOUtils; - -import javax.servlet.ServletException; import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +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 is the entry point for Cocoon execution as an HTTP Servlet. - * It also creates a buffer by loading the whole servlet inside a ClassLoader. - * It has been changed to extend <code>CocoonServlet</code> so that it is - * easier to add and change functionality between the two servlets. - * The only real differences are the ClassLoader and instantiating Cocoon inside - * of it. + * 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 + * accept only one additional parameter, <code>servlet-class</code>, which defined the + * sandboxed servlet class. The default is [EMAIL PROTECTED] CocoonServlet}. * * @author <a href="mailto:[EMAIL PROTECTED]">Berin Loritsch</a> + * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a> * @version CVS $Id$ */ -public class ParanoidCocoonServlet extends CocoonServlet { +public class ParanoidCocoonServlet extends HttpServlet { - protected RepositoryClassLoader repositoryLoader; + /** + * The name of the actual servlet class. + */ + public static final String DEFAULT_SERVLET_CLASS = "org.apache.cocoon.servlet.CocoonServlet"; - public ParanoidCocoonServlet() { - super(); - // Override the parent class classloader - this.repositoryLoader = new RepositoryClassLoader(new URL[] {}, this.getClass().getClassLoader()); - super.classLoader = this.repositoryLoader; - } - - /** - * This builds the important ClassPath used by this Servlet. It - * does so in a Servlet Engine neutral way. It uses the - * <code>ServletContext</code>'s <code>getRealPath</code> method - * to get the Servlet 2.2 identified classes and lib directories. - * It iterates through every file in the lib directory and adds - * it to the classpath. - * - * Also, we add the files to the ClassLoader for the Cocoon system. - * In order to protect ourselves from skitzofrantic classloaders, - * we need to work with a known one. - * - * @param context The ServletContext to perform the lookup. - * - * @throws ServletException - */ - protected String getClassPath() - throws ServletException { - - StringBuffer buildClassPath = new StringBuffer(); - String classDirPath = getInitParameter("class-dir"); - String libDirPath = getInitParameter("lib-dir"); - String classDir; - File root; - - if ((classDirPath != null) && !classDirPath.trim().equals("")) { - classDir = classDirPath; - } else { - classDir = this.servletContext.getRealPath("/WEB-INF/classes"); - } - - if ((libDirPath != null) && !libDirPath.trim().equals("")) { - root = new File(libDirPath); - } else { - root = new File(this.servletContext.getRealPath("/WEB-INF/lib")); - } - - addClassLoaderDirectory(classDir); - - buildClassPath.append(classDir); - - if (root.isDirectory()) { - File[] libraries = root.listFiles(); - - for (int i = 0; i < libraries.length; i++) { - String fullName = IOUtils.getFullFilename(libraries[i]); - buildClassPath.append(File.pathSeparatorChar).append(fullName); - - addClassLoaderDirectory(fullName); - } + private Servlet servlet; + + private ClassLoader classloader; + + public void init(ServletConfig config) throws ServletException { + + super.init(config); + + // Create the classloader in which we will load the servlet + this.classloader = getClassLoader(this.getContextDir()); + + String servletName = config.getInitParameter("servlet-class"); + if (servletName == null) { + servletName = DEFAULT_SERVLET_CLASS; } + + // 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. + Thread.currentThread().setContextClassLoader(this.classloader); + + // Inlitialize the actual servlet + initServlet(servlet); + + } + + /** + * Initialize the wrapped servlet. Subclasses (see [EMAIL PROTECTED] BootstrapServlet} change the + * <code>ServletConfig</code> given to the servlet. + * + * @param servlet the servlet to initialize + * @throws ServletException + */ + protected void initServlet(Servlet servlet) throws ServletException { + this.servlet.init(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); + } + + /** + * 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. + */ + private ClassLoader getClassLoader(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 FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".zip") || name.endsWith(".jar"); + } + } + ); + + 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 ParanoidClassLoader.newInstance(urls, this.getClass().getClassLoader()); + } + + /** + * Service the request by delegating the call to the real servlet + */ + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException { + + Thread.currentThread().setContextClassLoader(this.classloader); + this.servlet.service(request, response); + } + + /** + * Destroy the actual servlet + */ + public void destroy() { - buildClassPath.append(File.pathSeparatorChar) - .append(System.getProperty("java.class.path")); - - buildClassPath.append(File.pathSeparatorChar) - .append(getExtraClassPath()); - - return buildClassPath.toString(); - } - - /** - * Adds an URL to the classloader. - */ - protected void addClassLoaderURL(URL url) { - try { - this.repositoryLoader.addURL(url); - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Could not add URL" + url, e); - } - } - } + Thread.currentThread().setContextClassLoader(this.classloader); + this.servlet.destroy(); - /** - * Adds a directory to the classloader. - */ - protected void addClassLoaderDirectory(String dir) { - try { - this.repositoryLoader.addDirectory(new File(dir)); - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Could not add directory" + dir, e); - } - } - } + super.destroy(); + } }