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, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.xbean.osgi.bundle.util; 021 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.net.URL; 025 import java.util.ArrayList; 026 import java.util.Collection; 027 import java.util.Enumeration; 028 import java.util.HashMap; 029 import java.util.HashSet; 030 import java.util.LinkedHashSet; 031 import java.util.List; 032 import java.util.Map; 033 import java.util.Set; 034 import java.util.zip.ZipEntry; 035 import java.util.zip.ZipInputStream; 036 037 import org.apache.xbean.osgi.bundle.util.BundleDescription.ExportPackage; 038 import org.apache.xbean.osgi.bundle.util.BundleDescription.HeaderEntry; 039 import org.apache.xbean.osgi.bundle.util.BundleDescription.RequireBundle; 040 import org.osgi.framework.Bundle; 041 import org.osgi.service.packageadmin.ExportedPackage; 042 import org.osgi.service.packageadmin.PackageAdmin; 043 import org.osgi.service.packageadmin.RequiredBundle; 044 import org.slf4j.Logger; 045 import org.slf4j.LoggerFactory; 046 047 /** 048 * Finds all available classes to a bundle by scanning Bundle-ClassPath, 049 * Import-Package, and Require-Bundle headers of the given bundle and its fragments. 050 * DynamicImport-Package header is not considered during scanning. 051 * 052 * @version $Rev: 942661 $ $Date: 2010-05-10 07:17:20 +0200 (Mon, 10 May 2010) $ 053 */ 054 public class BundleClassFinder { 055 056 private static final Logger logger = LoggerFactory.getLogger(BundleClassFinder.class); 057 058 public static final ClassDiscoveryFilter FULL_CLASS_DISCOVERY_FILTER = new DummyDiscoveryFilter(); 059 060 public static final ClassDiscoveryFilter IMPORTED_PACKAGE_EXCLUSIVE_FILTER = new NonImportedPackageDiscoveryFilter(); 061 062 protected static final String EXT = ".class"; 063 064 protected static final String PATTERN = "*.class"; 065 066 protected Bundle bundle; 067 068 protected PackageAdmin packageAdmin; 069 070 private Map<Bundle, Set<String>> classMap; 071 072 protected ClassDiscoveryFilter discoveryFilter; 073 074 public BundleClassFinder(PackageAdmin packageAdmin, Bundle bundle) { 075 this(packageAdmin, bundle, FULL_CLASS_DISCOVERY_FILTER); 076 } 077 078 public BundleClassFinder(PackageAdmin packageAdmin, Bundle bundle, ClassDiscoveryFilter discoveryFilter) { 079 this.packageAdmin = packageAdmin; 080 this.bundle = bundle; 081 this.discoveryFilter = discoveryFilter; 082 } 083 084 public List<Class> loadClasses(Set<String> classes) { 085 List<Class> loadedClasses = new ArrayList<Class>(classes.size()); 086 for (String clazz : classes) { 087 try { 088 loadedClasses.add(bundle.loadClass(clazz)); 089 } catch (Exception ignore) { 090 // ignore 091 } 092 } 093 return loadedClasses; 094 } 095 096 /** 097 * Finds all available classes to the bundle. Some of the classes in the returned set 098 * might not be loadable. 099 * 100 * @return classes visible to the bundle. Not all classes returned might be loadable. 101 */ 102 public Set<String> find() { 103 Set<String> classes = new LinkedHashSet<String>(); 104 classMap = new HashMap<Bundle, Set<String>>(); 105 if (discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.IMPORT_PACKAGES)) { 106 scanImportPackages(classes, bundle, bundle); 107 } 108 if (discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.REQUIRED_BUNDLES)) { 109 scanRequireBundles(classes, bundle); 110 } 111 if (discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.BUNDLE_CLASSPATH)) { 112 scanBundleClassPath(classes, bundle); 113 } 114 if (discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.FRAGMENT_BUNDLES)) { 115 Bundle[] fragments = packageAdmin.getFragments(bundle); 116 if (fragments != null) { 117 for (Bundle fragment : fragments) { 118 scanImportPackages(classes, bundle, fragment); 119 scanRequireBundles(classes, fragment); 120 scanBundleClassPath(classes, fragment); 121 } 122 } 123 } 124 classMap.clear(); 125 return classes; 126 } 127 128 protected boolean isClassAcceptable(String name, InputStream in) throws IOException { 129 return true; 130 } 131 132 protected boolean isClassAcceptable(URL url) { 133 return true; 134 } 135 136 protected BundleClassFinder createSubBundleClassFinder(PackageAdmin packageAdmin, Bundle bundle, ClassDiscoveryFilter classDiscoveryFilter) { 137 return new BundleClassFinder(packageAdmin, bundle, classDiscoveryFilter); 138 } 139 140 protected String toJavaStyleClassName(String name) { 141 if (name.endsWith(EXT)) { 142 name = name.substring(0, name.length() - EXT.length()); 143 } 144 name = name.replace('/', '.'); 145 return name; 146 } 147 148 /** 149 * Get the normal Java style package name from the parameter className. 150 * If the className is ended with .class extension, e.g. /org/apache/geronimo/TestCass.class or org.apache.geronimo.TestClass.class, 151 * then org/apache/geronimo is returned 152 * If the className is not ended with .class extension, e.g. /org/apache/geronimo/TestCass or org.apache.geronimo.TestClass, 153 * then org/apache/geronimo is returned 154 * @return Normal Java style package name, should be like org.apache.geronimo 155 */ 156 protected String toJavaStylePackageName(String className) { 157 if (className.endsWith(EXT)) { 158 className = className.substring(0, className.length() - EXT.length()); 159 } 160 className = className.replace('/', '.'); 161 int iLastDotIndex = className.lastIndexOf('.'); 162 if (iLastDotIndex != -1) { 163 return className.substring(0, iLastDotIndex); 164 } else { 165 return ""; 166 } 167 } 168 169 private Set<String> findAllClasses(Bundle bundle, ClassDiscoveryFilter userClassDiscoveryFilter, Set<String> exportedPackageNames) { 170 Set<String> allClasses = classMap.get(bundle); 171 if (allClasses == null) { 172 BundleClassFinder finder = createSubBundleClassFinder(packageAdmin, bundle, new ImportExclusivePackageDiscoveryFilterAdapter(userClassDiscoveryFilter, exportedPackageNames)); 173 allClasses = finder.find(); 174 classMap.put(bundle, allClasses); 175 } 176 return allClasses; 177 } 178 179 private Set<String> findAllClasses(Bundle bundle, String packageName) { 180 Set<String> allClasses = classMap.get(bundle); 181 if (allClasses == null) { 182 BundleClassFinder finder = createSubBundleClassFinder(packageAdmin, bundle, new ImportExclusivePackageDiscoveryFilter(packageName)); 183 allClasses = finder.find(); 184 classMap.put(bundle, allClasses); 185 } 186 return allClasses; 187 } 188 189 private void scanImportPackages(Collection<String> classes, Bundle host, Bundle fragment) { 190 BundleDescription description = new BundleDescription(fragment.getHeaders()); 191 List<BundleDescription.ImportPackage> imports = description.getExternalImports(); 192 for (BundleDescription.ImportPackage packageImport : imports) { 193 String packageName = packageImport.getName(); 194 if (discoveryFilter.packageDiscoveryRequired(packageName)) { 195 ExportedPackage[] exports = packageAdmin.getExportedPackages(packageName); 196 Bundle wiredBundle = isWired(host, exports); 197 if (wiredBundle != null) { 198 Set<String> allClasses = findAllClasses(wiredBundle, packageName); 199 classes.addAll(allClasses); 200 } 201 } 202 } 203 } 204 205 private void scanRequireBundles(Collection<String> classes, Bundle bundle) { 206 BundleDescription description = new BundleDescription(bundle.getHeaders()); 207 List<RequireBundle> requiredBundleList = description.getRequireBundle(); 208 for (RequireBundle requiredBundle : requiredBundleList) { 209 RequiredBundle[] requiredBundles = packageAdmin.getRequiredBundles(requiredBundle.getName()); 210 Bundle wiredBundle = isWired(bundle, requiredBundles); 211 if (wiredBundle != null) { 212 BundleDescription wiredBundleDescription = new BundleDescription(wiredBundle.getHeaders()); 213 List<ExportPackage> exportPackages = wiredBundleDescription.getExportPackage(); 214 Set<String> exportedPackageNames = new HashSet<String>(); 215 for (ExportPackage exportPackage : exportPackages) { 216 exportedPackageNames.add(exportPackage.getName()); 217 } 218 Set<String> allClasses = findAllClasses(wiredBundle, discoveryFilter, exportedPackageNames); 219 classes.addAll(allClasses); 220 } 221 } 222 } 223 224 private void scanBundleClassPath(Collection<String> resources, Bundle bundle) { 225 BundleDescription description = new BundleDescription(bundle.getHeaders()); 226 List<HeaderEntry> paths = description.getBundleClassPath(); 227 if (paths.isEmpty()) { 228 scanDirectory(resources, bundle, "/"); 229 } else { 230 for (HeaderEntry path : paths) { 231 String name = path.getName(); 232 if (name.equals(".") || name.equals("/")) { 233 // scan root 234 scanDirectory(resources, bundle, "/"); 235 } else if (name.endsWith(".jar") || name.endsWith(".zip")) { 236 // scan embedded jar/zip 237 scanZip(resources, bundle, name); 238 } else { 239 // assume it's a directory 240 scanDirectory(resources, bundle, "/" + name); 241 } 242 } 243 } 244 } 245 246 private void scanDirectory(Collection<String> classes, Bundle bundle, String basePath) { 247 basePath = addSlash(basePath); 248 if (!discoveryFilter.directoryDiscoveryRequired(basePath)) { 249 return; 250 } 251 Enumeration<URL> e = bundle.findEntries(basePath, PATTERN, true); 252 if (e != null) { 253 while (e.hasMoreElements()) { 254 URL u = e.nextElement(); 255 String entryName = u.getPath().substring(basePath.length()); 256 if (discoveryFilter.packageDiscoveryRequired(toJavaStylePackageName(entryName))) { 257 if (isClassAcceptable(u)) { 258 classes.add(toJavaStyleClassName(entryName)); 259 } 260 } 261 } 262 } 263 } 264 265 private void scanZip(Collection<String> classes, Bundle bundle, String zipName) { 266 if (!discoveryFilter.jarFileDiscoveryRequired(zipName)) { 267 return; 268 } 269 URL zipEntry = bundle.getEntry(zipName); 270 if (zipEntry == null) { 271 return; 272 } 273 ZipInputStream in = null; 274 try { 275 in = new ZipInputStream(zipEntry.openStream()); 276 ZipEntry entry; 277 while ((entry = in.getNextEntry()) != null) { 278 String name = entry.getName(); 279 if (name.endsWith(EXT) && discoveryFilter.packageDiscoveryRequired(toJavaStylePackageName(name))) { 280 if (isClassAcceptable(name, in)) { 281 classes.add(toJavaStyleClassName(name)); 282 } 283 } 284 } 285 } catch (IOException ignore) { 286 logger.warn("Fail to check zip file " + zipName, ignore); 287 } finally { 288 if (in != null) { 289 try { 290 in.close(); 291 } catch (IOException e) { 292 } 293 } 294 } 295 } 296 297 protected String addSlash(String name) { 298 if (!name.endsWith("/")) { 299 name = name + "/"; 300 } 301 return name; 302 } 303 304 protected Bundle isWired(Bundle bundle, ExportedPackage[] exports) { 305 if (exports != null) { 306 for (ExportedPackage exportedPackage : exports) { 307 Bundle[] importingBundles = exportedPackage.getImportingBundles(); 308 if (importingBundles != null) { 309 for (Bundle importingBundle : importingBundles) { 310 if (importingBundle == bundle) { 311 return exportedPackage.getExportingBundle(); 312 } 313 } 314 } 315 } 316 } 317 return null; 318 } 319 320 protected Bundle isWired(Bundle bundle, RequiredBundle[] requiredBundles) { 321 if (requiredBundles != null) { 322 for (RequiredBundle requiredBundle : requiredBundles) { 323 Bundle[] requiringBundles = requiredBundle.getRequiringBundles(); 324 if (requiringBundles != null) { 325 for (Bundle requiringBundle : requiringBundles) { 326 if (requiringBundle == bundle) { 327 return requiredBundle.getBundle(); 328 } 329 } 330 } 331 } 332 } 333 return null; 334 } 335 336 public static class DummyDiscoveryFilter implements ClassDiscoveryFilter { 337 338 339 public boolean directoryDiscoveryRequired(String url) { 340 return true; 341 } 342 343 344 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) { 345 return true; 346 } 347 348 349 public boolean jarFileDiscoveryRequired(String url) { 350 return true; 351 } 352 353 354 public boolean packageDiscoveryRequired(String packageName) { 355 return true; 356 } 357 } 358 359 public static class NonImportedPackageDiscoveryFilter implements ClassDiscoveryFilter { 360 361 362 public boolean directoryDiscoveryRequired(String url) { 363 return true; 364 } 365 366 367 public boolean jarFileDiscoveryRequired(String url) { 368 return true; 369 } 370 371 372 public boolean packageDiscoveryRequired(String packageName) { 373 return true; 374 } 375 376 377 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) { 378 return !discoveryRange.equals(DiscoveryRange.IMPORT_PACKAGES); 379 } 380 } 381 382 private static class ImportExclusivePackageDiscoveryFilter implements ClassDiscoveryFilter { 383 384 private String expectedPckageName; 385 386 public ImportExclusivePackageDiscoveryFilter(String expectedPckageName) { 387 this.expectedPckageName = expectedPckageName; 388 } 389 390 391 public boolean directoryDiscoveryRequired(String url) { 392 return true; 393 } 394 395 396 public boolean jarFileDiscoveryRequired(String url) { 397 return true; 398 } 399 400 401 public boolean packageDiscoveryRequired(String packageName) { 402 return expectedPckageName.equals(packageName); 403 } 404 405 406 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) { 407 return !discoveryRange.equals(DiscoveryRange.IMPORT_PACKAGES); 408 } 409 } 410 411 private static class ImportExclusivePackageDiscoveryFilterAdapter implements ClassDiscoveryFilter { 412 413 private Set<String> acceptedPackageNames; 414 415 private ClassDiscoveryFilter classDiscoveryFilter; 416 417 public ImportExclusivePackageDiscoveryFilterAdapter(ClassDiscoveryFilter classDiscoveryFilter, Set<String> acceptedPackageNames) { 418 this.classDiscoveryFilter = classDiscoveryFilter; 419 this.acceptedPackageNames = acceptedPackageNames; 420 } 421 422 423 public boolean directoryDiscoveryRequired(String url) { 424 return true; 425 } 426 427 428 public boolean jarFileDiscoveryRequired(String url) { 429 return true; 430 } 431 432 433 public boolean packageDiscoveryRequired(String packageName) { 434 return acceptedPackageNames.contains(packageName) && classDiscoveryFilter.packageDiscoveryRequired(packageName); 435 } 436 437 438 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) { 439 return !discoveryRange.equals(DiscoveryRange.IMPORT_PACKAGES); 440 } 441 } 442 }