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