1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.util;
19
20 import java.io.File;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.net.URL;
24 import java.security.AccessController;
25 import java.security.PrivilegedAction;
26 import java.util.Collection;
27 import java.util.Enumeration;
28 import java.util.HashSet;
29 import java.util.concurrent.ConcurrentMap;
30 import java.util.concurrent.locks.Lock;
31 import java.util.jar.JarEntry;
32 import java.util.jar.JarFile;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.hadoop.hbase.classification.InterfaceAudience;
39 import org.apache.hadoop.conf.Configuration;
40 import org.apache.hadoop.fs.FileSystem;
41 import org.apache.hadoop.fs.Path;
42 import org.apache.hadoop.io.IOUtils;
43
44 import com.google.common.base.Preconditions;
45 import com.google.common.collect.MapMaker;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 @InterfaceAudience.Private
75 public class CoprocessorClassLoader extends ClassLoaderBase {
76 private static final Log LOG = LogFactory.getLog(CoprocessorClassLoader.class);
77
78
79
80 private static final String TMP_JARS_DIR = File.separator
81 + "jars" + File.separator + "tmp" + File.separator;
82
83
84
85
86
87
88
89 private static final ConcurrentMap<Path, CoprocessorClassLoader> classLoadersCache =
90 new MapMaker().concurrencyLevel(3).weakValues().makeMap();
91
92
93
94
95
96
97 private static final String[] CLASS_PREFIX_EXEMPTIONS = new String[] {
98
99 "com.sun.",
100 "java.",
101 "javax.",
102 "org.ietf",
103 "org.omg",
104 "org.w3c",
105 "org.xml",
106 "sunw.",
107
108 "org.apache.commons.logging",
109 "org.apache.log4j",
110 "com.hadoop",
111
112 "org.apache.hadoop.security",
113 "org.apache.hadoop.HadoopIllegalArgumentException",
114 "org.apache.hadoop.conf",
115 "org.apache.hadoop.fs",
116 "org.apache.hadoop.http",
117 "org.apache.hadoop.io",
118 "org.apache.hadoop.ipc",
119 "org.apache.hadoop.metrics",
120 "org.apache.hadoop.metrics2",
121 "org.apache.hadoop.net",
122 "org.apache.hadoop.util",
123 "org.apache.hadoop.hdfs",
124 "org.apache.hadoop.hbase",
125 "org.apache.zookeeper",
126 };
127
128
129
130
131
132
133 private static final Pattern[] RESOURCE_LOAD_PARENT_FIRST_PATTERNS =
134 new Pattern[] {
135 Pattern.compile("^[^-]+-default\\.xml$")
136 };
137
138 private static final Pattern libJarPattern = Pattern.compile("[/]?lib/([^/]+\\.jar)");
139
140
141
142
143 private static final KeyLocker<String> locker = new KeyLocker<String>();
144
145
146
147
148
149 static final HashSet<String> parentDirLockSet = new HashSet<String>();
150
151
152
153
154 private CoprocessorClassLoader(ClassLoader parent) {
155 super(parent);
156 }
157
158 private void init(Path path, String pathPrefix,
159 Configuration conf) throws IOException {
160
161 String parentDirStr =
162 conf.get(LOCAL_DIR_KEY, DEFAULT_LOCAL_DIR) + TMP_JARS_DIR;
163 synchronized (parentDirLockSet) {
164 if (!parentDirLockSet.contains(parentDirStr)) {
165 Path parentDir = new Path(parentDirStr);
166 FileSystem fs = FileSystem.getLocal(conf);
167 fs.delete(parentDir, true);
168 parentDirLockSet.add(parentDirStr);
169 if (!fs.mkdirs(parentDir) && !fs.getFileStatus(parentDir).isDirectory()) {
170 throw new RuntimeException("Failed to create local dir " + parentDirStr
171 + ", CoprocessorClassLoader failed to init");
172 }
173 }
174 }
175
176 FileSystem fs = path.getFileSystem(conf);
177 File dst = new File(parentDirStr, "." + pathPrefix + "."
178 + path.getName() + "." + System.currentTimeMillis() + ".jar");
179 fs.copyToLocalFile(path, new Path(dst.toString()));
180 dst.deleteOnExit();
181
182 addURL(dst.getCanonicalFile().toURI().toURL());
183
184 JarFile jarFile = new JarFile(dst.toString());
185 try {
186 Enumeration<JarEntry> entries = jarFile.entries();
187 while (entries.hasMoreElements()) {
188 JarEntry entry = entries.nextElement();
189 Matcher m = libJarPattern.matcher(entry.getName());
190 if (m.matches()) {
191 File file = new File(parentDirStr, "." + pathPrefix + "."
192 + path.getName() + "." + System.currentTimeMillis() + "." + m.group(1));
193 IOUtils.copyBytes(jarFile.getInputStream(entry),
194 new FileOutputStream(file), conf, true);
195 file.deleteOnExit();
196 addURL(file.toURI().toURL());
197 }
198 }
199 } finally {
200 jarFile.close();
201 }
202 }
203
204
205 public static CoprocessorClassLoader getIfCached(final Path path) {
206 Preconditions.checkNotNull(path, "The jar path is null!");
207 return classLoadersCache.get(path);
208 }
209
210
211 public static Collection<? extends ClassLoader> getAllCached() {
212 return classLoadersCache.values();
213 }
214
215
216 public static void clearCache() {
217 classLoadersCache.clear();
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231 public static CoprocessorClassLoader getClassLoader(final Path path,
232 final ClassLoader parent, final String pathPrefix,
233 final Configuration conf) throws IOException {
234 CoprocessorClassLoader cl = getIfCached(path);
235 String pathStr = path.toString();
236 if (cl != null) {
237 LOG.debug("Found classloader "+ cl + " for "+ pathStr);
238 return cl;
239 }
240
241 if (!pathStr.endsWith(".jar")) {
242 throw new IOException(pathStr + ": not a jar file?");
243 }
244
245 Lock lock = locker.acquireLock(pathStr);
246 try {
247 cl = getIfCached(path);
248 if (cl != null) {
249 LOG.debug("Found classloader "+ cl + " for "+ pathStr);
250 return cl;
251 }
252
253 cl = AccessController.doPrivileged(
254 new PrivilegedAction<CoprocessorClassLoader>() {
255 @Override
256 public CoprocessorClassLoader run() {
257 return new CoprocessorClassLoader(parent);
258 }
259 });
260
261 cl.init(path, pathPrefix, conf);
262
263
264 CoprocessorClassLoader prev = classLoadersCache.putIfAbsent(path, cl);
265 if (prev != null) {
266
267 LOG.warn("THIS SHOULD NOT HAPPEN, a class loader"
268 +" is already cached for " + pathStr);
269 cl = prev;
270 }
271 return cl;
272 } finally {
273 lock.unlock();
274 }
275 }
276
277 @Override
278 public Class<?> loadClass(String name)
279 throws ClassNotFoundException {
280
281 if (isClassExempt(name)) {
282 if (LOG.isDebugEnabled()) {
283 LOG.debug("Skipping exempt class " + name +
284 " - delegating directly to parent");
285 }
286 return parent.loadClass(name);
287 }
288
289 synchronized (getClassLoadingLock(name)) {
290
291 Class<?> clasz = findLoadedClass(name);
292 if (clasz != null) {
293 if (LOG.isDebugEnabled()) {
294 LOG.debug("Class " + name + " already loaded");
295 }
296 }
297 else {
298 try {
299
300 if (LOG.isDebugEnabled()) {
301 LOG.debug("Finding class: " + name);
302 }
303 clasz = findClass(name);
304 } catch (ClassNotFoundException e) {
305
306 if (LOG.isDebugEnabled()) {
307 LOG.debug("Class " + name + " not found - delegating to parent");
308 }
309 try {
310 clasz = parent.loadClass(name);
311 } catch (ClassNotFoundException e2) {
312
313
314 if (LOG.isDebugEnabled()) {
315 LOG.debug("Class " + name + " not found in parent loader");
316 }
317 throw e2;
318 }
319 }
320 }
321 return clasz;
322 }
323 }
324
325 @Override
326 public URL getResource(String name) {
327 URL resource = null;
328 boolean parentLoaded = false;
329
330
331 if (loadResourceUsingParentFirst(name)) {
332 if (LOG.isDebugEnabled()) {
333 LOG.debug("Checking parent first for resource " + name);
334 }
335 resource = super.getResource(name);
336 parentLoaded = true;
337 }
338
339 if (resource == null) {
340 synchronized (getClassLoadingLock(name)) {
341
342 resource = findResource(name);
343 if ((resource == null) && !parentLoaded) {
344
345
346 resource = super.getResource(name);
347 }
348 }
349 }
350 return resource;
351 }
352
353
354
355
356
357
358
359
360 protected boolean isClassExempt(String name) {
361 for (String exemptPrefix : CLASS_PREFIX_EXEMPTIONS) {
362 if (name.startsWith(exemptPrefix)) {
363 return true;
364 }
365 }
366 return false;
367 }
368
369
370
371
372
373
374
375
376
377 protected boolean loadResourceUsingParentFirst(String name) {
378 for (Pattern resourcePattern : RESOURCE_LOAD_PARENT_FIRST_PATTERNS) {
379 if (resourcePattern.matcher(name).matches()) {
380 return true;
381 }
382 }
383 return false;
384 }
385 }