View Javadoc

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;
19  
20  import java.io.File;
21  import java.io.FileFilter;
22  import java.io.FileInputStream;
23  import java.io.IOException;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarInputStream;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  /**
39   * A class that finds a set of classes that are locally accessible
40   * (from .class or .jar files), and satisfy the conditions that are
41   * imposed by name and class filters provided by the user.
42   */
43  public class ClassFinder {
44    private static final Log LOG = LogFactory.getLog(ClassFinder.class);
45    private static String CLASS_EXT = ".class";
46  
47    private ResourcePathFilter resourcePathFilter;
48    private FileNameFilter fileNameFilter;
49    private ClassFilter classFilter;
50    private FileFilter fileFilter;
51    private ClassLoader classLoader;
52  
53    public interface ResourcePathFilter {
54      boolean isCandidatePath(String resourcePath, boolean isJar);
55    }
56  
57    public interface FileNameFilter {
58      boolean isCandidateFile(String fileName, String absFilePath);
59    }
60  
61    public interface ClassFilter {
62      boolean isCandidateClass(Class<?> c);
63    }
64  
65    public static class Not implements ResourcePathFilter, FileNameFilter, ClassFilter {
66      private ResourcePathFilter resourcePathFilter;
67      private FileNameFilter fileNameFilter;
68      private ClassFilter classFilter;
69  
70      public Not(ResourcePathFilter resourcePathFilter) {
71        this.resourcePathFilter = resourcePathFilter;
72      }
73  
74      public Not(FileNameFilter fileNameFilter) {
75        this.fileNameFilter = fileNameFilter;
76      }
77  
78      public Not(ClassFilter classFilter) {
79        this.classFilter = classFilter;
80      }
81  
82      @Override
83      public boolean isCandidatePath(String resourcePath, boolean isJar) {
84        return !resourcePathFilter.isCandidatePath(resourcePath, isJar);
85      }
86      @Override
87      public boolean isCandidateFile(String fileName, String absFilePath) {
88        return !fileNameFilter.isCandidateFile(fileName, absFilePath);
89      }
90      @Override
91      public boolean isCandidateClass(Class<?> c) {
92        return !classFilter.isCandidateClass(c);
93      }
94    }
95  
96    public static class And implements ClassFilter, ResourcePathFilter {
97      ClassFilter[] classFilters;
98      ResourcePathFilter[] resourcePathFilters;
99  
100     public And(ClassFilter...classFilters) {
101       this.classFilters = classFilters;
102     }
103 
104     public And(ResourcePathFilter... resourcePathFilters) {
105       this.resourcePathFilters = resourcePathFilters;
106     }
107 
108     @Override
109     public boolean isCandidateClass(Class<?> c) {
110       for (ClassFilter filter : classFilters) {
111         if (!filter.isCandidateClass(c)) {
112           return false;
113         }
114       }
115       return true;
116     }
117 
118     @Override public boolean isCandidatePath(String resourcePath, boolean isJar) {
119       for (ResourcePathFilter filter : resourcePathFilters) {
120         if (!filter.isCandidatePath(resourcePath, isJar)) {
121           return false;
122         }
123       }
124       return true;
125     }
126   }
127 
128   // To control which classloader to use while trying to find jars/classes
129   public ClassFinder(ClassLoader classLoader) {
130     this(null, null, null, classLoader);
131   }
132 
133   public ClassFinder(ResourcePathFilter resourcePathFilter, FileNameFilter fileNameFilter,
134       ClassFilter classFilter) {
135     this(resourcePathFilter, fileNameFilter, classFilter, ClassLoader.getSystemClassLoader());
136   }
137 
138   public ClassFinder(ResourcePathFilter resourcePathFilter, FileNameFilter fileNameFilter,
139       ClassFilter classFilter, ClassLoader classLoader) {
140     this.resourcePathFilter = resourcePathFilter;
141     this.classFilter = classFilter;
142     this.fileNameFilter = fileNameFilter;
143     this.fileFilter = new FileFilterWithName(fileNameFilter);
144     this.classLoader = classLoader;
145   }
146 
147   /**
148    * Finds the classes in current package (of ClassFinder) and nested packages.
149    * @param proceedOnExceptions whether to ignore exceptions encountered for
150    *        individual jars/files/classes, and proceed looking for others.
151    */
152   public Set<Class<?>> findClasses(boolean proceedOnExceptions)
153     throws ClassNotFoundException, IOException, LinkageError {
154     return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
155   }
156 
157   /**
158    * Finds the classes in a package and nested packages.
159    * @param packageName package names
160    * @param proceedOnExceptions whether to ignore exceptions encountered for
161    *        individual jars/files/classes, and proceed looking for others.
162    */
163   public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
164     throws ClassNotFoundException, IOException, LinkageError {
165     final String path = packageName.replace('.', '/');
166     final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
167 
168     Enumeration<URL> resources = this.classLoader.getResources(path);
169     List<File> dirs = new ArrayList<>();
170     List<String> jars = new ArrayList<>();
171 
172     while (resources.hasMoreElements()) {
173       URL resource = resources.nextElement();
174       String resourcePath = resource.getFile();
175       Matcher matcher = jarResourceRe.matcher(resourcePath);
176       boolean isJar = matcher.find();
177       resourcePath = isJar ? matcher.group(1) : resourcePath;
178       if (null == this.resourcePathFilter
179           || this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) {
180         LOG.debug("Will look for classes in " + resourcePath);
181         if (isJar) {
182           jars.add(resourcePath);
183         } else {
184           dirs.add(new File(resourcePath));
185         }
186       }
187     }
188 
189     Set<Class<?>> classes = new HashSet<>();
190     for (File directory : dirs) {
191       classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
192     }
193     for (String jarFileName : jars) {
194       classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
195     }
196     return classes;
197   }
198 
199   private Set<Class<?>> findClassesFromJar(String jarFileName,
200       String packageName, boolean proceedOnExceptions)
201     throws IOException, ClassNotFoundException, LinkageError {
202     JarInputStream jarFile;
203     try {
204       jarFile = new JarInputStream(new FileInputStream(jarFileName));
205     } catch (IOException ioEx) {
206       LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
207       throw ioEx;
208     }
209 
210     Set<Class<?>> classes = new HashSet<>();
211     JarEntry entry;
212     try {
213       while (true) {
214         try {
215           entry = jarFile.getNextJarEntry();
216         } catch (IOException ioEx) {
217           if (!proceedOnExceptions) {
218             throw ioEx;
219           }
220           LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
221           break;
222         }
223         if (entry == null) {
224           break; // loop termination condition
225         }
226 
227         String className = entry.getName();
228         if (!className.endsWith(CLASS_EXT)) {
229           continue;
230         }
231         int ix = className.lastIndexOf('/');
232         String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
233         if (null != this.fileNameFilter
234             && !this.fileNameFilter.isCandidateFile(fileName, className)) {
235           continue;
236         }
237         className =
238             className.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
239         if (!className.startsWith(packageName)) {
240           continue;
241         }
242         Class<?> c = makeClass(className, proceedOnExceptions);
243         if (c != null) {
244           if (!classes.add(c)) {
245             LOG.warn("Ignoring duplicate class " + className);
246           }
247         }
248       }
249       return classes;
250     } finally {
251       jarFile.close();
252     }
253   }
254 
255   private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
256       boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
257     Set<Class<?>> classes = new HashSet<>();
258     if (!baseDirectory.exists()) {
259       LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
260       return classes;
261     }
262 
263     File[] files = baseDirectory.listFiles(this.fileFilter);
264     if (files == null) {
265       LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
266       return classes;
267     }
268 
269     for (File file : files) {
270       final String fileName = file.getName();
271       if (file.isDirectory()) {
272         classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
273             proceedOnExceptions));
274       } else {
275         String className = packageName + '.'
276             + fileName.substring(0, fileName.length() - CLASS_EXT.length());
277         Class<?> c = makeClass(className, proceedOnExceptions);
278         if (c != null) {
279           if (!classes.add(c)) {
280             LOG.warn("Ignoring duplicate class " + className);
281           }
282         }
283       }
284     }
285     return classes;
286   }
287 
288   private Class<?> makeClass(String className, boolean proceedOnExceptions)
289     throws ClassNotFoundException, LinkageError {
290     try {
291       Class<?> c = Class.forName(className, false, classLoader);
292       boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
293       return isCandidateClass ? c : null;
294     } catch (ClassNotFoundException | LinkageError exception) {
295       if (!proceedOnExceptions) {
296         throw exception;
297       }
298       LOG.debug("Failed to instantiate or check " + className + ": " + exception);
299     }
300     return null;
301   }
302 
303   private static class FileFilterWithName implements FileFilter {
304     private FileNameFilter nameFilter;
305 
306     public FileFilterWithName(FileNameFilter nameFilter) {
307       this.nameFilter = nameFilter;
308     }
309 
310     @Override
311     public boolean accept(File file) {
312       return file.isDirectory()
313           || (file.getName().endsWith(CLASS_EXT)
314               && (null == nameFilter
315                 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
316     }
317   }
318 }