/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.classloading.plugins;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.jboss.classloading.spi.ClassLoadingDomain;
import org.jboss.classloading.spi.DomainClassLoader;
import org.jboss.kernel.plugins.registry.AbstractKernelRegistryEntry;
import org.jboss.kernel.spi.registry.KernelRegistryEntry;
import org.jboss.kernel.spi.registry.KernelRegistryPlugin;
import org.jboss.util.ClassLoading;
import org.jboss.util.CollectionsFactory;
import org.jboss.util.JBossObject;
import org.jboss.util.JBossStringBuilder;
/**
* An abstract classloading domain.
*
* @author <a href="adrian@jboss.com">Adrian Brock</a>
* @version $Revision: 1.8 $
*/
public class AbstractClassLoadingDomain extends JBossObject implements ClassLoadingDomain, KernelRegistryPlugin
{
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
/** Whether java2 classloading compliance is supported */
protected boolean java2ClassLoadingCompliance;
/** The parent classloading domain */
protected ClassLoadingDomain parent;
/** The classloaders by package name */
protected Map pkgToCLs = CollectionsFactory.createConcurrentReaderMap();
/** The cached classes */
protected Map cachedClasses = CollectionsFactory.createConcurrentReaderMap();
/** The failed classloading attempts */
protected Map failedClasses = CollectionsFactory.createConcurrentReaderMap();
/** The cached resources */
protected Map cachedResources = CollectionsFactory.createConcurrentReaderMap();
/** The failed resource attempts */
protected Set failedResources = CollectionsFactory.createCopyOnWriteSet();
// Static --------------------------------------------------------
protected static AbstractClassLoadingDomain root;
static
{
root = new AbstractClassLoadingDomain(true, null);
SystemDomainClassLoader sdcl = new SystemDomainClassLoader();
root.addDomainClassLoader(sdcl);
}
// Constructors --------------------------------------------------
/**
* Create a new AbstractClassLoadingDomain.
*/
public AbstractClassLoadingDomain()
{
this(true, root);
}
/**
* Create a new AbstractClassLoadingDomain.
*
* @param java2ClassLoadingCompliance whether java2 classloading complaince is enabled
*/
public AbstractClassLoadingDomain(boolean java2ClassLoadingCompliance)
{
this(java2ClassLoadingCompliance, root);
}
/**
* Create a new AbstractClassLoadingDomain.
*
* @param parent the parent domain
*/
public AbstractClassLoadingDomain(ClassLoadingDomain parent)
{
this(true, parent);
}
/**
* Create a new AbstractClassLoadingDomain.
*
* @param java2ClassLoadingCompliance whether java2 classloading complaince is enabled
* @param parent the parent domain
*/
public AbstractClassLoadingDomain(boolean java2ClassLoadingCompliance, ClassLoadingDomain parent)
{
this.java2ClassLoadingCompliance = java2ClassLoadingCompliance;
this.parent = parent;
}
// Public --------------------------------------------------------
/**
* Add a domain classloader
*
* @param cl the classloader to add
*/
public synchronized void addDomainClassLoader(DomainClassLoader cl)
{
boolean trace = log.isTraceEnabled();
cl.setDomain(this);
Set packages = cl.getPackageNames();
if (trace)
log.trace(this.toShortString() + " adding classloader " + cl + " packages " + packages);
if (packages.isEmpty() == false)
{
for (Iterator i = packages.iterator(); i.hasNext();)
{
String pkg = (String) i.next();
String name = pkg.replace('.', '/');
List cls = (List) pkgToCLs.get(name);
if (cls == null)
{
cls = CollectionsFactory.createCopyOnWriteList();
pkgToCLs.put(name, cls);
}
cls.add(cl);
}
}
toString = null;
flushFailures();
if (trace)
log.trace(this.toShortString() + " added classloader " + cl + " packageMap=" + pkgToCLs);
}
/**
* Remove a domain classloader
*
* @param cl the classloader to remove
*/
public synchronized void removeDomainClassLoader(DomainClassLoader cl)
{
boolean trace = log.isTraceEnabled();
Set packages = cl.getPackageNames();
if (trace)
log.trace(this.toShortString() + " removing classloader " + cl + " packages " + packages);
if (packages.isEmpty() == false)
{
for (Iterator i = packages.iterator(); i.hasNext();)
{
String pkg = (String) i.next();
String name = pkg.replace('.', '/');
List cls = (List) pkgToCLs.get(name);
if (cls != null)
{
cls.remove(cl);
if (cls.isEmpty())
pkgToCLs.remove(name);
}
}
}
cl.setDomain(null);
toString = null;
flushCache();
if (trace)
log.trace(this.toShortString() + " removed classloader " + cl + " packageMap=" + pkgToCLs);
}
/**
* Flush the cache
*/
public void flushCache()
{
if (log.isTraceEnabled())
log.trace(this.toShortString() + " flushing the cache");
cachedClasses.clear();
cachedResources.clear();
}
/**
* Flush the failures
*/
public void flushFailures()
{
if (log.isTraceEnabled())
log.trace(this.toShortString() + " flushing the failures cache");
failedClasses.clear();
failedResources.clear();
}
// KernrelRegistryFactory implementation -------------------------
public KernelRegistryEntry getEntry(Object name)
{
if (name instanceof String == false)
return null;
String stringName = (String) name;
String slashName = stringName.replace('.', '/');
List pkgs = (List) pkgToCLs.get(slashName);
if (pkgs != null)
{
DomainClassLoader cl = (DomainClassLoader) pkgs.get(0);
return new AbstractKernelRegistryEntry(name, cl.getPackage(stringName));
}
if (parent != null && parent instanceof KernelRegistryPlugin)
{
KernelRegistryPlugin factory = (KernelRegistryPlugin) parent;
return factory.getEntry(name);
}
return null;
}
// ClassLoadingDomain implementation -----------------------------
public boolean getJava2ClassLoadingCompliance()
{
return java2ClassLoadingCompliance;
}
public ClassLoadingDomain getParent()
{
return parent;
}
public synchronized Class loadClass(String name, boolean resolve, DomainClassLoader classLoader) throws ClassNotFoundException
{
if (name == null)
throw new IllegalArgumentException("Null name");
boolean trace = log.isTraceEnabled();
if (trace)
log.trace(this.toShortString() + " loading class " + name + " resolve=" + resolve + " classLoader=" + classLoader);
Class clazz = null;
if (java2ClassLoadingCompliance && parent != null)
clazz = loadClassFromParent(trace, name, resolve, classLoader);
if (clazz == null)
checkLoadClassFailed(trace, name, resolve, classLoader);
if (clazz == null)
clazz = loadClassFromCache(trace, name, resolve, classLoader);
if (clazz == null)
clazz = loadClassFromClassLoaders(trace, name, resolve, classLoader);
if (clazz == null && java2ClassLoadingCompliance == false && parent != null)
clazz = loadClassFromParent(trace, name, resolve, classLoader);
if (clazz == null)
loadClassFailed(trace, name, resolve, classLoader);
return clazz;
}
public URL loadResource(String name, DomainClassLoader classLoader)
{
if (name == null)
throw new IllegalArgumentException("Null name");
boolean trace = log.isTraceEnabled();
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " classLoader=" + classLoader);
URL url = null;
if (classLoader != null)
loadResourceFromClassLoader(trace, name, classLoader);
if (url == null && java2ClassLoadingCompliance && parent != null)
url = loadResourceFromParent(trace, name, classLoader);
if (url == null)
{
if (checkLoadResourceFailed(trace, name, classLoader))
return null;
}
if (url == null)
url = loadResourceFromCache(trace, name, classLoader);
if (url == null)
url = loadResourceFromClassLoaders(trace, name, classLoader);
if (url == null && java2ClassLoadingCompliance == false && parent != null)
url = loadResourceFromParent(trace, name, classLoader);
if (url == null)
loadResourceFailed(trace, name, classLoader);
return url;
}
// JBossObject overrides -----------------------------------------
protected void toString(JBossStringBuilder buffer)
{
buffer.append("packages=").append(pkgToCLs.keySet());
if (parent != null)
buffer.append(" parent=").append(parent.toShortString());
}
public void toShortString(JBossStringBuilder buffer)
{
buffer.append(getClassShortName()).append('@');
buffer.append(Integer.toHexString(System.identityHashCode(this)));
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
/**
* Load a class from the parent
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param resolve whether the class should be resolved
* @param classLoader the classloader initiating the request
* @return the class or null if not found
*/
protected Class loadClassFromParent(boolean trace, String name, boolean resolve, DomainClassLoader classLoader)
{
try
{
if (trace)
log.trace(this.toShortString() + " loading class " + name + " from parent " + parent.toShortString());
Class clazz = parent.loadClass(name, resolve, null);
if (trace)
log.trace(this.toShortString() + " loaded " + clazz + " from parent " + parent.toShortString());
return clazz;
}
catch (ClassNotFoundException e)
{
if (trace)
log.trace(this.toShortString() + " loading class " + name + " failed for parent " + parent.toShortString());
return null;
}
}
/**
* Load a class from the classloaders
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param resolve whether the class should be resolved
* @param classLoader the classloader initiating the request
* @return the class or null if not found
*/
protected Class loadClassFromClassLoaders(boolean trace, String name, boolean resolve, DomainClassLoader classLoader)
{
String dotName = name.replace('.', '/');
List classLoaders = (List) pkgToCLs.get(ClassLoading.getPackageName(dotName));
if (trace)
log.trace(this.toShortString() + " loading class " + name + " from classloaders " + classLoaders);
if (classLoaders == null)
return null;
for (ListIterator i = classLoaders.listIterator(); i.hasNext();)
{
DomainClassLoader cl = (DomainClassLoader) i.next();
try
{
Class clazz = cl.loadClassLocally(name, resolve);
if (trace)
log.trace(this.toShortString() + " loaded " + clazz + " from classloader " + cl);
cachedClasses.put(name, clazz);
return clazz;
}
catch (ClassNotFoundException ignored)
{
if (trace)
log.trace(this.toShortString() + " loading class " + name + " failed for classloader " + cl, ignored);
}
}
return null;
}
/**
* Load a class from the cache
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param resolve whether the class should be resolved
* @param classLoader the classloader initiating the request
* @return the class or null if not found
*/
protected Class loadClassFromCache(boolean trace, String name, boolean resolve, DomainClassLoader classLoader)
{
Class clazz = (Class) cachedClasses.get(name);
if (trace && clazz != null)
log.trace(this.toShortString() + " loaded from cache " + clazz);
return clazz;
}
/**
* Check whether the request has already failed
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param resolve whether the class should be resolved
* @param classLoader the classloader initiating the request
* @throws ClassNotFoundException when already failed
*/
protected void checkLoadClassFailed(boolean trace, String name, boolean resolve, DomainClassLoader classLoader) throws ClassNotFoundException
{
ClassNotFoundException cnfe = (ClassNotFoundException) failedClasses.get(name);
if (cnfe != null)
{
if (trace)
log.trace(this.toShortString() + " loading class " + name + " already failed");
throw new ClassNotFoundException("Class " + name + " not found", cnfe);
}
}
/**
* Fail this classloading attempt
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param resolve whether the class should be resolved
* @param classLoader the classloader initiating the request
* @throws ClassNotFoundException always
*/
protected void loadClassFailed(boolean trace, String name, boolean resolve, DomainClassLoader classLoader) throws ClassNotFoundException
{
if (trace)
log.trace(this.toShortString() + " loading class " + name + " failed");
ClassNotFoundException cnfe = new ClassNotFoundException("Class " + name + " not found ");
failedClasses.put(name, cnfe);
throw cnfe;
}
/**
* Load a resource from the parent
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param classLoader the classloader initiating the request
* @return the resource or null if not found
*/
protected URL loadResourceFromParent(boolean trace, String name, DomainClassLoader classLoader)
{
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " from parent " + parent.toShortString());
URL url = parent.loadResource(name, null);
if (trace)
{
if (url != null)
log.trace(this.toShortString() + " loaded resource " + url + " from parent " + parent.toShortString());
else
log.trace(this.toShortString() + " loading resource " + name + " failed from parent " + parent.toShortString());
}
return url;
}
/**
* Load a resource from the specified classloader
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param classLoader the classloader initiating the request
* @return the resource or null if not found
*/
protected URL loadResourceFromClassLoader(boolean trace, String name, DomainClassLoader classLoader)
{
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " from classloader " + classLoader);
URL url = classLoader.loadResourceLocally(name);
if (url != null)
{
if (trace)
log.trace(this.toShortString() + " loaded resource " + url + " from classloader " + classLoader);
return url;
}
return null;
}
/**
* Load a resource from the classloaders
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param classLoader the classloader initiating the request
* @return the resource or null if not found
*/
protected URL loadResourceFromClassLoaders(boolean trace, String name, DomainClassLoader classLoader)
{
List classLoaders = (List) pkgToCLs.get(ClassLoading.getPackageName(name));
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " from classloaders " + classLoaders);
if (classLoaders == null)
return null;
for (ListIterator i = classLoaders.listIterator(); i.hasNext();)
{
DomainClassLoader cl = (DomainClassLoader) i.next();
URL url = cl.loadResourceLocally(name);
if (url != null)
{
if (trace)
log.trace(this.toShortString() + " loaded resource " + url + " from classloader " + cl);
cachedResources.put(name, url);
return url;
}
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " failed from classloader " + cl);
}
return null;
}
/**
* Load a resource from the cache
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param classLoader the classloader initiating the request
* @return the resource or null if not found
*/
protected URL loadResourceFromCache(boolean trace, String name, DomainClassLoader classLoader)
{
URL url = (URL) cachedResources.get(name);
if (trace && url != null)
log.trace(this.toShortString() + " loaded resource "+ url + " from cache ");
return url;
}
/**
* Check whether the request has already failed
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param classLoader the classloader initiating the request
* @return the true when it has already failed
*/
protected boolean checkLoadResourceFailed(boolean trace, String name, DomainClassLoader classLoader)
{
if (failedResources.contains(name))
{
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " already failed");
return true;
}
return false;
}
/**
* Fail this resource attempt
*
* @param trace whether trace is enabled
* @param name the name of the class
* @param classLoader the classloader initiating the request
*/
protected void loadResourceFailed(boolean trace, String name, DomainClassLoader classLoader)
{
failedResources.add(name);
if (trace)
log.trace(this.toShortString() + " loading resource " + name + " failed");
}
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
}