org.impalaframework.classloader.graph.GraphClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.impalaframework.classloader.graph.GraphClassLoader.java

Source

/*
 * Copyright 2007-2010 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.impalaframework.classloader.graph;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.impalaframework.classloader.ClassRetriever;
import org.impalaframework.classloader.ModularClassLoader;
import org.impalaframework.module.ModuleDefinition;

/**
 * Class loader backed by a graph of dependent class loaders. Each module will
 * have one of these. Includes a mechanism which delegates to first to the class
 * loaders of dependent modules, and only uses the local resource class loader
 * if this unsuccessful.
 * 
 * @author Phil Zoio
 */
public class GraphClassLoader extends ClassLoader implements ModularClassLoader {

    private static final Log logger = LogFactory.getLog(GraphClassLoader.class);

    private Map<String, Class<?>> loadedApplicationClasses = new ConcurrentHashMap<String, Class<?>>();

    private Map<String, Class<?>> loadedLibraryClasses = new ConcurrentHashMap<String, Class<?>>();

    private ModuleDefinition moduleDefinition;
    private ClassRetriever classRetriever;
    private DelegateClassLoader delegateClassLoader;
    private ClassLoaderOptions options;

    public GraphClassLoader(ClassLoader parentClassLoader, DelegateClassLoader delegateClassLoader,
            ClassRetriever classRetriever, ModuleDefinition definition, ClassLoaderOptions options) {

        super(parentClassLoader);
        this.moduleDefinition = definition;
        this.classRetriever = classRetriever;
        this.delegateClassLoader = delegateClassLoader;
        this.options = options;
    }

    /* ****************************** class loader methods ******************************** */

    /**
     * Classes are always considered to be reloadable
     */
    @SuppressWarnings("rawtypes")
    public boolean isClassReloadable(Class clazz) {
        return true;
    }

    /**
     * Calls {@link #loadClass(String, boolean)} with resolve set to false
     */
    @Override
    public synchronized Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    /**
     * Attempts to load class for given name. If {@link #loadParentFirst} is set true, 
     * then will first delegate to the parent class loader, which will typically either be the 
     * system class loader or the web application class loader for web applications.
     * It will then attempt to load the class from one of the modules, starting with the 
     * root module and modules without other dependencies, finally ending searching within
     * the current module (the module with with which this class loader instance is associated).
     * 
     *  Note that if {@link #loadParentFirst} is set to false, then the module graph is searched 
     *  first before delegating to the parent class loader. This is particularly useful in environments
     *  where certain modules are on the system class path (for example, when running integration tests as a 
     *  test suite within Eclipse).
     */
    @Override
    public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {

        if (logger.isDebugEnabled()) {
            logger.debug("Entering loading class '" + className + "' from " + this);
        }

        Class<?> loadClass = null;

        if (logger.isTraceEnabled()) {
            logger.trace("For class loader with options: " + options);
        }

        if (loadClass == null) {
            //attempt to load internal library class
            loadClass = loadLibraryClass(className, true);
        }

        if (!options.isParentLoaderFirst()) {
            if (loadClass == null) {
                loadClass = loadApplicationClass(className, true);
            }
        }

        if (loadClass == null) {
            try {
                if (logger.isDebugEnabled()) {
                    logger.debug("Delegating to parent class loader to load " + className);
                }
                loadClass = getParent().loadClass(className);
            } catch (ClassNotFoundException e) {
            }
        }

        if (options.isParentLoaderFirst()) {
            if (loadClass == null) {
                loadClass = loadApplicationClass(className, true);
            }
        }

        if (loadClass != null) {
            if (resolve) {
                resolveClass(loadClass);
            }
            return loadClass;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Unable to find class " + className);
            logger.debug("Using class loader: " + this);
        }

        throw new ClassNotFoundException("Unable to find class " + className);
    }

    /* ****************************** public methods ******************************** */

