001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase; 019 020import java.io.File; 021import java.io.FileFilter; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.net.URL; 025import java.util.ArrayList; 026import java.util.Enumeration; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Set; 030import java.util.jar.JarEntry; 031import java.util.jar.JarInputStream; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * A class that finds a set of classes that are locally accessible 040 * (from .class or .jar files), and satisfy the conditions that are 041 * imposed by name and class filters provided by the user. 042 */ 043public class ClassFinder { 044 private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class); 045 private static String CLASS_EXT = ".class"; 046 047 private ResourcePathFilter resourcePathFilter; 048 private FileNameFilter fileNameFilter; 049 private ClassFilter classFilter; 050 private FileFilter fileFilter; 051 private ClassLoader classLoader; 052 053 public interface ResourcePathFilter { 054 boolean isCandidatePath(String resourcePath, boolean isJar); 055 } 056 057 public interface FileNameFilter { 058 boolean isCandidateFile(String fileName, String absFilePath); 059 } 060 061 public interface ClassFilter { 062 boolean isCandidateClass(Class<?> c); 063 } 064 065 public static class Not implements ResourcePathFilter, FileNameFilter, ClassFilter { 066 private ResourcePathFilter resourcePathFilter; 067 private FileNameFilter fileNameFilter; 068 private ClassFilter classFilter; 069 070 public Not(ResourcePathFilter resourcePathFilter) { 071 this.resourcePathFilter = resourcePathFilter; 072 } 073 074 public Not(FileNameFilter fileNameFilter) { 075 this.fileNameFilter = fileNameFilter; 076 } 077 078 public Not(ClassFilter classFilter) { 079 this.classFilter = classFilter; 080 } 081 082 @Override 083 public boolean isCandidatePath(String resourcePath, boolean isJar) { 084 return !resourcePathFilter.isCandidatePath(resourcePath, isJar); 085 } 086 @Override 087 public boolean isCandidateFile(String fileName, String absFilePath) { 088 return !fileNameFilter.isCandidateFile(fileName, absFilePath); 089 } 090 @Override 091 public boolean isCandidateClass(Class<?> c) { 092 return !classFilter.isCandidateClass(c); 093 } 094 } 095 096 public static class And implements ClassFilter, ResourcePathFilter { 097 ClassFilter[] classFilters; 098 ResourcePathFilter[] resourcePathFilters; 099 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("Looking in " + resourcePath + "; isJar=" + isJar); 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(baseDirectory.getAbsolutePath() + " does not exist"); 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}