1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.hadoop.hbase.coprocessor; 19 20 import java.net.URL; 21 import java.net.URLClassLoader; 22 import java.util.List; 23 import java.util.regex.Pattern; 24 25 import org.apache.commons.logging.Log; 26 import org.apache.commons.logging.LogFactory; 27 28 /** 29 * ClassLoader used to load Coprocessor instances. 30 * 31 * This ClassLoader always tries to load classes from the Coprocessor jar first 32 * before delegating to the parent ClassLoader, thus avoiding dependency 33 * conflicts between HBase's classpath and classes in the coprocessor's jar. 34 * Certain classes are exempt from being loaded by this ClassLoader because it 35 * would prevent them from being cast to the equivalent classes in the region 36 * server. For example, the Coprocessor interface needs to be loaded by the 37 * region server's ClassLoader to prevent a ClassCastException when casting the 38 * coprocessor implementation. 39 * 40 * This ClassLoader also handles resource loading. In most cases this 41 * ClassLoader will attempt to load resources from the coprocessor jar first 42 * before delegating to the parent. However, like in class loading, 43 * some resources need to be handled differently. For all of the Hadoop 44 * default configurations (e.g. hbase-default.xml) we will check the parent 45 * ClassLoader first to prevent issues such as failing the HBase default 46 * configuration version check. 47 */ 48 public class CoprocessorClassLoader extends URLClassLoader { 49 private static final Log LOG = 50 LogFactory.getLog(CoprocessorClassLoader.class); 51 52 /** 53 * If the class being loaded starts with any of these strings, we will skip 54 * trying to load it from the coprocessor jar and instead delegate 55 * directly to the parent ClassLoader. 56 */ 57 private static final String[] CLASS_PREFIX_EXEMPTIONS = new String[] { 58 // Java standard library: 59 "com.sun.", 60 "launcher.", 61 "java.", 62 "javax.", 63 "org.ietf", 64 "org.omg", 65 "org.w3c", 66 "org.xml", 67 "sunw.", 68 // logging 69 "org.apache.commons.logging", 70 "org.apache.log4j", 71 "com.hadoop", 72 // Hadoop/HBase/ZK: 73 "org.apache.hadoop", 74 "org.apache.zookeeper", 75 }; 76 77 /** 78 * If the resource being loaded matches any of these patterns, we will first 79 * attempt to load the resource with the parent ClassLoader. Only if the 80 * resource is not found by the parent do we attempt to load it from the 81 * coprocessor jar. 82 */ 83 private static final Pattern[] RESOURCE_LOAD_PARENT_FIRST_PATTERNS = 84 new Pattern[] { 85 Pattern.compile("^[^-]+-default\\.xml$") 86 }; 87 88 /** 89 * Parent classloader used to load any class not matching the exemption list. 90 */ 91 private final ClassLoader parent; 92 93 /** 94 * Creates a CoprocessorClassLoader that loads classes from the given paths. 95 * @param paths paths from which to load classes. 96 * @param parent the parent ClassLoader to set. 97 */ 98 public CoprocessorClassLoader(List<URL> paths, ClassLoader parent) { 99 super(paths.toArray(new URL[]{}), parent); 100 this.parent = parent; 101 if (parent == null) { 102 throw new IllegalArgumentException("No parent classloader!"); 103 } 104 } 105 106 @Override 107 synchronized public Class<?> loadClass(String name) 108 throws ClassNotFoundException { 109 // Delegate to the parent immediately if this class is exempt 110 if (isClassExempt(name)) { 111 if (LOG.isDebugEnabled()) { 112 LOG.debug("Skipping exempt class " + name + 113 " - delegating directly to parent"); 114 } 115 return parent.loadClass(name); 116 } 117 118 // Check whether the class has already been loaded: 119 Class<?> clasz = findLoadedClass(name); 120 if (clasz != null) { 121 if (LOG.isDebugEnabled()) { 122 LOG.debug("Class " + name + " already loaded"); 123 } 124 } 125 else { 126 try { 127 // Try to find this class using the URLs passed to this ClassLoader, 128 // which includes the coprocessor jar 129 if (LOG.isDebugEnabled()) { 130 LOG.debug("Finding class: " + name); 131 } 132 clasz = findClass(name); 133 } catch (ClassNotFoundException e) { 134 // Class not found using this ClassLoader, so delegate to parent 135 if (LOG.isDebugEnabled()) { 136 LOG.debug("Class " + name + " not found - delegating to parent"); 137 } 138 try { 139 clasz = parent.loadClass(name); 140 } catch (ClassNotFoundException e2) { 141 // Class not found in this ClassLoader or in the parent ClassLoader 142 // Log some debug output before rethrowing ClassNotFoundException 143 if (LOG.isDebugEnabled()) { 144 LOG.debug("Class " + name + " not found in parent loader"); 145 } 146 throw e2; 147 } 148 } 149 } 150 151 return clasz; 152 } 153 154 @Override 155 synchronized public URL getResource(String name) { 156 URL resource = null; 157 boolean parentLoaded = false; 158 159 // Delegate to the parent first if necessary 160 if (loadResourceUsingParentFirst(name)) { 161 if (LOG.isDebugEnabled()) { 162 LOG.debug("Checking parent first for resource " + name); 163 } 164 resource = super.getResource(name); 165 parentLoaded = true; 166 } 167 168 if (resource == null) { 169 // Try to find the resource in the coprocessor jar 170 resource = findResource(name); 171 if ((resource == null) && !parentLoaded) { 172 // Not found in the coprocessor jar and we haven't attempted to load 173 // the resource in the parent yet; fall back to the parent 174 resource = super.getResource(name); 175 } 176 } 177 178 return resource; 179 } 180 181 /** 182 * Determines whether the given class should be exempt from being loaded 183 * by this ClassLoader. 184 * @param name the name of the class to test. 185 * @return true if the class should *not* be loaded by this ClassLoader; 186 * false otherwise. 187 */ 188 protected boolean isClassExempt(String name) { 189 for (String exemptPrefix : CLASS_PREFIX_EXEMPTIONS) { 190 if (name.startsWith(exemptPrefix)) { 191 return true; 192 } 193 } 194 return false; 195 } 196 197 /** 198 * Determines whether we should attempt to load the given resource using the 199 * parent first before attempting to load the resource using this ClassLoader. 200 * @param name the name of the resource to test. 201 * @return true if we should attempt to load the resource using the parent 202 * first; false if we should attempt to load the resource using this 203 * ClassLoader first. 204 */ 205 protected boolean loadResourceUsingParentFirst(String name) { 206 for (Pattern resourcePattern : RESOURCE_LOAD_PARENT_FIRST_PATTERNS) { 207 if (resourcePattern.matcher(name).matches()) { 208 return true; 209 } 210 } 211 return false; 212 } 213 }