    /**
     * Attempt to load a resource, first by calling
     * <code>getLocalResource</code>. If the resource is not found, the
     * {@link #delegateClassLoader} is asked to locate the resource.
     * 
     * If neither the local class loader nor the delegates can find the
     * resource, this means it is not within the module class path. At this
     * point, the rest of the class path is searched via a
     * <code>super.getResource(name)</code> class.
     * 
     * Note that while class loading first delegates the class loading request,
     * resource loading effectively works in reverse. When delegating the
     * resource loading request, modules nearest to the current module are
     * checked first. The check for the resource is only delegated to the system
     * or web application class loader if the resource is not found within the
     * module hierarchy.
     */
    @Override
    public URL getResource(String name) {

        URL url = getLocalResource(name);
        if (url != null) {
            return url;
        }

        url = delegateClassLoader.getResource(name);
        if (url != null) {
            return url;
        }

        return super.getResource(name);
    }

    /**
     * Returns enumeration of local resources, combined with those of parent
     * class loader.
     * 
     * The implementation of {@link #getResources(String)} is pretty
     * conservative. For a given resource name, only one resource URL will ever
     * be found from within the module hierarchy. Also, only the current module
     * is searched. It's ancestor and dependent modules are not searched for the
     * resource.
     * 
     * The module resource, if found, is returned as an {@link Enumeration}
     * which also includes the resources obtained from the standard or
     * application class loader via the super{@link #getResources(String)} call.
     */
    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        Enumeration<URL> resources = super.getResources(name);

        Enumeration<URL> localResources = getLocalResources(name);
        if (localResources != null) {
            List<URL> combined = new ArrayList<URL>();
            combined.addAll(Collections.list(localResources));
            combined.addAll(Collections.list(resources));
            return Collections.enumeration(combined);
        }

