/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.module.artifact.classloader;
import static java.lang.Boolean.valueOf;
import static java.lang.String.format;
import static java.lang.System.getProperty;
import static org.mule.runtime.api.util.Preconditions.checkArgument;
import static org.mule.runtime.core.api.config.MuleProperties.MULE_LOG_VERBOSE_CLASSLOADING;
import org.mule.runtime.core.util.ClassUtils;
import org.mule.runtime.module.artifact.classloader.exception.CompositeClassNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.CompoundEnumeration;
import sun.net.www.protocol.jar.Handler;
/**
* Defines a {@link ClassLoader} which enables the control of the class loading lookup mode.
* <p/>
* By using a {@link ClassLoaderLookupPolicy} this classLoader can use parent-first, parent-only or child-first classloading
* lookup mode per package.
*/
public class FineGrainedControlClassLoader extends URLClassLoader
implements DisposableClassLoader, ClassLoaderLookupPolicyProvider {
static {
registerAsParallelCapable();
}
protected Logger logger = LoggerFactory.getLogger(getClass());
private final ClassLoaderLookupPolicy lookupPolicy;
private final boolean verboseLogging;
public FineGrainedControlClassLoader(URL[] urls, ClassLoader parent, ClassLoaderLookupPolicy lookupPolicy) {
super(urls, parent, new NonCachingURLStreamHandlerFactory());
checkArgument(lookupPolicy != null, "Lookup policy cannot be null");
this.lookupPolicy = lookupPolicy;
verboseLogging = logger.isDebugEnabled() || isVerboseLoggingEnabled();
}
private boolean isVerboseLoggingEnabled() {
return logger.isInfoEnabled() && valueOf(getProperty(MULE_LOG_VERBOSE_CLASSLOADING));
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> result = findLoadedClass(name);
if (result != null) {
return result;
}
final LookupStrategy lookupStrategy = lookupPolicy.getClassLookupStrategy(name);
if (lookupStrategy == null) {
throw new NullPointerException(format("Unable to find a lookup strategy for '%s' from %s", name, this));
}
if (verboseLogging) {
logLoadingClass(name, lookupStrategy, "Loading class '%s' with '%s' on '%s'", this);
}
// Gather information about the exceptions in each of the searched class loaders to provide
// troubleshooting information in case of throwing a ClassNotFoundException.
List<ClassNotFoundException> exceptions = new ArrayList<>();
for (ClassLoader classLoader : lookupStrategy.getClassLoaders(this)) {
try {
if (classLoader == this) {
result = findLocalClass(name);
break;
} else {
result = findParentClass(name, classLoader);
break;
}
} catch (ClassNotFoundException e) {
exceptions.add(e);
}
}
if (result == null) {
throw new CompositeClassNotFoundException(name, lookupStrategy, exceptions);
}
if (verboseLogging) {
logLoadedClass(name, result);
}
if (resolve) {
resolveClass(result);
}
return result;
}
private void logLoadingClass(String name, LookupStrategy lookupStrategy, String format,
FineGrainedControlClassLoader fineGrainedControlClassLoader) {
final String message = format(format, name, lookupStrategy, fineGrainedControlClassLoader);
doVerboseLogging(message);
}
private void logLoadedClass(String name, Class<?> result) {
final boolean loadedFromChild = result.getClassLoader() == this;
final String message = format("Loaded class '%s' from %s: %s", name, (loadedFromChild ? "child" : "parent"),
(loadedFromChild ? this : getParent()));
doVerboseLogging(message);
}
private void doVerboseLogging(String message) {
if (logger.isDebugEnabled()) {
logger.debug(message);
} else {
logger.info(message);
}
}
protected Class<?> findParentClass(String name, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader != null) {
return classLoader.loadClass(name);
} else {
return findSystemClass(name);
}
}
@Override
public URL getResource(String name) {
URL url = findResource(name);
if (url == null && getParent() != null) {
url = getParent().getResource(name);
}
return url;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
tmp[0] = findResources(name);
if (getParent() != null) {
tmp[1] = getParent().getResources(name);
}
return new CompoundEnumeration<>(tmp);
}
public Class<?> findLocalClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> result = findLoadedClass(name);
if (result != null) {
return result;
}
return super.findClass(name);
}
}
@Override
public ClassLoaderLookupPolicy getClassLoaderLookupPolicy() {
return lookupPolicy;
}
/**
* Disposes the {@link ClassLoader} by closing all the resources opened by this {@link ClassLoader}. See
* {@link URLClassLoader#close()}.
*/
@Override
public void dispose() {
try {
// Java 7 added support for closing a URLClassLoader, it will close any resources opened by this classloader
close();
} catch (IOException e) {
// ignore
}
try {
// fix groovy compiler leaks http://www.mulesoft.org/jira/browse/MULE-5125
final Class clazz = ClassUtils.loadClass("org.codehaus.groovy.transform.ASTTransformationVisitor", getClass());
final Field compUnit = clazz.getDeclaredField("compUnit");
compUnit.setAccessible(true);
// static field
compUnit.set(null, null);
} catch (Throwable t) {
// ignore
}
}
protected static class NonCachingURLStreamHandlerFactory implements URLStreamHandlerFactory {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return new NonCachingJarResourceURLStreamHandler();
}
}
/**
* Prevents jar caching for this classloader, mainly to fix the static ResourceBundle mess/cache that keeps connections open no
* matter what.
*/
private static class NonCachingJarResourceURLStreamHandler extends Handler {
public NonCachingJarResourceURLStreamHandler() {
super();
}
@Override
protected java.net.URLConnection openConnection(URL u) throws IOException {
JarURLConnection c = new sun.net.www.protocol.jar.JarURLConnection(u, this);
c.setUseCaches(false);
return c;
}
}
}