1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase;
19
20 import static org.junit.Assert.assertArrayEquals;
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertTrue;
24
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.PrintStream;
30 import java.net.URL;
31 import java.net.URLClassLoader;
32 import java.util.HashSet;
33 import java.util.Set;
34 import java.util.concurrent.atomic.AtomicLong;
35 import java.util.jar.Attributes;
36 import java.util.jar.JarEntry;
37 import java.util.jar.JarOutputStream;
38 import java.util.jar.Manifest;
39
40 import javax.tools.JavaCompiler;
41 import javax.tools.ToolProvider;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45 import org.apache.hadoop.hbase.testclassification.SmallTests;
46 import org.junit.AfterClass;
47 import org.junit.BeforeClass;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.experimental.categories.Category;
51 import org.junit.rules.TestName;
52
53 @Category(SmallTests.class)
54 public class TestClassFinder {
55 private static final Log LOG = LogFactory.getLog(TestClassFinder.class);
56
57 @Rule public TestName name = new TestName();
58 private static final HBaseCommonTestingUtility testUtil = new HBaseCommonTestingUtility();
59 private static final String BASEPKG = "tfcpkg";
60 private static final String PREFIX = "Prefix";
61
62
63
64
65 private static AtomicLong testCounter = new AtomicLong(0);
66 private static AtomicLong jarCounter = new AtomicLong(0);
67
68 private static String basePath = null;
69
70 private static CustomClassloader classLoader;
71
72 @BeforeClass
73 public static void createTestDir() throws IOException {
74 basePath = testUtil.getDataTestDir(TestClassFinder.class.getSimpleName()).toString();
75 if (!basePath.endsWith("/")) {
76 basePath += "/";
77 }
78
79 File testDir = new File(basePath);
80 if (testDir.exists()) {
81 deleteTestDir();
82 }
83 assertTrue(testDir.mkdirs());
84 LOG.info("Using new, clean directory=" + testDir);
85
86 classLoader = new CustomClassloader(new URL[0], ClassLoader.getSystemClassLoader());
87 }
88
89 @AfterClass
90 public static void deleteTestDir() {
91 testUtil.cleanupTestDir(TestClassFinder.class.getSimpleName());
92 }
93
94 @Test
95 public void testClassFinderCanFindClassesInJars() throws Exception {
96 long counter = testCounter.incrementAndGet();
97 FileAndPath c1 = compileTestClass(counter, "", "c1");
98 FileAndPath c2 = compileTestClass(counter, ".nested", "c2");
99 FileAndPath c3 = compileTestClass(counter, "", "c3");
100 packageAndLoadJar(c1, c3);
101 packageAndLoadJar(c2);
102
103 ClassFinder allClassesFinder = new ClassFinder(classLoader);
104 Set<Class<?>> allClasses = allClassesFinder.findClasses(
105 makePackageName("", counter), false);
106 assertEquals(3, allClasses.size());
107 }
108
109 @Test
110 public void testClassFinderHandlesConflicts() throws Exception {
111 long counter = testCounter.incrementAndGet();
112 FileAndPath c1 = compileTestClass(counter, "", "c1");
113 FileAndPath c2 = compileTestClass(counter, "", "c2");
114 packageAndLoadJar(c1, c2);
115 packageAndLoadJar(c1);
116
117 ClassFinder allClassesFinder = new ClassFinder(classLoader);
118 Set<Class<?>> allClasses = allClassesFinder.findClasses(
119 makePackageName("", counter), false);
120 assertEquals(2, allClasses.size());
121 }
122
123 @Test
124 public void testClassFinderHandlesNestedPackages() throws Exception {
125 final String NESTED = ".nested";
126 final String CLASSNAME1 = name.getMethodName() + "1";
127 final String CLASSNAME2 = name.getMethodName() + "2";
128 long counter = testCounter.incrementAndGet();
129 FileAndPath c1 = compileTestClass(counter, "", "c1");
130 FileAndPath c2 = compileTestClass(counter, NESTED, CLASSNAME1);
131 FileAndPath c3 = compileTestClass(counter, NESTED, CLASSNAME2);
132 packageAndLoadJar(c1, c2);
133 packageAndLoadJar(c3);
134
135 ClassFinder allClassesFinder = new ClassFinder(classLoader);
136 Set<Class<?>> nestedClasses = allClassesFinder.findClasses(
137 makePackageName(NESTED, counter), false);
138 assertEquals(2, nestedClasses.size());
139 Class<?> nestedClass1 = makeClass(NESTED, CLASSNAME1, counter);
140 assertTrue(nestedClasses.contains(nestedClass1));
141 Class<?> nestedClass2 = makeClass(NESTED, CLASSNAME2, counter);
142 assertTrue(nestedClasses.contains(nestedClass2));
143 }
144
145 @Test
146 public void testClassFinderFiltersByNameInJar() throws Exception {
147 final long counter = testCounter.incrementAndGet();
148 final String classNamePrefix = name.getMethodName();
149 LOG.info("Created jar " + createAndLoadJar("", classNamePrefix, counter));
150
151 ClassFinder.FileNameFilter notExcNameFilter = new ClassFinder.FileNameFilter() {
152 @Override
153 public boolean isCandidateFile(String fileName, String absFilePath) {
154 return !fileName.startsWith(PREFIX);
155 }
156 };
157 ClassFinder incClassesFinder = new ClassFinder(null, notExcNameFilter, null, classLoader);
158 Set<Class<?>> incClasses = incClassesFinder.findClasses(
159 makePackageName("", counter), false);
160 assertEquals(1, incClasses.size());
161 Class<?> incClass = makeClass("", classNamePrefix, counter);
162 assertTrue(incClasses.contains(incClass));
163 }
164
165 @Test
166 public void testClassFinderFiltersByClassInJar() throws Exception {
167 final long counter = testCounter.incrementAndGet();
168 final String classNamePrefix = name.getMethodName();
169 LOG.info("Created jar " + createAndLoadJar("", classNamePrefix, counter));
170
171 final ClassFinder.ClassFilter notExcClassFilter = new ClassFinder.ClassFilter() {
172 @Override
173 public boolean isCandidateClass(Class<?> c) {
174 return !c.getSimpleName().startsWith(PREFIX);
175 }
176 };
177 ClassFinder incClassesFinder = new ClassFinder(null, null, notExcClassFilter, classLoader);
178 Set<Class<?>> incClasses = incClassesFinder.findClasses(
179 makePackageName("", counter), false);
180 assertEquals(1, incClasses.size());
181 Class<?> incClass = makeClass("", classNamePrefix, counter);
182 assertTrue(incClasses.contains(incClass));
183 }
184
185 private static String createAndLoadJar(final String packageNameSuffix,
186 final String classNamePrefix, final long counter) throws Exception {
187 FileAndPath c1 = compileTestClass(counter, packageNameSuffix, classNamePrefix);
188 FileAndPath c2 = compileTestClass(counter, packageNameSuffix, PREFIX + "1");
189 FileAndPath c3 = compileTestClass(counter, packageNameSuffix, PREFIX + classNamePrefix + "2");
190 return packageAndLoadJar(c1, c2, c3);
191 }
192
193 @Test
194 public void testClassFinderFiltersByPathInJar() throws Exception {
195 final String CLASSNAME = name.getMethodName();
196 long counter = testCounter.incrementAndGet();
197 FileAndPath c1 = compileTestClass(counter, "", CLASSNAME);
198 FileAndPath c2 = compileTestClass(counter, "", "c2");
199 packageAndLoadJar(c1);
200 final String excludedJar = packageAndLoadJar(c2);
201
202
203
204
205 final String excludedJarResource =
206 new File(excludedJar).toURI().getRawSchemeSpecificPart();
207
208 final ClassFinder.ResourcePathFilter notExcJarFilter =
209 new ClassFinder.ResourcePathFilter() {
210 @Override
211 public boolean isCandidatePath(String resourcePath, boolean isJar) {
212 return !isJar || !resourcePath.equals(excludedJarResource);
213 }
214 };
215 ClassFinder incClassesFinder = new ClassFinder(notExcJarFilter, null, null, classLoader);
216 Set<Class<?>> incClasses = incClassesFinder.findClasses(
217 makePackageName("", counter), false);
218 assertEquals(1, incClasses.size());
219 Class<?> incClass = makeClass("", CLASSNAME, counter);
220 assertTrue(incClasses.contains(incClass));
221 }
222
223 @Test
224 public void testClassFinderCanFindClassesInDirs() throws Exception {
225
226
227 final long counter = testCounter.incrementAndGet();
228 final String classNamePrefix = name.getMethodName();
229 String pkgNameSuffix = name.getMethodName();
230 LOG.info("Created jar " + createAndLoadJar(pkgNameSuffix, classNamePrefix, counter));
231 ClassFinder allClassesFinder = new ClassFinder(classLoader);
232 String pkgName = makePackageName(pkgNameSuffix, counter);
233 Set<Class<?>> allClasses = allClassesFinder.findClasses(pkgName, false);
234 assertTrue("Classes in " + pkgName, allClasses.size() > 0);
235 String classNameToFind = classNamePrefix + counter;
236 assertTrue(contains(allClasses, classNameToFind));
237 }
238
239 private static boolean contains(final Set<Class<?>> classes, final String simpleName) {
240 for (Class<?> c: classes) {
241 if (c.getSimpleName().equals(simpleName)) {
242 return true;
243 }
244 }
245 return false;
246 }
247
248 @Test
249 public void testClassFinderFiltersByNameInDirs() throws Exception {
250
251
252 final long counter = testCounter.incrementAndGet();
253 final String classNamePrefix = name.getMethodName();
254 String pkgNameSuffix = name.getMethodName();
255 LOG.info("Created jar " + createAndLoadJar(pkgNameSuffix, classNamePrefix, counter));
256 final String classNameToFilterOut = classNamePrefix + counter;
257 final ClassFinder.FileNameFilter notThisFilter = new ClassFinder.FileNameFilter() {
258 @Override
259 public boolean isCandidateFile(String fileName, String absFilePath) {
260 return !fileName.equals(classNameToFilterOut + ".class");
261 }
262 };
263 String pkgName = makePackageName(pkgNameSuffix, counter);
264 ClassFinder allClassesFinder = new ClassFinder(classLoader);
265 Set<Class<?>> allClasses = allClassesFinder.findClasses(pkgName, false);
266 assertTrue("Classes in " + pkgName, allClasses.size() > 0);
267 ClassFinder notThisClassFinder = new ClassFinder(null, notThisFilter, null, classLoader);
268 Set<Class<?>> notAllClasses = notThisClassFinder.findClasses(pkgName, false);
269 assertFalse(contains(notAllClasses, classNameToFilterOut));
270 assertEquals(allClasses.size() - 1, notAllClasses.size());
271 }
272
273 @Test
274 public void testClassFinderFiltersByClassInDirs() throws Exception {
275
276
277 final long counter = testCounter.incrementAndGet();
278 final String classNamePrefix = name.getMethodName();
279 String pkgNameSuffix = name.getMethodName();
280 LOG.info("Created jar " + createAndLoadJar(pkgNameSuffix, classNamePrefix, counter));
281 final Class<?> clazz = makeClass(pkgNameSuffix, classNamePrefix, counter);
282 final ClassFinder.ClassFilter notThisFilter = new ClassFinder.ClassFilter() {
283 @Override
284 public boolean isCandidateClass(Class<?> c) {
285 return c != clazz;
286 }
287 };
288 String pkgName = makePackageName(pkgNameSuffix, counter);
289 ClassFinder allClassesFinder = new ClassFinder(classLoader);
290 Set<Class<?>> allClasses = allClassesFinder.findClasses(pkgName, false);
291 assertTrue("Classes in " + pkgName, allClasses.size() > 0);
292 ClassFinder notThisClassFinder = new ClassFinder(null, null, notThisFilter, classLoader);
293 Set<Class<?>> notAllClasses = notThisClassFinder.findClasses(pkgName, false);
294 assertFalse(contains(notAllClasses, clazz.getSimpleName()));
295 assertEquals(allClasses.size() - 1, notAllClasses.size());
296 }
297
298 @Test
299 public void testClassFinderFiltersByPathInDirs() throws Exception {
300 final String hardcodedThisSubdir = "hbase-common";
301 final ClassFinder.ResourcePathFilter notExcJarFilter = new ClassFinder.ResourcePathFilter() {
302 @Override
303 public boolean isCandidatePath(String resourcePath, boolean isJar) {
304 return isJar || !resourcePath.contains(hardcodedThisSubdir);
305 }
306 };
307 String thisPackage = this.getClass().getPackage().getName();
308 ClassFinder notThisClassFinder = new ClassFinder(notExcJarFilter, null, null, classLoader);
309 Set<Class<?>> notAllClasses = notThisClassFinder.findClasses(thisPackage, false);
310 assertFalse(notAllClasses.contains(this.getClass()));
311 }
312
313 @Test
314 public void testClassFinderDefaultsToOwnPackage() throws Exception {
315
316
317 ClassFinder allClassesFinder = new ClassFinder(classLoader);
318 Set<Class<?>> pkgClasses = allClassesFinder.findClasses(
319 ClassFinder.class.getPackage().getName(), false);
320 Set<Class<?>> defaultClasses = allClassesFinder.findClasses(false);
321 assertArrayEquals(pkgClasses.toArray(), defaultClasses.toArray());
322 }
323
324 private static class FileAndPath {
325 String path;
326 File file;
327 public FileAndPath(String path, File file) {
328 this.file = file;
329 this.path = path;
330 }
331 }
332
333 private static Class<?> makeClass(String nestedPkgSuffix,
334 String className, long counter) throws ClassNotFoundException {
335 String name = makePackageName(nestedPkgSuffix, counter) + "." + className + counter;
336 return Class.forName(name, true, classLoader);
337 }
338
339 private static String makePackageName(String nestedSuffix, long counter) {
340 return BASEPKG + counter + nestedSuffix;
341 }
342
343
344
345
346
347
348
349
350 private static FileAndPath compileTestClass(long counter,
351 String packageNameSuffix, String classNamePrefix) throws Exception {
352 classNamePrefix = classNamePrefix + counter;
353 String packageName = makePackageName(packageNameSuffix, counter);
354 String javaPath = basePath + classNamePrefix + ".java";
355 String classPath = basePath + classNamePrefix + ".class";
356 PrintStream source = new PrintStream(javaPath);
357 source.println("package " + packageName + ";");
358 source.println("public class " + classNamePrefix
359 + " { public static void main(String[] args) { } };");
360 source.close();
361 JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
362 int result = jc.run(null, null, null, javaPath);
363 assertEquals(0, result);
364 File classFile = new File(classPath);
365 assertTrue(classFile.exists());
366 return new FileAndPath(packageName.replace('.', '/') + '/', classFile);
367 }
368
369
370
371
372
373
374 private static String packageAndLoadJar(FileAndPath... filesInJar) throws Exception {
375
376 String path = basePath + "jar" + jarCounter.incrementAndGet() + ".jar";
377 Manifest manifest = new Manifest();
378 manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
379 FileOutputStream fos = new FileOutputStream(path);
380 JarOutputStream jarOutputStream = new JarOutputStream(fos, manifest);
381
382
383
384 Set<String> pathsInJar = new HashSet<>();
385 for (FileAndPath fileAndPath : filesInJar) {
386 String pathToAdd = fileAndPath.path;
387 while (pathsInJar.add(pathToAdd)) {
388 int ix = pathToAdd.lastIndexOf('/', pathToAdd.length() - 2);
389 if (ix < 0) {
390 break;
391 }
392 pathToAdd = pathToAdd.substring(0, ix);
393 }
394 }
395 for (String pathInJar : pathsInJar) {
396 jarOutputStream.putNextEntry(new JarEntry(pathInJar));
397 jarOutputStream.closeEntry();
398 }
399 for (FileAndPath fileAndPath : filesInJar) {
400 File file = fileAndPath.file;
401 jarOutputStream.putNextEntry(
402 new JarEntry(fileAndPath.path + file.getName()));
403 byte[] allBytes = new byte[(int)file.length()];
404 FileInputStream fis = new FileInputStream(file);
405 fis.read(allBytes);
406 fis.close();
407 jarOutputStream.write(allBytes);
408 jarOutputStream.closeEntry();
409 }
410 jarOutputStream.close();
411 fos.close();
412
413
414 File jarFile = new File(path);
415 assertTrue(jarFile.exists());
416 classLoader.addURL(jarFile.toURI().toURL());
417 return jarFile.getAbsolutePath();
418 }
419
420
421 private static class CustomClassloader extends URLClassLoader {
422 public CustomClassloader(URL[] urls, ClassLoader parentLoader) {
423 super(urls, parentLoader);
424 }
425
426 public void addURL(URL url) {
427 super.addURL(url);
428 }
429 }
430 }