package org.apache.catalina.loader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.Permission;
import java.util.Enumeration;
import java.util.jar.JarFile;

import javax.naming.directory.DirContext;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;

/**
 * <p>
 * A special ClassLoader to work around classloader (and thus permgen) leaks
 * caused by never-ending threads spawned during the execution of the web app.
 * If no care is taken, such threads have the webapp's classloader as context
 * classloader. So, if such a thread is still alive after the application is
 * undeployed, the application's classloader cannot be garbage-collected.
 * </p>
 * <p>
 * When the application is undeployed, the reference to the delegated
 * classloader is nullified so that the latter can be garbage-collected
 * (provided it is not held by other means).
 * </p>
 * <p>
 * As of march 2009, there are several such "leaking" threads in Sun's JRE
 * library : Java2DDisposer {@link see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6489540}, LDAP connection pool manager... A very simple way
 * of provoking such a leak is with the following servlet code :
 * 
 * <pre>
 * public class MyServlet extends HttpServlet {
 * 
 * 	&#064;Override
 * 	protected void doGet(HttpServletRequest req, HttpServletResponse response)
 * 			throws ServletException, IOException {
 * 		BufferedImage image = new BufferedImage(20, 20,
 * 				BufferedImage.TYPE_INT_RGB);
 * 		Graphics2D g = image.createGraphics();
 * 		response.setContentType(&quot;image/png&quot;);
 * 		OutputStream out = response.getOutputStream();
 * 		ImageIO.write(image, &quot;png&quot;, out);
 * 		out.close();
 * 		out.flush();
 * 		g.dispose();
 * 	}
 * }
 * </pre>
 * 
 * </p>
 * <p>
 * By using this ExpendableClassLoader as the webapp's classloader (and thus the
 * context classloader), such dangling threads only keep a reference to a very
 * light classloader, drastically reducing the leak.
 * </p>
 * <p>
 * NOTE: the class has to be in the same package as {@link WebappClassLoader} in
 * order to override package-protected methods.
 * </p>
 * 
 * @author Sylvain LAURENT
 * 
 */
public class ExpendableClassLoader extends WebappClassLoader {

	private WebappClassLoader delegatedClassLoader;

	public ExpendableClassLoader(ClassLoader parentClassLoader) {
		delegatedClassLoader = new WebappClassLoader(parentClassLoader);
	}

	public void stop() throws LifecycleException {
		ClassLoader savedParentClassLoader = delegatedClassLoader.getParent();

		delegatedClassLoader.stop();

		// release reference to the delegated classloader, potentially
		// sacrificing the current instance
		delegatedClassLoader = null;

		// recreate a delegated classloader, just in case a dangling Thread
		// (e.g. JDK threads like Java2Disposer) needs to load a class
		// we use the same parent CL as the previous delegate
		delegatedClassLoader = new WebappClassLoader(savedParentClassLoader);

	}

	public String toString() {
		return "ExpendableClassLoader@" + System.identityHashCode(this)
				+ " delegates to " + delegatedClassLoader.toString();
	}

	/* ------------------- delegated methods --------------------- */

	void addJar(String jar, JarFile jarFile, File file) throws IOException {
		delegatedClassLoader.addJar(jar, jarFile, file);
	}

	void addRepository(String repository, File file) {
		delegatedClassLoader.addRepository(repository, file);
	}

	public void addLifecycleListener(LifecycleListener listener) {
		delegatedClassLoader.addLifecycleListener(listener);
	}

	public void addPermission(Permission permission) {
		delegatedClassLoader.addPermission(permission);
	}

	public void addPermission(String arg0) {
		delegatedClassLoader.addPermission(arg0);
	}

	public void addPermission(URL url) {
		delegatedClassLoader.addPermission(url);
	}

	public void addRepository(String arg0) {
		delegatedClassLoader.addRepository(arg0);
	}

	public void clearAssertionStatus() {
		delegatedClassLoader.clearAssertionStatus();
	}

	public void closeJARs(boolean arg0) {
		delegatedClassLoader.closeJARs(arg0);
	}

	public Class findClass(String arg0) throws ClassNotFoundException {
		return delegatedClassLoader.findClass(arg0);
	}

	public LifecycleListener[] findLifecycleListeners() {
		return delegatedClassLoader.findLifecycleListeners();
	}

	public String[] findRepositories() {
		return delegatedClassLoader.findRepositories();
	}

	public URL findResource(String name) {
		return delegatedClassLoader.findResource(name);
	}

	public Enumeration findResources(String arg0) throws IOException {
		return delegatedClassLoader.findResources(arg0);
	}

	public boolean getAntiJARLocking() {
		return delegatedClassLoader.getAntiJARLocking();
	}

	public boolean getDelegate() {
		return delegatedClassLoader.getDelegate();
	}

	public String getJarPath() {
		return delegatedClassLoader.getJarPath();
	}

	public URL getResource(String arg0) {
		return delegatedClassLoader.getResource(arg0);
	}

	public InputStream getResourceAsStream(String arg0) {
		return delegatedClassLoader.getResourceAsStream(arg0);
	}

	public DirContext getResources() {
		return delegatedClassLoader.getResources();
	}

	public Enumeration<URL> getResources(String name) throws IOException {
		return delegatedClassLoader.getResources(name);
	}

	public URL[] getURLs() {
		return delegatedClassLoader.getURLs();
	}

	public Class loadClass(String arg0, boolean arg1)
			throws ClassNotFoundException {
		return delegatedClassLoader.loadClass(arg0, arg1);
	}

	public Class loadClass(String name) throws ClassNotFoundException {
		return delegatedClassLoader.loadClass(name);
	}

	public boolean modified() {
		return delegatedClassLoader.modified();
	}

	public void removeLifecycleListener(LifecycleListener listener) {
		delegatedClassLoader.removeLifecycleListener(listener);
	}

	public void setAntiJARLocking(boolean antiJARLocking) {
		delegatedClassLoader.setAntiJARLocking(antiJARLocking);
	}

	public void setClassAssertionStatus(String className, boolean enabled) {
		delegatedClassLoader.setClassAssertionStatus(className, enabled);
	}

	public void setDefaultAssertionStatus(boolean enabled) {
		delegatedClassLoader.setDefaultAssertionStatus(enabled);
	}

	public void setDelegate(boolean delegate) {
		this.delegatedClassLoader.setDelegate(delegate);
	}

	public void setJarPath(String jarPath) {
		delegatedClassLoader.setJarPath(jarPath);
	}

	public void setPackageAssertionStatus(String packageName, boolean enabled) {
		delegatedClassLoader.setPackageAssertionStatus(packageName, enabled);
	}

	public void setResources(DirContext resources) {
		delegatedClassLoader.setResources(resources);
	}

	public void setWorkDir(File workDir) {
		delegatedClassLoader.setWorkDir(workDir);
	}

	public void start() throws LifecycleException {
		delegatedClassLoader.start();
	}

}