        return resources;
    }

    /**
     * Returns true if classes loaded by the specified class loader are visible to the current class loader.
     */
    public boolean hasVisibilityOf(ClassLoader classLoader) {
        if (classLoader == this) {
            return true;
        }
        boolean hasVisibilityOf = delegateClassLoader.hasVisibilityOf(classLoader);

        if (hasVisibilityOf)
            return hasVisibilityOf;

        ClassLoader child = this;
        ClassLoader parent = null;

        while ((parent = child.getParent()) != null) {
            if (parent == classLoader) {
                return true;
            }
            child = parent;
        }

        return false;
    }

    public void addTransformer(ClassFileTransformer transformer) {
        logger.warn(
                "No-op implementation of 'addTransformer()' invoked. Use 'load.time.weaving.enabled=true' and start JVM with '-javaagent:/path_to_aspectj_weaver/aspectjweaver.jar' switch to enable load time weaving of aspects.");
    }

    /**
     * Invokes {@link #loadCustomClass(String, boolean, boolean)} with the library class parameter set to true
     */
    public Class<?> loadLibraryClass(String className, boolean tryDelegate) {

        //FIXME issue 361 test
        if (options.isSupportsModuleLibraries()) {
            return loadCustomClass(className, tryDelegate, true);
        }
        return null;
    }

    /**
     * Invokes {@link #loadCustomClass(String, boolean, boolean)} with the library class parameter set to false
     */
    public Class<?> loadApplicationClass(String className, boolean tryDelegate) {
        return loadCustomClass(className, tryDelegate, false);
    }

    /* **************************** protected methods ***************************** */

    public ClassRetriever getClassRetriever() {
        return classRetriever;
    }

    /**
     * Implements the mechanism for loading a class within the module.
     * 
     * First checks to see whether the class is present in the local class loader cache.
     * If not, and <code>tryDelegate</code> is true, then will pass the request to the {@link #delegateClassLoader}
     * instance, which will attempts to load the class from dependent modules.
     * 
     * Finally, if the class is not found, then attempts to load it locally within the current module.
     * If the class is still not found, a {@link ClassNotFoundException} is thrown.
     * 
     * @param libraryClass if true, then assume we are trying to load class from library location
     */
    protected Class<?> loadCustomClass(String className, boolean internalLoad, boolean libraryClass) {

        if (logger.isDebugEnabled()) {
            logger.debug("Loading class '" + className + "' from " + this);
        }

        final Class<?> alreadyLoaded = getLoadedClass(className, internalLoad);

        if (alreadyLoaded != null) {

            if (logger.isDebugEnabled()) {
                logger.debug("Returning already loaded class for '" + className + "' from " + this);
            }
            return alreadyLoaded;
        }

        //first try the delegate, so that the class loaders for modules higher in the dependency
        //chain can be tried first.
        Class<?> clazz = null;

        if (clazz == null && libraryClass) {
            clazz = maybeFindLibraryClassLocally(className, internalLoad);
        }

        if (clazz == null && internalLoad) {
            if (libraryClass) {
                clazz = delegateClassLoader.loadLibraryClass(className);
            } else {
                clazz = delegateClassLoader.loadApplicationClass(className);
            }
        }

        if (clazz == null && !libraryClass) {
            clazz = attemptToLoadUsingRetriever(this.classRetriever, className, internalLoad, false);
        }

        return clazz;
    }

    /**
     * Hook which subclasses can use to attempt to load class which is not a
     * module custom class, but without delegating to the parent class loader.
     * Useful for subclasses to implement
     * @param internalLoad whether this is an internal load
     */
    protected Class<?> maybeFindLibraryClassLocally(String className, boolean internalLoad) {
        return null;
    }

    /**
     * Attempts to load class using retriever
     */
    protected Class<?> attemptToLoadUsingRetriever(ClassRetriever retriever, String className, boolean internalLoad,
            boolean libraryClass) throws ClassFormatError {

        //FIXME issue 361 test
        //FIXME if libraryClass but not internalLoad, then check to see whether options.exportsModuleLibraries is set 

        Class<?> clazz = null;
        byte[] bytes = retriever.getClassBytes(className);
        if (bytes != null) {

            if (logger.isDebugEnabled()) {
                logger.debug("Found bytes for '" + className + "' from " + this);
            }

            //bytes found - define class
            clazz = defineClass(className, bytes, 0, bytes.length, null);
            putLoadedClass(className, clazz, libraryClass);

            logger.info("Class '" + className + "' found using class loader for " + this.getModuleName());
        }
        return clazz;
    }

    /**
     * Attempts to find a resource from one of the file system locations
     * specified in a constructor.
     * 
     * @param name the name of the resource to load
     * @return a <code>URL</code> instance, if the resource can be found,
     * otherwise null.
     */
    protected URL getLocalResource(String name) {
        return classRetriever.findResource(name);
    }

    protected Enumeration<URL> getLocalResources(String name) {
        return classRetriever.findResources(name);
    }

    protected final String getModuleName() {
        return moduleDefinition.getName();
    }

    protected ClassLoaderOptions getOptions() {
        return options;
    }

    /**
     * Returns the {@link DelegateClassLoader} used to find classes and resources in other modules within the application
     */
    protected DelegateClassLoader getDelegateClassLoader() {
        return delegateClassLoader;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (Boolean.valueOf(System.getProperty("display.classloader.finalize"))) {
                System.out.println("Finalizing class loader for " + moduleDefinition.getName());
            }
            if (Boolean.valueOf(System.getProperty("log.classloader.finalize"))) {
                logger.info("Finalizing class loader for " + moduleDefinition.getName());
            }
        } finally {
            super.finalize();
        }
    }

    private void putLoadedClass(String className, Class<?> clazz, boolean libraryClass) {
        if (libraryClass) {
            loadedLibraryClasses.put(className, clazz);
        } else {
            loadedApplicationClasses.put(className, clazz);
        }
    }

    private Class<?> getLoadedClass(String className, boolean internalLoad) {

        Class<?> alreadyLoaded = loadedApplicationClasses.get(className);
        if (alreadyLoaded == null) {

            //FIXME if internal load, then check to see whether exportModuleLibraries is set

            alreadyLoaded = loadedLibraryClasses.get(className);
            if (alreadyLoaded != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Retrieved already loaded library class " + className + " from module "
                            + moduleDefinition.getName());
                }
            }
        }
        return alreadyLoaded;
    }

    Map<String, Class<?>> getLoadedApplicationClasses() {
        return Collections.unmodifiableMap(loadedApplicationClasses);
    }

    @Override
    public String toString() {
        String lineSeparator = System.getProperty("line.separator");

        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("Class loader for " + moduleDefinition.getName()).append(lineSeparator);
        stringBuffer.append("Options: " + options).append(lineSeparator);
        stringBuffer.append(delegateClassLoader);

        return stringBuffer.toString();
    }

}