| /** |
| * 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; |
| } |
| } |