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 }