//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.ireland.jnetty.loader;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/* ------------------------------------------------------------ */
/**
* ClassLoader for HttpContext. Specializes URLClassLoader with some utility and file mapping methods.
*
* This loader defaults to the 2.3 servlet spec behavior where non system classes are loaded from the classpath in
* preference to the parent loader. Java2 compliant loading, where the parent loader always has priority, can be
* selected with the {@link org.eclipse.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)} method and
* influenced with {@link WebAppContext#isServerClass(String)} and {@link WebAppContext#isSystemClass(String)}.
*
* If no parent class loader is provided, then the current thread context classloader will be used. If that is null then
* the classloader that loaded this class is used as the parent.
*
*/
public class WebAppClassLoader extends URLClassLoader
{
private static final Log LOG = LogFactory.getLog(WebAppClassLoader.class);
private static final boolean isDebugEnabled = LOG.isDebugEnabled();
//Default: SystemClassLoader
private final ClassLoader _parent;
private String _name = String.valueOf(hashCode());
/**
* Should this class loader delegate to the parent class loader <strong>before</strong> searching its own
* repositories (i.e. the usual Java2 delegation model)? If set to <code>false</code>, this class loader will search
* its own repositories first, and delegate to the parent only if the class or resource is not found locally. Note
* that the default, <code>false</code>, is the behavior called for by the servlet specification.
*/
protected boolean delegate = true;
/* ------------------------------------------------------------ */
/**
* Constructor.
*/
public WebAppClassLoader() throws IOException
{
this(null);
}
/* ------------------------------------------------------------ */
/**
* Constructor.
*/
public WebAppClassLoader(ClassLoader parent) throws IOException
{
super(new URL[] {},
parent != null ? parent : (Thread.currentThread().getContextClassLoader() != null ? Thread.currentThread().getContextClassLoader()
: (WebAppClassLoader.class.getClassLoader() != null ? WebAppClassLoader.class.getClassLoader() : ClassLoader.getSystemClassLoader())));
_parent = getParent();
if (_parent == null)
throw new IllegalArgumentException("no parent classloader!");
}
/* ------------------------------------------------------------ */
/**
* @return the name of the classloader
*/
public String getName()
{
return _name;
}
/* ------------------------------------------------------------ */
/**
* @param name
* the name of the classloader
*/
public void setName(String name)
{
_name = name;
}
/* ------------------------------------------------------------ */
/**
* @param classPath
* Comma or semicolon separated path of filenames or URLs pointing to directories or jar files.
* Directories should end with '/'.
*/
public void addClassPath(URL classPath)
{
addURL(classPath);
}
/* ------------------------------------------------------------ */
/**
* Add elements to the class path for the context from the jar and zip files found in the specified resource.
*
* @param lib
* the resource that contains the jar and/or zip files.
*/
public void addJar(URL jarFile)
{
addURL(jarFile);
}
/* ------------------------------------------------------------ */
@Override
public Enumeration<URL> getResources(String name) throws IOException
{
List<URL> from_parent = toList( _parent.getResources(name));
List<URL> from_webapp = toList( this.findResources(name));
if (delegate)
{
from_parent.addAll(from_webapp);
return Collections.enumeration(from_parent);
}
from_webapp.addAll(from_parent);
return Collections.enumeration(from_webapp);
}
/* ------------------------------------------------------------ */
private List<URL> toList(Enumeration<URL> e)
{
if (e == null)
return new ArrayList<URL>();
return Collections.list(e);
}
/* ------------------------------------------------------------ */
/**
* Get a resource from the classloader
*
*/
@Override
public URL getResource(String name)
{
URL url = null;
// (1) Delegate to parent if requested
if (delegate)
{
url = _parent.getResource(name);
if (url != null) {
if (isDebugEnabled)
LOG.debug(" --> Returning '" + url.toString() + "' from parent classloader: "+_parent);
return (url);
}
}
// (2) Search local resources
url = this.findResource(name);
if (url != null) {
if (isDebugEnabled)
LOG.debug(" --> Returning '" + url.toString() + "' from "+toString());
return (url);
}
// (3) Delegate to parent unconditionally if not already attempted
if (!delegate)
{
url = _parent.getResource(name);
if (url != null) {
if (isDebugEnabled)
LOG.debug(" --> Returning '" + url.toString() + "' from parent classloader: "+_parent);
return (url);
}
}
// (4) Resource was not found
if (isDebugEnabled)
LOG.debug(" --> Resource not found, returning null");
return (null);
}
/* ------------------------------------------------------------ */
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
return loadClass(name, false);
}
/* ------------------------------------------------------------ */
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
Class<?> clazz = loadClass0(name);
if (clazz != null)
{
if (resolve)
resolveClass(clazz);
return (clazz);
}
throw new ClassNotFoundException(name);
}
/**
* Load class but not resolve
* @param name
* @return
* @throws ClassNotFoundException
*/
protected synchronized Class<?> loadClass0(String name) throws ClassNotFoundException
{
// (0) Check our previously loaded class cache
Class<?> clazz = findLoadedClass(name);
if (clazz != null)
{
//if (isDebugEnabled) LOG.debug(" Returning class from cache,class: "+clazz);
return (clazz);
}
// (1) Delegate to our parent if requested
if (delegate)
{
if (isDebugEnabled)
LOG.debug(" Delegating to parent classloader1: " + _parent);
ClassLoader loader = _parent;
try
{
clazz = Class.forName(name, false, loader);
if (clazz != null)
{
if (isDebugEnabled)
LOG.debug(" Loaded class from parent,class: "+clazz);
return (clazz);
}
}
catch (ClassNotFoundException e)
{
// Ignore
}
}
// (2) Search local resources
if (isDebugEnabled)
LOG.debug(" Searching local ClassPaths @ " + name);
try
{
clazz = findClass(name);
if (clazz != null)
{
if (isDebugEnabled)
LOG.debug(" Loaded class from local ClassPaths,class: "+clazz);
return (clazz);
}
}
catch (ClassNotFoundException e)
{
// Ignore
}
// (3) Delegate to parent unconditionally
if (!delegate)
{
if (isDebugEnabled)
LOG.debug(" Delegating to parent classloader at end: " + _parent);
ClassLoader loader = _parent;
try
{
clazz = Class.forName(name, false, loader);
if (clazz != null)
{
if (isDebugEnabled)
LOG.debug(" Loaded class from parent,class: "+clazz);
return (clazz);
}
}
catch (ClassNotFoundException e)
{
// Ignore
}
}
throw new ClassNotFoundException(name);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return "WebAppClassLoader=" + _name + "@" + Long.toHexString(hashCode());
}
}