org.apache.hadoop.hbase.coprocessor.CoprocessorClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.coprocessor.CoprocessorClassLoader.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.hadoop.hbase.coprocessor;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * ClassLoader used to load Coprocessor instances.
 * 
 * This ClassLoader always tries to load classes from the Coprocessor jar first 
 * before delegating to the parent ClassLoader, thus avoiding dependency 
 * conflicts between HBase's classpath and classes in the coprocessor's jar.  
 * Certain classes are exempt from being loaded by this ClassLoader because it 
 * would prevent them from being cast to the equivalent classes in the region 
 * server.  For example, the Coprocessor interface needs to be loaded by the 
 * region server's ClassLoader to prevent a ClassCastException when casting the 
 * coprocessor implementation.
 * 
 * This ClassLoader also handles resource loading.  In most cases this 
 * ClassLoader will attempt to load resources from the coprocessor jar first 
 * before delegating to the parent.  However, like in class loading, 
 * some resources need to be handled differently.  For all of the Hadoop 
 * default configurations (e.g. hbase-default.xml) we will check the parent 
 * ClassLoader first to prevent issues such as failing the HBase default 
 * configuration version check.
 */
public class CoprocessorClassLoader extends URLClassLoader {
    private static final Log LOG = LogFactory.getLog(CoprocessorClassLoader.class);

    /**
     * If the class being loaded starts with any of these strings, we will skip
     * trying to load it from the coprocessor jar and instead delegate 
     * directly to the parent ClassLoader.
     */
    private static final String[] CLASS_PREFIX_EXEMPTIONS = new String[] {
            // Java standard library:
            "com.sun.", "launcher.", "java.", "javax.", "org.ietf", "org.omg", "org.w3c", "org.xml", "sunw.",
            // Hadoop/HBase:
            "org.apache.hadoop", };

    /**
     * If the resource being loaded matches any of these patterns, we will first 
     * attempt to load the resource with the parent ClassLoader.  Only if the 
     * resource is not found by the parent do we attempt to load it from the 
     * coprocessor jar.
     */
    private static final Pattern[] RESOURCE_LOAD_PARENT_FIRST_PATTERNS = new Pattern[] {
            Pattern.compile("^[^-]+-default\\.xml$") };

    /**
     * Creates a CoprocessorClassLoader that loads classes from the given paths.
     * @param paths paths from which to load classes.
     * @param parent the parent ClassLoader to set.
     */
    public CoprocessorClassLoader(List<URL> paths, ClassLoader parent) {
        super(paths.toArray(new URL[] {}), parent);
    }

    @Override
    synchronized public Class<?> loadClass(String name) throws ClassNotFoundException {
        // Delegate to the parent immediately if this class is exempt
        if (isClassExempt(name)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Skipping exempt class " + name + " - delegating directly to parent");
            }
            return super.loadClass(name);
        }

        // Check whether the class has already been loaded:
        Class<?> clasz = findLoadedClass(name);
        if (clasz != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Class " + name + " already loaded");
            }
        } else {
            try {
                // Try to find this class using the URLs passed to this ClassLoader, 
                // which includes the coprocessor jar
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Finding class: " + name);
                }
                clasz = findClass(name);
            } catch (ClassNotFoundException e) {
                // Class not found using this ClassLoader, so delegate to parent
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Class " + name + " not found - delegating to parent");
                }
                try {
                    clasz = super.loadClass(name);
                } catch (ClassNotFoundException e2) {
                    // Class not found in this ClassLoader or in the parent ClassLoader
                    // Log some debug output before rethrowing ClassNotFoundException
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Class " + name + " not found in parent loader");
                    }
                    throw e2;
                }
            }
        }

        return clasz;
    }

    @Override
    synchronized public URL getResource(String name) {
        URL resource = null;
        boolean parentLoaded = false;

        // Delegate to the parent first if necessary
        if (loadResourceUsingParentFirst(name)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Checking parent first for resource " + name);
            }
            resource = super.getResource(name);
            parentLoaded = true;
        }

        if (resource == null) {
            // Try to find the resource in the coprocessor jar
            resource = findResource(name);
            if ((resource == null) && !parentLoaded) {
                // Not found in the coprocessor jar and we haven't attempted to load 
                // the resource in the parent yet; fall back to the parent
                resource = super.getResource(name);
            }
        }

        return resource;
    }

    /**
     * Determines whether the given class should be exempt from being loaded 
     * by this ClassLoader.
     * @param name the name of the class to test.
     * @return true if the class should *not* be loaded by this ClassLoader; 
     * false otherwise.
     */
    protected boolean isClassExempt(String name) {
        for (String exemptPrefix : CLASS_PREFIX_EXEMPTIONS) {
            if (name.startsWith(exemptPrefix)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines whether we should attempt to load the given resource using the  
     * parent first before attempting to load the resource using this ClassLoader.
     * @param name the name of the resource to test.
     * @return true if we should attempt to load the resource using the parent 
     * first; false if we should attempt to load the resource using this 
     * ClassLoader first.
     */
    protected boolean loadResourceUsingParentFirst(String name) {
        for (Pattern resourcePattern : RESOURCE_LOAD_PARENT_FIRST_PATTERNS) {
            if (resourcePattern.matcher(name).matches()) {
                return true;
            }
        }
        return false;
    }
}